/* cdrom.c: * * Basic interaction with the CDROM drive. * * ---------------------------------------------------------------------- * This code is (C) Copyright 1993,1994 by Frank Munkert. * All rights reserved. * This software may be freely distributed and redistributed for * non-commercial purposes, provided this notice is included. * ---------------------------------------------------------------------- * History: * * 02-Sep-94 fmu Display READ TOC for Apple CD 150 drives. * 01-Sep-94 fmu Workaround for bad NEC 3X READ TOC command in * Has_Audio_Tracks() and Data_Tracks(). * 20-Aug-94 fmu New function Find_Last_Session (). * 23-Jul-94 fmu Last index modified from 99 to 1 in Start_Play_Audio(). * 18-May-94 fmu New drive model: MODEL_CDU_8002 (= Apple CD 150). * 17-May-94 fmu Sense length 20 instead of 18 (needed by ALF controller). * 17-Feb-94 fmu Added support for Toshiba 4101. * 06-Feb-94 dmb - Fixed bug in Test_Unit_Ready() trackdisk support. * - Fixed bug in Open_CDROM (size of request) * - Added function Clear_Sector_Buffers(). * 01-Jan-94 fmu Added function Data_Tracks() for multisession support. * 11-Dec-93 fmu - Memory type can now be chosen by the user. * - Addional parameter p_direction for Do_SCSI_Command(). * - Start_Play_Audio() now plays all tracks. * - Mode_Select() instead of Select_XA_Mode(). * - Support for CDROM drives with 512, 1024 or 2048 bytes * per block. * 06-Dec-93 fmu New drive type DRIVE_SCSI_2. * 09-Nov-93 fmu Added Select_XA_Mode. * 23-Oct-93 fmu Open_CDROM now returns an error code that tell what * went wrong. * 09-Oct-93 fmu SAS/C support added. * 03-Oct-93 fmu New buffering algorithm. * 27-Sep-93 fmu Added support for multi-LUN devices. * 24-Sep-93 fmu - SCSI buffers may now reside in fast or chip memory. * - TD_CHANGESTATE instead of CMD_READ in Test_Unit_Ready */ #include #include #include #ifdef LATTICE #include #endif #include #include "cdrom.h" #include int g_cdrom_errno; int g_ignore_blocklength = FALSE; void Determine_Drive_Type (CDROM *p_cd) { t_inquiry_data data; char buf[33]; p_cd->scsi_compliance = 1; p_cd->model = MODEL_ANY; if (!Inquire (p_cd, &data)) return; if ((data.version & 0x7) >= 2) p_cd->scsi_compliance = 2; if (strncmp (data.vendor, "TOSHIBA", 7) == 0) { memcpy (buf, data.product, 32); buf[32] = 0; if (strstr (buf, "3401") || strstr (buf, "4101")) p_cd->model = MODEL_TOSHIBA_3401; } else if (strncmp (data.vendor, "SONY", 4) == 0) { memcpy (buf, data.product, 32); buf[32] = 0; if (strstr (buf, "CDU-8002")) p_cd->model = MODEL_CDU_8002; } } CDROM *Open_CDROM (char *p_device, int p_scsi_id, int p_use_trackdisk, unsigned long p_memory_type, int p_std_buffers, int p_file_buffers) { CDROM *cd; int i; int bufs = p_std_buffers + p_file_buffers + 1; g_cdrom_errno = CDROMERR_NO_MEMORY; cd = AllocMem (sizeof (CDROM), MEMF_PUBLIC | MEMF_CLEAR | p_memory_type); if (!cd) return NULL; cd->std_buffers = p_std_buffers; cd->file_buffers = p_file_buffers; cd->buffer_data = AllocMem (SCSI_BUFSIZE * bufs + 15, MEMF_PUBLIC | p_memory_type); if (!cd->buffer_data) { Cleanup_CDROM (cd); return NULL; } cd->buffers = AllocMem (sizeof (unsigned char *) * bufs, MEMF_PUBLIC); if (!cd->buffers) { Cleanup_CDROM (cd); return NULL; } cd->current_sectors = AllocMem (sizeof (long) * bufs, MEMF_PUBLIC); if (!cd->current_sectors) { Cleanup_CDROM (cd); return NULL; } cd->last_used = AllocMem (sizeof (unsigned long) * p_std_buffers, MEMF_PUBLIC | MEMF_CLEAR); if (!cd->last_used) { Cleanup_CDROM (cd); return NULL; } /* make the buffer quad-word aligned. This greatly helps * performance on '040-powered systems with DMA SCSI * controllers. */ cd->buffers[0] = (UBYTE *)(((ULONG)cd->buffer_data + 15) & ~15); for (i=1; ibuffers[i] = cd->buffers[0] + i * SCSI_BUFSIZE; cd->port = CreateMsgPort (); if (!cd->port) { g_cdrom_errno = CDROMERR_MSGPORT; Cleanup_CDROM (cd); return NULL; } cd->scsireq = CreateIORequest (cd->port, sizeof (struct IOExtTD)); if (!cd->scsireq) { g_cdrom_errno = CDROMERR_IOREQ; Cleanup_CDROM (cd); return NULL; } if (OpenDevice ((UBYTE *) p_device, p_scsi_id, (struct IORequest *) cd->scsireq, 0)) { g_cdrom_errno = CDROMERR_DEVICE; Cleanup_CDROM (cd); return NULL; } cd->device_open = TRUE; for (i=0; icurrent_sectors[i] = -1; if (p_use_trackdisk) { cd->scsireq->io_Command = CMD_CLEAR; DoIO ((struct IORequest *) cd->scsireq); } cd->use_trackdisk = p_use_trackdisk; cd->t_changeint = -1; cd->t_changeint2 = -2; /* The LUN is the 2nd digit of the SCSI id number: */ cd->lun = (p_scsi_id / 10) % 10; /* 'tick' is incremented every time a sector is accessed. */ cd->tick = 0; g_cdrom_errno = 0; Determine_Drive_Type (cd); if (g_ignore_blocklength) { cd->block_length = 0; cd->blocking_factor = 0; } else { cd->block_length = Block_Length (cd); switch (cd->block_length) { case 2048: cd->blocking_factor = 0; break; case 1024: cd->blocking_factor = 1; break; case 512: cd->blocking_factor = 2; break; case 0: cd->blocking_factor = 0; break; default: g_cdrom_errno = CDROMERR_BLOCKSIZE; Cleanup_CDROM (cd); return NULL; } } return cd; } int Do_SCSI_Command (CDROM *p_cd, unsigned char *p_buf, long p_buf_length, unsigned char *p_command, int p_length, int p_direction) { int bufs = p_cd->std_buffers + p_cd->file_buffers + 1; p_cd->scsireq->io_Length = sizeof (struct SCSICmd); p_cd->scsireq->io_Data = (APTR) &p_cd->cmd; p_cd->scsireq->io_Command = HD_SCSICMD; p_cd->cmd.scsi_Data = (UWORD *) p_buf; p_cd->cmd.scsi_Length = p_buf_length; p_cd->cmd.scsi_Flags = SCSIF_AUTOSENSE | p_direction; p_cd->cmd.scsi_SenseData = (UBYTE *) p_cd->sense; p_cd->cmd.scsi_SenseLength = 20; p_cd->cmd.scsi_SenseActual = 0; p_cd->cmd.scsi_Command = (UBYTE *) p_command; p_cd->cmd.scsi_CmdLength = p_length; p_command[1] |= p_cd->lun << 5; DoIO ((struct IORequest *) p_cd->scsireq); if (p_cd->cmd.scsi_Status) { int i; for (i=0; icurrent_sectors[i] = -1; return 0; } else return 1; } int Read_From_Drive (CDROM *p_cd, unsigned char *p_buf, long p_buf_length, long p_sector, int p_number_of_sectors) { static unsigned char cmd[10] = { 0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int bufs = p_cd->std_buffers + p_cd->file_buffers + 1; #ifdef DEBUG_SECTORS extern void *dbprintf (char *, ...); if (p_number_of_sectors == 1) dbprintf ("[%ld]", p_sector); else dbprintf ("[%ld-%ld]", p_sector, p_sector + p_number_of_sectors - 1); #endif if (p_cd->use_trackdisk) { p_cd->scsireq->io_Length = 2048 * p_number_of_sectors; p_cd->scsireq->io_Data = (APTR) p_buf; p_cd->scsireq->io_Offset = (ULONG) p_sector * 2048; p_cd->scsireq->io_Command = CMD_READ; DoIO ((struct IORequest *) p_cd->scsireq); if (p_cd->scsireq->io_Error) { int i; for (i=0; icurrent_sectors[i] = -1; return 0; } else return 1; } else { long sector = p_sector << p_cd->blocking_factor; cmd[5] = sector & 0xff; cmd[4] = (sector >> 8) & 0xff; cmd[3] = (sector >> 16) & 0x1f; cmd[8] = p_number_of_sectors << p_cd->blocking_factor; return Do_SCSI_Command (p_cd, p_buf, p_buf_length, cmd, sizeof (cmd), SCSIF_READ); } } /* Read one sector from the CDROM drive. */ int Read_Sector (CDROM *p_cd, long p_sector) { int status; int i; int maxbuf = p_cd->std_buffers; int loc; p_cd->tick++; for (i=0; icurrent_sectors[i] == p_sector) { p_cd->buffer = p_cd->buffers[i]; p_cd->last_used[i] = p_cd->tick; return 1; } /* find an empty buffer position: */ for (loc=0; loccurrent_sectors[loc] == -1) break; if (loc==maxbuf) { /* no free buffer position; remove the buffer that is unused for the longest time: */ unsigned long oldest_tick = ULONG_MAX; unsigned long tick; for (loc=0, i=0; ilast_used[i]; if (tick < oldest_tick) loc = i, oldest_tick = tick; } } status = Read_From_Drive (p_cd, p_cd->buffers[loc], SCSI_BUFSIZE, p_sector, 1); if (status) { p_cd->current_sectors[loc] = p_sector; p_cd->buffer = p_cd->buffers[loc]; p_cd->last_used[loc] = p_cd->tick; } return status; } /* Read_Contiguous_Sectors uses the 'file buffers' instead of the * 'standard buffers'. Additionaly, more than one sector may be read * with a single SCSI command. This may cause a substantial increase * in speed when reading large files. */ int Read_Contiguous_Sectors (CDROM *p_cd, long p_sector, long p_last_sector) { int status; int i; int maxbuf = p_cd->std_buffers + p_cd->file_buffers; int len; for (i=p_cd->std_buffers; icurrent_sectors[i] == p_sector) { p_cd->buffer = p_cd->buffers[i]; return 1; } if (p_last_sector <= p_sector) len = 1; else { len = p_last_sector - p_sector + 1; if (len > p_cd->file_buffers) len = p_cd->file_buffers; if (len > 255) len = 255; } status = Read_From_Drive (p_cd, p_cd->buffers[p_cd->std_buffers], SCSI_BUFSIZE * len, p_sector, len); if (status) { long sector = p_sector; for (i=p_cd->std_buffers; len; i++, len--) p_cd->current_sectors[i] = sector++; p_cd->buffer = p_cd->buffers[p_cd->std_buffers]; } return status; } int Test_Unit_Ready (CDROM *p_cd) { if (p_cd->use_trackdisk) { p_cd->scsireq->io_Command = TD_CHANGENUM; if (!DoIO ((struct IORequest *) p_cd->scsireq)) { if (p_cd->scsireq->io_Error==0) p_cd->t_changeint = p_cd->scsireq->io_Actual; } p_cd->scsireq->io_Command = TD_CHANGESTATE; if (!DoIO ((struct IORequest *) p_cd->scsireq)) { if (p_cd->scsireq->io_Error==0 && p_cd->scsireq->io_Actual==0) return TRUE; } return FALSE; } else { int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; static unsigned char cmd[6] = { 0, 0, 0, 0, 0, 0 }; return Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], SCSI_BUFSIZE, cmd, 6, SCSIF_READ); } } int Is_XA_Mode_Disk (CDROM *p_cd) { static unsigned char cmd[10] = { 0xC7, 3, 0, 0, 0, 0, 0, 0, 0, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; if (!Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], 4, cmd, 10, SCSIF_READ)) return FALSE; return *(p_cd->buffers[dummy_buf]) == 0x20; } int Mode_Select (CDROM *p_cd, int p_mode, int p_block_length) { static unsigned char cmd[6] = { 0x15, 0x10, 0, 0, 12, 0 }; static unsigned char mode[12] = { 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; if (p_cd->use_trackdisk) return FALSE; p_cd->block_length = p_block_length; switch (p_cd->block_length) { case 2048: p_cd->blocking_factor = 0; break; case 1024: p_cd->blocking_factor = 1; break; case 512: p_cd->blocking_factor = 2; break; } mode[4] = p_mode; mode[9] = p_block_length >> 16; mode[10] = (p_block_length >> 8) & 0xff; mode[11] = p_block_length & 0xff; memcpy (p_cd->buffers[dummy_buf], mode, sizeof (mode)); return Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], sizeof (mode), cmd, 6, SCSIF_WRITE); } int Inquire (CDROM *p_cd, t_inquiry_data *p_data) { static unsigned char cmd[6] = { 0x12, 0, 0, 0, 96, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; if (p_cd->use_trackdisk) return FALSE; if (!Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], 96, cmd, 6, SCSIF_READ)) return FALSE; memcpy (p_data, p_cd->buffers[dummy_buf], sizeof (*p_data)); return 1; } #define TOC_SIZE 804 t_toc_data *Read_TOC (CDROM *p_cd, t_toc_header *p_toc_header) { static unsigned char cmd[10] = { 0x43, 0, 0, 0, 0, 0, 0, TOC_SIZE >> 8, TOC_SIZE & 0xff, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; if (p_cd->use_trackdisk) return NULL; if (p_cd->model == MODEL_CDU_8002) /* READ TOC not supported by this drive */ return NULL; if (!Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], TOC_SIZE, cmd, 10, SCSIF_READ)) return NULL; memcpy (p_toc_header, p_cd->buffers[dummy_buf], sizeof (*p_toc_header)); return (t_toc_data *) (p_cd->buffers[dummy_buf] + 4); } int Has_Audio_Tracks (CDROM *p_cd) { t_toc_header hdr; t_toc_data *toc; int i, len; if (!(toc = Read_TOC (p_cd, &hdr))) return FALSE; len = hdr.length / 8; for (i=0; istd_buffers + p_cd->file_buffers; if (p_cd->use_trackdisk || (p_cd->scsi_compliance == 1 && p_cd->model == MODEL_ANY)) return FALSE; if (p_cd->model == MODEL_CDU_8002) cmd[0] = 0xC9; cmd[4] = Has_Audio_Tracks (p_cd); return Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], 0, cmd, 10, SCSIF_WRITE); } int Stop_Play_Audio (CDROM *p_cd) { static unsigned char cmd[6] = { 0x1B, 0, 0, 0, 0, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; if (p_cd->use_trackdisk) return FALSE; return Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], 0, cmd, 6, SCSIF_WRITE); } int Block_Length (CDROM *p_cd) { static unsigned char cmd[6] = { 0x1A, 0, 1, 0, 100, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; unsigned char *buf = p_cd->buffers[dummy_buf]; if (p_cd->use_trackdisk) return 0; if (!Do_SCSI_Command (p_cd, buf, 100, cmd, 6, SCSIF_READ)) return 0; if (buf[3] == 0) return 0; return (buf[9]<<16) + (buf[10]<<8) + buf[11]; } void Cleanup_CDROM (CDROM *p_cd) { int bufs = p_cd->std_buffers + p_cd->file_buffers + 1; if (p_cd->device_open) CloseDevice ((struct IORequest *) p_cd->scsireq); if (p_cd->scsireq) DeleteIORequest (p_cd->scsireq); if (p_cd->port) DeleteMsgPort (p_cd->port); if (p_cd->last_used) FreeMem (p_cd->last_used, sizeof (unsigned long) * p_cd->std_buffers); if (p_cd->current_sectors) FreeMem (p_cd->current_sectors, sizeof (long) * bufs); if (p_cd->buffers) FreeMem (p_cd->buffers, sizeof (unsigned char *) * bufs); if (p_cd->buffer_data) FreeMem (p_cd->buffer_data, SCSI_BUFSIZE * bufs + 15); FreeMem (p_cd, sizeof (CDROM)); } void Clear_Sector_Buffers (CDROM *p_cd) { int i; int bufs = p_cd->std_buffers + p_cd->file_buffers + 1; for (i=0; icurrent_sectors[i] = -1; } /* Finds offset of last session. (Not supported by all CDROM drives) * * Returns: - FALSE if there is no special SCSI command to determine the * offset of the last session. * - TRUE if the offset of the last session has been determined. */ int Find_Last_Session (CDROM *p_cd, unsigned long *p_result) { static unsigned char cmd[10] = { 0xC7, 3, 0, 0, 0, 0, 0, 0, 0, 0 }; int dummy_buf = p_cd->std_buffers + p_cd->file_buffers; unsigned char *buf = p_cd->buffers[dummy_buf]; int min, sec, frame; if (p_cd->use_trackdisk) return FALSE; if (p_cd->model != MODEL_TOSHIBA_3401) return FALSE; if (!Do_SCSI_Command (p_cd, p_cd->buffers[dummy_buf], SCSI_BUFSIZE, cmd, sizeof (cmd), SCSIF_READ)) return FALSE; min = (buf[1] & 0xF) + ((buf[1] & 0xF0) >> 4) * 10; sec = (buf[2] & 0xF) + ((buf[2] & 0xF0) >> 4) * 10; frame = (buf[3] & 0xF) + ((buf[3] & 0xF0) >> 4) * 10; *p_result = frame + sec * 75 + min * (75 * 60) - 150; return TRUE; }