diff --git a/devices/common/ata/cmd646.cpp b/devices/common/ata/cmd646.cpp
new file mode 100644
index 0000000..052b470
--- /dev/null
+++ b/devices/common/ata/cmd646.cpp
@@ -0,0 +1,238 @@
+/*
+DingusPPC - The Experimental PowerPC Macintosh emulator
+Copyright (C) 2018-24 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 CMD646U2 PCI Ultra ATA controller emulation. */
+
+#include
+#include
+#include
+#include
+
+CmdIdeCtrl::CmdIdeCtrl() : PCIDevice("cmd-ide") {
+ this->supports_types(HWCompType::PCI_DEV | HWCompType::IDE_HOST);
+
+ // set up PCI configuration space header
+ this->vendor_id = this->subsys_vndr = PCI_VENDOR_SILICON_IMAGE;
+ this->device_id = this->subsys_id = DEV_ID_CMD646;
+ this->class_rev = ((MY_DEV_CLASS | this->prog_if) << 8) | MY_REV_ID;
+ this->max_lat = 4;
+ this->min_gnt = 2;
+ this->irq_pin = 1;
+
+ this->bars_cfg[0] = 0xFFFFFFF9; // Command block I/O space, primary channel
+ this->bars_cfg[1] = 0xFFFFFFFD; // Control block I/O space, primary channel
+ this->bars_cfg[2] = 0xFFFFFFF9; // Command block I/O space, secondary channel
+ this->bars_cfg[3] = 0xFFFFFFFD; // Control block I/O space, secondary channel
+ this->bars_cfg[4] = 0xFFFFFFF1; // Bus master I/O space, both channels
+
+ this->finish_config_bars();
+
+ this->pci_notify_bar_change = [this](int bar_num) {
+ this->notify_bar_change(bar_num);
+ };
+
+ gMachineObj->add_device("CmdAta0", std::unique_ptr(new IdeChannel("CmdAta0")));
+ gMachineObj->add_device("CmdAta1", std::unique_ptr(new IdeChannel("CmdAta1")));
+
+ this->ch0 = dynamic_cast(gMachineObj->get_comp_by_name("CmdAta0"));
+ this->ch1 = dynamic_cast(gMachineObj->get_comp_by_name("CmdAta1"));
+
+ this->ch0->set_irq_callback([this](const uint8_t intrq_state) {
+ LOG_F(INFO, "CmdAta0 INTRQ updated to %d", intrq_state);
+ this->update_irq(0, intrq_state);
+ });
+
+ this->ch1->set_irq_callback([this](const uint8_t intrq_state) {
+ LOG_F(INFO, "CmdAta1 INTRQ updated to %d", intrq_state);
+ this->update_irq(1, intrq_state);
+ });
+}
+
+uint32_t CmdIdeCtrl::pci_cfg_read(uint32_t reg_offs, AccessDetails &details) {
+ if (reg_offs < 64)
+ return PCIDevice::pci_cfg_read(reg_offs, details);
+
+ if (details.size != 1)
+ ABORT_F("%s: non-byte read from PCI config reg 0x%X", this->name.c_str(),
+ reg_offs + details.offset);
+
+ if (reg_offs < 112)
+ return this->read_config_reg(reg_offs + details.offset);
+
+ LOG_F(WARNING, "%s: reading config reg at 0x%X", this->name.c_str(), reg_offs);
+
+ return 0;
+}
+
+void CmdIdeCtrl::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details) {
+ if (reg_offs < 64) {
+ PCIDevice::pci_cfg_write(reg_offs, value, details);
+ return;
+ }
+
+ if (details.size != 1)
+ ABORT_F("%s: non-byte write to PCI config reg 0x%X", this->name.c_str(),
+ reg_offs + details.offset);
+
+ if (reg_offs < 112)
+ this->write_config_reg(reg_offs + details.offset, value);
+ else
+ LOG_F(WARNING, "%s: writing config reg at 0x%X", this->name.c_str(), reg_offs);
+}
+
+void CmdIdeCtrl::notify_bar_change(int bar_num) {
+ if (bar_num >= 0 && bar_num <= 4)
+ this->io_bases[bar_num] = this->bars[bar_num] & ~3;
+}
+
+bool CmdIdeCtrl::pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) {
+ *res = 0;
+
+ if ((offset & ~7) == this->io_bases[0]) {
+ *res = this->ch0->read(offset & 7, size);
+ } else if ((offset & ~7) == this->io_bases[2]) {
+ *res = this->ch1->read(offset & 7, size);
+ } else if ((offset & ~3) == this->io_bases[1]) {
+ *res = this->ch0->read((offset & 3) + DEV_CTRL_BLK_OFFSET, size);
+ } else if ((offset & ~3) == this->io_bases[3]) {
+ *res = this->ch1->read((offset & 3) + DEV_CTRL_BLK_OFFSET, size);
+ } else if ((offset & ~0xF) == this->io_bases[4]) {
+ *res = this->read_bus_master_reg(offset & 0xF);
+ } else {
+ *res = 0xFFFFFFFFUL;
+ return false;
+ }
+
+ return true;
+}
+
+bool CmdIdeCtrl::pci_io_write(uint32_t offset, uint32_t value, uint32_t size) {
+ if ((offset & ~7) == this->io_bases[0]) {
+ this->ch0->write(offset & 7, value, size);
+ } else if ((offset & ~7) == this->io_bases[2]) {
+ this->ch1->write(offset & 7, value, size);
+ } else if ((offset & ~3) == this->io_bases[1]) {
+ this->ch0->write((offset & 3) + DEV_CTRL_BLK_OFFSET, value, size);
+ } else if ((offset & ~3) == this->io_bases[3]) {
+ this->ch1->write((offset & 3) + DEV_CTRL_BLK_OFFSET, value, size);
+ } else if ((offset & ~0xF) == this->io_bases[4]) {
+ this->write_bus_master_reg(offset & 0xF, value & 0xFFU);
+ } else
+ return false;
+
+ return true;
+};
+
+uint8_t CmdIdeCtrl::read_config_reg(uint32_t reg_offset) {
+ switch(reg_offset) {
+ case ARTTIM0:
+ return this->addr_setup_time_0;
+ case DRWTIM0:
+ return this->data_rw_time_0;
+ case ARTTIM1:
+ return this->addr_setup_time_1;
+ case DRWTIM1:
+ return this->data_rw_time_1;
+ default:
+ LOG_F(ERROR, "%s: unimplemented config reg at 0x%X", this->name.c_str(),
+ reg_offset);
+ }
+
+ return 0;
+}
+
+void CmdIdeCtrl::write_config_reg(uint32_t reg_offset, uint8_t val) {
+ switch(reg_offset) {
+ case ARTTIM0:
+ this->addr_setup_time_0 = val;
+ LOG_F(9, "%s: ARTTIM0 set to 0x%X", this->name.c_str(), val);
+ break;
+ case DRWTIM0:
+ this->data_rw_time_0 = val;
+ LOG_F(9, "%s: DRWTIM0 set to 0x%X", this->name.c_str(), val);
+ break;
+ case ARTTIM1:
+ this->addr_setup_time_1 = val;
+ LOG_F(9, "%s: ARTTIM1 set to 0x%X", this->name.c_str(), val);
+ break;
+ case DRWTIM1:
+ this->data_rw_time_1 = val;
+ LOG_F(9, "%s: DRWTIM1 set to 0x%X", this->name.c_str(), val);
+ break;
+ default:
+ LOG_F(ERROR, "%s: unimplemented config reg at 0x%X", this->name.c_str(),
+ reg_offset);
+ }
+}
+
+uint8_t CmdIdeCtrl::read_bus_master_reg(const uint8_t reg_offset) {
+ switch(reg_offset) {
+ case MRDMODE:
+ return this->mrdmode;
+ default:
+ LOG_F(ERROR, "%s: unimplemented bus master reg at 0x%X", this->name.c_str(),
+ reg_offset);
+ }
+
+ return 0;
+}
+
+void CmdIdeCtrl::write_bus_master_reg(const uint8_t reg_offset, const uint8_t val) {
+ switch(reg_offset) {
+ case MRDMODE:
+ if (val & BM_CH0_INT)
+ this->mrdmode &= ~BM_CH0_INT;
+ if (val & BM_CH1_INT)
+ this->mrdmode &= ~BM_CH1_INT;
+ this->mrdmode = (this->mrdmode & ~(BM_BLOCK_CH0_INT | BM_BLOCK_CH1_INT)) |
+ (val & (BM_BLOCK_CH0_INT | BM_BLOCK_CH1_INT));
+ LOG_F(INFO, "%s: MRDMODE set to 0x%X", this->name.c_str(), val);
+ if ((this->mrdmode & BM_CH0_INT) && !(this->mrdmode & BM_BLOCK_CH0_INT))
+ this->update_irq(0, 1);
+ if ((this->mrdmode & BM_CH1_INT) && !(this->mrdmode & BM_BLOCK_CH1_INT))
+ this->update_irq(1, 1);
+ break;
+ case UDIDETCR0:
+ this->udma_time_cr = val;
+ break;
+ default:
+ LOG_F(ERROR, "%s: unimplemented bus master reg at 0x%X", this->name.c_str(),
+ reg_offset);
+ }
+}
+
+void CmdIdeCtrl::update_irq(const int ch_num, const uint8_t irq_level) {
+ bool forward_irq = !(this->mrdmode & (ch_num ? BM_BLOCK_CH1_INT : BM_BLOCK_CH0_INT));
+
+ if (irq_level)
+ this->mrdmode |= ch_num ? BM_CH1_INT : BM_CH0_INT;
+ else
+ this->mrdmode &= ~(ch_num ? BM_CH1_INT : BM_CH0_INT);
+
+ if (!irq_level || forward_irq)
+ this->irq_info.int_ctrl_obj->ack_int(this->irq_info.irq_id, irq_level);
+}
+
+static const DeviceDescription CmdIde_Descriptor = {
+ CmdIdeCtrl::create, {}, {}
+};
+
+REGISTER_DEVICE(CmdAta, CmdIde_Descriptor);
diff --git a/devices/common/ata/cmd646.h b/devices/common/ata/cmd646.h
new file mode 100644
index 0000000..63a6a11
--- /dev/null
+++ b/devices/common/ata/cmd646.h
@@ -0,0 +1,115 @@
+/*
+DingusPPC - The Experimental PowerPC Macintosh emulator
+Copyright (C) 2018-24 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 CMD646U2 PCI Ultra ATA controller definitions. */
+
+#ifndef CMD646_IDE_H
+#define CMD646_IDE_H
+
+#include
+#include
+#include
+#include
+
+#include
+
+#define DEV_ID_CMD646 0x646
+#define MY_DEV_CLASS 0x010180 // mass storage | IDE controller | IDE master
+#define MY_REV_ID 7
+
+// Offset for converting addresses of the device control block registers
+// defined in the PCI IDE Controller specification, rev. 1.0 3/4/94
+// to the addresses used in IdeChannel
+#define DEV_CTRL_BLK_OFFSET 0x14
+
+/** CMD646 control/status registers. */
+enum {
+ ARTTIM0 = 0x53,
+ DRWTIM0 = 0x54,
+ ARTTIM1 = 0x55,
+ DRWTIM1 = 0x56,
+};
+
+/** CMD646 bus master registers. */
+enum {
+ MRDMODE = 1, // misnomer, contains interrupt control/status bits (CMD646U2 specific)
+ UDIDETCR0 = 3, // Ultra DMA timing control register (CMD646U2 specific)
+};
+
+/** Bit definitions for the MRDMODE register. */
+enum {
+ BM_CH0_INT = 1 << 2,
+ BM_CH1_INT = 1 << 3,
+ BM_BLOCK_CH0_INT = 1 << 4,
+ BM_BLOCK_CH1_INT = 1 << 5,
+};
+
+class CmdIdeCtrl : public PCIDevice {
+public:
+ CmdIdeCtrl();
+ ~CmdIdeCtrl() = default;
+
+ static std::unique_ptr create() {
+ return std::unique_ptr(new CmdIdeCtrl());
+ }
+
+ // PCIDevice methods
+ uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details) override;
+ void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details) override;
+
+ bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) override;
+ bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size) override;
+
+ int device_postinit() override {
+ this->irq_info = this->host_instance->register_pci_int(this);
+ return 0;
+ };
+
+private:
+ void notify_bar_change(int bar_num);
+ uint8_t read_config_reg(uint32_t reg_offset);
+ void write_config_reg(uint32_t reg_offset, uint8_t val);
+ uint8_t read_bus_master_reg(const uint8_t reg_offset);
+ void write_bus_master_reg(const uint8_t reg_offset, const uint8_t val);
+ void update_irq(const int ch_num, const uint8_t irq_level);
+
+ // on reset, programming interface defaults to
+ // "both channels operating in native mode"
+ uint8_t prog_if = 0x0F;
+
+ uint32_t io_bases[5] = {};
+
+ IdeChannel *ch0 = nullptr;
+ IdeChannel *ch1 = nullptr;
+
+ // unknown default, set it to 2 clocks (60 ns)
+ uint8_t addr_setup_time_0 = 0x40; // address setup time for drive 0
+ uint8_t addr_setup_time_1 = 0x40; // address setup time for drive 1
+ uint8_t data_rw_time_0 = 0x00; // data read/write time for drive 0
+ uint8_t data_rw_time_1 = 0x00; // data read/write time for drive 1
+
+ uint8_t mrdmode = 0;
+ uint8_t udma_time_cr = 0;
+
+ IntDetails irq_info = {};
+};
+
+#endif // CMD646_IDE_H
diff --git a/devices/common/pci/pcibase.h b/devices/common/pci/pcibase.h
index 46f42f3..86c660e 100644
--- a/devices/common/pci/pcibase.h
+++ b/devices/common/pci/pcibase.h
@@ -52,11 +52,12 @@ enum {
/** PCI Vendor IDs for devices used in Power Macintosh computers. */
enum {
- PCI_VENDOR_ATI = 0x1002,
- PCI_VENDOR_DEC = 0x1011,
- PCI_VENDOR_MOTOROLA = 0x1057,
- PCI_VENDOR_APPLE = 0x106B,
- PCI_VENDOR_NVIDIA = 0x10DE,
+ PCI_VENDOR_ATI = 0x1002,
+ PCI_VENDOR_DEC = 0x1011,
+ PCI_VENDOR_MOTOROLA = 0x1057,
+ PCI_VENDOR_APPLE = 0x106B,
+ PCI_VENDOR_SILICON_IMAGE = 0x1095,
+ PCI_VENDOR_NVIDIA = 0x10DE,
};
/** PCI BAR types */