From e42af9169762c0d3d8bb8a83b053443ea35af08b Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Mon, 28 Sep 2026 12:34:44 -0700 Subject: [PATCH 1/2] Handle CD-ROM commands when no media is present The 7300 ROM tries to read from an empty CD-ROM drive during boot. We would try to transfer data from an unallocated buffer and crash. Return NOT READY / MEDIUM NOT PRESENT for reads, capacity requests, and TOC requests. Also covers the separate ATAPI READ CD path. This matches the behavior described in ATAPI 2.6 section 8.5 and the per-command error tables: https://www.grix.it/UserFiles/marco.c/INF-8020.pdf --- devices/common/ata/atapicdrom.cpp | 6 ++++++ devices/common/scsi/scsiblockcmds.cpp | 5 +++++ devices/common/scsi/scsicdromcmds.cpp | 5 +++++ devices/common/scsi/scsicommoncmds.cpp | 21 +++++++++++++++++++-- devices/common/scsi/scsicommoncmds.h | 2 ++ 5 files changed, 37 insertions(+), 2 deletions(-) diff --git a/devices/common/ata/atapicdrom.cpp b/devices/common/ata/atapicdrom.cpp index d43750ae50..3372098cfe 100644 --- a/devices/common/ata/atapicdrom.cpp +++ b/devices/common/ata/atapicdrom.cpp @@ -100,6 +100,12 @@ void AtapiCdrom::perform_packet_command() { switch (this->cmd_pkt[0]) { case ScsiCommand::READ_CD: { + // READ_CD currently bypasses the shared CD-ROM command dispatcher. + if (!this->ready_for_command()) { + this->present_status(); + break; + } + lba = READ_DWORD_BE_U(&this->cmd_pkt[2]); xfer_len = (this->cmd_pkt[6] << 16) | READ_WORD_BE_U(&this->cmd_pkt[7]); if (this->cmd_pkt[9] == 0 && (this->cmd_pkt[10] & 7) == 0) { diff --git a/devices/common/scsi/scsiblockcmds.cpp b/devices/common/scsi/scsiblockcmds.cpp index 58186c8904..91060d1aca 100644 --- a/devices/common/scsi/scsiblockcmds.cpp +++ b/devices/common/scsi/scsiblockcmds.cpp @@ -79,6 +79,11 @@ void ScsiBlockCmds::init_block_device(uint8_t medium_type, uint8_t dev_flags, } void ScsiBlockCmds::process_command() { + if (!this->ready_for_command()) { + phy_impl->switch_phase(ScsiPhase::STATUS); + return; + } + int next_phase; // use non-disk buffer by default diff --git a/devices/common/scsi/scsicdromcmds.cpp b/devices/common/scsi/scsicdromcmds.cpp index 67946c8f40..2399994fc8 100644 --- a/devices/common/scsi/scsicdromcmds.cpp +++ b/devices/common/scsi/scsicdromcmds.cpp @@ -42,6 +42,11 @@ ScsiCdromCmds::ScsiCdromCmds() { } void ScsiCdromCmds::process_command() { + if (!this->ready_for_command()) { + phy_impl->switch_phase(ScsiPhase::STATUS); + return; + } + int next_phase; // use non-disk buffer by default diff --git a/devices/common/scsi/scsicommoncmds.cpp b/devices/common/scsi/scsicommoncmds.cpp index 1795e0cfc2..8a70c7f528 100644 --- a/devices/common/scsi/scsicommoncmds.cpp +++ b/devices/common/scsi/scsicommoncmds.cpp @@ -109,16 +109,33 @@ int ScsiCommonCmds::verify_cdb() { } int ScsiCommonCmds::test_unit_ready() { - // Assume that LUN is okay and the status has been set to GOOD + this->ready_for_command(true); + return ScsiPhase::STATUS; +} + +bool ScsiCommonCmds::ready_for_command(bool force_check) { + // TUR forces a check from its handler; dispatch lets it and media-independent + // commands through to ScsiCommonCmds. + if (!force_check) { + switch (this->cdb_ptr[0]) { + case ScsiCommand::INQUIRY: + case ScsiCommand::REQ_SENSE: + case ScsiCommand::TEST_UNIT_READY: + case ScsiCommand::MODE_SENSE_6: + case ScsiCommand::MODE_SENSE_10: + return true; + } + } if (!phy_impl->is_device_ready()) { this->sense_key = ScsiSense::NOT_READY; this->asc = phy_impl->not_ready_reason(); this->ascq = 0; phy_impl->set_status(ScsiStatus::CHECK_CONDITION, this->sense_key); + return false; } - return ScsiPhase::STATUS; + return true; } int ScsiCommonCmds::inquiry() { diff --git a/devices/common/scsi/scsicommoncmds.h b/devices/common/scsi/scsicommoncmds.h index 9c02cff1d8..7e63700a8d 100644 --- a/devices/common/scsi/scsicommoncmds.h +++ b/devices/common/scsi/scsicommoncmds.h @@ -98,6 +98,8 @@ class ScsiCommonCmds { virtual void invalid_command(); virtual void illegal_request(uint8_t asc, uint8_t ascq, bool is_cdb = true); + bool ready_for_command(bool force_check = false); + int get_one_page(uint8_t ctrl, uint8_t page, uint8_t subpage, uint8_t* out_ptr, int avail_len); From 92b956e20ab2222ef7edfbfd6ddf52184046c12f Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Mon, 28 Sep 2026 12:34:53 -0700 Subject: [PATCH 2/2] Create empty CD-ROM drives for SCSI-based machines We only created SCSI CD-ROM drives when an image was supplied at startup, so later insertion requests had no drive to go to. The Beige G3 already worked because its ATAPI drive is always created. Extend the ATAPI approach (where a `cdr_config` property defines the bus where a CD-ROM drive should be attached) for SCSI-based machines too. Use it to define default drive on the appropriate bus for PDM, TNT, Catalyst, Alchemy, Gazelle, and Pippin. Supplying images at startup still uses the existing drive allocation behavior. --- devices/common/ata/atapicdrom.cpp | 5 +++ devices/common/scsi/scsibus.cpp | 73 +++++++++++++++++++++++-------- machines/machinealchemy.cpp | 2 + machines/machinecatalyst.cpp | 2 + machines/machinegazelle.cpp | 2 + machines/machinepdm.cpp | 2 + machines/machinepippin.cpp | 2 + machines/machineproperties.cpp | 16 ++++--- machines/machinetnt.cpp | 1 + 9 files changed, 80 insertions(+), 25 deletions(-) diff --git a/devices/common/ata/atapicdrom.cpp b/devices/common/ata/atapicdrom.cpp index 3372098cfe..7f6b374d48 100644 --- a/devices/common/ata/atapicdrom.cpp +++ b/devices/common/ata/atapicdrom.cpp @@ -67,6 +67,11 @@ int AtapiCdrom::device_postinit() { parse_device_path(cdr_config, bus_id, dev_num); auto bus_obj = dynamic_cast(gMachineObj->get_comp_by_name(bus_id)); + if (!bus_obj) { + LOG_F(ERROR, "%s: cdr_config device path %s does not specify a known IDE bus", + this->name.c_str(), cdr_config.c_str()); + return -1; + } bus_obj->register_device(dev_num, this); std::string cdr_image_path = GET_STR_PROP("cdr_img"); diff --git a/devices/common/scsi/scsibus.cpp b/devices/common/scsi/scsibus.cpp index 4e341fa0d9..fb3c5902bb 100644 --- a/devices/common/scsi/scsibus.cpp +++ b/devices/common/scsi/scsibus.cpp @@ -31,6 +31,7 @@ along with this program. If not, see . #include #include +#include ScsiBus::ScsiBus(const std::string name) { @@ -338,29 +339,65 @@ void ScsiBus::attach_scsi_devices(const std::string bus_suffix) } } + int default_cdrom_id = -1; + std::string cdr_config = GET_STR_PROP("cdr_config"); + if (!cdr_config.empty()) { + std::string bus_id; + uint32_t dev_num; + parse_device_path(cdr_config, bus_id, dev_num); + // Only consume SCSI paths; other buses handle their own CD-ROM configuration. + if (bus_id.starts_with("Scsi")) { + auto bus_obj = dynamic_cast(gMachineObj->get_comp_by_name_optional(bus_id)); + if (!bus_obj) + ABORT_F("cdr_config device path %s does not specify a known SCSI bus", cdr_config.c_str()); + if (dev_num >= SCSI_MAX_DEVS) + ABORT_F("Invalid SCSI ID in cdr_config device path %s", cdr_config.c_str()); + if (bus_obj == this) { + if (this->devices[dev_num]) + ABORT_F("%s: cdr_config SCSI ID %u is already occupied", bus_id.c_str(), dev_num); + default_cdrom_id = dev_num; + } + } + } + image_path = GET_STR_PROP("cdr_img" + bus_suffix); + std::vector cdrom_paths; if (!image_path.empty()) { - std::istringstream image_stream(image_path); - while (std::getline(image_stream, path, ':')) { - // do two passes because we start at ID 3. + // Keep empty entries, including the last one, as empty CD-ROM drives. + size_t start = 0; + size_t end; + do { + end = image_path.find(':', start); + cdrom_paths.push_back(image_path.substr(start, end - start)); + start = end + 1; + } while (end != std::string::npos); + } else if (default_cdrom_id >= 0) { + cdrom_paths.push_back(""); + } + for (const auto& path : cdrom_paths) { + if (default_cdrom_id >= 0) { + scsi_id = default_cdrom_id; + default_cdrom_id = -1; + } else { + // do two passes because we start at ID 3. for (scsi_id = 3; scsi_id < SCSI_MAX_DEVS * 2 && this->devices[scsi_id % SCSI_MAX_DEVS]; scsi_id++) {} + } - if (scsi_id < SCSI_MAX_DEVS * 2) { - scsi_id = scsi_id % SCSI_MAX_DEVS; - std::string scsi_device_name = "ScsiCdrom" + bus_suffix + "," + - std::to_string(scsi_id); - ScsiCdrom *scsi_device = new ScsiCdrom(scsi_device_name, scsi_id); - gMachineObj->add_device(scsi_device_name, - std::unique_ptr(scsi_device)); - this->register_device(scsi_id, scsi_device); - if (!scsi_device->insert_image(path)) - ABORT_F("Could not insert CD-ROM image, %s", path.c_str()); - } - else { - LOG_F(ERROR, "%s: Too many devices. CD-ROM \"%s\" was not added.", - this->get_name().c_str(), path.c_str()); - } + if (scsi_id < SCSI_MAX_DEVS * 2) { + scsi_id = scsi_id % SCSI_MAX_DEVS; + std::string scsi_device_name = "ScsiCdrom" + bus_suffix + "," + + std::to_string(scsi_id); + ScsiCdrom *scsi_device = new ScsiCdrom(scsi_device_name, scsi_id); + gMachineObj->add_device(scsi_device_name, + std::unique_ptr(scsi_device)); + this->register_device(scsi_id, scsi_device); + if (!path.empty() && !scsi_device->insert_image(path)) + ABORT_F("Could not insert CD-ROM image, %s", path.c_str()); + } + else { + LOG_F(ERROR, "%s: Too many devices. CD-ROM \"%s\" was not added.", + this->get_name().c_str(), path.c_str()); } } } diff --git a/machines/machinealchemy.cpp b/machines/machinealchemy.cpp index d7c0b6d84d..a62c14ab4d 100644 --- a/machines/machinealchemy.cpp +++ b/machines/machinealchemy.cpp @@ -126,6 +126,8 @@ static const PropMap pm6400_settings = { new IntProperty( 0, std::vector({0, 4, 8, 16, 32}))}, {"hdd_config", new StrProperty("Ide0:0")}, + {"cdr_config", + new StrProperty("ScsiMesh:3")}, {"emmo", new BinProperty(0)}, }; diff --git a/machines/machinecatalyst.cpp b/machines/machinecatalyst.cpp index 67fbc09933..18279e6d8f 100644 --- a/machines/machinecatalyst.cpp +++ b/machines/machinecatalyst.cpp @@ -123,6 +123,8 @@ static const PropMap pm7200_settings = { new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64, 128}))}, {"rambank4_size", new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64, 128}))}, + {"cdr_config", + new StrProperty("ScsiCurio:3")}, {"emmo", new BinProperty(0)}, {"cpu", diff --git a/machines/machinegazelle.cpp b/machines/machinegazelle.cpp index 894a9349a2..633893d263 100644 --- a/machines/machinegazelle.cpp +++ b/machines/machinegazelle.cpp @@ -145,6 +145,8 @@ static const PropMap pm6500_settings = { new IntProperty(32, std::vector({ 4, 8, 16, 32, 64}))}, {"rambank2_size", new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64}))}, + {"cdr_config", + new StrProperty("ScsiMesh:3")}, {"emmo", new BinProperty(0)}, {"hdd_config", diff --git a/machines/machinepdm.cpp b/machines/machinepdm.cpp index 443a7752d0..94278da290 100644 --- a/machines/machinepdm.cpp +++ b/machines/machinepdm.cpp @@ -158,6 +158,8 @@ static const PropMap pm6100_settings = { new StrProperty("HiRes12-14in", PDMBuiltinMonitorIDs)}, {"pds", new StrProperty("")}, + {"cdr_config", + new StrProperty("ScsiCurio:3")}, {"emmo", new BinProperty(0)}, }; diff --git a/machines/machinepippin.cpp b/machines/machinepippin.cpp index 8ced27d21b..cc509569b3 100644 --- a/machines/machinepippin.cpp +++ b/machines/machinepippin.cpp @@ -88,6 +88,8 @@ static const PropMap Pippin_settings = { new IntProperty(0, std::vector({0, 1, 4, 8, 16}))}, {"rambank4_size", new IntProperty(0, std::vector({0, 1, 4, 8, 16}))}, + {"cdr_config", + new StrProperty("ScsiCurio:3")}, {"emmo", new BinProperty(0)}, {"adb_devices", diff --git a/machines/machineproperties.cpp b/machines/machineproperties.cpp index 0eaa89a506..60b50ed548 100644 --- a/machines/machineproperties.cpp +++ b/machines/machineproperties.cpp @@ -23,6 +23,7 @@ along with this program. If not, see . #include #include +#include #include #include #include @@ -151,15 +152,16 @@ void parse_device_path(std::string dev_path, std::string& bus_id, uint32_t& dev_ dev_num = -1; size_t delimiter_pos = dev_path.find(":"); - if (delimiter_pos == std::string::npos) + if (delimiter_pos == std::string::npos || delimiter_pos == 0) ABORT_F("Invalid device path %s", dev_path.c_str()); bus_id = dev_path.substr(0, delimiter_pos); - try { - dev_num = (uint32_t)strtoul(dev_path.substr(delimiter_pos+1).c_str(), 0, 0); - } catch (std::string bad_string) { - ABORT_F("Invalid device number %s in device path %s", bad_string.c_str(), - dev_path.c_str()); - } + std::string number = dev_path.substr(delimiter_pos + 1); + char* end; + errno = 0; + unsigned long value = strtoul(number.c_str(), &end, 0); + if (end == number.c_str() || *end || errno == ERANGE || value > UINT32_MAX) + ABORT_F("Invalid device number %s in device path %s", number.c_str(), dev_path.c_str()); + dev_num = (uint32_t)value; } diff --git a/machines/machinetnt.cpp b/machines/machinetnt.cpp index 349ce232e5..041e44c5b5 100644 --- a/machines/machinetnt.cpp +++ b/machines/machinetnt.cpp @@ -172,6 +172,7 @@ static const PropMap pm7500_settings_ ## cpu = { \ {"rambank10_size", new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64, 128}))}, \ {"rambank11_size", new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64, 128}))}, \ {"rambank12_size", new IntProperty( 0, std::vector({0, 4, 8, 16, 32, 64, 128}))}, \ + {"cdr_config", new StrProperty("ScsiCurio:3")}, \ {"emmo", new BinProperty(0)}, \ {"cpu", new StrProperty(# cpu, std::vector({"601", "604", "604e", "750"}))}, \ };