diff options
Diffstat (limited to 'src/devices/bus/nscsi/cd.cpp')
-rw-r--r-- | src/devices/bus/nscsi/cd.cpp | 1258 |
1 files changed, 1172 insertions, 86 deletions
diff --git a/src/devices/bus/nscsi/cd.cpp b/src/devices/bus/nscsi/cd.cpp index 93424f493fa..5bad79ed8c4 100644 --- a/src/devices/bus/nscsi/cd.cpp +++ b/src/devices/bus/nscsi/cd.cpp @@ -1,21 +1,41 @@ // license:BSD-3-Clause // copyright-holders:Olivier Galibert #include "emu.h" +#include "emuopts.h" + #include "bus/nscsi/cd.h" -#define VERBOSE 0 +#include "coreutil.h" +#include "multibyte.h" + +#define VERBOSE (0) #include "logmacro.h" DEFINE_DEVICE_TYPE(NSCSI_CDROM, nscsi_cdrom_device, "scsi_cdrom", "SCSI CD-ROM") DEFINE_DEVICE_TYPE(NSCSI_CDROM_SGI, nscsi_cdrom_sgi_device, "scsi_cdrom_sgi", "SCSI CD-ROM SGI") +DEFINE_DEVICE_TYPE(NSCSI_CDROM_NEWS, nscsi_cdrom_news_device, "scsi_cdrom_news", "SCSI CD-ROM NEWS") DEFINE_DEVICE_TYPE(NSCSI_RRD45, nscsi_dec_rrd45_device, "nrrd45", "RRD45 CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_XM3301, nscsi_toshiba_xm3301_device, "nxm3301", "XM-3301TA CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_XM5301SUN, nscsi_toshiba_xm5301_sun_device, "nxm5301sun", "XM-5301B Sun 4x CD-ROM (New)") +DEFINE_DEVICE_TYPE(NSCSI_XM5401, nscsi_toshiba_xm5401_device, "nxm5401", "XM-5401B 4x CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_XM5401SUN, nscsi_toshiba_xm5401_sun_device, "nxm5401sun", "XM-5401B Sun 4x CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_XM5701, nscsi_toshiba_xm5701_device, "nxm5701", "XM-5701B 12x CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_XM5701SUN, nscsi_toshiba_xm5701_sun_device, "nxm5701sun", "XM-5701B Sun 12x CD-ROM (New)") DEFINE_DEVICE_TYPE(NSCSI_CDROM_APPLE, nscsi_cdrom_apple_device, "scsi_cdrom_apple", "Apple SCSI CD-ROM") +// ZuluSCSI/BlueSCSI toolbox commands. All are 10 bytes as of protocol version 0. +static constexpr uint8_t TOOLBOX_LIST_FILES = 0xd0; +static constexpr uint8_t TOOLBOX_GET_FILE = 0xd1; +static constexpr uint8_t TOOLBOX_COUNT_FILES = 0xd2; +static constexpr uint8_t TOOLBOX_SEND_FILE_PREP = 0xd3; +static constexpr uint8_t TOOLBOX_SEND_FILE_10 = 0xd4; +static constexpr uint8_t TOOLBOX_SEND_FILE_END = 0xd5; +static constexpr uint8_t TOOLBOX_TOGGLE_DEBUG = 0xd6; +static constexpr uint8_t TOOLBOX_LIST_CDS = 0xd7; +static constexpr uint8_t TOOLBOX_SET_NEXT_CD = 0xd8; +static constexpr uint8_t TOOLBOX_LIST_DEVICES = 0xd9; +static constexpr uint8_t TOOLBOX_COUNT_CDS = 0xda; + nscsi_cdrom_device::nscsi_cdrom_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : nscsi_cdrom_device(mconfig, NSCSI_CDROM, tag, owner, "Sony", "CDU-76S", "1.0", 0x00, 0x05) { @@ -26,6 +46,11 @@ nscsi_cdrom_sgi_device::nscsi_cdrom_sgi_device(const machine_config &mconfig, co { } +nscsi_cdrom_news_device::nscsi_cdrom_news_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + nscsi_cdrom_device(mconfig, NSCSI_CDROM_NEWS, tag, owner, "Sony", "CD-ROM CDU-541", "1.0A", 0x00, 0x05) +{ +} + nscsi_dec_rrd45_device::nscsi_dec_rrd45_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : nscsi_cdrom_device(mconfig, NSCSI_RRD45, tag, owner, "DEC ", "RRD45 (C) DEC ", "0436", 0x98, 0x02) { @@ -41,6 +66,11 @@ nscsi_toshiba_xm5301_sun_device::nscsi_toshiba_xm5301_sun_device(const machine_c { } +nscsi_toshiba_xm5401_device::nscsi_toshiba_xm5401_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : + nscsi_cdrom_device(mconfig, NSCSI_XM5401, tag, owner, "TOSHIBA ", "CD-ROM XM-5401TA", "3605", 0x98, 0x02) +{ +} + nscsi_toshiba_xm5401_sun_device::nscsi_toshiba_xm5401_sun_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : nscsi_cdrom_device(mconfig, NSCSI_XM5401SUN, tag, owner, "TOSHIBA ", "XM-5401TASUN4XCD", "1036", 0x98, 0x02) { @@ -56,41 +86,56 @@ nscsi_toshiba_xm5701_sun_device::nscsi_toshiba_xm5701_sun_device(const machine_c { } +// drive identifies as an original Apple CDSC (Sony CDU-8001 with custom firmware) nscsi_cdrom_apple_device::nscsi_cdrom_apple_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : - nscsi_cdrom_device(mconfig, NSCSI_CDROM_APPLE, tag, owner, "Sony", "CDU-76S", "1.0", 0x00, 0x05) + nscsi_cdrom_device(mconfig, NSCSI_CDROM_APPLE, tag, owner, "SONY ", "CD-ROM CDU-8002 ", "1.8g", 0x00, 0x05), + m_stopped(true), + m_stop_position(0) { } nscsi_cdrom_device::nscsi_cdrom_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : nscsi_full_device(mconfig, type, tag, owner, clock) - , cdrom(nullptr) + , image(*this, "image") + , cdda(*this, "cdda") + , m_removal_prevented(false) , bytes_per_block(bytes_per_sector) , lba(0) , cur_sector(0) - , image(*this, "image") { } void nscsi_cdrom_device::device_start() { nscsi_full_device::device_start(); + + m_xfer_buffer.resize(8192); + save_item(NAME(sector_buffer)); save_item(NAME(lba)); save_item(NAME(cur_sector)); save_item(NAME(bytes_per_block)); + save_item(NAME(mode_data_size)); + save_item(NAME(m_xfer_position)); + save_item(NAME(m_write_length)); + save_item(NAME(m_write_offset)); + save_item(NAME(m_write_is_setup)); } void nscsi_cdrom_device::device_reset() { nscsi_full_device::device_reset(); - cdrom = image->get_cdrom_file(); + sequence_counter = image->sequence_counter(); lba = 0; cur_sector = -1; + mode_data_size = 12; } void nscsi_cdrom_device::device_add_mconfig(machine_config &config) { CDROM(config, image).set_interface("cdrom"); + + CDDA(config, cdda).set_cdrom_tag(image); } int nscsi_cdrom_device::to_msf(int frame) @@ -105,21 +150,39 @@ int nscsi_cdrom_device::to_msf(int frame) void nscsi_cdrom_device::set_block_size(u32 block_size) { if (bytes_per_sector % block_size) - throw emu_fatalerror("nscsi_cdrom_device(%s): block size must be a factor of sector size", tag()); + { + logerror("nscsi_cdrom_device(%s): block size %d must be a factor of sector size %d", tag(), block_size, bytes_per_sector); + return; + } bytes_per_block = block_size; }; +bool nscsi_cdrom_device::scsi_command_done(uint8_t command, uint8_t length) +{ + switch (command & 0xd0) + { + case 0xd0: // ZuluSCSI/BlueSCSI toolbox commands + return length == 10; + + default: + return nscsi_full_device::scsi_command_done(command, length); + } +} uint8_t nscsi_cdrom_device::scsi_get_data(int id, int pos) { + if (id == 4) + return m_xfer_buffer[m_xfer_position++]; + if(id != 2) return nscsi_full_device::scsi_get_data(id, pos); + const int sector = (lba * bytes_per_block + pos) / bytes_per_sector; const int extra_pos = (lba * bytes_per_block) % bytes_per_sector; if(sector != cur_sector) { cur_sector = sector; - if(!cdrom_read_data(cdrom, sector, sector_buffer, CD_TRACK_MODE1)) { + if(!image->read_data(sector, sector_buffer, cdrom_file::CD_TRACK_MODE1)) { LOG("CD READ ERROR sector %d!\n", sector); std::fill_n(sector_buffer, sizeof(sector_buffer), 0); } @@ -129,20 +192,101 @@ uint8_t nscsi_cdrom_device::scsi_get_data(int id, int pos) void nscsi_cdrom_device::scsi_put_data(int id, int pos, uint8_t data) { + if (id == 4) { + m_xfer_buffer[m_xfer_position] = data; + m_xfer_position++; + if (m_write_is_setup) { + if (m_xfer_position == 33) { + osd_file::ptr file; + uint64_t filesize; + m_write_path.clear(); + m_write_path = machine().options().share_directory(); + m_write_path.append(PATH_SEPARATOR); + m_write_path.append((char *)&m_xfer_buffer[0]); + if (osd_file::open(m_write_path, OPEN_FLAG_CREATE | OPEN_FLAG_WRITE, file, filesize)) { + LOG("Open for write/creation failed for [%s]\n", m_write_path.c_str()); + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, SK_ASC_INVALID_FIELD_IN_CDB); + return; + } + } + } + else + { + if (m_xfer_position == m_write_length) { + uint32_t actualWritten; + osd_file::ptr file; + uint64_t filesize; + + LOG("flushing [%s] to disk at %08x\n", m_write_path.c_str(), m_write_offset * 512); + osd_file::open(m_write_path, OPEN_FLAG_WRITE, file, filesize); + file->write(&m_xfer_buffer[0], m_write_offset * 512, m_write_length, actualWritten); + + if (actualWritten != m_write_length) { + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, SK_ASC_INVALID_FIELD_IN_CDB); + } + } + } + return; + } + if(id != 2) { nscsi_full_device::scsi_put_data(id, pos, data); return; } // process mode parameter header and one block descriptor - if(pos < sizeof(mode_data)) { + if(pos < mode_data_size) { mode_data[pos] = data; // is this the last byte of the mode parameter block descriptor? - if(pos == sizeof(mode_data) - 1) + if(pos == mode_data_size - 1) { // is there exactly one block descriptor? if(mode_data[3] == 8) - set_block_size((mode_data[9] << 16) | (mode_data[10] << 8) | (mode_data[11] << 0)); + set_block_size(get_u24be(&mode_data[9])); + + // Size of block descriptor should change based on if it's mode select (6) or (10) + // TODO: Find a usage of mode select (10) for testing + int pagedata_offset = 4 + mode_data[3]; + switch(mode_data[pagedata_offset] & 0x3f) { + case 0x0e: // CD Audio Control Mode Page + cdda_sotc = BIT(mode_data[pagedata_offset+2], 1); + LOG("Ch 0 route: %x vol: %x\n", mode_data[pagedata_offset+8], mode_data[pagedata_offset+9]); + LOG("Ch 1 route: %x vol: %x\n", mode_data[pagedata_offset+10], mode_data[pagedata_offset+11]); + LOG("Ch 2 route: %x vol: %x\n", mode_data[pagedata_offset+12], mode_data[pagedata_offset+13]); + LOG("Ch 3 route: %x vol: %x\n", mode_data[pagedata_offset+14], mode_data[pagedata_offset+15]); + cdda->set_output_gain(0, mode_data[pagedata_offset+9] / 255.0f); + cdda->set_output_gain(1, mode_data[pagedata_offset+11] / 255.0f); + break; + } + } + } +} + +void nscsi_cdrom_device::update_directory() +{ + std::string tmpPath(machine().options().share_directory()); + osd::directory::ptr directory = osd::directory::open(tmpPath); + + m_directory.clear(); + + if (directory != nullptr) + { + const osd::directory::entry *ourEntry; + + while ((ourEntry = directory->read()) != nullptr) + { + m_directory.push_back(*ourEntry); + + // API version 0 has a hard cap of 100 files + if (m_directory.size() >= 99) + { + break; + } + } + + directory.reset(); } } @@ -157,11 +301,11 @@ void nscsi_cdrom_device::scsi_command() int blocks; // check for media change - if((cdrom != image->get_cdrom_file()) && (scsi_cmdbuf[0] != SC_INQUIRY)) + if( sequence_counter != image->sequence_counter() && (scsi_cmdbuf[0] != SC_INQUIRY)) { // clear media change condition - cdrom = image->get_cdrom_file(); cur_sector = -1; + sequence_counter = image->sequence_counter(); // report unit attention condition sense(false, SK_UNIT_ATTENTION); @@ -172,19 +316,19 @@ void nscsi_cdrom_device::scsi_command() switch(scsi_cmdbuf[0]) { case SC_TEST_UNIT_READY: LOG("command TEST UNIT READY\n"); - if(cdrom) + if(image->exists()) scsi_status_complete(SS_GOOD); else return_no_cd(); break; case SC_READ_6: - if(!cdrom) { + if(!image->exists()) { return_no_cd(); break; } - lba = ((scsi_cmdbuf[1] & 0x1f)<<16) | (scsi_cmdbuf[2]<<8) | scsi_cmdbuf[3]; + lba = get_u24be(&scsi_cmdbuf[1]) & 0x1fffff; blocks = scsi_cmdbuf[4]; if(!blocks) blocks = 256; @@ -219,7 +363,7 @@ void nscsi_cdrom_device::scsi_command() int size = scsi_cmdbuf[4]; switch(page) { case 0: - std::fill_n(scsi_cmdbuf, 36, 0); + std::fill_n(scsi_cmdbuf, size, 0); if (lun != 0) scsi_cmdbuf[0] = 0x7f; @@ -239,8 +383,6 @@ void nscsi_cdrom_device::scsi_command() if(scsi_cmdbuf[i] == 0) scsi_cmdbuf[i] = 0x20; - if(size > 36) - size = 36; scsi_data_in(SBUF_MAIN, size); break; } @@ -252,8 +394,9 @@ void nscsi_cdrom_device::scsi_command() LOG("command MODE SELECT 6 length %d\n", scsi_cmdbuf[4]); // accept mode select parameter data - if(scsi_cmdbuf[4]) - scsi_data_out(2, scsi_cmdbuf[4]); + mode_data_size = scsi_cmdbuf[4]; + if(mode_data_size) + scsi_data_out(2, mode_data_size); scsi_status_complete(SS_GOOD); break; @@ -264,9 +407,9 @@ void nscsi_cdrom_device::scsi_command() scsi_status_complete(SS_GOOD); break; - case SC_RECIEVE_DIAG_RES: { - LOG("command RECIEVE DIAGNOSTICS RESULTS"); - int size = (scsi_cmdbuf[3] << 8) | scsi_cmdbuf[4]; + case SC_RECEIVE_DIAGNOSTIC_RESULTS: { + LOG("command RECEIVE DIAGNOSTIC RESULTS"); + int size = get_u16be(&scsi_cmdbuf[3]); int pos = 0; scsi_cmdbuf[pos++] = 0; scsi_cmdbuf[pos++] = 6; @@ -282,9 +425,9 @@ void nscsi_cdrom_device::scsi_command() break; } - case SC_SEND_DIAGNOSTICS: { - LOG("command SEND DIAGNOSTICS"); - int size = (scsi_cmdbuf[3] << 8) | scsi_cmdbuf[4]; + case SC_SEND_DIAGNOSTIC: { + LOG("command SEND DIAGNOSTIC"); + int size = get_u16be(&scsi_cmdbuf[3]); if(scsi_cmdbuf[1] & 4) { // Self-test scsi_status_complete(SS_GOOD); @@ -307,24 +450,20 @@ void nscsi_cdrom_device::scsi_command() } case SC_READ_CAPACITY: { - if(!cdrom) { + LOG("command READ CAPACITY\n"); + + if(!image->exists()) { return_no_cd(); break; } - LOG("command READ CAPACITY\n"); - // get the last used block on the disc - const u32 temp = cdrom_get_track_start(cdrom, 0xaa) * (bytes_per_sector / bytes_per_block) - 1; + const u32 temp = image->get_track_start(0xaa) * (bytes_per_sector / bytes_per_block) - 1; - scsi_cmdbuf[0] = (temp>>24) & 0xff; - scsi_cmdbuf[1] = (temp>>16) & 0xff; - scsi_cmdbuf[2] = (temp>>8) & 0xff; - scsi_cmdbuf[3] = (temp & 0xff); + put_u32be(&scsi_cmdbuf[0], temp); scsi_cmdbuf[4] = 0; scsi_cmdbuf[5] = 0; - scsi_cmdbuf[6] = (bytes_per_block>>8)&0xff; - scsi_cmdbuf[7] = (bytes_per_block & 0xff); + put_u16be(&scsi_cmdbuf[6], bytes_per_block); scsi_data_in(SBUF_MAIN, 8); scsi_status_complete(SS_GOOD); @@ -332,11 +471,11 @@ void nscsi_cdrom_device::scsi_command() } case SC_READ_10: - lba = (scsi_cmdbuf[2]<<24) | (scsi_cmdbuf[3]<<16) | (scsi_cmdbuf[4]<<8) | scsi_cmdbuf[5]; - blocks = (scsi_cmdbuf[7] << 8) | scsi_cmdbuf[8]; + lba = get_u32be(&scsi_cmdbuf[2]); + blocks = get_u16be(&scsi_cmdbuf[7]); LOG("command READ EXTENDED start=%08x blocks=%04x\n", lba, blocks); - if(!cdrom) { + if(!image->exists()) { return_no_cd(); break; } @@ -354,24 +493,27 @@ void nscsi_cdrom_device::scsi_command() return; } - int page = scsi_cmdbuf[2] & 0x3f; - int size = scsi_cmdbuf[4]; + const int page = scsi_cmdbuf[2] & 0x3f; + const int size = scsi_cmdbuf[4]; + const bool noBlockDesc = BIT(scsi_cmdbuf[1], 3); int pos = 1; scsi_cmdbuf[pos++] = 0x00; // medium type scsi_cmdbuf[pos++] = 0x80; // WP, cache // get the last used block on the disc - const u32 temp = cdrom_get_track_start(cdrom, 0xaa) * (bytes_per_sector / bytes_per_block) - 1; - scsi_cmdbuf[pos++] = 0x08; // Block descriptor length + const u32 temp = image->exists() ? image->get_track_start(0xaa) * (bytes_per_sector / bytes_per_block) - 1 : 0; + scsi_cmdbuf[pos++] = noBlockDesc ? 0x00 : 0x08; // 0 or 8 bytes of block descriptor - scsi_cmdbuf[pos++] = 0x00; // density code - scsi_cmdbuf[pos++] = (temp>>16) & 0xff; - scsi_cmdbuf[pos++] = (temp>>8) & 0xff; - scsi_cmdbuf[pos++] = (temp & 0xff); - scsi_cmdbuf[pos++] = 0; - scsi_cmdbuf[pos++] = 0; - scsi_cmdbuf[pos++] = (bytes_per_block>>8)&0xff; - scsi_cmdbuf[pos++] = (bytes_per_block & 0xff); + if (!noBlockDesc) + { + scsi_cmdbuf[pos++] = 0x00; // density code + put_u24be(&scsi_cmdbuf[pos], temp); + pos += 3; + scsi_cmdbuf[pos++] = 0; + scsi_cmdbuf[pos++] = 0; + put_u16be(&scsi_cmdbuf[pos], bytes_per_block); + pos += 2; + } bool fail = false; int pmax = page == 0x3f ? 0x3e : page; @@ -420,20 +562,72 @@ void nscsi_cdrom_device::scsi_command() scsi_cmdbuf[pos++] = 0x00; // Reserved break; - default: - if (page != 0x3f) { - LOG("mode sense page %02x unhandled\n", p); - fail = true; + case 0x0e: // CD Audio control page + scsi_cmdbuf[pos++] = 0x8e; // PS, page id + scsi_cmdbuf[pos++] = 0x0e; // Page length + scsi_cmdbuf[pos++] = (1 << 2) | (cdda_sotc << 1); // IMMED = 1 + scsi_cmdbuf[pos++] = 0x00; // Reserved + scsi_cmdbuf[pos++] = 0x00; + scsi_cmdbuf[pos++] = 0x00; + scsi_cmdbuf[pos++] = 0x00; + scsi_cmdbuf[pos++] = 0x00; + scsi_cmdbuf[pos++] = 0x01; // Ch 1 connected + scsi_cmdbuf[pos++] = 0xff; // Ch 1 max volume + scsi_cmdbuf[pos++] = 0x02; // Ch 2 connected + scsi_cmdbuf[pos++] = 0xff; // Ch 2 max volume + scsi_cmdbuf[pos++] = 0x04; // Ch 3 connected + scsi_cmdbuf[pos++] = 0xff; // Ch 3 max volume + scsi_cmdbuf[pos++] = 0x08; // Ch 4 connected + scsi_cmdbuf[pos++] = 0xff; // Ch 4 max volume + break; + + case 0x30: // magic Apple page + { + static const u8 apple_magic[0x17] = + { + 0x00, 0x41, 0x50, 0x50, 0x4C, 0x45, 0x20, 0x43, 0x4F, 0x4D, 0x50, 0x55, + 0x54, 0x45, 0x52, 0x2C, 0x20, 0x49, 0x4E, 0x43, 0x20, 0x20, 0x20 + }; + + LOG("Apple special MODE SENSE page\n"); + scsi_cmdbuf[pos++] = 0x30; // PS, page id + memcpy(&scsi_cmdbuf[pos], apple_magic, 0x17); + pos += 0x17; + } + break; + + case 0x31: // magic BlueSCSI page; official clients require this to recognize a toolbox-capable device + { + // Yes this is lame for a GPLv3 project. + static const u8 bluescsi_magic[] = + { + "BlueSCSI is the BEST STOLEN FROM BLUESCSI\0" + }; + + scsi_cmdbuf[pos++] = 0x31; // page ID + scsi_cmdbuf[pos++] = 42; // page length + memcpy(&scsi_cmdbuf[pos], bluescsi_magic, 42); + pos += 42; } break; + + default: + if (page != 0x3f) + { + LOG("mode sense page %02x unhandled\n", p); + fail = true; + } + break; } } - scsi_cmdbuf[0] = pos; - if(pos > size) - pos = size; + scsi_cmdbuf[0] = pos - 1; // return -1 for mode sense 6 if (!fail) { - scsi_data_in(0, pos); + if (size < pos) + scsi_data_in(0, size); + else + scsi_data_in(0, pos); + scsi_status_complete(SS_GOOD); } else { scsi_status_complete(SS_CHECK_CONDITION); @@ -442,11 +636,117 @@ void nscsi_cdrom_device::scsi_command() break; } + case SC_READ_DISC_INFORMATION: + LOG("command READ DISC INFORMATION\n"); + if(!image->exists()) { + return_no_cd(); + break; + } + + std::fill_n(scsi_cmdbuf, 34, 0); + scsi_cmdbuf[1] = 32; + scsi_cmdbuf[2] = 2; // disc is complete + scsi_cmdbuf[3] = 1; // first track + scsi_cmdbuf[4] = 1; // number of sessions (TODO: session support for CHDv6) + scsi_cmdbuf[5] = 1; // first track in last session + scsi_cmdbuf[6] = image->get_last_track(); // last track in last session + scsi_cmdbuf[8] = 0; // CD-ROM, not XA + + // lead in start time in MSF + { + u32 tstart = image->get_track_start(0); + tstart = to_msf(tstart + 150); + + put_u32be(&scsi_cmdbuf[16], tstart); + + // lead-out start time in MSF + tstart = image->get_track_start(0xaa); + tstart = to_msf(tstart + 150); + + put_u32be(&scsi_cmdbuf[20], tstart); + } + + scsi_data_in(0, 34); + scsi_status_complete(SS_GOOD); + break; + case SC_PREVENT_ALLOW_MEDIUM_REMOVAL: // TODO: support eject prevention LOG("command %s MEDIUM REMOVAL\n", (scsi_cmdbuf[4] & 0x1) ? "PREVENT" : "ALLOW"); + m_removal_prevented = BIT(scsi_cmdbuf[4], 0); + scsi_status_complete(SS_GOOD); + break; + + case SC_READ_SUB_CHANNEL: { + const bool msf = BIT(scsi_cmdbuf[1], 1) != 0; + const bool subq = BIT(scsi_cmdbuf[2], 6); + const int param = scsi_cmdbuf[3]; + const int track = scsi_cmdbuf[6]; + const int alloc_length = get_u16be(&scsi_cmdbuf[7]); + + LOG("command READ SUB CHANNEL Param = %d, Track = %d, MSF = %d, SUBQ = %d\n", param, track, msf, subq); + + if(!image->exists()) { + return_no_cd(); + break; + } + + std::fill_n(scsi_cmdbuf, alloc_length, 0); + + scsi_cmdbuf[0] = 0x00; // Reserved + + int audio_active = cdda->audio_active(); + if(audio_active) { + if(cdda->audio_paused()) + scsi_cmdbuf[1] = 0x12; // Audio play operation paused + else + scsi_cmdbuf[1] = 0x11; // Audio play operation in progress + } else { + if(cdda->audio_ended()) + scsi_cmdbuf[1] = 0x13; // Audio play operation successfully completed + else + scsi_cmdbuf[1] = 0x15; // No current audio status to return + } + + if(subq) { + switch(param) { + case 1: { // CD current position + int m_last_lba = audio_active ? cdda->get_audio_lba() : 0; + + scsi_cmdbuf[2] = 0; // Sub-channel Data Length + scsi_cmdbuf[3] = 12; + + scsi_cmdbuf[4] = 0x01; // Sub-channel Data Format Code + scsi_cmdbuf[5] = (audio_active ? 0 : 4) | 0x10; // Q Sub-channel encodes current position data + scsi_cmdbuf[6] = image->get_track(m_last_lba) + 1; // Track Number + scsi_cmdbuf[7] = 0x00; // Index Number + + uint32_t frame = m_last_lba; + if(msf) + frame = to_msf(frame); + + put_u32be(&scsi_cmdbuf[8], frame); // Absolute CD Address + + frame = m_last_lba - image->get_track_start(scsi_cmdbuf[6] - 1); + if(msf) + frame = to_msf(frame); + + put_u32be(&scsi_cmdbuf[12], frame); // Track Relative CD Address + break; + } + + case 0x02: // Media Catalog number (UPC/bar code) + case 0x03: // Track International standard recording code (ISRC) + default: + LOG("Unknown subchannel type %d requested\n", scsi_cmdbuf[3]); + break; + } + } + + scsi_data_in(0, alloc_length); scsi_status_complete(SS_GOOD); break; + } case SC_READ_TOC_PMA_ATIP: { /* @@ -460,7 +760,7 @@ void nscsi_cdrom_device::scsi_command() "Session info", "Full TOC", "PMA", - "ATIP" + "ATIP", "Reserved 5", "Reserved 6", "Reserved 7", @@ -475,7 +775,7 @@ void nscsi_cdrom_device::scsi_command() }; bool msf = (scsi_cmdbuf[1] & 0x2) != 0; - u16 size = (scsi_cmdbuf[7] << 7) | scsi_cmdbuf[8]; + u16 size = get_u16be(&scsi_cmdbuf[7]); u8 format = scsi_cmdbuf[2] & 15; /// SFF8020 legacy format field (see T10/1836-D Revision 2g page 643) @@ -484,11 +784,16 @@ void nscsi_cdrom_device::scsi_command() LOG("command READ TOC PMA ATIP, format %s msf=%d size=%d\n", format_names[format], msf, size); + if(!image->exists()) { + return_no_cd(); + break; + } + int pos = 0; switch (format) { case 0: { int start_track = scsi_cmdbuf[6]; - int end_track = cdrom_get_last_track(cdrom); + int end_track = image->get_last_track(); int tracks; if(start_track == 0) @@ -504,10 +809,10 @@ void nscsi_cdrom_device::scsi_command() // the returned TOC DATA LENGTH must be the full amount, // regardless of how much we're able to pass back due to size - scsi_cmdbuf[pos++] = (len>>8) & 0xff; - scsi_cmdbuf[pos++] = (len & 0xff); + put_u16be(&scsi_cmdbuf[pos], len); + pos += 2; scsi_cmdbuf[pos++] = 1; - scsi_cmdbuf[pos++] = cdrom_get_last_track(cdrom); + scsi_cmdbuf[pos++] = image->get_last_track(); if (start_track == 0) start_track = 1; @@ -521,19 +826,17 @@ void nscsi_cdrom_device::scsi_command() } scsi_cmdbuf[pos++] = 0; - scsi_cmdbuf[pos++] = cdrom_get_adr_control(cdrom, cdrom_track); + scsi_cmdbuf[pos++] = image->get_adr_control(cdrom_track); scsi_cmdbuf[pos++] = track; scsi_cmdbuf[pos++] = 0; - u32 tstart = cdrom_get_track_start(cdrom, cdrom_track); + u32 tstart = image->get_track_start(cdrom_track); if(msf) tstart = to_msf(tstart+150); - scsi_cmdbuf[pos++] = (tstart>>24) & 0xff; - scsi_cmdbuf[pos++] = (tstart>>16) & 0xff; - scsi_cmdbuf[pos++] = (tstart>>8) & 0xff; - scsi_cmdbuf[pos++] = (tstart & 0xff); + put_u32be(&scsi_cmdbuf[pos], tstart); + pos += 4; } break; } @@ -541,25 +844,23 @@ void nscsi_cdrom_device::scsi_command() case 1: { int len = 2 + (8 * 1); - scsi_cmdbuf[pos++] = (len>>8) & 0xff; - scsi_cmdbuf[pos++] = (len & 0xff); + put_u16be(&scsi_cmdbuf[pos], len); + pos += 2; scsi_cmdbuf[pos++] = 1; scsi_cmdbuf[pos++] = 1; scsi_cmdbuf[pos++] = 0; - scsi_cmdbuf[pos++] = cdrom_get_adr_control(cdrom, 0); + scsi_cmdbuf[pos++] = image->get_adr_control(0); scsi_cmdbuf[pos++] = 1; scsi_cmdbuf[pos++] = 0; - u32 tstart = cdrom_get_track_start(cdrom, 0); + u32 tstart = image->get_track_start(0); if (msf) tstart = to_msf(tstart+150); - scsi_cmdbuf[pos++] = (tstart>>24) & 0xff; - scsi_cmdbuf[pos++] = (tstart>>16) & 0xff; - scsi_cmdbuf[pos++] = (tstart>>8) & 0xff; - scsi_cmdbuf[pos++] = (tstart & 0xff); + put_u32be(&scsi_cmdbuf[pos], tstart); + pos += 4; break; } @@ -583,6 +884,162 @@ void nscsi_cdrom_device::scsi_command() break; } + case SC_PLAY_AUDIO_TRACK_INDEX: { + LOG("command PLAY AUDIO TRACK INDEX tr %d/idx %d to tr %d/idx %d\n", scsi_cmdbuf[4], scsi_cmdbuf[5], scsi_cmdbuf[7], scsi_cmdbuf[8]); + + if(scsi_cmdbuf[4] > scsi_cmdbuf[7]) { + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, 0x00, 0x14); + break; + } + + // TODO: Index isn't accounted for at all + const uint32_t start_track = scsi_cmdbuf[4]; + const uint32_t end_track = cdda_sotc ? start_track : std::min(image->get_last_track(), (int)scsi_cmdbuf[7]); + const uint32_t lba = image->get_track_start(start_track - 1); + const uint32_t blocks = image->get_track_start(end_track) - lba; + const uint32_t track = image->get_track(lba); + + if(image->get_track_type(track) == cdrom_file::CD_TRACK_AUDIO) { + LOG("Playing %d blocks from track %d (lba %d)\n", blocks, start_track, lba); + + cdda->start_audio(lba, blocks); + scsi_status_complete(SS_GOOD); + sense(false, SK_NO_SENSE, 0x00, 0x11); + } else { + LOG("Track %d is not audio\n", start_track); + + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, 0x64, 0x00); + } + break; + } + + case SC_PAUSE_RESUME: + if (image->exists()) + cdda->pause_audio(BIT(scsi_cmdbuf[8], 0) ^ 1); + + scsi_status_complete(SS_GOOD); + break; + + case TOOLBOX_COUNT_FILES: + LOG("TOOLBOX_COUNT_FILES\n"); + + update_directory(); + + scsi_cmdbuf[0] = m_directory.size(); + scsi_data_in(0, 1); + scsi_status_complete(SS_GOOD); + break; + + case TOOLBOX_LIST_FILES: + { + LOG("TOOLBOX_LIST_FILES\n"); + + // it's assumed clients will always do COUNT_FILES before LIST_FILES, because otherwise + // they'll have no idea how much data is coming. + int pos = 0; + int index = 0; + + if (m_directory.size() > 0) + { + while (index < m_directory.size()) + { + scsi_cmdbuf[pos] = index; + scsi_cmdbuf[pos + 1] = m_directory[index].type != osd::directory::entry::entry_type::DIR; + // There's a guaranteed null terminator one byte after the name field + strncpy(reinterpret_cast<char *>(&scsi_cmdbuf[pos + 2]), m_directory[index].name, 31); + scsi_cmdbuf[pos + 36] = (m_directory[index].size >> 24) & 0xff; + scsi_cmdbuf[pos + 37] = (m_directory[index].size >> 16) & 0xff; + scsi_cmdbuf[pos + 38] = (m_directory[index].size >> 8) & 0xff; + scsi_cmdbuf[pos + 39] = m_directory[index].size & 0xff; + + LOG("%02d: %s %08x\n", index, m_directory[index].name, (uint32_t)m_directory[index].size); + + index++; + pos += 40; + } + } + + scsi_data_in(0, pos); + scsi_status_complete(SS_GOOD); + break; + } + + case TOOLBOX_GET_FILE: + { + update_directory(); + + if (scsi_cmdbuf[1] >= m_directory.size()) + { + LOG("TOOLBOX_GET_FILE: out of range file %d, %d available\n", scsi_cmdbuf[1], (int)m_directory.size()); + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, SK_ASC_INVALID_FIELD_IN_CDB); + return; + } + + uint32_t offset = scsi_cmdbuf[2] << 24 | scsi_cmdbuf[3] << 16 | scsi_cmdbuf[4] << 8 | scsi_cmdbuf[5]; + LOG("TOOLBOX_GET_FILE: file # %d (%s), offset %08x\n", scsi_cmdbuf[1], m_directory[scsi_cmdbuf[1]].name, offset); + + std::string tmpPath(machine().options().share_directory()); + tmpPath.append(PATH_SEPARATOR); + tmpPath.append(m_directory[scsi_cmdbuf[1]].name); + + osd_file::ptr file; + u64 size; + if (osd_file::open(tmpPath, OPEN_FLAG_READ, file, size)) + { + LOG("Open failed for [%s]\n", tmpPath.c_str()); + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, SK_ASC_INVALID_FIELD_IN_CDB); + return; + } + u32 actualRead; + file->read(&m_xfer_buffer[0], offset * 4096, 4096, actualRead); + + if (actualRead == 0) + { + scsi_status_complete(SS_CHECK_CONDITION); + sense(false, SK_ILLEGAL_REQUEST, SK_ASC_INVALID_FIELD_IN_CDB); + return; + } + + m_xfer_position = 0; + scsi_data_in(4, actualRead); + scsi_status_complete(SS_GOOD); + break; + } + + case TOOLBOX_SEND_FILE_PREP: + LOG("TOOLBOX_SEND_FILE_PREP\n"); + m_xfer_position = 0; + m_write_is_setup = true; + scsi_data_out(4, 33); + scsi_status_complete(SS_GOOD); + break; + + case TOOLBOX_SEND_FILE_10: + m_xfer_position = 0; + m_write_is_setup = false; + m_write_length = scsi_cmdbuf[1] << 8 | scsi_cmdbuf[2]; + m_write_offset = scsi_cmdbuf[3] << 16 | scsi_cmdbuf[4] << 8 | scsi_cmdbuf[5]; + LOG("TOOLBOX_SEND_FILE_10 offset %08x len %04x\n", m_write_offset, m_write_length); + scsi_data_out(4, m_write_length); + scsi_status_complete(SS_GOOD); + break; + + case TOOLBOX_SEND_FILE_END: + LOG("TOOLBOX_SEND_FILE_END\n"); + scsi_status_complete(SS_GOOD); + break; + + case TOOLBOX_COUNT_CDS: + LOG("TOOLBOX_COUNT_CDS\n"); + scsi_cmdbuf[0] = 0; + scsi_data_in(0, 1); + scsi_status_complete(SS_GOOD); + break; + default: nscsi_full_device::scsi_command(); break; @@ -604,13 +1061,23 @@ enum sgi_scsi_command_e : uint8_t { * in the kernel runs, if there are SCSI problems. */ SGI_HD2CDROM = 0xc9, + /* + * IRIX 5.3 sends this command when it wants to eject the drive + */ + SGI_EJECT = 0xc4, }; void nscsi_cdrom_sgi_device::scsi_command() { switch (scsi_cmdbuf[0]) { case SGI_HD2CDROM: - LOG("command SGI_HD2CDROM"); + LOG("command SGI_HD2CDROM\n"); + // No need to do anything (yet). Just acknowledge the command. + scsi_status_complete(SS_GOOD); + break; + + case SGI_EJECT: + LOG("command SGI_EJECT\n"); // No need to do anything (yet). Just acknowledge the command. scsi_status_complete(SS_GOOD); break; @@ -627,34 +1094,653 @@ bool nscsi_cdrom_sgi_device::scsi_command_done(uint8_t command, uint8_t length) case SGI_HD2CDROM: return length == 10; + case SGI_EJECT: + return length == 10; + default: - return nscsi_full_device::scsi_command_done(command, length); + return nscsi_cdrom_device::scsi_command_done(command, length); } } + +// Apple CDSC commands, from "Apple CD-ROM SCSI Command Set", version 1.4, February 15, 1988. +// These are related to the Sony CDU-541 commands but customized by Apple. +enum apple_scsi_command_e : uint8_t { + APPLE_EJECT = 0xc0, // EJECT DISC + APPLE_READ_TOC = 0xc1, // READ TOC + APPLE_READ_Q_SUBCODE = 0xc2, // READ Q SUBCODE + APPLE_READ_HEADER = 0xc3, // READ HEADER + APPLE_AUDIO_TRK_SEARCH = 0xc8, // AUDIO TRACK SEARCH + APPLE_AUDIO_PLAY = 0xc9, // AUDIO PLAY + APPLE_AUDIO_PAUSE = 0xca, // AUDIO PAUSE + APPLE_AUDIO_STOP = 0xcb, // AUDIO STOP + APPLE_AUDIO_STATUS = 0xcc, // AUDIO STATUS + APPLE_AUDIO_SCAN = 0xcd, // AUDIO SCAN + APPLE_AUDIO_CONTROL = 0xce +}; + +void nscsi_cdrom_apple_device::device_start() +{ + nscsi_cdrom_device::device_start(); + + save_item(NAME(m_stopped)); + save_item(NAME(m_stop_position)); +} + /* - The Apple II SCSI Card firmware demands that ASC on a failing TEST_UNIT_READY be either 0x28 or 0xb0. - 0x28 is MEDIA_CHANGED, 0xb0 is vendor-specific. If the drive returns the normal 0x3A for disc-not-present, + The Apple II SCSI Card firmware demands that ASC on a failing TEST_UNIT_READY be either 0x28 or 0xB0. + 0x28 is MEDIA_CHANGED, 0xB0 is vendor-specific. If the drive returns the normal 0x3A for disc-not-present, the firmware assumes the drive is broken and retries the TEST_UNIT_READY for 60 seconds before giving up and booting the machine. + + MacOS will see the normal 0x3A disc-not-present and simply disbelieve it and hammer on the drive while + asking the user to format it because it's unreadable. 0xB0 makes it behave as expected. */ + +void nscsi_cdrom_apple_device::return_no_cd() +{ + sense(false, SK_NOT_READY, 0xb0); + scsi_status_complete(SS_CHECK_CONDITION); +} + void nscsi_cdrom_apple_device::scsi_command() { + #if 1 + if (scsi_cmdbuf[0] != 8 && scsi_cmdbuf[0] != 0x28 && scsi_cmdbuf[0] != 0 && scsi_cmdbuf[0] != 0x03) + { + LOG("CD command: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + scsi_cmdbuf[0], scsi_cmdbuf[1], scsi_cmdbuf[2], + scsi_cmdbuf[3], scsi_cmdbuf[4], scsi_cmdbuf[5], + scsi_cmdbuf[6], scsi_cmdbuf[7], scsi_cmdbuf[8], + scsi_cmdbuf[9]); + } + #endif switch (scsi_cmdbuf[0]) { case SC_TEST_UNIT_READY: - LOG("command TEST UNIT READY (AppleCD)\n"); - if(cdrom) + //LOG("command TEST UNIT READY (AppleCD)\n"); + if(image->exists()) { scsi_status_complete(SS_GOOD); } else { - sense(false, SK_NOT_READY, 0xb0); + return_no_cd(); + } + break; + + case SC_INQUIRY: + { + int lun = get_lun(scsi_cmdbuf[1] >> 5); + LOG("command INQUIRY lun=%d EVPD=%d page=%d alloc=%02x link=%02x\n", + lun, scsi_cmdbuf[1] & 1, scsi_cmdbuf[2], scsi_cmdbuf[4], scsi_cmdbuf[5]); + + int page = scsi_cmdbuf[2]; + int size = scsi_cmdbuf[4]; + switch (page) + { + case 0: + std::fill_n(scsi_cmdbuf, size, 0); + + // data taken from the ROM of an AppleCD 150 + scsi_cmdbuf[0] = 0x05; // device is present, device is CD/DVD (MMC-3) + scsi_cmdbuf[1] = 0x80; // media is removable + scsi_cmdbuf[2] = 0x01; + scsi_cmdbuf[3] = 0x01; + scsi_cmdbuf[4] = 0x31; // additional length + memcpy((char *)&scsi_cmdbuf[8], "SONY ", 8); + memcpy((char *)&scsi_cmdbuf[16], "CD-ROM CDU-8002 ", 16); + memcpy((char *)&scsi_cmdbuf[32], "1.8g", 4); + scsi_cmdbuf[39] = 0xd0; + scsi_cmdbuf[40] = 0x90; + scsi_cmdbuf[41] = 0x27; + scsi_cmdbuf[42] = 0x3e; + scsi_cmdbuf[43] = 0x01; + scsi_cmdbuf[44] = 0x04; + scsi_cmdbuf[45] = 0x91; + scsi_cmdbuf[47] = 0x18; + scsi_cmdbuf[48] = 0x06; + scsi_cmdbuf[49] = 0xf0; + scsi_cmdbuf[50] = 0xfe; + + scsi_data_in(SBUF_MAIN, size); + break; + } + scsi_status_complete(SS_GOOD); + } + break; + + case APPLE_READ_TOC: + { + if (!image->exists()) + { + return_no_cd(); + break; + } + + u16 size = get_u16be(&scsi_cmdbuf[7]); + LOG("command READ TOC (AppleCD), size=%d, track=%d, type1=%02x type2=%02x\n", size, scsi_cmdbuf[2], scsi_cmdbuf[9], scsi_cmdbuf[5]); + + // cmdbuf 9 bits 6 & 7: + // 00 = first and last track # in BCD + // 01 = starting address of the lead-out in M/S/F + // 10 = starting address of a range of tracks from a starting track + // 11 = reserved + const uint8_t operation = scsi_cmdbuf[9] & 0xc0; + if (operation == 0x80) + { + // get TOC info for 1 or more tracks from a specified starting track + assert(scsi_cmdbuf[5] > 0); + + const int start_track = bcd_2_dec(scsi_cmdbuf[5]) - 1; + const int num_trks = size / 4; + int pos = 0; + + for (int trk = 0; trk < num_trks; trk++) + { + // Coverity: The emulated program could conceivably request a buffer size for + // more tracks than exist, but we must still return the requested amount of data. + if ((trk + start_track) < image->get_last_track()) + { + const uint32_t start_lba = image->get_track_start(trk + start_track); + const uint32_t start_frame = to_msf(start_lba); + + scsi_cmdbuf[pos++] = image->get_adr_control(trk + start_track); + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 16, 8)); // minutes + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 8, 8)); // seconds + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 0, 8)); // frames + } + else // keep returning the last track if necessary + { + const uint32_t start_lba = image->get_track_start(image->get_last_track()); + const uint32_t start_frame = to_msf(start_lba); + + scsi_cmdbuf[pos++] = image->get_adr_control(image->get_last_track()); + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 16, 8)); // minutes + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 8, 8)); // seconds + scsi_cmdbuf[pos++] = dec_2_bcd(BIT(start_frame, 0, 8)); // frames + } + } + + scsi_data_in(SBUF_MAIN, pos); + scsi_status_complete(SS_GOOD); + } + else if (operation == 0x40) // get MSF of the start of the leadout + { + const int track = image->get_last_track(); + const uint32_t frame = to_msf(image->get_track_start(track)); + scsi_cmdbuf[0] = dec_2_bcd(BIT(frame, 16, 8)); // minutes + scsi_cmdbuf[1] = dec_2_bcd(BIT(frame, 8, 8)); // seconds + scsi_cmdbuf[2] = dec_2_bcd(BIT(frame, 0, 8)); // frames + scsi_cmdbuf[3] = 0; + + scsi_data_in(SBUF_MAIN, 4); + scsi_status_complete(SS_GOOD); + } + else if (operation == 0x00) // get start and end track numbers + { + scsi_cmdbuf[0] = 1; + scsi_cmdbuf[1] = dec_2_bcd(image->get_last_track()); + scsi_cmdbuf[2] = 0; + scsi_cmdbuf[3] = 0; + + scsi_data_in(SBUF_MAIN, 4); + scsi_status_complete(SS_GOOD); + } + } + break; + + case APPLE_AUDIO_STATUS: + LOG("command APPLE AUDIO STATUS, type %02x, want %d bytes\n", scsi_cmdbuf[3], scsi_cmdbuf[8]); + + if (!image->exists()) + { + return_no_cd(); + break; + } + + // Audio status codes, adapted from CDRemote.equ in GS/OS 6.0.1 (matches Sony CDU-541 as well) + enum cd_status_t: u8 + { + PLAYING = 0, // Audio is playing + PAUSED, // Audio is paused + PLAYING_MUTED, // Playing, but muted + REACHED_END, // Playback reached the end + ERROR, // an error occured + VOID // really "idle" + }; + + { + const int type = scsi_cmdbuf[3]; + + scsi_cmdbuf[2] = image->get_adr_control(image->get_track(cdda->get_audio_lba())); + + const uint32_t frame = to_msf(cdda->get_audio_lba()); + scsi_cmdbuf[3] = dec_2_bcd(BIT(frame, 16, 8)); // minute + scsi_cmdbuf[4] = dec_2_bcd(BIT(frame, 8, 8)); // second + scsi_cmdbuf[5] = dec_2_bcd(BIT(frame, 0, 8)); // frame + + if (type == 0) + { + scsi_cmdbuf[1] = 0; + if (m_stopped) + { + scsi_cmdbuf[0] = VOID; + } + else + { + if (cdda->audio_ended()) + { + scsi_cmdbuf[0] = REACHED_END; + } + else + { + if (cdda->audio_active()) + { + if (cdda->audio_paused()) + { + scsi_cmdbuf[0] = PAUSED; + } + else + { + scsi_cmdbuf[0] = PLAYING; + } + } + else + { + scsi_cmdbuf[0] = VOID; + } + } + } + } + else if (type == 1) // "volume status" + { + scsi_cmdbuf[0] = (u8)(cdda->output_gain(0) * 255.0f); + scsi_cmdbuf[1] = (u8)(cdda->output_gain(1) * 255.0f); + + // Other 4 bytes unknown in this mode, going with the same as type 0 for now + } + } + scsi_data_in(SBUF_MAIN, 6); + scsi_status_complete(SS_GOOD); + break; + + case APPLE_READ_Q_SUBCODE: + { + LOG("command READ SUB CHANNEL\n"); + + scsi_cmdbuf[0] = 0; // Control nibble + scsi_cmdbuf[1] = 0; // Track + scsi_cmdbuf[2] = 0; // Index + scsi_cmdbuf[3] = 0; // Relative Minute + scsi_cmdbuf[4] = 0; // Relative Second + scsi_cmdbuf[5] = 0; // Relative Frame + scsi_cmdbuf[6] = 0; // AMinute + scsi_cmdbuf[7] = 0; // ASecond + scsi_cmdbuf[8] = 0; // AFrame + + const int track = image->get_track(cdda->get_audio_lba()); + scsi_cmdbuf[1] = dec_2_bcd(track+1); + scsi_cmdbuf[2] = 1; // Index + + const uint32_t track_start = image->get_track_start(track); + uint32_t cur_lba = cdda->get_audio_lba(); + + if (cur_lba < track_start) + { + cur_lba = track_start; + } + + const uint32_t relframe = to_msf(cur_lba - track_start); + scsi_cmdbuf[3] = dec_2_bcd(BIT(relframe, 16, 8)); // minute + scsi_cmdbuf[4] = dec_2_bcd(BIT(relframe, 8, 8)); // second + scsi_cmdbuf[5] = dec_2_bcd(BIT(relframe, 0, 8)); // frame + + const uint32_t frame = to_msf(cur_lba); + scsi_cmdbuf[6] = dec_2_bcd(BIT(frame, 16, 8)); // minute + scsi_cmdbuf[7] = dec_2_bcd(BIT(frame, 8, 8)); // second + scsi_cmdbuf[8] = dec_2_bcd(BIT(frame, 0, 8)); // frame + + scsi_data_in(SBUF_MAIN, 9); + scsi_status_complete(SS_GOOD); + } + break; + + // This command does not change the play or pause status. + case APPLE_AUDIO_TRK_SEARCH: + { + const uint8_t val = scsi_cmdbuf[5]; + const uint8_t start_track = (((val & 0xf0) >> 4) * 10) + (val & 0x0f); + + cdda->cancel_scan(); + if (cdda->audio_paused()) + { + cdda->pause_audio(0); + } + + uint32_t start_lba = 0; + + // cmdbuf[9] bits 6 & 7: + // 00 = search address is LBA + // 01 = search address is M/S/F + // 10 = search address is track number + if (scsi_cmdbuf[9] == 0x40) + { + LOG("command APPLE AUDIO TRACK SEARCH MSF = %02x:%02x:%02x\n", scsi_cmdbuf[5], scsi_cmdbuf[6], scsi_cmdbuf[7]); + const uint32_t msf = (bcd_2_dec(scsi_cmdbuf[5]) << 16) | (bcd_2_dec(scsi_cmdbuf[6]) << 8) | bcd_2_dec(scsi_cmdbuf[7]); + start_lba = cdrom_file::msf_to_lba(msf); + } + else if (scsi_cmdbuf[9] == 0x80) + { + LOG("command APPLE AUDIO TRACK SEARCH track = %d\n", start_track); + if (start_track == 0) + { + cdda->stop_audio(); + scsi_status_complete(SS_GOOD); + return; + } + else + { + start_lba = image->get_track_start(start_track - 1); + m_stop_position = image->get_track_start(start_track); // does this actually update the stop position? + } + } + else if (scsi_cmdbuf[9] == 0x00) // LBA + { + start_lba = (scsi_cmdbuf[5] << 24) | (scsi_cmdbuf[4] << 16) | (scsi_cmdbuf[3] << 8) || scsi_cmdbuf[2]; + } + else + { + logerror("nscsi_cdrom_apple_device: Unknown APPLE AUDIO PLAY address mode %02x\n", scsi_cmdbuf[9]); + } + + // PLAY / PAUSE bit + if ((scsi_cmdbuf[1] & 0x10) == 0x10) + { + cdda->start_audio(start_lba, m_stop_position - start_lba); + } + else + { + cdda->start_audio(start_lba, m_stop_position - start_lba); + cdda->pause_audio(true); + } + m_stopped = false; + + scsi_status_complete(SS_GOOD); + } + break; + + case APPLE_AUDIO_PLAY: + { + const uint8_t val = scsi_cmdbuf[5]; + const uint8_t start_track = (((val & 0xf0) >> 4) * 10) + (val & 0x0f); + + cdda->cancel_scan(); + if (cdda->audio_paused()) + { + cdda->pause_audio(0); + } + + // cmdbuf[9] bits 6 & 7: + // 00 = address is LBA + // 01 = address is M/S/F + // 10 = address is track number + uint32_t address = 0; + if (scsi_cmdbuf[9] == 0x80) + { + LOG("command APPLE AUDIO PLAY track = %d\n", start_track); + + // The Mac version of Apple CD-ROM Explorer issues PLAY on track 0 to stop + if (start_track == 0) + { + cdda->stop_audio(); + m_stopped = true; + scsi_status_complete(SS_GOOD); + return; + } + else + { + address = image->get_track_start(start_track - 1); + } + } + else if (scsi_cmdbuf[9] == 0x40) + { + LOG("command APPLE AUDIO PLAY TRACK MSF = %02x:%02x:%02x\n", scsi_cmdbuf[3], scsi_cmdbuf[4], scsi_cmdbuf[5]); + + const uint32_t msf = (bcd_2_dec(scsi_cmdbuf[3]) << 16) | (bcd_2_dec(scsi_cmdbuf[4]) << 8) | bcd_2_dec(scsi_cmdbuf[5]); + address = cdrom_file::msf_to_lba(msf); + } + else if (scsi_cmdbuf[9] == 0x00) + { + address = (scsi_cmdbuf[5] << 24) | (scsi_cmdbuf[4] << 16) | (scsi_cmdbuf[3] << 8) || scsi_cmdbuf[2]; + LOG("command APPLE AUDIO PLAY lba = %d\n", address); + } + else + { + fatalerror("nscsi_cdrom_apple_device: unknown mode for APPLE AUDIO PLAY %02x\n", scsi_cmdbuf[9]); + } + + // cmdbuf[1] & 0x10: + // 0 = address is the starting address, stop address was set by a STOP command + // 1 = address is the stop address, start address was set by a TRACK SEARCH command + if (scsi_cmdbuf[1] & 0x10) + { + const auto start = cdda->get_audio_lba(); + cdda->start_audio(start, address - start); + } + else + { + cdda->start_audio(address, m_stop_position - address); + m_stopped = false; + } + scsi_status_complete(SS_GOOD); + } + break; + + case APPLE_AUDIO_SCAN: + /* + 3/4/5 = MSF of starting position + 1 = 00 for forward scan, 0x10 for reverse + Scan ends with a PAUSE OFF command. + */ + LOG("command APPLE_AUDIO_SCAN: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", + scsi_cmdbuf[0], scsi_cmdbuf[1], scsi_cmdbuf[2], + scsi_cmdbuf[3], scsi_cmdbuf[4], scsi_cmdbuf[5], + scsi_cmdbuf[6], scsi_cmdbuf[7], scsi_cmdbuf[8], + scsi_cmdbuf[9]); + + if (!m_stopped) + { + uint32_t start_lba = 0; + switch (scsi_cmdbuf[9] & 0xc0) + { + case 0x00: // stop at LBA + start_lba = (scsi_cmdbuf[5] << 24) | (scsi_cmdbuf[4] << 16) | (scsi_cmdbuf[3] << 8) || scsi_cmdbuf[2]; + LOG("command APPLE AUDIO SCAN at LBA %d\n", m_stop_position); + break; + + case 0x40: // stop at MSF + { + const uint32_t msf = (bcd_2_dec(scsi_cmdbuf[5]) << 16) | (bcd_2_dec(scsi_cmdbuf[6]) << 8) | bcd_2_dec(scsi_cmdbuf[7]); + start_lba = cdrom_file::msf_to_lba(msf); + LOG("command APPLE AUDIO SCAN at MSF %02x:%02x:%02x\n", scsi_cmdbuf[5], scsi_cmdbuf[6], scsi_cmdbuf[7]); + } + break; + + case 0x80: // stop at track number + { + const uint8_t start_track = bcd_2_dec(scsi_cmdbuf[5]); + if (start_track >= image->get_last_track()) + { + start_lba = image->get_track_start(0xaa); + } + else + { + start_lba = image->get_track_start(start_track + 1); + } + start_lba--; + LOG("command APPLE AUDIO SCAN at end of track %d (LBA %d)\n", start_track, m_stop_position); + } + break; + + default: + logerror("nscsi_cdrom_apple_device: Unknown APPLE AUDIO SCAN address mode %02x\n", scsi_cmdbuf[9]); + break; + } + + // begin seeking from the specified address + cdda->set_audio_lba(start_lba); + + if ((scsi_cmdbuf[1] & 0x10) == 0) + { + cdda->scan_forward(); + } + else + { + cdda->scan_reverse(); + } + } + scsi_status_complete(SS_GOOD); + break; + + case APPLE_AUDIO_PAUSE: + LOG("command APPLE AUDIO PAUSE, on/off=%02x\n", scsi_cmdbuf[1]); + if (scsi_cmdbuf[1] == 0x10) + { + // set pause + if (!cdda->audio_paused()) + { + cdda->pause_audio(1); + } + } + else + { + // release pause and stop scanning + cdda->cancel_scan(); + if (cdda->audio_paused()) + { + cdda->pause_audio(0); + } + } + scsi_status_complete(SS_GOOD); + break; + + case APPLE_AUDIO_STOP: + switch (scsi_cmdbuf[9] & 0xc0) + { + case 0x00: // stop at LBA + m_stop_position = (scsi_cmdbuf[5] << 24) | (scsi_cmdbuf[4] << 16) | (scsi_cmdbuf[3] << 8) || scsi_cmdbuf[2]; + LOG("command APPLE AUDIO STOP at LBA %d\n", m_stop_position); + break; + + case 0x40: // stop at MSF + { + const uint32_t msf = (bcd_2_dec(scsi_cmdbuf[5]) << 16) | (bcd_2_dec(scsi_cmdbuf[6]) << 8) | bcd_2_dec(scsi_cmdbuf[7]); + m_stop_position = cdrom_file::msf_to_lba(msf); + LOG("command APPLE AUDIO STOP at MSF %02x:%02x:%02x\n", scsi_cmdbuf[5], scsi_cmdbuf[6], scsi_cmdbuf[7]); + } + break; + + case 0x80: // stop at track number + { + const uint8_t start_track = bcd_2_dec(scsi_cmdbuf[5]); + if (start_track >= image->get_last_track()) + { + m_stop_position = image->get_track_start(0xaa); + } + else + { + m_stop_position = image->get_track_start(start_track + 1); + } + m_stop_position--; + LOG("command APPLE AUDIO STOP at end of track %d (LBA %d)\n", start_track, m_stop_position); + } + break; + + default: + logerror("nscsi_cdrom_apple_device: Unknown APPLE AUDIO STOP address mode %02x\n", scsi_cmdbuf[9]); + break; + } + + if ((!m_stopped) && (cdda->get_audio_lba() >= m_stop_position)) + { + m_stopped = true; + cdda->stop_audio(); + } + + scsi_status_complete(SS_GOOD); + break; + + case APPLE_AUDIO_CONTROL: + LOG("command APPLE AUDIO CONTROL, size %d\n", scsi_cmdbuf[8]); + + if (image->exists()) + { + scsi_data_out(3, scsi_cmdbuf[8]); + scsi_status_complete(SS_GOOD); + } + else + { + return_no_cd(); + } + break; + + case APPLE_EJECT: + LOG("command APPLE EJECT\n"); + if (!m_removal_prevented) + { + cdda->stop_audio(); + m_stopped = true; + image->unload(); + sense(false, SK_NOT_READY, SK_ASC_MEDIUM_NOT_PRESENT); + scsi_status_complete(SS_GOOD); + } + else + { + LOG("Eject not allowed by PREVENT_ALLOW_MEDIA_REMOVAL\n"); + sense(false, SK_ILLEGAL_REQUEST, 0x80); // "Prevent bit is set" scsi_status_complete(SS_CHECK_CONDITION); } break; + case SC_READ_6: + case SC_READ_10: + case SC_READ_12: + cdda->stop_audio(); + m_stopped = true; + [[fallthrough]]; + default: nscsi_cdrom_device::scsi_command(); break; } } + +bool nscsi_cdrom_apple_device::scsi_command_done(uint8_t command, uint8_t length) +{ + switch (command & 0xf0) + { + case 0xc0: + return length == 10; + + default: + return nscsi_cdrom_device::scsi_command_done(command, length); + } +} + +void nscsi_cdrom_apple_device::scsi_put_data(int id, int pos, uint8_t data) +{ + if (id != 3) + { + nscsi_cdrom_device::scsi_put_data(id, pos, data); + return; + } + + if (pos == 0) + { + cdda->set_output_gain(0, data / 255.0f); + } + else if (pos == 1) + { + cdda->set_output_gain(1, data / 255.0f); + } + +} |