diff --git a/lib/SCSI2SD/src/firmware/mode.c b/lib/SCSI2SD/src/firmware/mode.c index 0ef3c4b5..9d25a076 100755 --- a/lib/SCSI2SD/src/firmware/mode.c +++ b/lib/SCSI2SD/src/firmware/mode.c @@ -514,6 +514,8 @@ static void doModeSense( idx += sizeof(SequentialDeviceConfigPage); } + idx += modeSenseCDCapabilitiesPage(pc, idx, pageCode, &pageFound); + if (( (scsiDev.target->cfg->quirks == S2S_CFG_QUIRKS_APPLE) || (idx + sizeof(AppleVendorPage) <= allocLength) diff --git a/src/ZuluSCSI_cdrom.cpp b/src/ZuluSCSI_cdrom.cpp index 17b3771f..6cb1c3d9 100644 --- a/src/ZuluSCSI_cdrom.cpp +++ b/src/ZuluSCSI_cdrom.cpp @@ -186,6 +186,38 @@ static const uint8_t DiscInformation[] = 0x00, // 33: number of opc tables }; +static const uint8_t TrackInformation[] = +{ + 0x00, // 0: data length, MSB + 0x1A, // 1: data length, LSB + 0x01, // 2: track number + 0x01, // 3: session number + 0x00, // 4: reserved + 0x04, // 5: track mode and flags + 0x8F, // 6: data mode and flags + 0x00, // 7: nwa_v + 0x00, // 8: track start address (MSB) + 0x00, // 9: . + 0x00, // 10: . + 0x00, // 11: track start address (LSB) + 0xFF, // 12: next writable address (MSB) + 0xFF, // 13: . + 0xFF, // 14: . + 0xFF, // 15: next writable address (LSB) + 0x00, // 16: free blocks (MSB) + 0x00, // 17: . + 0x00, // 18: . + 0x00, // 19: free blocks (LSB) + 0x00, // 20: fixed packet size (MSB) + 0x00, // 21: . + 0x00, // 22: . + 0x00, // 23: fixed packet size (LSB) + 0x00, // 24: track size (MSB) + 0x00, // 25: . + 0x00, // 26: . + 0x00, // 27: track size (LSB) +}; + // Convert logical block address to CD-ROM time static void LBA2MSF(int32_t LBA, uint8_t* MSF, bool relative) { @@ -384,6 +416,39 @@ void doReadDiscInformationSimple(uint16_t allocationLength) scsiDev.phase = DATA_IN; } +void doReadTrackInformationSimple(bool track, uint32_t lba, uint16_t allocationLength) +{ + uint32_t len = sizeof(TrackInformation); + memcpy(scsiDev.data, TrackInformation, len); + + uint32_t capacity = getScsiCapacity( + scsiDev.target->cfg->sdSectorStart, + scsiDev.target->liveCfg.bytesPerSector, + scsiDev.target->cfg->scsiSectors); + if (!track && lba >= capacity) + { + scsiDev.status = CHECK_CONDITION; + scsiDev.target->sense.code = ILLEGAL_REQUEST; + scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB; + scsiDev.phase = STATUS; + } + else + { + // update track size + scsiDev.data[24] = capacity >> 24; + scsiDev.data[25] = capacity >> 16; + scsiDev.data[26] = capacity >> 8; + scsiDev.data[27] = capacity; + + if (len > allocationLength) + { + len = allocationLength; + } + scsiDev.dataLen = len; + scsiDev.phase = DATA_IN; + } +} + /*********************************/ /* TOC generation from cue sheet */ /*********************************/ @@ -765,6 +830,279 @@ void doReadDiscInformation(uint16_t allocationLength) scsiDev.phase = DATA_IN; } +void doReadTrackInformation(bool track, uint32_t lba, uint16_t allocationLength) +{ + image_config_t &img = *(image_config_t*)scsiDev.target->cfg; + CUEParser parser; + if (!loadCueSheet(img, parser)) + { + // No CUE sheet, use hardcoded data + return doReadTrackInformationSimple(track, lba, allocationLength); + } + + // Take the hardcoded header as base + uint32_t len = sizeof(TrackInformation); + memcpy(scsiDev.data, TrackInformation, len); + + // Step through the tracks until the one requested is found + // Result will be placed in mtrack for later use if found + bool trackfound = false; + uint32_t tracklen = 0; + CUETrackInfo mtrack = {0}; + const CUETrackInfo *trackinfo; + while ((trackinfo = parser.next_track()) != NULL) + { + if (mtrack.track_number != 0) // skip 1st track, just store later + { + if ((track && lba == mtrack.track_number) + || (!track && lba < trackinfo->data_start)) + { + trackfound = true; + tracklen = trackinfo->data_start - mtrack.data_start; + break; + } + } + mtrack = *trackinfo; + } + // try the last track as a final attempt if no match found beforehand + if (!trackfound) + { + uint32_t lastLba = getLeadOutLBA(&mtrack); + if ((track && lba == mtrack.track_number) + || (!track && lba < lastLba)) + { + trackfound = true; + tracklen = lastLba - mtrack.data_start; + } + } + + // bail out if no match found + if (!trackfound) + { + scsiDev.status = CHECK_CONDITION; + scsiDev.target->sense.code = ILLEGAL_REQUEST; + scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB; + scsiDev.phase = STATUS; + return; + } + + // rewrite relevant bytes, starting with track number + scsiDev.data[3] = mtrack.track_number; + + // track mode + if (mtrack.track_mode == CUETrack_AUDIO) + { + scsiDev.data[5] = 0x00; + } + + // track start + uint32_t start = mtrack.data_start; + scsiDev.data[8] = start >> 24; + scsiDev.data[9] = start >> 16; + scsiDev.data[10] = start >> 8; + scsiDev.data[11] = start; + + // track size + scsiDev.data[24] = tracklen >> 24; + scsiDev.data[25] = tracklen >> 16; + scsiDev.data[26] = tracklen >> 8; + scsiDev.data[27] = tracklen; + + dbgmsg("------ Reporting track ", mtrack.track_number, ", start ", start, + ", length ", tracklen); + if (len > allocationLength) + { + len = allocationLength; + } + scsiDev.dataLen = len; + scsiDev.phase = DATA_IN; +} + +void doGetConfiguration(uint8_t rt, uint16_t startFeature, uint16_t allocationLength) +{ + // rt = 0 is all features, rt = 1 is current features, + // rt = 2 only startFeature, others reserved + if (rt > 2) + { + scsiDev.status = CHECK_CONDITION; + scsiDev.target->sense.code = ILLEGAL_REQUEST; + scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB; + scsiDev.phase = STATUS; + return; + } + + image_config_t &img = *(image_config_t*)scsiDev.target->cfg; + + // write feature header + uint32_t len = 8; // length bytes set at end of call + scsiDev.data[4] = 0; // reserved + scsiDev.data[5] = 0; // reserved + if (!img.ejected) + { + // disk in drive, current profile is CD-ROM + scsiDev.data[6] = 0x00; + scsiDev.data[7] = 0x08; + } + else + { + // no disk, report no current profile + scsiDev.data[6] = 0; + scsiDev.data[7] = 0; + } + + // profile list (0) + if ((rt == 2 && 0 == startFeature) + || (rt == 1 && startFeature <= 0) + || (rt == 0 && startFeature <= 0)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x03; // ver 0, persist=1,current=1 + scsiDev.data[len++] = 8; // 2 more + // CD-ROM profile + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x08; + scsiDev.data[len++] = (img.ejected) ? 0x00 : 0x01; + scsiDev.data[len++] = 0; + // removable disk profile + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x02; + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0; + } + + // core feature (1) + if ((rt == 2 && startFeature == 1) + || (rt == 1 && startFeature <= 1) + || (rt == 0 && startFeature <= 1)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x01; + scsiDev.data[len++] = 0x0B; // ver 2, persist=1,current=1 + scsiDev.data[len++] = 8; + // physical interface standard (SCSI) + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x01; + scsiDev.data[len++] = 0x03; // support INQ2 and DBE + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + } + + // morphing feature (2) + if ((rt == 2 && startFeature == 2) + || (rt == 1 && startFeature <= 2) + || (rt == 0 && startFeature <= 2)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x02; + scsiDev.data[len++] = 0x07; // ver 1, persist=1,current=1 + scsiDev.data[len++] = 4; + scsiDev.data[len++] = 0x02; // OCEvent=1,async=0 + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + } + + // removable medium feature (3) + if ((rt == 2 && startFeature == 3) + || (rt == 1 && startFeature <= 3) + || (rt == 0 && startFeature <= 3)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x03; + scsiDev.data[len++] = 0x03; // ver 0, persist=1,current=1 + scsiDev.data[len++] = 4; + scsiDev.data[len++] = 0x28; // matches 0x2A mode page version + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + } + + // random readable feature (0x10, 16) + if ((rt == 2 && startFeature == 16) + || (rt == 1 && startFeature <= 16 && !img.ejected) + || (rt == 0 && startFeature <= 16)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x10; + // ver 0, persist=0,current=drive state + scsiDev.data[len++] = (img.ejected) ? 0x00 : 0x01; + scsiDev.data[len++] = 8; + scsiDev.data[len++] = 0x00; // 2048 (MSB) + scsiDev.data[len++] = 0x00; // . + scsiDev.data[len++] = 0x08; // . + scsiDev.data[len++] = 0x00; // 2048 (LSB) + scsiDev.data[len++] = 0x00; + // one block min when disk in drive only + scsiDev.data[len++] = (img.ejected) ? 0x00 : 0x01; + scsiDev.data[len++] = 0x00; // no support for PP error correction (TODO?) + scsiDev.data[len++] = 0; + } + + // multi-read feature (0x1D, 29) + if ((rt == 2 && startFeature == 29) + || (rt == 1 && startFeature <= 29 && !img.ejected) + || (rt == 0 && startFeature <= 29)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x1D; + // ver 0, persist=0,current=drive state + scsiDev.data[len++] = (img.ejected) ? 0x00 : 0x01; + scsiDev.data[len++] = 0; + } + + // CD read feature (0x1E, 30) + if ((rt == 2 && startFeature == 30) + || (rt == 1 && startFeature <= 30 && !img.ejected) + || (rt == 0 && startFeature <= 30)) + { + scsiDev.data[len++] = 0x00; + scsiDev.data[len++] = 0x1E; + // ver 2, persist=0,current=drive state + scsiDev.data[len++] = (img.ejected) ? 0x08 : 0x09; + scsiDev.data[len++] = 4; + scsiDev.data[len++] = 0x00; // dap=0,c2=0,cd-text=0 + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0; + } + +#ifdef ENABLE_AUDIO_OUTPUT + // CD audio feature (0x103, 259) + if ((rt == 2 && startFeature == 259) + || (rt == 1 && startFeature <= 259 && !img.ejected) + || (rt == 0 && startFeature <= 259)) + { + scsiDev.data[len++] = 0x01; + scsiDev.data[len++] = 0x03; + // ver 1, persist=0,current=drive state + scsiDev.data[len++] = (img.ejected) ? 0x04 : 0x05; + scsiDev.data[len++] = 4; + scsiDev.data[len++] = 0x03; // scan=0,scm=1,sv=1 + scsiDev.data[len++] = 0; + scsiDev.data[len++] = 0x01; // 256 volume levels + scsiDev.data[len++] = 0x00; // . + } +#endif + + // finally, rewrite data length to match + uint32_t dlen = len - 8; + scsiDev.data[0] = dlen >> 24; + scsiDev.data[1] = dlen >> 16; + scsiDev.data[2] = dlen >> 8; + scsiDev.data[3] = dlen; + + if (len > allocationLength) + { + len = allocationLength; + } + scsiDev.dataLen = len; + scsiDev.phase = DATA_IN; +} + /****************************************/ /* CUE sheet check at image load time */ /****************************************/ @@ -1605,6 +1943,18 @@ extern "C" int scsiCDRomCommand() scsiDev.cdb[8]; doReadHeader(MSF, lba, allocationLength); } + else if (command == 0x46) + { + // GET CONFIGURATION + uint8_t rt = (scsiDev.cdb[1] & 0x03); + uint16_t startFeature = + (((uint16_t) scsiDev.cdb[2]) << 8) + + scsiDev.cdb[3]; + uint16_t allocationLength = + (((uint32_t) scsiDev.cdb[7]) << 8) + + scsiDev.cdb[8]; + doGetConfiguration(rt, startFeature, allocationLength); + } else if (command == 0x51) { uint16_t allocationLength = @@ -1612,6 +1962,19 @@ extern "C" int scsiCDRomCommand() scsiDev.cdb[8]; doReadDiscInformation(allocationLength); } + else if (command == 0x52) + { + // READ TRACK INFORMATION + bool track = (scsiDev.cdb[1] & 0x01); + uint32_t lba = (((uint32_t) scsiDev.cdb[2]) << 24) + + (((uint32_t) scsiDev.cdb[3]) << 16) + + (((uint32_t) scsiDev.cdb[4]) << 8) + + scsiDev.cdb[5]; + uint16_t allocationLength = + (((uint32_t) scsiDev.cdb[7]) << 8) + + scsiDev.cdb[8]; + doReadTrackInformation(track, lba, allocationLength); + } else if (command == 0x4A) { // Get event status notifications (media change notifications) diff --git a/src/ZuluSCSI_mode.cpp b/src/ZuluSCSI_mode.cpp index 0fb7676b..415233ad 100644 --- a/src/ZuluSCSI_mode.cpp +++ b/src/ZuluSCSI_mode.cpp @@ -62,6 +62,39 @@ static const uint8_t CDROMAudioControlParametersPage[] = }; #endif +// 0x2A CD-ROM Capabilities and Mechanical Status Page +// This seems to have been standardized in MMC-1 but was de-facto present in +// earlier SCSI-2 drives. The below mirrors one of those earlier SCSI-2 +// implementations, being is slightly shorter than the spec format but +// otherwise returning identical information within the same bytes. +static const uint8_t CDROMCapabilitiesPage[] = +{ +0x2A, // page code +0x0E, // page length +0x00, // CD-R/RW reading not supported +0x00, // CD-R/RW writing not supported +#ifdef ENABLE_AUDIO_OUTPUT +0x01, // byte 4: audio play supported +#else +0x00, // byte 4: no features supported +#endif +0x03, // byte 5: CD-DA ok with accurate streaming, no other features +0x28, // byte 6: tray loader, ejection ok, but prevent/allow not supported +#ifdef ENABLE_AUDIO_OUTPUT +0x03, // byte 7: separate channel mute and volumes +#else +0x00, // byte 7: no features supported +#endif +0x05, 0x62, // max read speed, state (8X, ~1378KB/s) +#ifdef ENABLE_AUDIO_OUTPUT +0x01, 0x00, // 256 volume levels supported +#else +0x00, 0x00, // no volume levels supported +#endif +0x00, 0x40, // read buffer (64KB) +0x05, 0x62, // current read speed, matching max speed +}; + static void pageIn(int pc, int dataIdx, const uint8_t* pageData, int pageLen) { memcpy(&scsiDev.data[dataIdx], pageData, pageLen); @@ -144,6 +177,25 @@ int modeSenseCDAudioControlPage(int pc, int idx, int pageCode, int* pageFound) #endif } +int modeSenseCDCapabilitiesPage(int pc, int idx, int pageCode, int* pageFound) +{ + if ((scsiDev.target->cfg->deviceType == S2S_CFG_OPTICAL) + && (pageCode == 0x2A || pageCode == 0x3F)) + { + *pageFound = 1; + pageIn( + pc, + idx, + CDROMCapabilitiesPage, + sizeof(CDROMCapabilitiesPage)); + return sizeof(CDROMCapabilitiesPage); + } + else + { + return 0; + } +} + extern "C" int modeSelectCDAudioControlPage(int pageLen, int idx) { diff --git a/src/ZuluSCSI_mode.h b/src/ZuluSCSI_mode.h index 73fb8b8c..d128ddf9 100644 --- a/src/ZuluSCSI_mode.h +++ b/src/ZuluSCSI_mode.h @@ -23,5 +23,6 @@ int modeSenseCDDevicePage(int pc, int idx, int pageCode, int* pageFound); int modeSenseCDAudioControlPage(int pc, int idx, int pageCode, int* pageFound); +int modeSenseCDCapabilitiesPage(int pc, int idx, int pageCode, int* pageFound); int modeSelectCDAudioControlPage(int pageLen, int idx);