/* DingusPPC - The Experimental PowerPC Macintosh emulator Copyright (C) 2018-23 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 . */ /** MPC106 (Grackle) emulation. */ #include #include #include #include #include #include #include #include #include MPC106::MPC106() : MemCtrlBase(), PCIDevice("Grackle"), PCIHost() { supports_types(HWCompType::MEM_CTRL | HWCompType::MMIO_DEV | HWCompType::PCI_HOST | HWCompType::PCI_DEV); // populate PCI config header this->vendor_id = PCI_VENDOR_MOTOROLA; this->device_id = 0x0002; this->class_rev = 0x06000040; this->cache_ln_sz = 8; this->command = 6; this->status = 0x80; // assign PCI device number zero to myself this->pci_register_device(DEV_FUN(0,0), this); // add PCI/ISA I/O space, 64K for now add_mmio_region(0xFE000000, 0x10000, this); // add memory mapped I/O region for MPC106 registers add_mmio_region(0xFEC00000, 0x300000, this); } int MPC106::device_postinit() { std::string pci_dev_name; static const std::map pci_slots = { {"pci_PERCH", DEV_FUN(0xC,0)}, {"pci_A1", DEV_FUN(0xD,0)}, {"pci_B1", DEV_FUN(0xE,0)}, {"pci_C1", DEV_FUN(0xF,0)}, {"pci_GPU", DEV_FUN(0x12,0)} }; for (auto& slot : pci_slots) { pci_dev_name = GET_STR_PROP(slot.first); if (!pci_dev_name.empty()) { this->attach_pci_device(pci_dev_name, slot.second, std::string("@") + slot.first); } } this->int_ctrl = dynamic_cast( gMachineObj->get_comp_by_type(HWCompType::INT_CTRL)); this->irq_id_PCI_A = this->int_ctrl->register_dev_int(IntSrc::PCI_A ); this->irq_id_PCI_B = this->int_ctrl->register_dev_int(IntSrc::PCI_B ); this->irq_id_PCI_C = this->int_ctrl->register_dev_int(IntSrc::PCI_C ); this->irq_id_PCI_GPU = this->int_ctrl->register_dev_int(IntSrc::PCI_GPU ); this->irq_id_PCI_PERCH = this->int_ctrl->register_dev_int(IntSrc::PCI_PERCH); return 0; } void MPC106::pci_interrupt(uint8_t irq_line_state, PCIBase *dev) { auto it = std::find_if(dev_map.begin(), dev_map.end(), [&dev](const std::pair &p) { return p.second == dev; } ); if (it == dev_map.end()) { LOG_F(ERROR, "Interrupt from unknown device %s", dev->get_name().c_str()); } else { uint32_t irq_id; switch (it->first) { case DEV_FUN(0x0C,0): irq_id = this->irq_id_PCI_PERCH; break; case DEV_FUN(0x0D,0): irq_id = this->irq_id_PCI_A ; break; case DEV_FUN(0x0E,0): irq_id = this->irq_id_PCI_B ; break; case DEV_FUN(0x0F,0): irq_id = this->irq_id_PCI_C ; break; case DEV_FUN(0x12,0): irq_id = this->irq_id_PCI_GPU ; break; default: LOG_F(ERROR, "Interrupt from device %s at unexpected device/function %02x.%x", dev->get_name().c_str(), it->first >> 3, it->first & 7); return; } if (this->int_ctrl) this->int_ctrl->ack_int(irq_id, irq_line_state); } } uint32_t MPC106::read(uint32_t rgn_start, uint32_t offset, int size) { if (rgn_start == 0xFE000000) { return pci_io_read_broadcast(offset, size); } if (offset < 0x200000) { return this->config_addr; } if (this->config_addr & 0x80) { // process only if bit E (enable) is set int bus_num, dev_num, fun_num; uint8_t reg_offs; AccessDetails details; PCIBase *device; cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device); details.flags |= PCI_CONFIG_READ; if (device) { uint32_t value = device->pci_cfg_read(reg_offs, details); // bytes 0 to 3 repeat return pci_conv_rd_data(value, value, details); } LOG_READ_NON_EXISTENT_PCI_DEVICE(); return 0xFFFFFFFFUL; // PCI spec §6.1 } return 0; } void MPC106::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { if (rgn_start == 0xFE000000) { pci_io_write_broadcast(offset, size, value); return; } if (offset < 0x200000) { this->config_addr = value; return; } if (this->config_addr & 0x80) { // process only if bit E (enable) is set int bus_num, dev_num, fun_num; uint8_t reg_offs; AccessDetails details; PCIBase *device; cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device); details.flags |= PCI_CONFIG_WRITE; if (device) { if (size == 4 && !details.offset) { // aligned DWORD writes -> fast path device->pci_cfg_write(reg_offs, BYTESWAP_32(value), details); return; } // otherwise perform necessary data transformations -> slow path uint32_t old_val = details.size == 4 ? 0 : device->pci_cfg_read(reg_offs, details); uint32_t new_val = pci_conv_wr_data(old_val, value, details); device->pci_cfg_write(reg_offs, new_val, details); return; } LOG_WRITE_NON_EXISTENT_PCI_DEVICE(); } } inline void MPC106::cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIBase *&device) { details.size = size; details.offset = offset & 3; bus_num = (this->config_addr >> 8) & 0xFF; dev_num = (this->config_addr >> 19) & 0x1F; fun_num = (this->config_addr >> 16) & 0x07; reg_offs = (this->config_addr >> 24) & 0xFC; if (bus_num) { details.flags = PCI_CONFIG_TYPE_1; device = pci_find_device(bus_num, dev_num, fun_num); } else { details.flags = PCI_CONFIG_TYPE_0; device = pci_find_device(dev_num, fun_num); } } uint32_t MPC106::pci_cfg_read(uint32_t reg_offs, AccessDetails &details) { if (reg_offs < 64) { return PCIDevice::pci_cfg_read(reg_offs, details); } switch (reg_offs) { case GrackleReg::CFG10: return 0; case GrackleReg::PMCR1: return (this->odcr << 24) | (this->pmcr2 << 16) | this->pmcr1; case GrackleReg::MSAR1: case GrackleReg::MSAR2: return this->mem_start[(reg_offs >> 2) & 1]; case GrackleReg::EMSAR1: case GrackleReg::EMSAR2: return this->ext_mem_start[(reg_offs >> 2) & 1]; case GrackleReg::MEAR1: case GrackleReg::MEAR2: return this->mem_end[(reg_offs >> 2) & 1]; case GrackleReg::EMEAR1: case GrackleReg::EMEAR2: return this->ext_mem_end[(reg_offs >> 2) & 1]; case GrackleReg::MBER: return this->mem_bank_en; case GrackleReg::PICR1: return this->picr1; case GrackleReg::PICR2: return this->picr2; case GrackleReg::MCCR1: return this->mccr1; case GrackleReg::MCCR2: return this->mccr2; case GrackleReg::MCCR3: return this->mccr3; case GrackleReg::MCCR4: return this->mccr4; default: LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER(); } return 0; // PCI Spec §6.1 } void MPC106::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; } switch (reg_offs) { case GrackleReg::CFG10: // Open Firmware writes 0 to subordinate bus # - we don't care break; case GrackleReg::PMCR1: this->pmcr1 = value & 0xFFFFU; this->pmcr2 = (value >> 16) & 0xFF; this->odcr = value >> 24; break; case GrackleReg::MSAR1: case GrackleReg::MSAR2: this->mem_start[(reg_offs >> 2) & 1] = value; break; case GrackleReg::EMSAR1: case GrackleReg::EMSAR2: this->ext_mem_start[(reg_offs >> 2) & 1] = value; break; case GrackleReg::MEAR1: case GrackleReg::MEAR2: this->mem_end[(reg_offs >> 2) & 1] = value; break; case GrackleReg::EMEAR1: case GrackleReg::EMEAR2: this->ext_mem_end[(reg_offs >> 2) & 1] = value; break; case GrackleReg::MBER: this->mem_bank_en = value & 0xFFU; break; case GrackleReg::PICR1: this->picr1 = value; break; case GrackleReg::PICR2: this->picr2 = value; break; case GrackleReg::MCCR1: if ((value ^ this->mccr1) & MEMGO) { if (value & MEMGO) setup_ram(); } this->mccr1 = value; break; case GrackleReg::MCCR2: this->mccr2 = value; break; case GrackleReg::MCCR3: this->mccr3 = value; break; case GrackleReg::MCCR4: this->mccr4 = value; break; default: LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER(); } } void MPC106::setup_ram() { uint32_t bank_start[8]; uint32_t bank_end[8]; int bank_order[8]; int bank_count = 0; int region_count = 0; // get non-empty banks for (int bank = 0; bank < 8; bank++) { if (this->mem_bank_en & (1 << bank)) { int b = (bank >= 4); bank_start[bank_count] = (((ext_mem_start[b] >> bank * 8) & 3) << 28) | (((mem_start[b] >> bank * 8) & 0xFF) << 20); bank_end[bank_count] = (((ext_mem_end[b] >> bank * 8) & 3) << 28) | (((mem_end[b] >> bank * 8) & 0xFF) << 20) | 0xFFFFFUL; bank_order[bank_count] = bank_count; bank_count++; } } // sort banks by start address for (int i = 0; i < bank_count; i++) { for (int j = i + 1; j < bank_count; j++) { if (bank_start[bank_order[j]] < bank_start[bank_order[i]]) { int temp = bank_order[i]; bank_order[i] = bank_order[j]; bank_order[j] = temp; } } } // squash adjacent banks into memory regions for (int i = 0; i < bank_count; i++) { if (region_count > 0 && bank_start[bank_order[i]] == bank_end[bank_order[region_count - 1]] + 1) bank_end[bank_order[region_count - 1]] = bank_end[bank_order[i]]; else { bank_order[region_count] = bank_order[i]; region_count++; } } // allocate memory regions for (int i = 0; i < region_count; i++) { uint32_t region_size = bank_end[bank_order[i]] - bank_start[bank_order[i]] + 1; if (!this->add_ram_region(bank_start[bank_order[i]], region_size)) { LOG_F(WARNING, "Grackle: %d MB RAM allocation 0x%X..0x%X failed (maybe already exists?)", region_size / (1024 * 1024), bank_start[bank_order[i]], bank_end[bank_order[i]] ); } } } static const PropMap Grackle_Properties = { {"pci_PERCH", new StrProperty("")}, {"pci_A1", new StrProperty("")}, {"pci_B1", new StrProperty("")}, {"pci_C1", new StrProperty("")}, {"pci_GPU", new StrProperty("")}, }; static const DeviceDescription Grackle_Descriptor = { MPC106::create, {}, Grackle_Properties }; REGISTER_DEVICE(Grackle, Grackle_Descriptor);