summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/bus/nscsi/cd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/bus/nscsi/cd.cpp')
-rw-r--r--src/devices/bus/nscsi/cd.cpp1258
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);
+ }
+
+}