diff --git a/devices/common/scsi/scsi.h b/devices/common/scsi/scsi.h index 34d2bdf..f8b51fe 100644 --- a/devices/common/scsi/scsi.h +++ b/devices/common/scsi/scsi.h @@ -85,27 +85,31 @@ enum ScsiMsg : int { }; enum ScsiCommand : int { - TEST_UNIT_READY = 0x0, - REWIND = 0x1, - REQ_SENSE = 0x3, - FORMAT = 0x4, - READ_BLK_LIMITS = 0x5, - READ_6 = 0x8, - WRITE_6 = 0xA, - SEEK_6 = 0xB, - INQUIRY = 0x12, - VERIFY_6 = 0x13, - MODE_SELECT_6 = 0x15, - RELEASE_UNIT = 0x17, - ERASE_6 = 0x19, - MODE_SENSE_6 = 0x1A, - DIAG_RESULTS = 0x1C, - SEND_DIAGS = 0x1D, - READ_CAPAC_10 = 0x25, - READ_10 = 0x28, - WRITE_10 = 0x2A, - VERIFY_10 = 0x2F, - READ_LONG_10 = 0x35, + TEST_UNIT_READY = 0x0, + REWIND = 0x1, + REQ_SENSE = 0x3, + FORMAT = 0x4, + READ_BLK_LIMITS = 0x5, + READ_6 = 0x8, + WRITE_6 = 0xA, + SEEK_6 = 0xB, + INQUIRY = 0x12, + VERIFY_6 = 0x13, + MODE_SELECT_6 = 0x15, + RELEASE_UNIT = 0x17, + ERASE_6 = 0x19, + MODE_SENSE_6 = 0x1A, + DIAG_RESULTS = 0x1C, + SEND_DIAGS = 0x1D, + PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E, + READ_CAPACITY_10 = 0x25, + READ_10 = 0x28, + WRITE_10 = 0x2A, + VERIFY_10 = 0x2F, + READ_LONG_10 = 0x35, + + // CD-ROM specific commands + READ_TOC = 0x43, }; enum ScsiSense : int { @@ -179,6 +183,7 @@ protected: int data_size; int incoming_size; uint8_t status; + int sense; ScsiBus* bus_obj; diff --git a/devices/common/scsi/scsi_hd.cpp b/devices/common/scsi/scsi_hd.cpp index fa22fb8..e62ce11 100644 --- a/devices/common/scsi/scsi_hd.cpp +++ b/devices/common/scsi/scsi_hd.cpp @@ -127,7 +127,7 @@ void ScsiHardDisk::process_command() { alloc_len = cmd[4]; mode_sense_6(page_code, subpage_code, alloc_len); break; - case ScsiCommand::READ_CAPAC_10: + case ScsiCommand::READ_CAPACITY_10: read_capacity_10(); break; default: diff --git a/devices/common/scsi/scsicdrom.cpp b/devices/common/scsi/scsicdrom.cpp new file mode 100644 index 0000000..b15ff47 --- /dev/null +++ b/devices/common/scsi/scsicdrom.cpp @@ -0,0 +1,322 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** @file SCSI CD-ROM emulation. */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; + +static char cdrom_vendor_sony_id[] = "SONY "; +static char cdu8003a_product_id[] = "CD-ROM CDU-8003A"; +static char cdu8003a_revision_id[] = "1.9a"; + +ScsiCdrom::ScsiCdrom(int my_id) : ScsiDevice(my_id) +{ + supports_types(HWCompType::SCSI_DEV); + + this->sector_size = 2048; + + this->pre_xfer_action = nullptr; + this->post_xfer_action = nullptr; +} + +void ScsiCdrom::insert_image(std::string filename) +{ + this->cdr_img.open(filename, ios::out | ios::in | ios::binary); + + struct stat stat_buf; + int rc = stat(filename.c_str(), &stat_buf); + if (!rc) { + this->img_size = stat_buf.st_size; + this->total_frames = (this->img_size + this->sector_size - 1) / this->sector_size; + + // create single track descriptor + this->tracks[0] = {.trk_num = 1, .adr_ctrl = 0x14, .start_lba = 0}; + this->num_tracks = 1; + + // create Lead-out descriptor containing all data + this->tracks[1] = {.trk_num = LEAD_OUT_TRK_NUM, .adr_ctrl = 0x14, + .start_lba = static_cast(this->total_frames)}; + } else { + ABORT_F("SCSI-CDROM: could not determine file size using stat()"); + } + this->cdr_img.seekg(0, std::ios_base::beg); +} + +void ScsiCdrom::process_command() +{ + uint32_t lba, xfer_len; + + this->pre_xfer_action = nullptr; + this->post_xfer_action = nullptr; + + // assume successful command execution + this->status = ScsiStatus::GOOD; + + switch (cmd_buf[0]) { + case ScsiCommand::TEST_UNIT_READY: + this->switch_phase(ScsiPhase::STATUS); + break; + case ScsiCommand::READ_6: + lba = ((cmd_buf[1] & 0x1F) << 16) + (cmd_buf[2] << 8) + cmd_buf[3]; + this->read(lba, cmd_buf[4], 6); + break; + case ScsiCommand::INQUIRY: + this->inquiry(); + break; + case ScsiCommand::MODE_SELECT_6: + this->incoming_size = this->cmd_buf[4]; + this->switch_phase(ScsiPhase::DATA_OUT); + break; + case ScsiCommand::MODE_SENSE_6: + this->mode_sense(); + break; + case ScsiCommand::PREVENT_ALLOW_MEDIUM_REMOVAL: + this->eject_allowed = (this->cmd_buf[4] & 1) == 0; + this->switch_phase(ScsiPhase::STATUS); + break; + case ScsiCommand::READ_10: + lba = (cmd_buf[2] << 24) + (cmd_buf[3] << 16) + (cmd_buf[4] << 8) + cmd_buf[5]; + xfer_len = (cmd_buf[7] << 8) + cmd_buf[8]; + read(lba, xfer_len, 10); + break; + case ScsiCommand::READ_TOC: + this->read_toc(); + break; + default: + ABORT_F("SCSI_CDROM: unsupported command %d", this->cmd_buf[0]); + } +} + +bool ScsiCdrom::prepare_data() +{ + switch (this->cur_phase) { + case ScsiPhase::DATA_IN: + this->data_ptr = (uint8_t*)this->data_buf; + this->data_size = this->bytes_out; + break; + case ScsiPhase::DATA_OUT: + this->data_ptr = (uint8_t*)this->data_buf; + this->data_size = 0; + break; + case ScsiPhase::STATUS: + break; + default: + LOG_F(WARNING, "SCSI_CDROM: unexpected phase in prepare_data"); + return false; + } + return true; +} + +void ScsiCdrom::read(const uint32_t lba, const uint16_t transfer_len, const uint8_t cmd_len) +{ + uint32_t transfer_size = transfer_len; + + std::memset(this->data_buf, 0, sizeof(this->data_buf)); + + if (cmd_len == 6 && transfer_len == 0) { + transfer_size = 256; + } + + transfer_size *= this->sector_size; + uint64_t device_offset = lba * this->sector_size; + + this->cdr_img.seekg(device_offset, this->cdr_img.beg); + this->cdr_img.read(this->data_buf, transfer_size); + + this->bytes_out = transfer_size; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + +void ScsiCdrom::inquiry() +{ + int page_num = cmd_buf[2]; + int alloc_len = cmd_buf[4]; + + if (page_num) { + ABORT_F("SCSI_CDROM: invalid page number in INQUIRY"); + } + + if (alloc_len > 36) { + LOG_F(WARNING, "SCSI_CDROM: more than 36 bytes requested in INQUIRY"); + } + + this->data_buf[0] = 5; // device type: CD-ROM + this->data_buf[1] = 0x80; // removable media + this->data_buf[2] = 2; // ANSI version: SCSI-2 + this->data_buf[3] = 1; // response data format + this->data_buf[4] = 0x1F; // additional length + this->data_buf[5] = 0; + this->data_buf[6] = 0; + this->data_buf[7] = 0x18; // supports synchronous xfers and linked commands + std::memcpy(&this->data_buf[8], cdrom_vendor_sony_id, 8); + std::memcpy(&this->data_buf[16], cdu8003a_product_id, 16); + std::memcpy(&this->data_buf[32], cdu8003a_revision_id, 4); + + this->bytes_out = 36; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + +static char Apple_Copyright_Page_Data[] = "APPLE COMPUTER, INC "; + +void ScsiCdrom::mode_sense() +{ + uint8_t page_code = this->cmd_buf[2] & 0x3F; + uint8_t alloc_len = this->cmd_buf[4]; + + int num_blocks = (this->img_size + this->sector_size) / this->sector_size; + + this->data_buf[ 0] = 13; // initial data size + this->data_buf[ 1] = 0; // medium type + this->data_buf[ 2] = 0x80; // medium is write protected + this->data_buf[ 3] = 8; // block description length + + this->data_buf[ 4] = 0; // density code + this->data_buf[ 5] = (num_blocks >> 16) & 0xFFU; + this->data_buf[ 6] = (num_blocks >> 8) & 0xFFU; + this->data_buf[ 7] = (num_blocks ) & 0xFFU; + this->data_buf[ 8] = 0; + this->data_buf[ 9] = 0; + this->data_buf[10] = (this->sector_size >> 8) & 0xFFU; + this->data_buf[11] = (this->sector_size ) & 0xFFU; + + this->data_buf[12] = page_code; + + switch (page_code) { + case 0x01: + this->data_buf[13] = 6; // data size + std::memset(&this->data_buf[14], 0, 6); + this->data_buf[0] += 7; + break; + case 0x30: + this->data_buf[13] = 22; // data size + std::memcpy(&this->data_buf[14], Apple_Copyright_Page_Data, 22); + this->data_buf[0] += 23; + break; + default: + ABORT_F("SCSI-HD: unsupported page %d in MODE_SENSE_6", page_code); + } + + this->bytes_out = this->data_buf[0]; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + +void ScsiCdrom::read_toc() +{ + int tot_tracks; + uint8_t start_track = this->cmd_buf[6]; + uint16_t alloc_len = (this->cmd_buf[7] << 8) + this->cmd_buf[8]; + bool is_msf = !!(this->cmd_buf[1] & 2); + + if (this->cmd_buf[2] & 0xF) { + ABORT_F("SCSI-CDROM: unsupported format in READ_TOC"); + } + + if (!alloc_len) { + LOG_F(WARNING, "SCSI-CDROM: zero allocation length passed to READ_TOC"); + return; + } + + if (!start_track) { // special case: track zero (lead-in) as starting track + // return all tracks starting with track 1 plus lead-out + start_track = 1; + tot_tracks = this->num_tracks + 1; + } else if (start_track == LEAD_OUT_TRK_NUM) { + start_track = this->num_tracks + 1; + tot_tracks = 1; + } else if (start_track <= this->num_tracks) { + tot_tracks = (this->num_tracks - start_track) + 2; + } else { + LOG_F(ERROR, "SCSI-CDROM: invalid starting track %d in READ_TOC", start_track); + this->status = ScsiStatus::CHECK_CONDITION; + this->sense = ScsiSense::ILLEGAL_REQ; + } + + char* buf_ptr = this->data_buf; + + int data_len = (tot_tracks * 8) + 2; + + // write TOC data header + *buf_ptr++ = (data_len >> 8) & 0xFFU; + *buf_ptr++ = (data_len >> 0) & 0xFFU; + *buf_ptr++ = 1; // 1st track number + *buf_ptr++ = this->num_tracks; // last track number + + for (int tx = 0; tx < tot_tracks; tx++) { + if ((buf_ptr - this->data_buf + 8) > alloc_len) + break; // exit the loop if the output buffer length is exhausted + + TrackDescriptor& td = this->tracks[start_track + tx - 1]; + + *buf_ptr++ = 0; // reserved + *buf_ptr++ = td.adr_ctrl; + *buf_ptr++ = td.trk_num; + *buf_ptr++ = 0; // reserved + + if (is_msf) { + AddrMsf msf = lba_to_msf(td.start_lba + 150); + *buf_ptr++ = 0; // reserved + *buf_ptr++ = msf.min; + *buf_ptr++ = msf.sec; + *buf_ptr++ = msf.frm; + } else { + *buf_ptr++ = (td.start_lba >> 24) & 0xFFU; + *buf_ptr++ = (td.start_lba >> 16) & 0xFFU; + *buf_ptr++ = (td.start_lba >> 8) & 0xFFU; + *buf_ptr++ = (td.start_lba >> 0) & 0xFFU; + } + } + + this->bytes_out = alloc_len; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + +AddrMsf ScsiCdrom::lba_to_msf(const int lba) +{ + return {.min = lba / 4500, .sec = (lba / 75) % 60, .frm = lba % 75}; +} + +static const PropMap ScsiCdromProperties = { + {"cdr_img", new StrProperty("")}, +}; + +static const DeviceDescription ScsiCdromDescriptor = + {ScsiCdrom::create, {}, ScsiCdromProperties}; + +REGISTER_DEVICE(ScsiCdrom, ScsiCdromDescriptor); diff --git a/devices/common/scsi/scsicdrom.h b/devices/common/scsi/scsicdrom.h new file mode 100644 index 0000000..72d7000 --- /dev/null +++ b/devices/common/scsi/scsicdrom.h @@ -0,0 +1,88 @@ +/* +DingusPPC - The Experimental PowerPC Macintosh emulator +Copyright (C) 2018-22 divingkatae and maximum + (theweirdo) spatium + +(Contact divingkatae#1017 or powermax#2286 on Discord for more info) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +/** @file SCSI CD-ROM definitions. */ + +#ifndef SCSI_CDROM_H +#define SCSI_CDROM_H + +#include + +#include +#include +#include +#include + +/* Original CD-ROM addressing mode expressed + in minutes, seconds and frames */ +typedef struct { + int min; + int sec; + int frm; +} AddrMsf; + +/* Descriptor for CD-ROM tracks in TOC */ +typedef struct { + uint8_t trk_num; + uint8_t adr_ctrl; + uint32_t start_lba; +} TrackDescriptor; + +#define CDROM_MAX_TRACKS 100 +#define LEAD_OUT_TRK_NUM 0xAA + +class ScsiCdrom : public ScsiDevice { +public: + ScsiCdrom(int my_id); + ~ScsiCdrom() = default; + + static std::unique_ptr create() { + return std::unique_ptr(new ScsiCdrom(3)); + } + + void insert_image(std::string filename); + + virtual void process_command(); + virtual bool prepare_data(); + +protected: + void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len); + void inquiry(); + void mode_sense(); + void read_toc(); + +private: + AddrMsf lba_to_msf(const int lba); + +private: + std::fstream cdr_img; + uint64_t img_size; + int total_frames; + int num_tracks; + int sector_size; + bool eject_allowed = true; + int bytes_out = 0; + TrackDescriptor tracks[CDROM_MAX_TRACKS]; + + char data_buf[1 << 16]; // TODO: proper buffer management +}; + +#endif // SCSI_CDROM_H diff --git a/machines/machinefactory.cpp b/machines/machinefactory.cpp index 0b5d15b..0782784 100644 --- a/machines/machinefactory.cpp +++ b/machines/machinefactory.cpp @@ -80,6 +80,7 @@ static const map PropHelp = { {"fdd_wr_prot", "toggles floppy disk's write protection"}, {"hdd_img", "specifies path to hard disk image"}, {"hdd_wr_prot", "toggles hard disk's write protection"}, + {"cdr_img", "specifies path to CD-ROM image"}, {"mon_id", "specifies which monitor to emulate"}, {"pci_A1", "insert a PCI device into A1 slot"}, {"pci_B1", "insert a PCI device into B1 slot"}, diff --git a/machines/machinepdm.cpp b/machines/machinepdm.cpp index 2049e63..ca90200 100644 --- a/machines/machinepdm.cpp +++ b/machines/machinepdm.cpp @@ -27,6 +27,7 @@ along with this program. If not, see . #include #include #include +#include #include #include #include @@ -92,6 +93,15 @@ int initialize_pdm(std::string& id) my_hd->insert_image(hd_image_path); } + std::string cdr_image_path = GET_STR_PROP("cdr_img"); + if (!cdr_image_path.empty()) { + // attach SCSI CD-ROM to the main bus, ID #3 + auto my_cdr = dynamic_cast(gMachineObj->get_comp_by_name("ScsiCdrom")); + scsi_bus->register_device(3, my_cdr); + // insert specified disk image + my_cdr->insert_image(cdr_image_path); + } + // Init virtual CPU and request MPC601 ppc_cpu_init(hmc_obj, PPC_VER::MPC601, 7812500ULL); @@ -118,7 +128,7 @@ static const PropMap pm6100_settings = { }; static vector pm6100_devices = { - "HMC", "Amic", "ScsiHD" + "HMC", "Amic", "ScsiHD", "ScsiCdrom" }; static const MachineDescription pm6100_descriptor = {