diff --git a/CMakeSettings.json b/CMakeSettings.json deleted file mode 100644 index cec0924..0000000 --- a/CMakeSettings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "configurations": [ - { - "name": "x64-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "inheritEnvironments": [ "clang_cl_x64" ], - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "variables": [] - }, - { - "name": "x64-Release", - "generator": "Ninja", - "configurationType": "Release", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "-v", - "ctestCommandArgs": "", - "inheritEnvironments": [ "clang_cl_x64" ], - "variables": [] - } - ] -} \ No newline at end of file diff --git a/cpu/ppc/poweropcodes.cpp b/cpu/ppc/poweropcodes.cpp index 1dd5b29..e78bd78 100644 --- a/cpu/ppc/poweropcodes.cpp +++ b/cpu/ppc/poweropcodes.cpp @@ -136,64 +136,40 @@ void dppc_interpreter::power_dozi() { void dppc_interpreter::power_lscbx() { ppc_grab_regsdab(); - ppc_effective_address = (reg_a == 0) ? ppc_result_b : ppc_result_a + ppc_result_b; + ppc_effective_address = (reg_a == 0) ? ppc_result_b : ppc_result_a + ppc_result_b; + //ppc_result_d = 0xFFFFFFFF; uint8_t return_value = 0; uint32_t bytes_to_load = (ppc_state.spr[SPR::XER] & 0x7f); uint32_t bytes_copied = 0; uint8_t matching_byte = (uint8_t)((ppc_state.spr[SPR::XER] & 0xFF00) >> 8); - uint32_t byte_offset = 0; - //for storing each byte - uint32_t bitmask = 0; - uint32_t shift_amount = 0; + // for storing each byte + uint32_t bitmask = 0xFF000000; + uint8_t shift_amount = 24; while (bytes_to_load > 0) { - if (byte_offset == 24) { - reg_d = (reg_d + 1) % 32; - ppc_result_d = 0xFFFFFFFF; - ppc_store_result_regd(); - } - - - switch (byte_offset) { - case 0: - bitmask = 0x00FFFFFF; - shift_amount = 24; - break; - case 8: - bitmask = 0xFF00FFFF; - shift_amount = 16; - break; - case 16: - bitmask = 0xFFFF00FF; - shift_amount = 8; - break; - case 24: - bitmask = 0xFFFFFF00; - shift_amount = 0; - break; - } - return_value = mmu_read_vmem(ppc_effective_address); // return_value = mem_grab_byte(ppc_effective_address); - ppc_result_d = (ppc_result_d & bitmask) | (return_value << shift_amount); + ppc_result_d = (ppc_result_d & ~(bitmask)) | (return_value << shift_amount); ppc_store_result_regd(); - bytes_copied++; - - if (return_value == matching_byte) { - //Match has been found - Time to break out - break; - } - - byte_offset += 8; - - if (byte_offset == 32) { - byte_offset = 0; + if (bitmask == 0x000000FF) { + reg_d = (reg_d + 1) & 31; + //ppc_result_d = 0xFFFFFFFF; + bitmask = 0xFF000000; + shift_amount = 24; + } + else { + bitmask >>= 8; + shift_amount -= 8; } ppc_effective_address++; + bytes_copied++; bytes_to_load--; + + if (return_value == matching_byte) + break; } ppc_state.spr[SPR::XER] = (ppc_state.spr[SPR::XER] & 0xFFFFFF80) | bytes_copied; diff --git a/cpu/ppc/ppcopcodes.cpp b/cpu/ppc/ppcopcodes.cpp index 895bc23..b18243f 100644 --- a/cpu/ppc/ppcopcodes.cpp +++ b/cpu/ppc/ppcopcodes.cpp @@ -2004,7 +2004,6 @@ void dppc_interpreter::ppc_lswi() { ppc_effective_address = reg_a ? ppc_result_a : 0; grab_inb = (ppc_cur_instruction >> 11) & 0x1F; grab_inb = grab_inb ? grab_inb : 32; - uint32_t stringed_word = 0; while (grab_inb > 0) { switch (grab_inb) { @@ -2017,9 +2016,8 @@ void dppc_interpreter::ppc_lswi() { grab_inb = 0; break; case 3: - stringed_word = mmu_read_vmem(ppc_effective_address) << 16; - stringed_word += mmu_read_vmem(ppc_effective_address + 2) << 8; - ppc_state.gpr[reg_d] = stringed_word; + ppc_state.gpr[reg_d] = mmu_read_vmem(ppc_effective_address) << 16; + ppc_state.gpr[reg_d] += mmu_read_vmem(ppc_effective_address + 2) << 8; grab_inb = 0; break; default: @@ -2047,7 +2045,6 @@ void dppc_interpreter::ppc_lswx() { ppc_effective_address = reg_a ? (ppc_result_a + ppc_result_b) : ppc_result_b; grab_inb = ppc_state.spr[SPR::XER] & 0x7F; - uint32_t stringed_word = 0; while (grab_inb > 0) { switch (grab_inb) { @@ -2060,10 +2057,9 @@ void dppc_interpreter::ppc_lswx() { grab_inb = 0; break; case 3: - stringed_word = mmu_read_vmem(ppc_effective_address) << 16; - stringed_word += mmu_read_vmem(ppc_effective_address + 2) << 8; - ppc_state.gpr[reg_d] = stringed_word; - grab_inb = 0; + ppc_state.gpr[reg_d] = mmu_read_vmem(ppc_effective_address) << 16; + ppc_state.gpr[reg_d] += mmu_read_vmem(ppc_effective_address + 2) << 8; + grab_inb = 0; break; default: ppc_state.gpr[reg_d] = mmu_read_vmem(ppc_effective_address); diff --git a/devices/CMakeLists.txt b/devices/CMakeLists.txt index 8a0bf92..4be1edd 100644 --- a/devices/CMakeLists.txt +++ b/devices/CMakeLists.txt @@ -6,6 +6,7 @@ file(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/common/adb/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/common/i2c/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/common/ata/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/common/pci/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/common/scsi/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ethernet/*.cpp" diff --git a/devices/common/adb/adb.cpp b/devices/common/adb/adb.cpp index 858da59..1f4aefa 100644 --- a/devices/common/adb/adb.cpp +++ b/devices/common/adb/adb.cpp @@ -402,7 +402,7 @@ bool ADB_Bus::adb_keybd_listen(int reg) { } bool ADB_Bus::adb_mouse_listen(int reg) { - if ((reg != 0) | (reg != 3)) { + if ((reg != 0) || (reg != 3)) { return false; } diff --git a/devices/common/ata/atabasedevice.cpp b/devices/common/ata/atabasedevice.cpp new file mode 100644 index 0000000..e831921 --- /dev/null +++ b/devices/common/ata/atabasedevice.cpp @@ -0,0 +1,129 @@ +/* +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 Heathrow hard drive controller */ + +#include +#include +#include +#include + +#include + +using namespace ata_interface; + +AtaBaseDevice::AtaBaseDevice(const std::string name) +{ + this->set_name(name); + supports_types(HWCompType::IDE_DEV); + + device_reset(); +} + +void AtaBaseDevice::device_reset(const uint8_t dev_head_val) +{ + this->r_error = 1; // Device 0 passed, Device 1 passed or not present + + this->r_sect_count = 1; + this->r_sect_num = 1; + this->r_dev_ctrl = 0; + + // set protocol signature + if (this->device_type == DEVICE_TYPE_ATAPI) { + this->r_cylinder_lo = 0x14; + this->r_cylinder_hi = 0xEB; + this->r_dev_head = dev_head_val; + } else { // assume ATA by default + this->r_cylinder_lo = 0; + this->r_cylinder_hi = 0; + this->r_dev_head = 0; + this->r_status = DRDY | DSC; + } +} + +uint16_t AtaBaseDevice::read(const uint8_t reg_addr) { + switch (reg_addr) { + case ATA_Reg::DATA: + LOG_F(WARNING, "Retrieving data from %s", this->name.c_str()); + return 0xFFFFU; + case ATA_Reg::ERROR: + return this->r_error; + case ATA_Reg::SEC_COUNT: + return this->r_sect_count; + case ATA_Reg::SEC_NUM: + return this->r_sect_num; + case ATA_Reg::CYL_LOW: + return this->r_cylinder_lo; + case ATA_Reg::CYL_HIGH: + return this->r_cylinder_hi; + case ATA_Reg::DEVICE_HEAD: + return this->r_dev_head; + case ATA_Reg::STATUS: + // TODO: clear pending interrupt + return this->r_status; + case ATA_Reg::ALT_STATUS: + return this->r_status; + default: + LOG_F(WARNING, "Attempted to read unknown IDE register: %x", reg_addr); + return 0; + } +} + +void AtaBaseDevice::write(const uint8_t reg_addr, const uint16_t value) { + switch (reg_addr) { + case ATA_Reg::DATA: + LOG_F(WARNING, "Pushing data to %s", this->name.c_str()); + break; + case ATA_Reg::FEATURES: + this->r_features = value; + break; + case ATA_Reg::SEC_COUNT: + this->r_sect_count = value; + break; + case ATA_Reg::SEC_NUM: + this->r_sect_num = value; + break; + case ATA_Reg::CYL_LOW: + this->r_cylinder_lo = value; + break; + case ATA_Reg::CYL_HIGH: + this->r_cylinder_hi = value; + break; + case ATA_Reg::DEVICE_HEAD: + this->r_dev_head = value; + break; + case ATA_Reg::COMMAND: + this->r_command = value; + if (is_selected() || this->r_command == DIAGNOSTICS) { + perform_command(); + } + break; + case ATA_Reg::DEV_CTRL: + if (!(this->r_dev_ctrl & SRST) && (value & SRST)) { + LOG_F(INFO, "%s: soft reset triggered", this->name.c_str()); + this->r_status |= BSY; + } + this->r_dev_ctrl = value; + break; + default: + LOG_F(WARNING, "Attempted to write unknown IDE register: %x", reg_addr); + } +} diff --git a/devices/common/ata/atabasedevice.h b/devices/common/ata/atabasedevice.h new file mode 100644 index 0000000..687746e --- /dev/null +++ b/devices/common/ata/atabasedevice.h @@ -0,0 +1,67 @@ +/* +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 Base class for ATA devices. */ + +#ifndef ATA_BASE_DEVICE_H +#define ATA_BASE_DEVICE_H + +#include +#include + +#include + +class AtaBaseDevice : public HWComponent, public AtaInterface +{ +public: + AtaBaseDevice(const std::string name); + ~AtaBaseDevice() = default; + + uint16_t read(const uint8_t reg_addr); + void write(const uint8_t reg_addr, const uint16_t value); + + virtual int perform_command() = 0; + + int get_device_id() { return this->my_dev_id; }; + + void device_reset(const uint8_t dev_head_val = 0); + +private: + bool is_selected() { return ((this->r_dev_head >> 4) & 1) == this->my_dev_id; }; + +protected: + uint8_t my_dev_id = 0; // my IDE device ID configured by the host + uint8_t device_type = ata_interface::DEVICE_TYPE_UNKNOWN; + + // IDE aka task file registers + uint8_t r_error; + uint8_t r_features; + uint8_t r_sect_count; + uint8_t r_sect_num; + uint8_t r_cylinder_lo; + uint8_t r_cylinder_hi; + uint8_t r_dev_head; + uint8_t r_command; + uint8_t r_status; + uint8_t r_dev_ctrl; +}; + +#endif // ATA_BASE_DEVICE_H diff --git a/devices/common/ata/atadefs.h b/devices/common/ata/atadefs.h new file mode 100644 index 0000000..a75fd88 --- /dev/null +++ b/devices/common/ata/atadefs.h @@ -0,0 +1,155 @@ +/* +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 ATA interface definitions. */ + +#ifndef ATA_INTERFACE_H +#define ATA_INTERFACE_H + +#include + +namespace ata_interface { + +/** Device IDs according with the Apple ATA zero/one specification. */ +enum { + DEVICE_ID_INVALID = -1, + DEVICE_ID_ZERO = 0, + DEVICE_ID_ONE = 1 +}; + +/** Device types. */ +enum { + DEVICE_TYPE_UNKNOWN = -1, + DEVICE_TYPE_ATA = 0, + DEVICE_TYPE_ATAPI = 1, +}; + +/** ATA register offsets. */ +enum ATA_Reg : int { + DATA = 0x0, + ERROR = 0x1, // error (read) + FEATURES = 0x1, // features (write) + SEC_COUNT = 0x2, // sector count + SEC_NUM = 0x3, // sector number + CYL_LOW = 0x4, // cylinder low + CYL_HIGH = 0x5, // cylinder high + DEVICE_HEAD = 0x6, // device/head + STATUS = 0x7, // status (read) + COMMAND = 0x7, // command (write) + ALT_STATUS = 0x16, // alt status (read) + DEV_CTRL = 0x16, // device control (write) + TIME_CONFIG = 0x20 // Apple ASIC specific timing configuration +}; + +/** Status register bits. */ +enum ATA_Status : int { + ERR = 0x1, + IDX = 0x2, + CORR = 0x4, + DRQ = 0x8, + DSC = 0x10, + DWF = 0x20, + DRDY = 0x40, + BSY = 0x80 +}; + +/** Error register bits. */ +enum ATA_Error : int { + ANMF = 0x1, //no address mark + TK0NF = 0x2, //track 0 not found + ABRT = 0x4, //abort command + MCR = 0x8, + IDNF = 0x10, //id mark not found + MC = 0x20, //media change request + UNC = 0x40, + BBK = 0x80 //bad block +}; + +/** Bit definition for the device control register. */ +enum ATA_CTRL : int{ + IEN = 0x2, + SRST = 0x4, + HOB = 0x80, +}; + +/* ATA commands. */ +enum ATA_Cmd : int { + NOP = 0x00, + RESET_ATAPI = 0x08, + RECALIBRATE = 0x10, + READ_SECTOR = 0x20, + READ_SECTOR_NR = 0x21, + READ_LONG = 0x22, + READ_SECTOR_EXT = 0x24, + WRITE_SECTOR = 0x30, + WRITE_SECTOR_NR = 0x31, + WRITE_LONG = 0x32, + READ_VERIFY = 0x40, + FORMAT_TRACKS = 0x50, + IDE_SEEK = 0x70, + DIAGNOSTICS = 0x90, + INIT_DEV_PARAM = 0x91, + PACKET = 0xA0, + IDFY_PKT_DEV = 0xA1, + READ_MULTIPLE = 0xC4, + WRITE_MULTIPLE = 0xC5, + READ_DMA = 0xC8, + WRITE_DMA = 0xCA, + WRITE_BUFFER_DMA = 0xE9, + READ_BUFFER_DMA = 0xEB, + IDENTIFY_DEVICE = 0xEC, +}; + +}; // namespace ata_interface + +/** Interface for ATA devices. */ +class AtaInterface { +public: + AtaInterface() = default; + virtual ~AtaInterface() = default; + virtual uint16_t read(const uint8_t reg_addr) = 0; + virtual void write(const uint8_t reg_addr, const uint16_t val) = 0; + + virtual int get_device_id() = 0; +}; + +/** Dummy ATA device. */ +class AtaNullDevice : public AtaInterface { +public: + AtaNullDevice() = default; + ~AtaNullDevice() = default; + + uint16_t read(const uint8_t reg_addr) { + // return all one's except DD7 if no device is present + // DD7 corresponds to the BSY bit of the status register + // The host should have a pull-down resistor on DD7 + // to prevent the software from waiting for a long time + // for empty slots + return 0xFF7FU; + }; + + void write(const uint8_t reg_addr, const uint16_t val) {}; + + // invalid device ID means no real device is present + int get_device_id() { return ata_interface::DEVICE_ID_INVALID; }; +}; + +#endif // ATA_INTERFACE_H diff --git a/devices/common/ata/atahd.cpp b/devices/common/ata/atahd.cpp new file mode 100644 index 0000000..3c519c3 --- /dev/null +++ b/devices/common/ata/atahd.cpp @@ -0,0 +1,117 @@ +/* +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 ATA hard disk emulation. */ + +#include + +#include +#include +#include +#include +#include + +using namespace ata_interface; + +AtaHardDisk::AtaHardDisk() : AtaBaseDevice("ATA-HD") +{ +} + +void AtaHardDisk::insert_image(std::string filename) { + this->hdd_img.open(filename, std::fstream::out | std::fstream::in | std::fstream::binary); + + struct stat stat_buf; + int rc = stat(filename.c_str(), &stat_buf); + if (!rc) { + this->img_size = stat_buf.st_size; + } else { + ABORT_F("AtaHardDisk: could not determine file size using stat()"); + } + this->hdd_img.seekg(0, std::ios_base::beg); +} + +int AtaHardDisk::perform_command() +{ + LOG_F(INFO, "%s: command 0x%x requested", this->name.c_str(), this->r_command); + this->r_status |= BSY; + this->r_status &= ~(DRDY); + switch (this->r_command) { + case NOP: + break; + case RESET_ATAPI: { + device_reset(); + break; + } + case RECALIBRATE: + hdd_img.seekg(0, std::ios::beg); + this->r_error = 0; + this->r_cylinder_lo = 0; + this->r_cylinder_hi = 0; + break; + case READ_SECTOR: + case READ_SECTOR_NR: { + this->r_status |= DRQ; + uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count; + uint32_t sector = (r_sect_num << 16); + sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi); + uint64_t offset = sector * 512; + hdd_img.seekg(offset, std::ios::beg); + hdd_img.read(buffer, sec_count * 512); + this->r_status &= ~(DRQ); + break; + } + case WRITE_SECTOR: + case WRITE_SECTOR_NR: { + this->r_status |= DRQ; + uint16_t sec_count = (this->r_sect_count == 0) ? 256 : this->r_sect_count; + uint32_t sector = (r_sect_num << 16); + sector |= ((this->r_cylinder_lo) << 8) + (this->r_cylinder_hi); + uint64_t offset = sector * 512; + hdd_img.seekg(offset, std::ios::beg); + hdd_img.write(buffer, sec_count * 512); + this->r_status &= ~(DRQ); + break; + } + case INIT_DEV_PARAM: + break; + case DIAGNOSTICS: { + this->r_status |= DRQ; + int ret_code = this->r_error; + this->r_status &= ~(DRQ); + return ret_code; + break; + } + case IDENTIFY_DEVICE: { + this->r_status |= DRQ; + std::memcpy(buffer, ide_hd_id, 512); + this->r_status &= ~(DRQ); + break; + } + default: + LOG_F(INFO, "Unknown ATA command 0x%x", this->r_command); + this->r_status &= ~(BSY); + this->r_status |= ERR; + return -1; + } + this->r_status &= ~(BSY); + this->r_status |= DRDY; + return 0; +} diff --git a/devices/common/ata/atahd.h b/devices/common/ata/atahd.h new file mode 100644 index 0000000..5e87ee8 --- /dev/null +++ b/devices/common/ata/atahd.h @@ -0,0 +1,50 @@ +/* +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 TA hard disk definitions. */ + +#ifndef ATA_HARD_DISK_H +#define ATA_HARD_DISK_H + +#include +#include +#include + +class AtaHardDisk : public AtaBaseDevice +{ +public: + AtaHardDisk(); + ~AtaHardDisk() = default; + + void insert_image(std::string filename); + int perform_command(); + +private: + std::fstream hdd_img; + uint64_t img_size; + char * buffer = new char[1 <<17]; + + char* ide_hd_id = { + 0x0 + }; +}; + +#endif // ATA_HARD_DISK_H diff --git a/devices/common/ata/idechannel.cpp b/devices/common/ata/idechannel.cpp new file mode 100644 index 0000000..09c708d --- /dev/null +++ b/devices/common/ata/idechannel.cpp @@ -0,0 +1,90 @@ +/* +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 . +*/ + +/** IDE Channel (aka IDE port) emulation. + + One IDE channel is capable of controlling up to two IDE devices. + This class handles device registration and passing of messages + from and to the host. + */ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace ata_interface; + +IdeChannel::IdeChannel(const std::string name) +{ + this->set_name(name); + this->supports_types(HWCompType::IDE_BUS); + + this->devices[0] = std::unique_ptr(new AtaNullDevice()); + this->devices[1] = std::unique_ptr(new AtaNullDevice()); +} + +uint32_t IdeChannel::read(const uint8_t reg_addr, const int size) +{ + if (reg_addr == TIME_CONFIG) { + if (size != 4) { + LOG_F(WARNING, "%s: non-DWORD read from the channel config", this->name.c_str()); + } + return this->ch_config; + } else { + return this->devices[this->cur_dev]->read(reg_addr); + } +} + +void IdeChannel::write(const uint8_t reg_addr, const uint32_t val, const int size) +{ + if (reg_addr == TIME_CONFIG) { + if (size != 4) { + LOG_F(WARNING, "%s: non-DWORD write to the channel config", this->name.c_str()); + } + this->ch_config = val; + } else { + // keep track of the currently selected device + if (reg_addr == DEVICE_HEAD) { + this->cur_dev = (val >> 4) & 1; + } + + // redirect register writes to both devices + for (auto& dev : this->devices) { + dev->write(reg_addr, val); + } + } +} + +static const DeviceDescription Ide0_Descriptor = { + IdeChannel::create_first, {}, {} +}; + +static const DeviceDescription Ide1_Descriptor = { + IdeChannel::create_second, {}, {} +}; + +REGISTER_DEVICE(Ide0, Ide0_Descriptor); +REGISTER_DEVICE(Ide1, Ide1_Descriptor); diff --git a/devices/common/ata/idechannel.h b/devices/common/ata/idechannel.h new file mode 100644 index 0000000..9fd8c63 --- /dev/null +++ b/devices/common/ata/idechannel.h @@ -0,0 +1,58 @@ +/* +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 IDE Channel (aka IDE port) definitions. */ + +#ifndef IDE_CHANNEL_H +#define IDE_CHANNEL_H + +#include +#include + +#include +#include +#include + +class IdeChannel : public HWComponent +{ +public: + IdeChannel(const std::string name); + ~IdeChannel() = default; + + static std::unique_ptr create_first() { + return std::unique_ptr(new IdeChannel("IDEO")); + } + + static std::unique_ptr create_second() { + return std::unique_ptr(new IdeChannel("IDE1")); + } + + uint32_t read(const uint8_t reg_addr, const int size); + void write(const uint8_t reg_addr, const uint32_t val, const int size); + +private: + int cur_dev = 0; + uint32_t ch_config = 0; // timing configuration for this channel + + std::unique_ptr devices[2]; +}; + +#endif // IDE_CHANNEL_H diff --git a/devices/common/hwcomponent.h b/devices/common/hwcomponent.h index 6543b1b..eb72508 100644 --- a/devices/common/hwcomponent.h +++ b/devices/common/hwcomponent.h @@ -43,6 +43,8 @@ enum HWCompType { SCSI_BUS = 1ULL << 20, /* SCSI bus */ SCSI_HOST = 1ULL << 21, /* SCSI host adapter */ SCSI_DEV = 1ULL << 22, /* SCSI device */ + IDE_BUS = 1ULL << 23, /* IDE bus */ + IDE_DEV = 1ULL << 25, /* IDE device */ SND_SERVER = 1ULL << 31, /* host sound server */ FLOPPY_CTRL = 1ULL << 32, /* floppy disk controller */ FLOPPY_DRV = 1ULL << 33, /* floppy disk drive */ diff --git a/devices/common/scsi/mesh.cpp b/devices/common/scsi/mesh.cpp index 0b5369a..78518b5 100644 --- a/devices/common/scsi/mesh.cpp +++ b/devices/common/scsi/mesh.cpp @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-21 divingkatae and maximum +Copyright (C) 2018-23 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -21,22 +21,57 @@ along with this program. If not, see . /** @file MESH (Macintosh Enhanced SCSI Hardware) controller emulation. */ +#include +#include #include #include +#include +#include #include -#include using namespace MeshScsi; -uint8_t MESHController::read(uint8_t reg_offset) +int MeshController::device_postinit() +{ + this->bus_obj = dynamic_cast(gMachineObj->get_comp_by_name("SCSI0")); + + this->int_ctrl = dynamic_cast( + gMachineObj->get_comp_by_type(HWCompType::INT_CTRL)); + this->irq_id = this->int_ctrl->register_dev_int(IntSrc::SCSI1); + + return 0; +} + +void MeshController::reset(bool is_hard_reset) +{ + this->cur_cmd = SeqCmd::NoOperation; + this->int_mask = 0; + + if (is_hard_reset) { + this->bus_stat = 0; + this->sync_params = 2; // guessed + } +} + +uint8_t MeshController::read(uint8_t reg_offset) { switch(reg_offset) { + case MeshReg::Sequence: + return this->cur_cmd; case MeshReg::BusStatus0: - LOG_F(9, "MESH: read from BusStatus0 register"); + return this->bus_obj->test_ctrl_lines(0xFFU); + case MeshReg::BusStatus1: + return this->bus_obj->test_ctrl_lines(0xE000U) >> 8; + case MeshReg::Exception: return 0; + case MeshReg::Error: + return 0; + case MeshReg::IntMask: + return this->int_mask; + case MeshReg::Interrupt: + return this->int_stat; case MeshReg::MeshID: - LOG_F(INFO, "MESH: read from MeshID register"); return this->chip_id; // tell them who we are default: LOG_F(WARNING, "MESH: read from unimplemented register at offset 0x%x", reg_offset); @@ -45,34 +80,166 @@ uint8_t MESHController::read(uint8_t reg_offset) return 0; } -void MESHController::write(uint8_t reg_offset, uint8_t value) +void MeshController::write(uint8_t reg_offset, uint8_t value) { + uint16_t new_stat; + switch(reg_offset) { case MeshReg::Sequence: - LOG_F(INFO, "MESH: write 0x%02X to Sequence register", value); + perform_command(value); break; case MeshReg::BusStatus1: - LOG_F(INFO, "MESH: write 0x%02X to BusStatus1 register", value); + new_stat = value << 8; + if (new_stat != this->bus_stat) { + for (uint16_t mask = SCSI_CTRL_RST; mask >= SCSI_CTRL_SEL; mask >>= 1) { + if ((new_stat ^ this->bus_stat) & mask) { + if (new_stat & mask) + this->bus_obj->assert_ctrl_line(new_stat, mask); + else + this->bus_obj->release_ctrl_line(new_stat, mask); + } + } + this->bus_stat = new_stat; + } break; case MeshReg::IntMask: - LOG_F(INFO, "MESH: write 0x%02X to IntMask register", value); + this->int_mask = value; break; case MeshReg::Interrupt: - LOG_F(INFO, "MESH: write 0x%02X to Interrupt register", value); + this->int_stat &= ~(value & INT_MASK); // clear requested interrupt bits + update_irq(); break; case MeshReg::SourceID: - LOG_F(INFO, "MESH: write 0x%02X to SourceID register", value); + this->src_id = value; + break; + case MeshReg::DestID: + this->dst_id = value; break; case MeshReg::SyncParms: - LOG_F(INFO, "MESH: write 0x%02X to SyncParms register", value); + this->sync_params = value; + break; + case MeshReg::SelTimeOut: + LOG_F(9, "MESH: selection timeout set to 0x%x", value); break; default: - LOG_F(WARNING, "MESH: write to unimplemented register at offset 0x%x", reg_offset); + LOG_F(WARNING, "MESH: write to unimplemented register at offset 0x%x", + reg_offset); + } +} + +void MeshController::perform_command(const uint8_t cmd) +{ + this->cur_cmd = cmd; + + this->int_stat &= ~INT_CMD_DONE; + + switch (this->cur_cmd & 0xF) { + case SeqCmd::Arbitrate: + this->cur_state = SeqState::BUS_FREE; + this->sequencer(); + break; + case SeqCmd::Select: + this->cur_state = SeqState::SEL_BEGIN; + this->sequencer(); + break; + case SeqCmd::DisReselect: + LOG_F(INFO, "MESH: DisReselect command requested"); + break; + case SeqCmd::ResetMesh: + this->reset(false); + this->int_stat |= INT_CMD_DONE; + break; + case SeqCmd::FlushFIFO: + LOG_F(INFO, "MESH: FlushFIFO command requested"); + break; + default: + LOG_F(ERROR, "MESH: unsupported sequencer command 0x%X", this->cur_cmd); + } +} + +void MeshController::seq_defer_state(uint64_t delay_ns) +{ + seq_timer_id = TimerManager::get_instance()->add_oneshot_timer( + delay_ns, + [this]() { + // re-enter the sequencer with the state specified in next_state + this->cur_state = this->next_state; + this->sequencer(); + }); +} + +void MeshController::sequencer() +{ + switch (this->cur_state) { + case SeqState::IDLE: + break; + case SeqState::BUS_FREE: + if (this->bus_obj->current_phase() == ScsiPhase::BUS_FREE) { + this->next_state = SeqState::ARB_BEGIN; + this->seq_defer_state(BUS_FREE_DELAY + BUS_SETTLE_DELAY); + } else { // continue waiting + this->next_state = SeqState::BUS_FREE; + this->seq_defer_state(BUS_FREE_DELAY); + } + break; + case SeqState::ARB_BEGIN: + if (!this->bus_obj->begin_arbitration(this->src_id)) { + LOG_F(ERROR, "MESH: arbitration error, bus not free!"); + this->bus_obj->release_ctrl_lines(this->src_id); + this->next_state = SeqState::BUS_FREE; + this->seq_defer_state(BUS_CLEAR_DELAY); + break; + } + this->next_state = SeqState::ARB_END; + this->seq_defer_state(ARB_DELAY); + break; + case SeqState::ARB_END: + if (this->bus_obj->end_arbitration(this->src_id) && + !this->bus_obj->test_ctrl_lines(SCSI_CTRL_SEL)) { // arbitration won + this->bus_obj->assert_ctrl_line(this->src_id, SCSI_CTRL_SEL); + } else { // arbitration lost + LOG_F(INFO, "MESH: arbitration lost!"); + this->bus_obj->release_ctrl_lines(this->src_id); + this->exception |= EXC_ARB_LOST; + this->int_stat |= INT_EXCEPTION; + } + this->int_stat |= INT_CMD_DONE; + update_irq(); + break; + case SeqState::SEL_BEGIN: + this->bus_obj->begin_selection(this->src_id, this->dst_id, this->cur_cmd & 0x20); + this->next_state = SeqState::SEL_END; + this->seq_defer_state(SEL_TIME_OUT); + break; + case SeqState::SEL_END: + if (this->bus_obj->end_selection(this->src_id, this->dst_id)) { + this->bus_obj->release_ctrl_line(this->src_id, SCSI_CTRL_SEL); + LOG_F(9, "MESH: selection completed"); + } else { // selection timeout + this->bus_obj->disconnect(this->src_id); + this->cur_state = SeqState::IDLE; + this->exception |= EXC_SEL_TIMEOUT; + this->int_stat |= INT_EXCEPTION; + } + this->int_stat |= INT_CMD_DONE; + update_irq(); + break; + default: + ABORT_F("MESH: unimplemented sequencer state %d", this->cur_state); + } +} + +void MeshController::update_irq() +{ + uint8_t new_irq = !!(this->int_stat & this->int_mask); + if (new_irq != this->irq) { + this->irq = new_irq; + this->int_ctrl->ack_int(this->irq_id, new_irq); } } static const DeviceDescription Mesh_Descriptor = { - MESHController::create, {}, {} + MeshController::create, {}, {} }; REGISTER_DEVICE(Mesh, Mesh_Descriptor); diff --git a/devices/common/scsi/mesh.h b/devices/common/scsi/mesh.h index ff6bad6..8384eb7 100644 --- a/devices/common/scsi/mesh.h +++ b/devices/common/scsi/mesh.h @@ -1,6 +1,6 @@ /* DingusPPC - The Experimental PowerPC Macintosh emulator -Copyright (C) 2018-21 divingkatae and maximum +Copyright (C) 2018-23 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) @@ -25,6 +25,8 @@ along with this program. If not, see . #define MESH_H #include +#include +#include #include #include @@ -34,7 +36,7 @@ along with this program. If not, see . namespace MeshScsi { -// MESH registers offsets +// MESH registers offsets. enum MeshReg : uint8_t { XferCount0 = 0, XferCount1 = 1, @@ -54,26 +56,102 @@ enum MeshReg : uint8_t { SelTimeOut = 0xF }; +// MESH Sequencer commands. +enum SeqCmd : uint8_t { + NoOperation = 0, + Arbitrate = 1, + Select = 2, + DisReselect = 0xD, + ResetMesh = 0xE, + FlushFIFO = 0xF, +}; + +// Exception register bits. +enum { + EXC_SEL_TIMEOUT = 1 << 0, + EXC_PHASE_MM = 1 << 1, + EXC_ARB_LOST = 1 << 2, +}; + +// Interrupt register bits. +enum { + INT_CMD_DONE = 1 << 0, + INT_EXCEPTION = 1 << 1, + INT_ERROR = 1 << 2, + INT_MASK = INT_CMD_DONE | INT_EXCEPTION | INT_ERROR +}; + + +enum SeqState : uint32_t { + IDLE = 0, + BUS_FREE, + ARB_BEGIN, + ARB_END, + SEL_BEGIN, + SEL_END, + SEND_MSG, + SEND_CMD, + CMD_COMPLETE, + XFER_BEGIN, + XFER_END, + SEND_DATA, + RCV_DATA, + RCV_STATUS, + RCV_MESSAGE, +}; + }; // namespace MeshScsi -class MESHController : public HWComponent { +class MeshController : public HWComponent { public: - MESHController(uint8_t mesh_id) { + MeshController(uint8_t mesh_id) { supports_types(HWCompType::SCSI_HOST | HWCompType::SCSI_DEV); this->chip_id = mesh_id; + this->reset(true); }; - ~MESHController() = default; + ~MeshController() = default; static std::unique_ptr create() { - return std::unique_ptr(new MESHController(HeathrowMESHID)); + return std::unique_ptr(new MeshController(HeathrowMESHID)); } // MESH registers access uint8_t read(uint8_t reg_offset); void write(uint8_t reg_offset, uint8_t value); + // HWComponent methods + int device_postinit(); + +protected: + void reset(bool is_hard_reset); + void perform_command(const uint8_t cmd); + void seq_defer_state(uint64_t delay_ns); + void sequencer(); + void update_irq(); + private: - uint8_t chip_id; // Chip ID for the MESH controller + uint8_t chip_id; + uint8_t int_mask; + uint8_t int_stat = 0; + uint8_t sync_params; + uint8_t src_id; + uint8_t dst_id; + uint8_t cur_cmd; + uint8_t error; + uint8_t exception; + + ScsiBus* bus_obj; + uint16_t bus_stat; + + // Sequencer state + uint32_t seq_timer_id; + uint32_t cur_state; + uint32_t next_state; + + // interrupt related stuff + InterruptCtrl* int_ctrl = nullptr; + uint32_t irq_id = 0; + uint8_t irq = 0; }; #endif // MESH_H diff --git a/devices/common/scsi/sc53c94.cpp b/devices/common/scsi/sc53c94.cpp index 18ab7a3..481b26e 100644 --- a/devices/common/scsi/sc53c94.cpp +++ b/devices/common/scsi/sc53c94.cpp @@ -30,8 +30,9 @@ along with this program. If not, see . #include #include +#include -Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) +Sc53C94::Sc53C94(uint8_t chip_id, uint8_t my_id) : ScsiDevice(my_id) { this->chip_id = chip_id; this->my_bus_id = my_id; @@ -74,13 +75,20 @@ void Sc53C94::reset_device() uint8_t Sc53C94::read(uint8_t reg_offset) { - uint8_t int_status; + uint8_t status, int_status; switch (reg_offset) { + case Read::Reg53C94::Xfer_Cnt_LSB: + return this->xfer_count & 0xFFU; + case Read::Reg53C94::Xfer_Cnt_MSB: + return (this->xfer_count >> 8) & 0xFFU; + case Read::Reg53C94::FIFO: + return this->fifo_pop(); case Read::Reg53C94::Command: return this->cmd_fifo[0]; case Read::Reg53C94::Status: - return this->status; + status = bus_obj->test_ctrl_lines(SCSI_CTRL_MSG | SCSI_CTRL_CD | SCSI_CTRL_IO); + return (this->status & 0xF8) | status; case Read::Reg53C94::Int_Status: int_status = this->int_status; this->seq_step = 0; @@ -91,6 +99,8 @@ uint8_t Sc53C94::read(uint8_t reg_offset) return this->seq_step; case Read::Reg53C94::FIFO_Flags: return (this->seq_step << 5) | (this->data_fifo_pos & 0x1F); + case Read::Reg53C94::Config_1: + return this->config1; case Read::Reg53C94::Config_3: return this->config3; case Read::Reg53C94::Xfer_Cnt_Hi: @@ -107,6 +117,12 @@ uint8_t Sc53C94::read(uint8_t reg_offset) void Sc53C94::write(uint8_t reg_offset, uint8_t value) { switch (reg_offset) { + case Write::Reg53C94::Xfer_Cnt_LSB: + this->set_xfer_count = (this->set_xfer_count & ~0xFFU) | value; + break; + case Write::Reg53C94::Xfer_Cnt_MSB: + this->set_xfer_count = (this->set_xfer_count & ~0xFF00U) | (value << 8); + break; case Write::Reg53C94::Command: update_command_reg(value); break; @@ -142,6 +158,37 @@ void Sc53C94::write(uint8_t reg_offset, uint8_t value) } } +uint16_t Sc53C94::pseudo_dma_read() +{ + uint16_t data_word; + bool is_done = false; + + if (this->data_fifo_pos >= 2) { + // remove one word from FIFO + data_word = (this->data_fifo[0] << 8) | this->data_fifo[1]; + this->data_fifo_pos -= 2; + std:memmove(this->data_fifo, &this->data_fifo[2], this->data_fifo_pos); + + // update DMA status + if ((this->cmd_fifo[0] & 0x80)) { + this->xfer_count -= 2; + if (!this->xfer_count) { + is_done = true; + this->status |= STAT_TC; // signal zero transfer count + this->cur_state = SeqState::XFER_END; + this->sequencer(); + } + } + } + + // see if we need to refill FIFO + if (!this->data_fifo_pos && !is_done) { + this->sequencer(); + } + + return data_word; +} + void Sc53C94::update_command_reg(uint8_t cmd) { if (this->on_reset && (cmd & 0x7F) != CMD_NOP) { @@ -166,13 +213,13 @@ void Sc53C94::update_command_reg(uint8_t cmd) } } else { LOG_F(ERROR, "SC53C94: the top of the command FIFO overwritten!"); - this->status |= 0x40; // signal IOE/Gross Error + this->status |= STAT_GE; // signal IOE/Gross Error } } void Sc53C94::exec_command() { - uint8_t cmd = this->cmd_fifo[0] & 0x7F; + uint8_t cmd = this->cur_cmd = this->cmd_fifo[0] & 0x7F; bool is_dma_cmd = !!(this->cmd_fifo[0] & 0x80); if (is_dma_cmd) { @@ -180,6 +227,9 @@ void Sc53C94::exec_command() this->xfer_count = this->set_xfer_count & 0xFFFFFFUL; } else { // standard mode: 16-bit this->xfer_count = this->set_xfer_count & 0xFFFFUL; + if (!this->xfer_count) { + this->xfer_count = 65536; + } } } @@ -192,7 +242,7 @@ void Sc53C94::exec_command() exec_next_command(); break; case CMD_CLEAR_FIFO: - this->data_fifo_pos = 0; // set the bottom of the FIFO to zero + this->data_fifo_pos = 0; // set the bottom of the data FIFO to zero this->data_fifo[0] = 0; exec_next_command(); break; @@ -216,26 +266,77 @@ void Sc53C94::exec_command() } exec_next_command(); break; + case CMD_XFER: + if (!this->is_initiator) { + // clear command FIFO + this->cmd_fifo_pos = 0; + this->int_status |= INTSTAT_ICMD; + this->update_irq(); + } else { + this->seq_step = 0; + this->cmd_steps = nullptr; + this->cur_state = SeqState::XFER_BEGIN; + this->sequencer(); + } + break; + case CMD_COMPLETE_STEPS: + static SeqDesc * complete_steps_desc = new SeqDesc[3]{ + {SeqState::RCV_STATUS, 0, 0}, + {SeqState::RCV_MESSAGE, 0, 0}, + {SeqState::CMD_COMPLETE, 0, INTSTAT_SR} + }; + if (this->bus_obj->current_phase() != ScsiPhase::STATUS) { + ABORT_F("Sc53C94: complete steps only works in the STATUS phase"); + } + this->seq_step = 0; + this->cmd_steps = complete_steps_desc; + this->cur_state = this->cmd_steps->next_step; + this->sequencer(); + break; + case CMD_MSG_ACCEPTED: + if (this->is_initiator) { + this->bus_obj->target_next_step(); + } + this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ACK); + this->int_status |= INTSTAT_SR; + this->int_status |= INTSTAT_DIS; // TODO: handle target disconnection properly + this->update_irq(); + exec_next_command(); + break; case CMD_SELECT_NO_ATN: static SeqDesc * sel_no_atn_desc = new SeqDesc[3]{ {SeqState::SEL_BEGIN, 0, INTSTAT_DIS }, - {SeqState::CMD_BEGIN, 3, INTSTAT_SR | INTSTAT_SO}, + {SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO}, {SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO}, }; this->seq_step = 0; this->cmd_steps = sel_no_atn_desc; this->cur_state = SeqState::BUS_FREE; - sequencer(); - LOG_F(INFO, "SC53C94: SELECT W/O ATN command started"); + this->sequencer(); + LOG_F(9, "SC53C94: SELECT W/O ATN command started"); + break; + case CMD_SELECT_WITH_ATN: + static SeqDesc * sel_with_atn_desc = new SeqDesc[4]{ + {SeqState::SEL_BEGIN, 0, INTSTAT_DIS }, + {SeqState::SEND_MSG, 2, INTSTAT_SR | INTSTAT_SO}, + {SeqState::SEND_CMD, 3, INTSTAT_SR | INTSTAT_SO}, + {SeqState::CMD_COMPLETE, 4, INTSTAT_SR | INTSTAT_SO}, + }; + this->seq_step = 0; + this->bytes_out = 1; // set message length + this->cmd_steps = sel_with_atn_desc; + this->cur_state = SeqState::BUS_FREE; + this->sequencer(); + LOG_F(9, "SC53C94: SELECT WITH ATN command started"); break; case CMD_ENA_SEL_RESEL: - LOG_F(INFO, "SC53C94: ENABLE SELECTION/RESELECTION command executed"); exec_next_command(); break; default: LOG_F(ERROR, "SC53C94: invalid/unimplemented command 0x%X", cmd); this->cmd_fifo_pos--; // remove invalid command from FIFO this->int_status |= INTSTAT_ICMD; + this->update_irq(); } } @@ -252,14 +353,30 @@ void Sc53C94::exec_next_command() void Sc53C94::fifo_push(const uint8_t data) { - if (this->data_fifo_pos < 16) { + if (this->data_fifo_pos < DATA_FIFO_MAX) { this->data_fifo[this->data_fifo_pos++] = data; } else { LOG_F(ERROR, "SC53C94: data FIFO overflow!"); - this->status |= 0x40; // signal IOE/Gross Error + this->status |= STAT_GE; // signal IOE/Gross Error } } +uint8_t Sc53C94::fifo_pop() +{ + uint8_t data = 0; + + if (this->data_fifo_pos < 1) { + LOG_F(ERROR, "SC53C94: data FIFO underflow!"); + this->status |= STAT_GE; // signal IOE/Gross Error + } else { + data = this->data_fifo[0]; + this->data_fifo_pos--; + std:memmove(this->data_fifo, &this->data_fifo[1], this->data_fifo_pos); + } + + return data; +} + void Sc53C94::seq_defer_state(uint64_t delay_ns) { seq_timer_id = TimerManager::get_instance()->add_oneshot_timer( @@ -316,8 +433,8 @@ void Sc53C94::sequencer() break; case SeqState::SEL_END: if (this->bus_obj->end_selection(this->my_bus_id, this->target_id)) { - LOG_F(INFO, "SC53C94: selection completed"); - this->cmd_steps++; + this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_SEL); + LOG_F(9, "SC53C94: selection completed"); } else { // selection timeout this->seq_step = this->cmd_steps->step_num; this->int_status |= this->cmd_steps->status; @@ -327,6 +444,84 @@ void Sc53C94::sequencer() exec_next_command(); } break; + case SeqState::SEND_MSG: + this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out); + this->bus_obj->push_data(this->target_id, this->data_fifo, this->bytes_out); + this->data_fifo_pos -= this->bytes_out; + if (this->data_fifo_pos > 0) { + std::memmove(this->data_fifo, &this->data_fifo[this->bytes_out], this->data_fifo_pos); + } + this->bus_obj->release_ctrl_line(this->my_bus_id, SCSI_CTRL_ATN); + break; + case SeqState::SEND_CMD: + this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out); + this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos); + this->data_fifo_pos = 0; + break; + case SeqState::CMD_COMPLETE: + this->seq_step = this->cmd_steps->step_num; + this->int_status |= this->cmd_steps->status; + this->update_irq(); + exec_next_command(); + break; + case SeqState::XFER_BEGIN: + this->cur_bus_phase = this->bus_obj->current_phase(); + switch (this->cur_bus_phase) { + case ScsiPhase::DATA_OUT: + if (this->cmd_fifo[0] & 0x80) { + this->cur_state = SeqState::SEND_DATA; + break; + } + this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos); + this->data_fifo_pos = 0; + this->cur_state = SeqState::XFER_END; + this->sequencer(); + break; + case ScsiPhase::DATA_IN: + this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out); + this->cur_state = SeqState::RCV_DATA; + this->rcv_data(); + if (!(this->cmd_fifo[0] & 0x80)) { + this->cur_state = SeqState::XFER_END; + this->sequencer(); + } + } + break; + case SeqState::XFER_END: + if (this->is_initiator) { + this->bus_obj->target_next_step(); + } + this->int_status |= INTSTAT_SR; + this->update_irq(); + exec_next_command(); + break; + case SeqState::SEND_DATA: + break; + case SeqState::RCV_DATA: + // check for unexpected bus phase changes + if (this->bus_obj->current_phase() != this->cur_bus_phase) { + this->cmd_fifo_pos = 0; // clear command FIFO + this->int_status |= INTSTAT_SR; + this->update_irq(); + } else { + this->rcv_data(); + } + break; + case SeqState::RCV_STATUS: + case SeqState::RCV_MESSAGE: + this->bus_obj->negotiate_xfer(this->data_fifo_pos, this->bytes_out); + this->rcv_data(); + if (this->is_initiator) { + if (this->cur_state == SeqState::RCV_STATUS) { + this->bus_obj->target_next_step(); + } else if (this->cur_state == SeqState::RCV_MESSAGE) { + this->bus_obj->assert_ctrl_line(this->my_bus_id, SCSI_CTRL_ACK); + this->cmd_steps++; + this->cur_state = this->cmd_steps->next_step; + this->sequencer(); + } + } + break; default: ABORT_F("SC53C94: unimplemented sequencer state %d", this->cur_state); } @@ -342,7 +537,7 @@ void Sc53C94::update_irq() } } -void Sc53C94::notify(ScsiMsg msg_type, int param) +void Sc53C94::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param) { switch (msg_type) { case ScsiMsg::CONFIRM_SEL: @@ -350,13 +545,111 @@ void Sc53C94::notify(ScsiMsg msg_type, int param) // cancel selection timeout timer TimerManager::get_instance()->cancel_timer(this->seq_timer_id); this->cur_state = SeqState::SEL_END; - sequencer(); + this->sequencer(); } else { LOG_F(WARNING, "SC53C94: ignore invalid selection confirmation message"); } break; + case ScsiMsg::BUS_PHASE_CHANGE: + if (param != ScsiPhase::BUS_FREE && this->cmd_steps != nullptr) { + this->cmd_steps++; + this->cur_state = this->cmd_steps->next_step; + this->sequencer(); + } + break; default: - LOG_F(WARNING, "SC53C94: ignore notification message, type: %d", msg_type); + LOG_F(9, "SC53C94: ignore notification message, type: %d", msg_type); + } +} + +int Sc53C94::send_data(uint8_t* dst_ptr, int count) +{ + if (dst_ptr == nullptr || !count) { + return 0; + } + + int actual_count = std::min(this->data_fifo_pos, count); + + // move data out of the data FIFO + std::memcpy(dst_ptr, this->data_fifo, actual_count); + + // remove the just readed data from the data FIFO + this->data_fifo_pos -= actual_count; + if (this->data_fifo_pos > 0) { + std::memmove(this->data_fifo, &this->data_fifo[actual_count], this->data_fifo_pos); + } else { + this->cmd_steps++; + this->cur_state = this->cmd_steps->next_step; + this->sequencer(); + } + + return actual_count; +} + +bool Sc53C94::rcv_data() +{ + int req_count; + + // return if REQ line is negated + if (!this->bus_obj->test_ctrl_lines(SCSI_CTRL_REQ)) { + return false; + } + + if ((this->cmd_fifo[0] & 0x80) && this->cur_bus_phase == ScsiPhase::DATA_IN) { + req_count = std::min((int)this->xfer_count, DATA_FIFO_MAX - this->data_fifo_pos); + } else { + req_count = 1; + } + + this->bus_obj->pull_data(this->target_id, &this->data_fifo[this->data_fifo_pos], req_count); + this->data_fifo_pos += req_count; + return true; +} + +void Sc53C94::real_dma_xfer(int direction) +{ + bool is_done = false; + + if (direction) { + uint32_t got_bytes; + uint8_t* src_ptr; + + while (this->xfer_count) { + this->dma_ch->pull_data(std::min((int)this->xfer_count, DATA_FIFO_MAX), + &got_bytes, &src_ptr); + std::memcpy(this->data_fifo, src_ptr, got_bytes); + this->data_fifo_pos = got_bytes; + this->bus_obj->push_data(this->target_id, this->data_fifo, this->data_fifo_pos); + + this->xfer_count -= this->data_fifo_pos; + this->data_fifo_pos = 0; + if (!this->xfer_count) { + is_done = true; + this->status |= STAT_TC; // signal zero transfer count + this->cur_state = SeqState::XFER_END; + this->sequencer(); + } + } + } else { // transfer data from target to host's memory + while (this->xfer_count) { + if (this->data_fifo_pos) { + this->dma_ch->push_data((char*)this->data_fifo, this->data_fifo_pos); + + this->xfer_count -= this->data_fifo_pos; + this->data_fifo_pos = 0; + if (!this->xfer_count) { + is_done = true; + this->status |= STAT_TC; // signal zero transfer count + this->cur_state = SeqState::XFER_END; + this->sequencer(); + } + } + + // see if we need to refill FIFO + if (!this->data_fifo_pos && !is_done) { + this->sequencer(); + } + } } } diff --git a/devices/common/scsi/sc53c94.h b/devices/common/scsi/sc53c94.h index 6e63a1b..ed95b3b 100644 --- a/devices/common/scsi/sc53c94.h +++ b/devices/common/scsi/sc53c94.h @@ -29,12 +29,15 @@ along with this program. If not, see . #ifndef SC_53C94_H #define SC_53C94_H +#include #include #include #include #include +#define DATA_FIFO_MAX 16 + /** 53C94 read registers */ namespace Read { enum Reg53C94 : uint8_t { @@ -78,13 +81,23 @@ namespace Write { /** NCR53C94/Am53CF94 commands. */ enum { - CMD_NOP = 0, - CMD_CLEAR_FIFO = 1, - CMD_RESET_DEVICE = 2, - CMD_RESET_BUS = 3, - CMD_DMA_STOP = 4, - CMD_SELECT_NO_ATN = 0x41, - CMD_ENA_SEL_RESEL = 0x44, + CMD_NOP = 0, + CMD_CLEAR_FIFO = 1, + CMD_RESET_DEVICE = 2, + CMD_RESET_BUS = 3, + CMD_DMA_STOP = 4, + CMD_XFER = 0x10, + CMD_COMPLETE_STEPS = 0x11, + CMD_MSG_ACCEPTED = 0x12, + CMD_SELECT_NO_ATN = 0x41, + CMD_SELECT_WITH_ATN = 0x42, + CMD_ENA_SEL_RESEL = 0x44, +}; + +/** Status register bits. **/ +enum { + STAT_TC = 1 << 4, // Terminal count (NCR) / count to zero (AMD) + STAT_GE = 1 << 6, // Gross Error (NCR) / Illegal Operation Error (AMD) }; /** Interrupt status register bits. */ @@ -112,8 +125,15 @@ namespace SeqState { ARB_END, SEL_BEGIN, SEL_END, - CMD_BEGIN, + SEND_MSG, + SEND_CMD, CMD_COMPLETE, + XFER_BEGIN, + XFER_END, + SEND_DATA, + RCV_DATA, + RCV_STATUS, + RCV_MESSAGE, }; }; @@ -137,11 +157,23 @@ public: int device_postinit(); // 53C94 registers access - uint8_t read(uint8_t reg_offset); - void write(uint8_t reg_offset, uint8_t value); + uint8_t read(uint8_t reg_offset); + void write(uint8_t reg_offset, uint8_t value); + uint16_t pseudo_dma_read(); + + // real DMA control + void real_dma_xfer(int direction); + + void set_dma_channel(DmaBidirChannel *dma_ch) { + this->dma_ch = dma_ch; + }; // ScsiDevice methods - void notify(ScsiMsg msg_type, int param); + void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param); + bool prepare_data() { return false; }; + bool has_data() { return this->data_fifo_pos != 0; }; + int send_data(uint8_t* dst_ptr, int count); + void process_command() {}; protected: void reset_device(); @@ -149,10 +181,13 @@ protected: void exec_command(); void exec_next_command(); void fifo_push(const uint8_t data); + uint8_t fifo_pop(); void sequencer(); void seq_defer_state(uint64_t delay_ns); + bool rcv_data(); + void update_irq(); private: @@ -165,6 +200,7 @@ private: uint8_t data_fifo[16]; int cmd_fifo_pos; int data_fifo_pos; + int bytes_out; bool on_reset = false; uint32_t xfer_count; uint32_t set_xfer_count; @@ -186,11 +222,15 @@ private: SeqDesc* cmd_steps; bool is_initiator; uint8_t cur_cmd; + int cur_bus_phase; // interrupt related stuff InterruptCtrl* int_ctrl = nullptr; uint32_t irq_id = 0; uint8_t irq = 0; + + // DMA related stuff + DmaBidirChannel* dma_ch; }; #endif // SC_53C94_H diff --git a/devices/common/scsi/scsi.h b/devices/common/scsi/scsi.h index b87b4f3..f19f16e 100644 --- a/devices/common/scsi/scsi.h +++ b/devices/common/scsi/scsi.h @@ -28,6 +28,7 @@ along with this program. If not, see . #include #include +#include #include /** SCSI control signals. @@ -40,22 +41,102 @@ enum { SCSI_CTRL_ATN = 1 << 3, SCSI_CTRL_ACK = 1 << 4, SCSI_CTRL_REQ = 1 << 5, - SCSI_CTRL_SEL = 1 << 6, - SCSI_CTRL_BSY = 1 << 7, - SCSI_CTRL_RST = 1 << 8, + SCSI_CTRL_SEL = 1 << 13, + SCSI_CTRL_BSY = 1 << 14, + SCSI_CTRL_RST = 1 << 15, }; -enum ScsiPhase : int { - BUS_FREE = 0, - ARBITRATION, - SELECTION, - RESELECTION, - RESET, +namespace ScsiPhase { + enum : int { + BUS_FREE = 0, + ARBITRATION, + SELECTION, + RESELECTION, + COMMAND, + DATA_IN, + DATA_OUT, + STATUS, + MESSAGE_IN, + MESSAGE_OUT, + RESET, + }; +}; + +namespace ScsiStatus { + enum : uint8_t { + GOOD = 0, + CHECK_CONDITION = 2, + }; +}; + +namespace ScsiMessage { + enum : uint8_t { + COMMAND_COMPLETE = 0, + }; }; enum ScsiMsg : int { CONFIRM_SEL = 1, BUS_PHASE_CHANGE, + SEND_CMD_BEGIN, + SEND_CMD_END, + MESSAGE_BEGIN, + MESSAGE_END, +}; + +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, + 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 { + NO_SENSE = 0x0, + RECOVERED = 0x1, + NOT_READY = 0x2, + MEDIUM_ERR = 0x3, + HW_ERROR = 0x4, + ILLEGAL_REQ = 0x5, + UNIT_ATTENTION = 0x6, + DATA_PROTECT = 0x7, + BLANK_CHECK = 0x8, + VOL_OVERFLOW = 0xD, + MISCOMPARE = 0xE, + COMPLETED = 0xF +}; + +enum ScsiError : int { + NO_ERROR = 0x0, + NO_SECTOR = 0x1, + WRITE_FAULT = 0x3, + DEV_NOT_READY = 0x4, + INVALID_CMD = 0x20, + INVALID_LBA = 0x21, + INVALID_CDB = 0x24, + INVALID_LUN = 0x25, + WRITE_PROTECT = 0x27 }; /** Standard SCSI bus timing values measured in ns. */ @@ -68,15 +149,46 @@ enum ScsiMsg : int { #define SCSI_MAX_DEVS 8 +class ScsiBus; + +typedef std::function action_callback; + class ScsiDevice : public HWComponent { public: - ScsiDevice() = default; + ScsiDevice(int my_id) { + this->scsi_id = my_id; + this->cur_phase = ScsiPhase::BUS_FREE; + }; ~ScsiDevice() = default; - virtual void notify(ScsiMsg msg_type, int param) = 0; + virtual void notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param); + virtual void next_step(ScsiBus* bus_obj); + virtual void prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out); + virtual void switch_phase(const int new_phase); -private: - int scsi_id; + virtual bool has_data() { return this->data_size != 0; }; + virtual int send_data(uint8_t* dst_ptr, int count); + virtual int rcv_data(const uint8_t* src_ptr, const int count); + + virtual bool prepare_data() = 0; + virtual void process_command() = 0; + +protected: + uint8_t cmd_buf[16] = {}; + uint8_t msg_buf[16] = {}; // TODO: clarify how big this one should be + int scsi_id; + int initiator_id; + int cur_phase; + uint8_t* data_ptr = nullptr; + int data_size; + int incoming_size; + uint8_t status; + int sense; + + ScsiBus* bus_obj; + + action_callback pre_xfer_action = nullptr; + action_callback post_xfer_action = nullptr; }; /** This class provides a higher level abstraction for the SCSI bus. */ @@ -85,20 +197,33 @@ public: ScsiBus(); ~ScsiBus() = default; - // low-level control/status - void register_device(int id, ScsiDevice* dev_obj); - void assert_ctrl_line(int id, uint16_t mask); - void release_ctrl_line(int id, uint16_t mask); - void release_ctrl_lines(int id); - int current_phase() { return this->cur_phase; }; + // low-level state management + void register_device(int id, ScsiDevice* dev_obj); + int current_phase() { return this->cur_phase; }; + int get_initiator_id() { return this->initiator_id; }; + int get_target_id() { return this->target_id; }; + + // reading/writing control lines + void assert_ctrl_line(int id, uint16_t mask); + void release_ctrl_line(int id, uint16_t mask); + void release_ctrl_lines(int id); + uint16_t test_ctrl_lines(uint16_t mask); + + // reading/writing data lines + uint8_t get_data_lines() { return this->data_lines; }; // high-level control/status + int switch_phase(int id, int new_phase); bool begin_arbitration(int id); bool end_arbitration(int id); bool begin_selection(int initiator_id, int target_id, bool atn); void confirm_selection(int target_id); bool end_selection(int initiator_id, int target_id); void disconnect(int dev_id); + bool pull_data(const int id, uint8_t* dst_ptr, const int size); + bool push_data(const int id, const uint8_t* src_ptr, const int size); + void target_next_step(); + bool negotiate_xfer(int& bytes_in, int& bytes_out); protected: void change_bus_phase(int initiator_id); @@ -108,7 +233,7 @@ private: std::array devices; // per-device state of the control lines - uint16_t dev_ctrl_lines[SCSI_MAX_DEVS]; + uint16_t dev_ctrl_lines[SCSI_MAX_DEVS] = {}; uint16_t ctrl_lines; int cur_phase; diff --git a/devices/common/scsi/scsi_bus.cpp b/devices/common/scsi/scsi_bus.cpp index 67a2563..0079fa9 100644 --- a/devices/common/scsi/scsi_bus.cpp +++ b/devices/common/scsi/scsi_bus.cpp @@ -36,11 +36,11 @@ ScsiBus::ScsiBus() this->dev_ctrl_lines[i] = 0; } - this->ctrl_lines = 0; // all control lines released - this->data_lines = 0; // data lines released + this->ctrl_lines = 0; // all control lines released + this->data_lines = 0; // data lines released this->arb_winner_id = -1; - this->initiator_id = -1; - this->target_id = -1; + this->initiator_id = -1; + this->target_id = -1; } void ScsiBus::register_device(int id, ScsiDevice* dev_obj) @@ -58,7 +58,7 @@ void ScsiBus::change_bus_phase(int initiator_id) if (i == initiator_id) continue; // don't notify the initiator if (this->devices[i] != nullptr) { - this->devices[i]->notify(ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase); + this->devices[i]->notify(this, ScsiMsg::BUS_PHASE_CHANGE, this->cur_phase); } } } @@ -67,15 +67,14 @@ void ScsiBus::assert_ctrl_line(int initiator_id, uint16_t mask) { uint16_t new_state = 0xFFFFU & mask; - this->dev_ctrl_lines[initiator_id] = new_state; + this->dev_ctrl_lines[initiator_id] |= new_state; if (new_state == this->ctrl_lines) { return; } - this->ctrl_lines = new_state; - if (new_state & SCSI_CTRL_RST) { + this->ctrl_lines |= SCSI_CTRL_RST; this->cur_phase = ScsiPhase::RESET; change_bus_phase(initiator_id); } @@ -98,6 +97,8 @@ void ScsiBus::release_ctrl_line(int id, uint16_t mask) this->cur_phase = ScsiPhase::BUS_FREE; change_bus_phase(id); } + } else { + this->ctrl_lines = new_state; } } @@ -106,6 +107,61 @@ void ScsiBus::release_ctrl_lines(int id) this->release_ctrl_line(id, 0xFFFFUL); } +uint16_t ScsiBus::test_ctrl_lines(uint16_t mask) +{ + uint16_t new_state = 0; + + // OR control lines of all devices together + for (int i = 0; i < SCSI_MAX_DEVS; i++) { + new_state |= this->dev_ctrl_lines[i]; + } + + return new_state & mask; +} + +int ScsiBus::switch_phase(int id, int new_phase) +{ + int old_phase = this->cur_phase; + + // leave the current phase (low-level) + switch (old_phase) { + case ScsiPhase::COMMAND: + this->release_ctrl_line(id, SCSI_CTRL_CD); + break; + case ScsiPhase::DATA_IN: + this->release_ctrl_line(id, SCSI_CTRL_IO); + break; + case ScsiPhase::STATUS: + this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_IO); + break; + case ScsiPhase::MESSAGE_OUT: + this->release_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG); + break; + } + + // enter new phase (low-level) + switch (new_phase) { + case ScsiPhase::COMMAND: + this->assert_ctrl_line(id, SCSI_CTRL_CD); + break; + case ScsiPhase::DATA_IN: + this->assert_ctrl_line(id, SCSI_CTRL_IO); + break; + case ScsiPhase::STATUS: + this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_IO); + break; + case ScsiPhase::MESSAGE_OUT: + this->assert_ctrl_line(id, SCSI_CTRL_CD | SCSI_CTRL_MSG); + break; + } + + // switch the bus to the new phase (high-level) + this->cur_phase = new_phase; + change_bus_phase(id); + + return old_phase; +} + bool ScsiBus::begin_arbitration(int initiator_id) { if (this->cur_phase == ScsiPhase::BUS_FREE) { @@ -143,7 +199,9 @@ bool ScsiBus::begin_selection(int initiator_id, int target_id, bool atn) if (this->cur_phase != ScsiPhase::ARBITRATION || this->arb_winner_id != initiator_id) return false; - this->data_lines |= (1 << initiator_id) | (1 << target_id); + this->assert_ctrl_line(initiator_id, SCSI_CTRL_SEL); + + this->data_lines = (1 << initiator_id) | (1 << target_id); if (atn) { assert_ctrl_line(initiator_id, SCSI_CTRL_ATN); @@ -161,7 +219,7 @@ void ScsiBus::confirm_selection(int target_id) // notify initiator about selection confirmation from target if (this->initiator_id >= 0) { - this->devices[this->initiator_id]->notify(ScsiMsg::CONFIRM_SEL, target_id); + this->devices[this->initiator_id]->notify(this, ScsiMsg::CONFIRM_SEL, target_id); } } @@ -171,6 +229,42 @@ bool ScsiBus::end_selection(int initiator_id, int target_id) return this->target_id == target_id; } +bool ScsiBus::pull_data(const int id, uint8_t* dst_ptr, const int size) +{ + if (dst_ptr == nullptr || !size) { + return false; + } + + if (!this->devices[id]->send_data(dst_ptr, size)) { + LOG_F(ERROR, "ScsiBus: error while transferring T->I data!"); + return false; + } + + return true; +} + +bool ScsiBus::push_data(const int id, const uint8_t* src_ptr, const int size) +{ + if (!this->devices[id]->rcv_data(src_ptr, size)) { + LOG_F(ERROR, "ScsiBus: error while transferring I->T data!"); + return false; + } + + return true; +} + +void ScsiBus::target_next_step() +{ + this->devices[this->target_id]->next_step(this); +} + +bool ScsiBus::negotiate_xfer(int& bytes_in, int& bytes_out) +{ + this->devices[this->target_id]->prepare_xfer(this, bytes_in, bytes_out); + + return true; +} + void ScsiBus::disconnect(int dev_id) { this->release_ctrl_lines(dev_id); diff --git a/devices/common/scsi/scsicdrom.cpp b/devices/common/scsi/scsicdrom.cpp new file mode 100644 index 0000000..515f192 --- /dev/null +++ b/devices/common/scsi/scsicdrom.cpp @@ -0,0 +1,364 @@ +/* +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 +#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_CAPACITY_10: + this->read_capacity(); + break; + case ScsiCommand::READ_10: + lba = READ_DWORD_BE_U(&this->cmd_buf[2]); + xfer_len = READ_WORD_BE_U(&this->cmd_buf[7]); + if (this->cmd_buf[1] & 1) { + ABORT_F("SCSI-CDROM: RelAdr bit set in READ_10"); + } + 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; + this->switch_phase(ScsiPhase::STATUS); + return; + } + + 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); +} + +void ScsiCdrom::read_capacity() +{ + uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]); + + if (this->cmd_buf[1] & 1) { + ABORT_F("SCSI-CDROM: RelAdr bit set in READ_CAPACITY_10"); + } + + if (!(this->cmd_buf[8] & 1) && lba) { + LOG_F(ERROR, "SCSI-CDROM: non-zero LBA for PMI=0"); + this->status = ScsiStatus::CHECK_CONDITION; + this->sense = ScsiSense::ILLEGAL_REQ; + this->switch_phase(ScsiPhase::STATUS); + return; + } + + int last_lba = this->total_frames - 1; + + this->data_buf[0] = (last_lba >> 24) & 0xFFU; + this->data_buf[1] = (last_lba >> 16) & 0xFFU; + this->data_buf[2] = (last_lba >> 8) & 0xFFU; + this->data_buf[3] = (last_lba >> 0) & 0xFFU; + this->data_buf[4] = 0; + this->data_buf[5] = 0; + this->data_buf[6] = (this->sector_size >> 8) & 0xFFU; + this->data_buf[7] = this->sector_size & 0xFFU; + + this->bytes_out = 8; + 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..bae5834 --- /dev/null +++ b/devices/common/scsi/scsicdrom.h @@ -0,0 +1,89 @@ +/* +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(); + void read_capacity(); + +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 << 18]; // TODO: proper buffer management +}; + +#endif // SCSI_CDROM_H diff --git a/devices/common/scsi/scsidevice.cpp b/devices/common/scsi/scsidevice.cpp new file mode 100644 index 0000000..5fcb002 --- /dev/null +++ b/devices/common/scsi/scsidevice.cpp @@ -0,0 +1,165 @@ +/* +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 . +*/ + +#include +#include +#include + +#include +#include + +void ScsiDevice::notify(ScsiBus* bus_obj, ScsiMsg msg_type, int param) +{ + if (msg_type == ScsiMsg::BUS_PHASE_CHANGE) { + switch (param) { + case ScsiPhase::RESET: + LOG_F(9, "ScsiDevice %d: bus reset aknowledged", this->scsi_id); + break; + case ScsiPhase::SELECTION: + // check if something tries to select us + if (bus_obj->get_data_lines() & (1 << scsi_id)) { + LOG_F(9, "ScsiDevice %d selected", this->scsi_id); + TimerManager::get_instance()->add_oneshot_timer( + BUS_SETTLE_DELAY, + [this, bus_obj]() { + // don't confirm selection if BSY or I/O are asserted + if (bus_obj->test_ctrl_lines(SCSI_CTRL_BSY | SCSI_CTRL_IO)) + return; + bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_BSY); + bus_obj->confirm_selection(this->scsi_id); + this->initiator_id = bus_obj->get_initiator_id(); + this->bus_obj = bus_obj; + if (bus_obj->test_ctrl_lines(SCSI_CTRL_ATN)) { + this->switch_phase(ScsiPhase::MESSAGE_OUT); + if (this->msg_buf[0] != 0x80) { + LOG_F(INFO, "ScsiDevice: received message 0x%X", this->msg_buf[0]); + } + } + this->switch_phase(ScsiPhase::COMMAND); + this->process_command(); + if (this->prepare_data()) { + bus_obj->assert_ctrl_line(this->scsi_id, SCSI_CTRL_REQ); + } else { + ABORT_F("ScsiDevice: prepare_data() failed"); + } + }); + } + break; + } + } +} + +void ScsiDevice::switch_phase(const int new_phase) +{ + this->cur_phase = new_phase; + this->bus_obj->switch_phase(this->scsi_id, this->cur_phase); +} + +void ScsiDevice::next_step(ScsiBus* bus_obj) +{ + switch (this->cur_phase) { + case ScsiPhase::COMMAND: + this->process_command(); + this->switch_phase(ScsiPhase::DATA_IN); + break; + case ScsiPhase::DATA_OUT: + if (this->data_size >= this->incoming_size) { + if (this->post_xfer_action != nullptr) { + this->post_xfer_action(); + } + this->switch_phase(ScsiPhase::STATUS); + } + break; + case ScsiPhase::DATA_IN: + if (!this->has_data()) { + this->switch_phase(ScsiPhase::STATUS); + } + break; + case ScsiPhase::STATUS: + this->switch_phase(ScsiPhase::MESSAGE_IN); + break; + case ScsiPhase::MESSAGE_IN: + case ScsiPhase::BUS_FREE: + bus_obj->release_ctrl_lines(this->scsi_id); + this->switch_phase(ScsiPhase::BUS_FREE); + break; + default: + LOG_F(WARNING, "ScsiDevice: nothing to do for phase %d", this->cur_phase); + } +} + +void ScsiDevice::prepare_xfer(ScsiBus* bus_obj, int& bytes_in, int& bytes_out) +{ + this->cur_phase = bus_obj->current_phase(); + + switch (this->cur_phase) { + case ScsiPhase::COMMAND: + this->data_ptr = this->cmd_buf; + this->data_size = bytes_in; + break; + case ScsiPhase::STATUS: + this->data_ptr = &this->status; + this->data_size = 1; + bytes_out = 1; + break; + case ScsiPhase::DATA_IN: + bytes_out = this->data_size; + break; + case ScsiPhase::DATA_OUT: + break; + case ScsiPhase::MESSAGE_OUT: + this->data_ptr = this->msg_buf; + this->data_size = bytes_in; + break; + case ScsiPhase::MESSAGE_IN: + this->data_ptr = this->msg_buf; + this->data_size = 1; + bytes_out = 1; + break; + default: + ABORT_F("ScsiDevice: unhandled phase %d in prepare_xfer()", this->cur_phase); + } +} + +int ScsiDevice::send_data(uint8_t* dst_ptr, const int count) +{ + if (dst_ptr == nullptr || !count) { + return 0; + } + + int actual_count = std::min(this->data_size, count); + + std::memcpy(dst_ptr, this->data_ptr, actual_count); + this->data_ptr += actual_count; + this->data_size -= actual_count; + + return actual_count; +} + +int ScsiDevice::rcv_data(const uint8_t* src_ptr, const int count) +{ + // accumulating incoming data in the pre-configured buffer + std::memcpy(this->data_ptr, src_ptr, count); + this->data_ptr += count; + this->data_size += count; + + return count; +} diff --git a/devices/common/scsi/scsihd.cpp b/devices/common/scsi/scsihd.cpp new file mode 100644 index 0000000..146fa54 --- /dev/null +++ b/devices/common/scsi/scsihd.cpp @@ -0,0 +1,307 @@ +/* +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 Generic SCSI Hard Disk emulation. */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define HDD_SECTOR_SIZE 512 + +using namespace std; + +ScsiHardDisk::ScsiHardDisk(int my_id) : ScsiDevice(my_id) { + supports_types(HWCompType::SCSI_DEV); +} + +void ScsiHardDisk::insert_image(std::string filename) { + //We don't want to store everything in memory, but + //we want to keep the hard disk available. + this->hdd_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_blocks = (this->img_size + HDD_SECTOR_SIZE - 1) / HDD_SECTOR_SIZE; + } else { + ABORT_F("ScsiHardDisk: could not determine file size using stat()"); + } + this->hdd_img.seekg(0, std::ios_base::beg); +} + +void ScsiHardDisk::process_command() { + uint32_t lba = 0; + uint16_t transfer_len = 0; + uint16_t alloc_len = 0; + uint8_t param_len = 0; + + uint8_t page_code = 0; + uint8_t subpage_code = 0; + + this->pre_xfer_action = nullptr; + this->post_xfer_action = nullptr; + + // assume successful command execution + this->status = ScsiStatus::GOOD; + + uint8_t* cmd = this->cmd_buf; + + if (cmd[0] != 0 && cmd[0] != 8 && cmd[0] != 0xA && cmd[0] != 0x28 + && cmd[0] != 0x2A && cmd[0] != 0x25) { + ABORT_F("SCSI-HD: untested command 0x%X", cmd[0]); + } + + switch (cmd[0]) { + case ScsiCommand::TEST_UNIT_READY: + test_unit_ready(); + break; + case ScsiCommand::REWIND: + rewind(); + break; + case ScsiCommand::REQ_SENSE: + alloc_len = cmd[4]; + req_sense(alloc_len); + break; + case ScsiCommand::INQUIRY: + alloc_len = (cmd[3] << 8) + cmd[4]; + inquiry(alloc_len); + break; + case ScsiCommand::READ_6: + lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3]; + transfer_len = cmd[4]; + read(lba, transfer_len, 6); + break; + case ScsiCommand::READ_10: + lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5]; + transfer_len = (cmd[7] << 8) + cmd[8]; + read(lba, transfer_len, 10); + break; + case ScsiCommand::WRITE_6: + lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3]; + transfer_len = cmd[4]; + write(lba, transfer_len, 6); + break; + case ScsiCommand::WRITE_10: + lba = (cmd[2] << 24) + (cmd[3] << 16) + (cmd[4] << 8) + cmd[5]; + transfer_len = (cmd[7] << 8) + cmd[8]; + write(lba, transfer_len, 10); + this->switch_phase(ScsiPhase::DATA_OUT); + break; + case ScsiCommand::SEEK_6: + lba = ((cmd[1] & 0x1F) << 16) + (cmd[2] << 8) + cmd[3]; + seek(lba); + break; + case ScsiCommand::MODE_SELECT_6: + param_len = cmd[4]; + mode_select_6(param_len); + break; + case ScsiCommand::MODE_SENSE_6: + page_code = cmd[2] & 0x1F; + subpage_code = cmd[3]; + alloc_len = cmd[4]; + mode_sense_6(page_code, subpage_code, alloc_len); + break; + case ScsiCommand::READ_CAPACITY_10: + read_capacity_10(); + break; + default: + LOG_F(WARNING, "SCSI_HD: unrecognized command: %x", cmd[0]); + } +} + +bool ScsiHardDisk::prepare_data() { + switch (this->cur_phase) { + case ScsiPhase::DATA_IN: + this->data_ptr = (uint8_t*)this->img_buffer; + this->data_size = this->cur_buf_cnt; + break; + case ScsiPhase::DATA_OUT: + this->data_ptr = (uint8_t*)this->img_buffer; + this->data_size = 0; + break; + case ScsiPhase::STATUS: + if (!error) { + this->img_buffer[0] = ScsiStatus::GOOD; + } else { + this->img_buffer[0] = ScsiStatus::CHECK_CONDITION; + } + this->cur_buf_cnt = 1; + break; + case ScsiPhase::MESSAGE_IN: + this->img_buffer[0] = this->msg_code; + this->cur_buf_cnt = 1; + break; + default: + LOG_F(WARNING, "SCSI_HD: unexpected phase in prepare_data"); + return false; + } + + return true; +} + +int ScsiHardDisk::test_unit_ready() { + this->switch_phase(ScsiPhase::STATUS); + return ScsiError::NO_ERROR; +} + +int ScsiHardDisk::req_sense(uint16_t alloc_len) { + if (alloc_len != 252) { + LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len); + } + return ScsiError::NO_ERROR; // placeholder - no sense +} + +void ScsiHardDisk::inquiry(uint16_t alloc_len) { + if (alloc_len >= 48) { + uint8_t empty_filler[1 << 17] = {0x0}; + std::memcpy(img_buffer, empty_filler, (1 << 17)); + img_buffer[2] = 0x1; + img_buffer[3] = 0x2; + img_buffer[4] = 0x31; + img_buffer[7] = 0x1C; + std::memcpy(img_buffer + 8, vendor_info, 8); + std::memcpy(img_buffer + 16, prod_info, 16); + std::memcpy(img_buffer + 32, rev_info, 8); + std::memcpy(img_buffer + 40, serial_info, 8); + } + else { + LOG_F(WARNING, "Inappropriate Allocation Length: %d", alloc_len); + } +} + +int ScsiHardDisk::send_diagnostic() { + return 0x0; +} + +int ScsiHardDisk::mode_select_6(uint8_t param_len) { + if (param_len == 0) { + return 0x0; + } + else { + LOG_F(WARNING, "Mode Select calling for param length of: %d", param_len); + return param_len; + } +} + +void ScsiHardDisk::mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len) { + LOG_F(WARNING, "Page Code %d; Subpage Code: %d", page_code, subpage_code); +} + +void ScsiHardDisk::read_capacity_10() { + uint32_t lba = READ_DWORD_BE_U(&this->cmd_buf[2]); + + if (this->cmd_buf[1] & 1) { + ABORT_F("SCSI-HD: RelAdr bit set in READ_CAPACITY_10"); + } + + if (!(this->cmd_buf[8] & 1) && lba) { + LOG_F(ERROR, "SCSI-HD: non-zero LBA for PMI=0"); + this->status = ScsiStatus::CHECK_CONDITION; + this->sense = ScsiSense::ILLEGAL_REQ; + this->switch_phase(ScsiPhase::STATUS); + return; + } + + uint32_t last_lba = this->total_blocks - 1; + uint32_t blk_len = HDD_SECTOR_SIZE; + + WRITE_DWORD_BE_A(&img_buffer[0], last_lba); + WRITE_DWORD_BE_A(&img_buffer[4], blk_len); + + this->cur_buf_cnt = 8; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + + +void ScsiHardDisk::format() { +} + +void ScsiHardDisk::read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) { + uint32_t transfer_size = transfer_len; + + std::memset(img_buffer, 0, sizeof(img_buffer)); + + if (cmd_len == 6 && transfer_len == 0) { + transfer_size = 256; + } + + transfer_size *= HDD_SECTOR_SIZE; + uint64_t device_offset = lba * HDD_SECTOR_SIZE; + + this->hdd_img.seekg(device_offset, this->hdd_img.beg); + this->hdd_img.read(img_buffer, transfer_size); + + this->cur_buf_cnt = transfer_size; + this->msg_buf[0] = ScsiMessage::COMMAND_COMPLETE; + + this->switch_phase(ScsiPhase::DATA_IN); +} + +void ScsiHardDisk::write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len) { + uint32_t transfer_size = transfer_len; + + if (cmd_len == 6 && transfer_len == 0) { + transfer_size = 256; + } + + transfer_size *= HDD_SECTOR_SIZE; + uint64_t device_offset = lba * HDD_SECTOR_SIZE; + + this->incoming_size = transfer_size; + + this->hdd_img.seekg(device_offset, this->hdd_img.beg); + + this->post_xfer_action = [this]() { + this->hdd_img.write(this->img_buffer, this->incoming_size); + }; +} + +void ScsiHardDisk::seek(uint32_t lba) { + uint64_t device_offset = lba * HDD_SECTOR_SIZE; + this->hdd_img.seekg(device_offset, this->hdd_img.beg); +} + +void ScsiHardDisk::rewind() { + this->hdd_img.seekg(0, this->hdd_img.beg); +} + +static const PropMap SCSI_HD_Properties = { + {"hdd_img", new StrProperty("")}, + {"hdd_wr_prot", new BinProperty(0)}, +}; + +static const DeviceDescription SCSI_HD_Descriptor = + {ScsiHardDisk::create, {}, SCSI_HD_Properties}; + +REGISTER_DEVICE(ScsiHD, SCSI_HD_Descriptor); diff --git a/devices/common/scsi/scsihd.h b/devices/common/scsi/scsihd.h new file mode 100644 index 0000000..99e39b1 --- /dev/null +++ b/devices/common/scsi/scsihd.h @@ -0,0 +1,82 @@ +/* +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 hard drive definitions. */ + +#ifndef SCSI_HD_H +#define SCSI_HD_H + +#include + +#include +#include +#include +#include +#include + +class ScsiHardDisk : public ScsiDevice { +public: + ScsiHardDisk(int my_id); + ~ScsiHardDisk() = default; + + static std::unique_ptr create() { + return std::unique_ptr(new ScsiHardDisk(0)); + } + + void insert_image(std::string filename); + void process_command(); + bool prepare_data(); + +protected: + int test_unit_ready(); + int req_sense(uint16_t alloc_len); + int send_diagnostic(); + int mode_select_6(uint8_t param_len); + + void mode_sense_6(uint8_t page_code, uint8_t subpage_code, uint8_t alloc_len); + void format(); + void inquiry(uint16_t alloc_len); + void read_capacity_10(); + void read(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len); + void write(uint32_t lba, uint16_t transfer_len, uint8_t cmd_len); + void seek(uint32_t lba); + void rewind(); + +private: + std::fstream hdd_img; + uint64_t img_size; + int total_blocks; + uint64_t file_offset = 0; + + char img_buffer[1 << 21]; // TODO: add proper buffer management! + + uint32_t cur_buf_cnt = 0; + uint8_t error = ScsiError::NO_ERROR; + uint8_t msg_code = 0; + + //inquiry info + char vendor_info[8] = {'D', 'i', 'n', 'g', 'u', 's', 'D', '\0'}; + char prod_info[16] = {'E', 'm', 'u', 'l', 'a', 't', 'e', 'd', ' ', 'D', 'i', 's', 'k', '\0'}; + char rev_info[8] = {'d', 'i', '0', '0', '0', '0', '0', '1'}; + char serial_info[8] = {'0', '0', '0', '0', '0', '0', '0', '0'}; +}; + +#endif // SCSI_HD_H diff --git a/devices/ioctrl/amic.cpp b/devices/ioctrl/amic.cpp index 558f1d4..ce5681b 100644 --- a/devices/ioctrl/amic.cpp +++ b/devices/ioctrl/amic.cpp @@ -53,6 +53,8 @@ AMIC::AMIC() : MMIODevice() // connect internal SCSI controller this->scsi = dynamic_cast(gMachineObj->get_comp_by_name("Sc53C94")); + this->scsi_dma = std::unique_ptr (new AmicScsiDma()); + this->scsi->set_dma_channel(this->scsi_dma.get()); // connect serial HW this->escc = dynamic_cast(gMachineObj->get_comp_by_name("Escc")); @@ -115,7 +117,11 @@ uint32_t AMIC::read(uint32_t rgn_start, uint32_t offset, int size) case 0xA: // MACE registers return this->mace->read((offset >> 4) & 0x1F); case 0x10: // SCSI registers - return this->scsi->read((offset >> 4) & 0xF); + if (offset & 0x100) { + return this->scsi->pseudo_dma_read(); + } else { + return this->scsi->read((offset >> 4) & 0xF); + } case 0x14: // Sound registers switch (offset) { case AMICReg::Snd_Stat_0: @@ -166,7 +172,7 @@ uint32_t AMIC::read(uint32_t rgn_start, uint32_t offset, int size) case AMICReg::DMA_Base_Addr_3: return (this->dma_base >> (3 - (offset & 3)) * 8) & 0xFF; case AMICReg::SCSI_DMA_Ctrl: - return this->scsi_dma_cs; + return this->scsi_dma->read_stat(); case AMICReg::Floppy_Addr_Ptr_0: case AMICReg::Floppy_Addr_Ptr_1: case AMICReg::Floppy_Addr_Ptr_2: @@ -251,6 +257,12 @@ void AMIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) } switch(offset) { + case AMICReg::VIA2_IFR: + // for each "1" in value clear the corresponding IRQ bit + // TODO: is bit 7 read only? + // TODO: update interrupts? + this->via2_ifr &= ~(value & 0x7F); + break; case AMICReg::VIA2_Slot_IER: LOG_F(INFO, "AMIC VIA2 Slot Interrupt Enable Register updated, val=%x", value); break; @@ -314,9 +326,24 @@ void AMIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) case AMICReg::Enet_DMA_Xmt_Ctrl: LOG_F(INFO, "AMIC Ethernet Transmit DMA Ctrl updated, val=%x", value); break; + case AMICReg::SCSI_DMA_Base_0: + case AMICReg::SCSI_DMA_Base_1: + case AMICReg::SCSI_DMA_Base_2: + case AMICReg::SCSI_DMA_Base_3: + SET_ADDR_BYTE(this->scsi_dma_base, offset, value); + this->scsi_dma_base &= 0xFFFFFFF8UL; + LOG_F(9, "AMIC: SCSI DMA base address set to 0x%X", this->scsi_dma_base); + break; case AMICReg::SCSI_DMA_Ctrl: - LOG_F(INFO, "AMIC SCSI DMA Ctrl updated, val=%x", value); - this->scsi_dma_cs = value; + if (value & 1) { // RST bit set? + this->scsi_addr_ptr = this->scsi_dma_base; + this->scsi_dma->reset(this->scsi_addr_ptr); + } + if (value & 2) { // RUN bit set? + this->scsi_dma->reinit(this->scsi_dma_base); + this->scsi->real_dma_xfer((value >> 6) & 1); + } + this->scsi_dma->write_ctrl(value); break; case AMICReg::Enet_DMA_Rcv_Ctrl: LOG_F(INFO, "AMIC Ethernet Receive DMA Ctrl updated, val=%x", value); @@ -549,6 +576,50 @@ DmaPullResult AmicFloppyDma::pull_data(uint32_t req_len, uint32_t *avail_len, return DmaPullResult::NoMoreData; } +// ============================ SCSI DMA stuff ================================ +void AmicScsiDma::reset(const uint32_t addr_ptr) +{ + this->stat &= 0x48; // clear interrupt flag, RUN and RST bits + this->addr_ptr = addr_ptr; + this->byte_count = 0; +} + +void AmicScsiDma::reinit(const uint32_t addr_ptr) +{ + this->addr_ptr = addr_ptr; + this->byte_count = 0; +} + +void AmicScsiDma::write_ctrl(uint8_t value) +{ + // copy over DIR, IE and RUN bits + this->stat = (this->stat & 0x81) | (value & 0x4A); + + // clear interrupt flag if requested + if (value & 0x80) { + this->stat &= 0x7F; + } +} + +int AmicScsiDma::push_data(const char* src_ptr, int len) +{ + uint8_t *p_data = mmu_get_dma_mem(this->addr_ptr, len); + std::memcpy(p_data, src_ptr, len); + + this->addr_ptr += len; + + return 0; +} + +DmaPullResult AmicScsiDma::pull_data(uint32_t req_len, uint32_t *avail_len, + uint8_t **p_data) +{ + *p_data = mmu_get_dma_mem(this->addr_ptr, req_len); + this->addr_ptr += req_len; + *avail_len = req_len; + return DmaPullResult::MoreData; +} + static vector Amic_Subdevices = { "Sc53C94", "Escc", "Mace", "ViaCuda", "Swim3" }; diff --git a/devices/ioctrl/amic.h b/devices/ioctrl/amic.h index ff46dd4..141589b 100644 --- a/devices/ioctrl/amic.h +++ b/devices/ioctrl/amic.h @@ -102,6 +102,27 @@ private: uint8_t stat; }; +/** AMIC-specific SCSI DMA implementation. */ +class AmicScsiDma : public DmaBidirChannel { +public: + AmicScsiDma() = default; + ~AmicScsiDma() = default; + + void reinit(const uint32_t addr_ptr); + void reset(const uint32_t addr_ptr); + void write_ctrl(const uint8_t value); + uint8_t read_stat() { return this->stat; }; + + int push_data(const char* src_ptr, int len); + DmaPullResult pull_data(uint32_t req_len, uint32_t *avail_len, + uint8_t **p_data); + +private: + uint32_t addr_ptr; + uint16_t byte_count; + uint8_t stat; +}; + // macro for byte wise updating of AMIC DMA address registers #define SET_ADDR_BYTE(reg, offset, value) \ mask = 0xFF000000UL >> (8 * ((offset) & 3)); \ @@ -150,6 +171,10 @@ enum AMICReg : uint32_t { // Interrupt registers Int_Ctrl = 0x2A000, + DMA_Int_0 = 0x2A008, + Bus_Err_Int_0 = 0x2A009, + DMA_Int_1 = 0x2A00A, + Bus_Err_Int_1 = 0x2A00B, // Undocumented diagnostics register Diag_Reg = 0x2C000, @@ -160,9 +185,15 @@ enum AMICReg : uint32_t { DMA_Base_Addr_2 = 0x31002, DMA_Base_Addr_3 = 0x31003, Enet_DMA_Xmt_Ctrl = 0x31C20, - SCSI_DMA_Ctrl = 0x32008, Enet_DMA_Rcv_Ctrl = 0x32028, + // SCSI DMA registers + SCSI_DMA_Base_0 = 0x32000, + SCSI_DMA_Base_1 = 0x32001, + SCSI_DMA_Base_2 = 0x32002, + SCSI_DMA_Base_3 = 0x32003, + SCSI_DMA_Ctrl = 0x32008, + // Floppy (SWIM3) DMA registers Floppy_Addr_Ptr_0 = 0x32060, Floppy_Addr_Ptr_1 = 0x32061, @@ -210,15 +241,19 @@ private: uint8_t emmo_pin; // EMMO aka factory tester pin status, active low - uint32_t dma_base = 0; // DMA physical base address - uint16_t snd_buf_size = 0; // sound buffer size in bytes - uint8_t snd_out_ctrl = 0; + uint32_t dma_base = 0; // DMA physical base address + uint32_t scsi_dma_base = 0; // physical base address for SCSI DMA + uint16_t snd_buf_size = 0; // sound buffer size in bytes + uint8_t snd_out_ctrl = 0; // floppy DMA state uint32_t floppy_addr_ptr; uint16_t floppy_byte_cnt; - uint8_t scsi_dma_cs = 0; // SCSI DMA control/status register value + // SCSI DMA state + uint32_t scsi_addr_ptr; + + //uint8_t scsi_dma_cs = 0; // SCSI DMA control/status register value // interrupt state uint8_t int_ctrl = 0; @@ -241,6 +276,7 @@ private: std::unique_ptr awacs; std::unique_ptr snd_out_dma; std::unique_ptr floppy_dma; + std::unique_ptr scsi_dma; // on-board video std::unique_ptr disp_id; diff --git a/devices/ioctrl/heathrow.cpp b/devices/ioctrl/heathrow.cpp index 22fe65f..a3a449a 100644 --- a/devices/ioctrl/heathrow.cpp +++ b/devices/ioctrl/heathrow.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . #include #include +#include #include #include #include @@ -76,7 +77,11 @@ HeathrowIC::HeathrowIC() : PCIDevice("mac-io/heathrow"), InterruptCtrl() ); // connect SCSI HW - this->mesh = dynamic_cast(gMachineObj->get_comp_by_name("Mesh")); + this->mesh = dynamic_cast(gMachineObj->get_comp_by_name("Mesh")); + + // connect IDE HW + this->ide_0 = dynamic_cast(gMachineObj->get_comp_by_name("Ide0")); + this->ide_1 = dynamic_cast(gMachineObj->get_comp_by_name("Ide1")); // connect serial HW this->escc = dynamic_cast(gMachineObj->get_comp_by_name("Escc")); @@ -161,10 +166,16 @@ uint32_t HeathrowIC::read(uint32_t rgn_start, uint32_t offset, int size) { break; case 0x15: // SWIM3 return this->swim3->read((offset >> 4 )& 0xF); - case 0x16: + case 0x16: // VIA-CUDA case 0x17: res = this->viacuda->read((offset - 0x16000) >> 9); break; + case 0x20: // IDE 0 + res = this->ide_0->read((offset >> 4) & 0x1F, size); + break; + case 0x21: // IDE 1 + res = this->ide_1->read((offset >> 4) & 0x1F, size); + break; default: if (sub_addr >= 0x60) { res = this->nvram->read_byte((offset - 0x60000) >> 4); @@ -206,10 +217,16 @@ void HeathrowIC::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int case 0x15: // SWIM3 this->swim3->write((offset >> 4) & 0xF, value); break; - case 0x16: + case 0x16: // VIA-CUDA case 0x17: this->viacuda->write((offset - 0x16000) >> 9, value); break; + case 0x20: // IDE O + this->ide_0->write((offset >> 4) & 0x1F, value, size); + break; + case 0x21: // IDE 1 + this->ide_1->write((offset >> 4) & 0x1F, value, size); + break; default: if (sub_addr >= 0x60) { this->nvram->write_byte((offset - 0x60000) >> 4, value); @@ -386,8 +403,7 @@ void HeathrowIC::clear_cpu_int() } static const vector Heathrow_Subdevices = { - "NVRAM", "ViaCuda", "Mesh", "Escc", "Swim3" -}; + "NVRAM", "ViaCuda", "Mesh", "Escc", "Swim3", "Ide0", "Ide1"}; static const DeviceDescription Heathrow_Descriptor = { HeathrowIC::create, Heathrow_Subdevices, {} diff --git a/devices/ioctrl/macio.h b/devices/ioctrl/macio.h index 8a09d22..0134de8 100644 --- a/devices/ioctrl/macio.h +++ b/devices/ioctrl/macio.h @@ -51,6 +51,7 @@ along with this program. If not, see . #ifndef MACIO_H #define MACIO_H +#include #include #include #include @@ -271,8 +272,10 @@ private: NVram* nvram; // NVRAM ViaCuda* viacuda; // VIA cell with Cuda MCU attached to it - MESHController* mesh; // MESH SCSI cell instance + MeshController* mesh; // MESH SCSI cell instance EsccController* escc; // ESCC serial controller + IdeChannel* ide_0; // Internal ATA + IdeChannel* ide_1; // Media Bay ATA Swim3::Swim3Ctrl* swim3; // floppy disk controller // DMA channels diff --git a/machines/machinefactory.cpp b/machines/machinefactory.cpp index 468bd32..c292d66 100644 --- a/machines/machinefactory.cpp +++ b/machines/machinefactory.cpp @@ -79,6 +79,9 @@ static const map PropHelp = { {"fdd_img", "specifies path to floppy disk image"}, {"fdd_fmt", "specifies floppy disk format"}, {"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/machinegossamer.cpp b/machines/machinegossamer.cpp index d4d24fd..9f691b6 100644 --- a/machines/machinegossamer.cpp +++ b/machines/machinegossamer.cpp @@ -28,12 +28,10 @@ along with this program. If not, see . #include #include #include -#include +#include #include #include #include -#include -#include #include #include #include @@ -128,6 +126,8 @@ int initialize_gossamer(std::string& id) grackle_obj->pci_register_device( DEV_FUN(0x12,0), dynamic_cast(gMachineObj->get_comp_by_name(id == "pmg3twr" ? "AtiRagePro" : "AtiRageGT"))); + gMachineObj->add_device("SCSI0", std::unique_ptr(new ScsiBus())); + // add Athens clock generator device and register it with the I2C host gMachineObj->add_device("Athens", std::unique_ptr(new AthensClocks(0x28))); I2CBus* i2c_bus = dynamic_cast(gMachineObj->get_comp_by_type(HWCompType::I2C_HOST)); diff --git a/machines/machinepdm.cpp b/machines/machinepdm.cpp index 196f7b7..a7f3b59 100644 --- a/machines/machinepdm.cpp +++ b/machines/machinepdm.cpp @@ -27,6 +27,8 @@ along with this program. If not, see . #include #include #include +#include +#include #include #include #include @@ -82,6 +84,25 @@ int initialize_pdm(std::string& id) // add internal SCSI bus gMachineObj->add_device("SCSI0", std::unique_ptr(new ScsiBus())); + auto scsi_bus = dynamic_cast(gMachineObj->get_comp_by_name("SCSI0")); + + std::string hd_image_path = GET_STR_PROP("hdd_img"); + if (!hd_image_path.empty()) { + // attach SCSI HD to the main bus, ID #0 + auto my_hd = dynamic_cast(gMachineObj->get_comp_by_name("ScsiHD")); + scsi_bus->register_device(0, my_hd); + // insert specified disk image + 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); @@ -109,7 +130,7 @@ static const PropMap pm6100_settings = { }; static vector pm6100_devices = { - "HMC", "Amic" + "HMC", "Amic", "ScsiHD", "ScsiCdrom" }; static const MachineDescription pm6100_descriptor = {