From f7280c316b4a1415e57eebf4911bfdad6fed605c Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 00:42:14 -0800 Subject: [PATCH 1/8] Fix 64 bit BAR. A 64 bit BAR has least significant 32 bits first as in the original pull request. --- devices/common/pci/pcidevice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices/common/pci/pcidevice.cpp b/devices/common/pci/pcidevice.cpp index 32fa0f0..3a7624b 100644 --- a/devices/common/pci/pcidevice.cpp +++ b/devices/common/pci/pcidevice.cpp @@ -271,8 +271,8 @@ void PCIDevice::finish_config_bars() this->pci_name.c_str(), bar_num); } else { - bars_typ[bar_num] = PCIBarType::Mem_64_Bit_Hi; bars_typ[bar_num++] = PCIBarType::Mem_64_Bit_Lo; + bars_typ[bar_num ] = PCIBarType::Mem_64_Bit_Hi; } break; default: From fd2e6c5b096a0bc57e81af9868db10969d5ee798 Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 00:47:57 -0800 Subject: [PATCH 2/8] Fix ATIRage I/O accesses. - Don't log anything if the I/O access is not for this device. A different device might handle it. - Don't return true for I/O access if an I/O access is not performed. Otherwise the I/O access won't be passed to other devices. --- devices/video/atirage.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/devices/video/atirage.cpp b/devices/video/atirage.cpp index 935dca1..d5c6176 100644 --- a/devices/video/atirage.cpp +++ b/devices/video/atirage.cpp @@ -348,17 +348,13 @@ void ATIRage::write_reg(uint32_t offset, uint32_t value, uint32_t size) } bool ATIRage::io_access_allowed(uint32_t offset) { - if (!(this->command & 1)) { + if (offset >= this->io_base && offset < (this->io_base + 0x100)) { + if (this->command & 1) { + return true; + } LOG_F(WARNING, "ATI I/O space disabled in the command reg"); - return false; } - - if (offset < this->io_base || offset > (this->io_base + 0x100)) { - LOG_F(WARNING, "Rage: I/O out of range, base=0x%X, offset=0x%X", io_base, offset); - return false; - } - - return true; + return false; } From b47212374631192966e6510fd812ed78f5f7b92b Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 00:37:29 -0800 Subject: [PATCH 3/8] Standardize PCIDevice slot_id for PCIHost. PCIHost - PCIHosts (bandit and grackle) now use device number and function number for identifying attached PCIDevices. A macro DEV_FUN is added to calculate this new slot_id. Bandit no longer uses IDSEL. Grackle no longer uses only device number. machinecatalyst, machinegossamer, machinetnt - Use DEV_FUN to attach PCI devices by device number and function number. --- devices/common/pci/bandit.cpp | 19 ++++++++++--------- devices/common/pci/bandit.h | 1 + devices/common/pci/pcihost.cpp | 6 +++--- devices/common/pci/pcihost.h | 4 +++- devices/memctrl/mpc106.cpp | 16 ++++++++-------- machines/machinecatalyst.cpp | 2 +- machines/machinegazelle.cpp | 2 +- machines/machinegossamer.cpp | 4 ++-- machines/machinetnt.cpp | 4 ++-- 9 files changed, 31 insertions(+), 27 deletions(-) diff --git a/devices/common/pci/bandit.cpp b/devices/common/pci/bandit.cpp index 675faa9..1291e39 100644 --- a/devices/common/pci/bandit.cpp +++ b/devices/common/pci/bandit.cpp @@ -39,6 +39,7 @@ const int MultiplyDeBruijnBitPosition2[] = /** finds the position of the bit that is set */ #define WHAT_BIT_SET(val) (MultiplyDeBruijnBitPosition2[(uint32_t)(val * 0x077CB531U) >> 27]) +#define IDSEL_TO_DEV_FUN(idsel) DEV_FUN(WHAT_BIT_SET(idsel) + 11,0) BanditPciDevice::BanditPciDevice(int bridge_num, std::string name, int dev_id, int rev) : PCIDevice(name) @@ -159,18 +160,18 @@ uint32_t BanditHost::read(uint32_t rgn_start, uint32_t offset, int size) return 0xFFFFFFFFUL; // PCI spec §6.1 } - if (this->dev_map.count(idsel)) { + if (this->dev_map.count(IDSEL_TO_DEV_FUN(idsel))) { AccessDetails details; details.offset = offset & 3; details.size = size; details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_READ; - result = this->dev_map[idsel]->pci_cfg_read(REG_NUM(), details); + result = this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_read(REG_NUM(), details); return pci_conv_rd_data(result, details); } else { LOG_F( ERROR, "%s err: read attempt from non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), WHAT_BIT_SET(idsel) + 11, FUN_NUM(), REG_NUM() + (offset & 3) + this->name.c_str(), IDSEL_TO_DEV_FUN(idsel), FUN_NUM(), REG_NUM() + (offset & 3) ); return 0xFFFFFFFFUL; // PCI spec §6.1 } @@ -215,23 +216,23 @@ void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int return; } - if (this->dev_map.count(idsel)) { + if (this->dev_map.count(IDSEL_TO_DEV_FUN(idsel))) { AccessDetails details; details.offset = offset & 3; details.size = size; details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_WRITE; if (size == 4 && !details.offset) { // aligned DWORD writes -> fast path - this->dev_map[idsel]->pci_cfg_write(REG_NUM(), BYTESWAP_32(value), details); + this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_write(REG_NUM(), BYTESWAP_32(value), details); } else { // otherwise perform necessary data transformations -> slow path - uint32_t old_val = this->dev_map[idsel]->pci_cfg_read(REG_NUM(), details); + uint32_t old_val = this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_read(REG_NUM(), details); uint32_t new_val = pci_conv_wr_data(old_val, value, details); - this->dev_map[idsel]->pci_cfg_write(REG_NUM(), new_val, details); + this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_write(REG_NUM(), new_val, details); } } else { LOG_F( ERROR, "%s err: write attempt to non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), WHAT_BIT_SET(idsel) + 11, FUN_NUM(), REG_NUM() + (offset & 3) + this->name.c_str(), IDSEL_TO_DEV_FUN(idsel), FUN_NUM(), REG_NUM() + (offset & 3) ); } break; @@ -276,7 +277,7 @@ Bandit::Bandit(int bridge_num, std::string name, int dev_id, int rev) this->my_pci_device = unique_ptr( new BanditPciDevice(bridge_num, name, dev_id, rev) ); - this->pci_register_device(1, this->my_pci_device.get()); + this->pci_register_device(DEV_FUN(BANDIT_DEV,0), this->my_pci_device.get()); } Chaos::Chaos(std::string name) : BanditHost() diff --git a/devices/common/pci/bandit.h b/devices/common/pci/bandit.h index 007442e..e693417 100644 --- a/devices/common/pci/bandit.h +++ b/devices/common/pci/bandit.h @@ -46,6 +46,7 @@ along with this program. If not, see . #include #include +#define BANDIT_DEV (11) // Bandit's own device number #define BANDIT_CAR_TYPE (1 << 0) // Bandit config address type bit /* Convenient macros for parsing CONFIG_ADDR fields. */ diff --git a/devices/common/pci/pcihost.cpp b/devices/common/pci/pcihost.cpp index 47d997c..d639a1f 100644 --- a/devices/common/pci/pcihost.cpp +++ b/devices/common/pci/pcihost.cpp @@ -28,13 +28,13 @@ along with this program. If not, see . #include -bool PCIHost::pci_register_device(int dev_num, PCIDevice* dev_instance) +bool PCIHost::pci_register_device(int dev_fun_num, PCIDevice* dev_instance) { // return false if dev_num already registered - if (this->dev_map.count(dev_num)) + if (this->dev_map.count(dev_fun_num)) return false; - this->dev_map[dev_num] = dev_instance; + this->dev_map[dev_fun_num] = dev_instance; dev_instance->set_host(this); diff --git a/devices/common/pci/pcihost.h b/devices/common/pci/pcihost.h index 4615dd2..0566569 100644 --- a/devices/common/pci/pcihost.h +++ b/devices/common/pci/pcihost.h @@ -48,6 +48,8 @@ typedef struct AccessDetails { uint8_t flags; } AccessDetails; +#define DEV_FUN(dev_num,fun_num) (((dev_num) << 3) | (fun_num)) + class PCIDevice; // forward declaration to prevent errors class PCIHost { @@ -58,7 +60,7 @@ public: }; ~PCIHost() = default; - virtual bool pci_register_device(int dev_num, PCIDevice* dev_instance); + virtual bool pci_register_device(int dev_fun_num, PCIDevice* dev_instance); virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); diff --git a/devices/memctrl/mpc106.cpp b/devices/memctrl/mpc106.cpp index bf62faf..5e7ce0a 100644 --- a/devices/memctrl/mpc106.cpp +++ b/devices/memctrl/mpc106.cpp @@ -48,7 +48,7 @@ MPC106::MPC106() : MemCtrlBase(), PCIDevice("Grackle"), PCIHost() this->status = 0x80; // assign PCI device number zero to myself - this->pci_register_device(0, this); + this->pci_register_device(DEV_FUN(0,0), this); // add PCI/ISA I/O space, 64K for now add_mmio_region(0xFE000000, 0x10000, this); @@ -62,7 +62,7 @@ int MPC106::device_postinit() std::string pci_dev_name; static const std::map pci_slots = { - {"pci_A1", 0xD}, {"pci_B1", 0xE}, {"pci_C1", 0xF} + {"pci_A1", DEV_FUN(0xD,0)}, {"pci_B1", DEV_FUN(0xE,0)}, {"pci_C1", DEV_FUN(0xF,0)} }; for (auto& slot : pci_slots) { @@ -131,12 +131,12 @@ uint32_t MPC106::pci_read(uint32_t offset, uint32_t size) { return 0xFFFFFFFFUL; // PCI spec §6.1 } - if (this->dev_map.count(dev_num)) { + if (this->dev_map.count(DEV_FUN(dev_num,fun_num))) { AccessDetails details; details.offset = offset & 3; details.size = size; details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_READ; - uint32_t result = this->dev_map[dev_num]->pci_cfg_read(reg_offs, details); + uint32_t result = this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_read(reg_offs, details); return pci_conv_rd_data(result, details); } else { LOG_F(ERROR, "%s: read attempt from non-existing PCI device ??:%02x.%x @%02x", @@ -158,18 +158,18 @@ void MPC106::pci_write(uint32_t offset, uint32_t value, uint32_t size) { return; } - if (this->dev_map.count(dev_num)) { + if (this->dev_map.count(DEV_FUN(dev_num,fun_num))) { AccessDetails details; details.offset = offset & 3; details.size = size; details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_WRITE; if (size == 4 && !details.offset) { // aligned DWORD writes -> fast path - this->dev_map[dev_num]->pci_cfg_write(reg_offs, BYTESWAP_32(value), details); + this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_write(reg_offs, BYTESWAP_32(value), details); } else { // otherwise perform necessary data transformations -> slow path - uint32_t old_val = this->dev_map[dev_num]->pci_cfg_read(reg_offs, details); + uint32_t old_val = this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_read(reg_offs, details); uint32_t new_val = pci_conv_wr_data(old_val, value, details); - this->dev_map[dev_num]->pci_cfg_write(reg_offs, new_val, details); + this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_write(reg_offs, new_val, details); } } else { LOG_F(ERROR, "%s: write attempt to non-existing PCI device ??:%02x.%x @%02x", diff --git a/machines/machinecatalyst.cpp b/machines/machinecatalyst.cpp index 831699f..474931a 100644 --- a/machines/machinecatalyst.cpp +++ b/machines/machinecatalyst.cpp @@ -44,7 +44,7 @@ int initialize_catalyst(std::string& id) // add the GrandCentral I/O controller pci_host->pci_register_device( - 32, dynamic_cast(gMachineObj->get_comp_by_name("GrandCentral"))); + DEV_FUN(0x10,0), dynamic_cast(gMachineObj->get_comp_by_name("GrandCentral"))); // get (raw) pointer to the memory controller platinum_obj = dynamic_cast(gMachineObj->get_comp_by_name("Platinum")); diff --git a/machines/machinegazelle.cpp b/machines/machinegazelle.cpp index db95645..064e92d 100644 --- a/machines/machinegazelle.cpp +++ b/machines/machinegazelle.cpp @@ -53,7 +53,7 @@ int initialize_gazelle(std::string& id) // register O'Hare I/O controller with the main PCI bus pci_host->pci_register_device( - 32, dynamic_cast(gMachineObj->get_comp_by_name("OHare"))); + DEV_FUN(0x10,0), dynamic_cast(gMachineObj->get_comp_by_name("OHare"))); PsxCtrl* psx_obj = dynamic_cast(gMachineObj->get_comp_by_name("Psx")); diff --git a/machines/machinegossamer.cpp b/machines/machinegossamer.cpp index 4ffa438..d4d24fd 100644 --- a/machines/machinegossamer.cpp +++ b/machines/machinegossamer.cpp @@ -124,9 +124,9 @@ int initialize_gossamer(std::string& id) // add pci devices grackle_obj->pci_register_device( - 16, dynamic_cast(gMachineObj->get_comp_by_name("Heathrow"))); + DEV_FUN(0x10,0), dynamic_cast(gMachineObj->get_comp_by_name("Heathrow"))); grackle_obj->pci_register_device( - 18, dynamic_cast(gMachineObj->get_comp_by_name(id == "pmg3twr" ? "AtiRagePro" : "AtiRageGT"))); + DEV_FUN(0x12,0), dynamic_cast(gMachineObj->get_comp_by_name(id == "pmg3twr" ? "AtiRagePro" : "AtiRageGT"))); // add Athens clock generator device and register it with the I2C host gMachineObj->add_device("Athens", std::unique_ptr(new AthensClocks(0x28))); diff --git a/machines/machinetnt.cpp b/machines/machinetnt.cpp index 0c281d1..e1a2648 100644 --- a/machines/machinetnt.cpp +++ b/machines/machinetnt.cpp @@ -44,14 +44,14 @@ int initialize_tnt(std::string& id) // connect GrandCentral I/O controller to the PCI1 bus pci_host->pci_register_device( - 32, dynamic_cast(gMachineObj->get_comp_by_name("GrandCentral"))); + DEV_FUN(0x10,0), dynamic_cast(gMachineObj->get_comp_by_name("GrandCentral"))); // get video PCI controller object PCIHost *vci_host = dynamic_cast(gMachineObj->get_comp_by_name("Chaos")); // connect built-in video device to the VCI bus vci_host->pci_register_device( - 1, dynamic_cast(gMachineObj->get_comp_by_name("ControlVideo"))); + DEV_FUN(0x0B,0), dynamic_cast(gMachineObj->get_comp_by_name("ControlVideo"))); // get (raw) pointer to the memory controller memctrl_obj = dynamic_cast(gMachineObj->get_comp_by_name("Hammerhead")); From be45a6a020f85e5b29be3aefed8a3f8f68c70f97 Mon Sep 17 00:00:00 2001 From: joevt Date: Tue, 25 Oct 2022 23:06:12 -0700 Subject: [PATCH 4/8] Add PCI bridge and multi-function device support. Add PCI bridge and multi-function device support. Overview: - A multi-function device is two or more PCIDevices with the same device number but one device is function zero (as with currently implemented PCIDevices) and the other functions have function numbers between 1 and 7. The device number and function number are properties of the PCIDevice's parent PCIHost connection. - A PCIBridge is a PCIHost (it can connect child PCI devices) and a PCIDevice (it has config space, BARs, and expansion ROM). - A PCIDevice has Type 0 header. It has 6 BARs. - A PCIBridge has Type 1 header. It has 2 BARs. The config space registers beginning from offset 0x18 differ from those of a PCIDevice. Possible future modifications: - Add a PCICardBus class. It is a PCIHost. It has Type 2 header. It has one BAR. The first 20 bytes match Type 0 and Type 1 headers. These exist in New World Macs. They allow hot-plug of PCI devices. - Split base PCI registers (first 16 bytes) into a PCIBase class. Type 1 and 2 have two or one BAR but I think all 6 BARs belong in PCIBase class anyway. - Split PCIHost into two classes: Currently existing PCIHosts (Bandit, Grackle) are PCIHost and PCIRoot (they have the broadcast I/O requests functionality) while PCIBridge is PCIHost only - it can propagate I/O requests but does not originate the broadcast. - pci_register_mmio_region should maybe return a pointer to a region struct so that it can be used for unregistering or modifying the region's range. This may be useful for PCI bridges which have ranges that may constrain memory BARs of their downstream devices. PCIDevice - Moved expansion ROM BAR handling to a separate function pci_wr_exp_rom_bar so that it can be used by both PCI devices and PCI bridges which have the ROM BAR in different locations. It now supports unmapping expansion ROM. Also made exp_rom_bar not writable if there's no ROM. - Added num_bars field which specifies the number of valid BARs since Type 0, 1, and 2 headers have different number of BARs. - map_exp_rom_mem now properly unmaps expansion ROM (using new function unmap_exp_rom_mem) before mapping it again. - Added function set_multi_function which modifies hdr_type to indicate if a device has other functions. This is to be applied only to devices with function number 0. PCIHost - When attaching a PCI device, it will check if it's a multi-function device (there exists an attached function that is not zero) and adjust hdr_type of function 0 of the device accordingly. - Attached PCI bridges are added to a list of PCI bridges attached to the host. - Added pci_io_read_loop and pci_io_write_loop which loop through attached PCI devices to find one that will perform the action for the given I/O address without logging an error (since some other device might perform the action). - Added pci_io_read_broadcast and pci_io_write_broadcast which are used by a PCI root (bandit/grackle). They will log an error if the action is not performed. They should probably do a machine check exception to match real Power Macs. - pci_find_device (used by PCI root) will recursively find a PCIDevice for type 1 config register accesses. - Logging from PCIHost now includes the name of the PCIHost instead of just "PCIHost" because there can be multiple PCI hosts. PCIBridge - Sets num_bars to 2 and hdr_type to 1. - I/O ranges set in the config registers are handled correctly by pci_io_read and pci_io_write. - Memory ranges set in the config registers do not currently affect memory mmio regions. It is assumed that Open Firmware and the OS will set the ranges and BARs correctly to allow all BARs to be accessed fully. bandit, mpc106 - Bandit and Grackle now call pci_io_read_broadcast and pci_io_read_broadcast to pass I/O accesses to downstream PCI devices. - Chaos is modified to work like Bandit even though it will never have PCI bridges attached or devices that support I/O accesses. It's simpler this way. --- devices/common/pci/bandit.cpp | 139 ++++++++++--------------- devices/common/pci/bandit.h | 3 + devices/common/pci/pcibridge.cpp | 168 +++++++++++++++++++++++++++++++ devices/common/pci/pcibridge.h | 139 +++++++++++++++++++++++++ devices/common/pci/pcidevice.cpp | 61 +++++++---- devices/common/pci/pcidevice.h | 8 +- devices/common/pci/pcihost.cpp | 92 ++++++++++++++++- devices/common/pci/pcihost.h | 10 +- devices/memctrl/mpc106.cpp | 124 ++++++++++------------- devices/memctrl/mpc106.h | 2 + 10 files changed, 566 insertions(+), 180 deletions(-) create mode 100644 devices/common/pci/pcibridge.cpp create mode 100644 devices/common/pci/pcibridge.h diff --git a/devices/common/pci/bandit.cpp b/devices/common/pci/bandit.cpp index 1291e39..828427b 100644 --- a/devices/common/pci/bandit.cpp +++ b/devices/common/pci/bandit.cpp @@ -39,7 +39,6 @@ const int MultiplyDeBruijnBitPosition2[] = /** finds the position of the bit that is set */ #define WHAT_BIT_SET(val) (MultiplyDeBruijnBitPosition2[(uint32_t)(val * 0x077CB531U) >> 27]) -#define IDSEL_TO_DEV_FUN(idsel) DEV_FUN(WHAT_BIT_SET(idsel) + 11,0) BanditPciDevice::BanditPciDevice(int bridge_num, std::string name, int dev_id, int rev) : PCIDevice(name) @@ -140,101 +139,50 @@ void BanditPciDevice::verbose_address_space() uint32_t BanditHost::read(uint32_t rgn_start, uint32_t offset, int size) { - uint32_t idsel, result; - switch (offset >> 22) { case 3: // CONFIG_DATA - if (this->config_addr & BANDIT_CAR_TYPE) { // type 1 configuration command - LOG_F( - WARNING, "%s: read config cycle type 1 not supported yet %02x:%02x.%x @%02x", - this->name.c_str(), BUS_NUM(), DEV_NUM(), FUN_NUM(), REG_NUM() + (offset & 3) - ); - return 0xFFFFFFFFUL; // PCI spec §6.1 + int bus_num, dev_num, fun_num; + uint8_t reg_offs; + AccessDetails details; + PCIDevice *device; + cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device); + details.flags |= PCI_CONFIG_READ; + if (device) { + return pci_conv_rd_data(device->pci_cfg_read(reg_offs, details), details); } - - idsel = (this->config_addr >> 11) & 0x1FFFFFU; - - if (!SINGLE_BIT_SET(idsel)) { - LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL", - this->name.c_str(), (uint32_t)this->config_addr); + LOG_READ_NON_EXISTENT_PCI_DEVICE(); return 0xFFFFFFFFUL; // PCI spec §6.1 - } - - if (this->dev_map.count(IDSEL_TO_DEV_FUN(idsel))) { - AccessDetails details; - details.offset = offset & 3; - details.size = size; - details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_READ; - - result = this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_read(REG_NUM(), details); - return pci_conv_rd_data(result, details); - } else { - LOG_F( - ERROR, "%s err: read attempt from non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), IDSEL_TO_DEV_FUN(idsel), FUN_NUM(), REG_NUM() + (offset & 3) - ); - return 0xFFFFFFFFUL; // PCI spec §6.1 - } - break; case 2: // CONFIG_ADDR return BYTESWAP_32(this->config_addr); default: // I/O space - // broadcast I/O request to devices that support I/O space - // until a device returns true that means "request accepted" - for (auto& dev : this->io_space_devs) { - if (dev->pci_io_read(offset, size, &result)) - return result; - } - LOG_F(ERROR, "%s: attempt to read from unmapped PCI I/O space, offset=0x%X", - this->name.c_str(), offset); - // FIXME: add machine check exception (DEFAULT CATCH!, code=FFF00200) - return 0; + return pci_io_read_broadcast(offset, size); } } void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { - uint32_t idsel; - switch (offset >> 22) { case 3: // CONFIG_DATA - if (this->config_addr & BANDIT_CAR_TYPE) { // type 1 configuration command - LOG_F( - WARNING, "%s: write config cycle type 1 not supported yet %02x:%02x.%x @%02x", - this->name.c_str(), BUS_NUM(), DEV_NUM(), FUN_NUM(), REG_NUM() + (offset & 3) - ); - return; - } - - idsel = (this->config_addr >> 11) & 0x1FFFFFU; - - if (!SINGLE_BIT_SET(idsel)) { - LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL", - this->name.c_str(), (uint32_t)this->config_addr); - return; - } - - if (this->dev_map.count(IDSEL_TO_DEV_FUN(idsel))) { - AccessDetails details; - details.offset = offset & 3; - details.size = size; - details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_WRITE; - + int bus_num, dev_num, fun_num; + uint8_t reg_offs; + AccessDetails details; + PCIDevice *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 - this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_write(REG_NUM(), BYTESWAP_32(value), details); - } else { // otherwise perform necessary data transformations -> slow path - uint32_t old_val = this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_read(REG_NUM(), details); - uint32_t new_val = pci_conv_wr_data(old_val, value, details); - this->dev_map[IDSEL_TO_DEV_FUN(idsel)]->pci_cfg_write(REG_NUM(), new_val, details); + device->pci_cfg_write(reg_offs, BYTESWAP_32(value), details); + return; } - } else { - LOG_F( - ERROR, "%s err: write attempt to non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), IDSEL_TO_DEV_FUN(idsel), FUN_NUM(), REG_NUM() + (offset & 3) - ); + // 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(); break; case 2: // CONFIG_ADDR @@ -242,14 +190,35 @@ void BanditHost::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int break; default: // I/O space - // broadcast I/O request to devices that support I/O space - // until a device returns true that means "request handled" - for (auto& dev : this->io_space_devs) { - if (dev->pci_io_write(offset, value, size)) - return; - } - LOG_F(ERROR, "%s: attempt to read from unmapped PCI I/O space, offset=0x%X", - this->name.c_str(), offset); + pci_io_write_broadcast(offset, size, value); + } +} + +inline void BanditHost::cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIDevice *&device) +{ + device = NULL; + details.size = size; + details.offset = offset & 3; + fun_num = FUN_NUM(); + reg_offs = REG_NUM(); + if (this->config_addr & BANDIT_CAR_TYPE) { // type 1 configuration command + details.flags = PCI_CONFIG_TYPE_1; + bus_num = BUS_NUM(); + dev_num = DEV_NUM(); + device = pci_find_device(bus_num, dev_num, fun_num); + return; + } + details.flags = PCI_CONFIG_TYPE_0; + bus_num = 0; // bus number is meaningless for type 0 configuration command; a type 1 configuration command cannot reach devices attached directly to the host + uint32_t idsel = this->config_addr & 0xFFFFF800U; + if (!SINGLE_BIT_SET(idsel)) { + for (dev_num = -1, idsel = this->config_addr; idsel; idsel >>= 1, dev_num++) {} + LOG_F(ERROR, "%s: config_addr 0x%08x does not contain valid IDSEL", this->name.c_str(), (uint32_t)this->config_addr); + return; + } + dev_num = WHAT_BIT_SET(idsel); + if (this->dev_map.count(DEV_FUN(dev_num, fun_num))) { + device = this->dev_map[DEV_FUN(dev_num, fun_num)]; } } diff --git a/devices/common/pci/bandit.h b/devices/common/pci/bandit.h index e693417..e7f2b3a 100644 --- a/devices/common/pci/bandit.h +++ b/devices/common/pci/bandit.h @@ -76,6 +76,9 @@ public: protected: uint32_t config_addr; + +private: + inline void cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIDevice *&device); }; /* diff --git a/devices/common/pci/pcibridge.cpp b/devices/common/pci/pcibridge.cpp new file mode 100644 index 0000000..158eccf --- /dev/null +++ b/devices/common/pci/pcibridge.cpp @@ -0,0 +1,168 @@ +/* +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 + +PCIBridge::PCIBridge(std::string name) : PCIDevice(name) +{ + this->num_bars = 2; + this->hdr_type = 1; + + this->pci_rd_primary_bus = [this]() { return this->primary_bus; }; + this->pci_rd_secondary_bus = [this]() { return this->secondary_bus; }; + this->pci_rd_subordinate_bus = [this]() { return this->subordinate_bus; }; + this->pci_rd_sec_latency_timer = [this]() { return this->sec_latency_timer; }; + this->pci_rd_sec_status = [this]() { return this->sec_status; }; + this->pci_rd_memory_base = [this]() { return this->memory_base; }; + this->pci_rd_memory_limit = [this]() { return this->memory_limit; }; + this->pci_rd_io_base = [this]() { return this->io_base; }; + this->pci_rd_io_limit = [this]() { return this->io_limit; }; + this->pci_rd_pref_mem_base = [this]() { return this->pref_mem_base; }; + this->pci_rd_pref_mem_limit = [this]() { return this->pref_mem_limit; }; + this->pci_rd_io_base_upper16 = [this]() { return this->io_base_upper16; }; + this->pci_rd_io_limit_upper16 = [this]() { return this->io_limit_upper16; }; + this->pci_rd_pref_base_upper32 = [this]() { return this->pref_base_upper32; }; + this->pci_rd_pref_limit_upper32 = [this]() { return this->pref_limit_upper32; }; + this->pci_rd_bridge_control = [this]() { return this->bridge_control; }; + + this->pci_wr_primary_bus = [this](uint8_t val) { this->primary_bus = val; }; + this->pci_wr_secondary_bus = [this](uint8_t val) { this->secondary_bus = val; }; + this->pci_wr_subordinate_bus = [this](uint8_t val) { this->subordinate_bus = val; }; + this->pci_wr_sec_latency_timer = [this](uint8_t val) { this->sec_latency_timer = (this->sec_latency_timer & ~this->sec_latency_timer_cfg) | (val & this->sec_latency_timer_cfg); }; + this->pci_wr_sec_status = [this](uint16_t val) {}; + this->pci_wr_memory_base = [this](uint16_t val) { this->memory_base = (val & this->memory_cfg ) | (this->memory_cfg & 15); this->memory_base_32 = ((this->memory_base & 0xfff0) << 16) ; }; + this->pci_wr_memory_limit = [this](uint16_t val) { this->memory_limit = (val & this->memory_cfg ) | (this->memory_cfg & 15); this->memory_limit_32 = ((this->memory_limit & 0xfff0) << 16) + 0x100000; }; + this->pci_wr_io_base = [this](uint8_t val) { this->io_base = (val & this->io_cfg ) | (this->io_cfg & 15); this->io_base_32 = ((uint32_t)this->io_base_upper16 << 16) | ((this->io_base & 0xf0) << 8) ; }; + this->pci_wr_io_limit = [this](uint8_t val) { this->io_limit = (val & this->io_cfg ) | (this->io_cfg & 15); this->io_limit_32 = (((uint32_t)this->io_limit_upper16 << 16) | ((this->io_limit & 0xf0) << 8)) + 0x1000 ; }; + this->pci_wr_pref_mem_base = [this](uint16_t val) { this->pref_mem_base = (val & this->pref_mem_cfg) | (this->pref_mem_cfg & 15); this->pref_mem_base_64 = ((uint64_t)this->pref_base_upper32 << 32) | ((this->pref_mem_base & 0xfff0) << 16) ; }; + this->pci_wr_pref_mem_limit = [this](uint16_t val) { this->pref_mem_limit = (val & this->pref_mem_cfg) | (this->pref_mem_cfg & 15); this->pref_mem_limit_64 = (((uint64_t)this->pref_limit_upper32 << 32) | ((this->pref_mem_limit & 0xfff0) << 16)) + 0x100000; }; + this->pci_wr_io_base_upper16 = [this](uint16_t val) { if ((this->io_base & 15) == 1) this->io_base_upper16 = val; { this->io_base_32 = ((uint32_t)this->io_base_upper16 << 16) | ((this->io_base & 0xf0) << 8) ; } }; + this->pci_wr_io_limit_upper16 = [this](uint16_t val) { if ((this->io_limit & 15) == 1) this->io_limit_upper16 = val; { this->io_limit_32 = (((uint32_t)this->io_limit_upper16 << 16) | ((this->io_limit & 0xf0) << 8)) + 0x1000 ; } }; + this->pci_wr_pref_base_upper32 = [this](uint32_t val) { if ((this->pref_mem_cfg & 15) == 1) this->pref_base_upper32 = val; { this->pref_mem_base_64 = ((uint64_t)this->pref_base_upper32 << 32) | ((this->pref_mem_base & 0xfff0) << 16) ; } }; + this->pci_wr_pref_limit_upper32 = [this](uint32_t val) { if ((this->pref_mem_cfg & 15) == 1) this->pref_limit_upper32 = val; { this->pref_mem_limit_64 = (((uint64_t)this->pref_limit_upper32 << 32) | ((this->pref_mem_limit & 0xfff0) << 16)) + 0x100000; } }; + this->pci_wr_bridge_control = [this](uint16_t val) { this->bridge_control = val; }; +}; + +bool PCIBridge::pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj) +{ + // FIXME: constrain region to memory range + return this->host_instance->pci_register_mmio_region(start_addr, size, obj); +} + +bool PCIBridge::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj) +{ + return this->host_instance->pci_unregister_mmio_region(start_addr, size, obj); +} + +uint32_t PCIBridge::pci_cfg_read(uint32_t reg_offs, AccessDetails &details) +{ + if (reg_offs < 0x18) { + return PCIDevice::pci_cfg_read(reg_offs, details); + } + + switch (reg_offs) { + case PCI_CFG_PRIMARY_BUS: + return (this->pci_rd_sec_latency_timer() << 24) | (this->pci_rd_subordinate_bus() << 16) | (this->pci_rd_secondary_bus() << 8) | (this->pci_rd_primary_bus()); + case PCI_CFG_IO_BASE: + return (this->pci_rd_sec_status() << 16) | (this->pci_rd_io_limit() << 8) | (this->pci_rd_io_base()); + case PCI_CFG_MEMORY_BASE: + return (this->pci_rd_memory_limit() << 16) | (this->pci_rd_memory_base()); + case PCI_CFG_PREF_MEM_BASE: + return (this->pci_rd_pref_mem_limit() << 16) | (this->pci_rd_pref_mem_base()); + case PCI_CFG_PREF_BASE_UPPER32: + return this->pci_rd_pref_base_upper32(); + case PCI_CFG_PREF_LIMIT_UPPER32: + return this->pci_rd_pref_limit_upper32(); + case PCI_CFG_IO_BASE_UPPER16: + return (this->pci_rd_io_limit_upper16() << 16) | (this->pci_rd_io_base_upper16()); + case PCI_CFG_CAP_PTR: + return cap_ptr; + case PCI_CFG_BRIDGE_ROM_ADDRESS: + return exp_rom_bar; + case PCI_CFG_INTERRUPT_LINE: + return (this->pci_rd_bridge_control() << 16) | (irq_pin << 8) | irq_line; + } + LOG_READ_UNIMPLEMENTED_CONFIG_REGISTER(); + return 0; +} + +void PCIBridge::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details) +{ + if (reg_offs < 0x18) { + return PCIDevice::pci_cfg_write(reg_offs, value, details); + } + + switch (reg_offs) { + case PCI_CFG_PRIMARY_BUS: + this->pci_wr_sec_latency_timer(value >> 24); + this->pci_wr_subordinate_bus(value >> 16); + this->pci_wr_secondary_bus(value >> 8); + this->pci_wr_primary_bus(value & 0xFFU); + break; + case PCI_CFG_IO_BASE: + this->pci_wr_sec_status(value >> 16); + this->pci_wr_io_limit(value >> 8); + this->pci_wr_io_base(value & 0xFFU); + break; + case PCI_CFG_MEMORY_BASE: + this->pci_wr_memory_limit(value >> 16); + this->pci_wr_memory_base(value & 0xFFFFU); + break; + case PCI_CFG_PREF_MEM_BASE: + this->pci_wr_pref_mem_limit(value >> 16); + this->pci_wr_pref_mem_base(value & 0xFFFFU); + break; + case PCI_CFG_PREF_BASE_UPPER32: + this->pci_wr_pref_base_upper32(value); + break; + case PCI_CFG_PREF_LIMIT_UPPER32: + this->pci_wr_pref_limit_upper32(value); + break; + case PCI_CFG_IO_BASE_UPPER16: + this->pci_wr_io_limit_upper16(value >> 16); + this->pci_wr_io_base_upper16(value & 0xFFFFU); + break; + case PCI_CFG_BRIDGE_ROM_ADDRESS: + this->pci_wr_exp_rom_bar(value); + break; + case PCI_CFG_INTERRUPT_LINE: + this->irq_line = value >> 24; + this->pci_wr_bridge_control(value >> 16); + break; + default: + LOG_WRITE_UNIMPLEMENTED_CONFIG_REGISTER(); + } +} + +bool PCIBridge::pci_io_read(uint32_t offset, uint32_t size, uint32_t* res) +{ + if (!(this->command & 1)) return false; + if (offset < this->io_base_32 || offset + size >= this->io_limit_32) return false; + return this->pci_io_read_loop(offset, size, *res); +} + +bool PCIBridge::pci_io_write(uint32_t offset, uint32_t value, uint32_t size) +{ + if (!(this->command & 1)) return false; + if (offset < this->io_base_32 || offset + size >= this->io_limit_32) return false; + return this->pci_io_read_loop(offset, size, value); +} diff --git a/devices/common/pci/pcibridge.h b/devices/common/pci/pcibridge.h new file mode 100644 index 0000000..e50cb4b --- /dev/null +++ b/devices/common/pci/pcibridge.h @@ -0,0 +1,139 @@ +/* +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 . +*/ + +#ifndef PCI_BRIDGE_H +#define PCI_BRIDGE_H + +#include +#include +#include + +#include +#include +#include +#include + +/** PCI configuration space registers offsets */ +enum { + PCI_CFG_PRIMARY_BUS = 0x18, // PRIMARY_BUS, SECONDARY_BUS, SUBORDINATE_BUS, SEC_LATENCY_TIMER + PCI_CFG_IO_BASE = 0x1C, // IO_BASE.b, IO_LIMIT.b, SEC_STATUS.w + PCI_CFG_MEMORY_BASE = 0x20, // MEMORY_BASE.w, MEMORY_LIMIT.w + PCI_CFG_PREF_MEM_BASE = 0x24, // PREF_MEMORY_BASE.w, PREF_MEMORY_LIMIT.w + PCI_CFG_PREF_BASE_UPPER32 = 0x28, // PREF_BASE_UPPER32 + PCI_CFG_PREF_LIMIT_UPPER32 = 0x2c, // PREF_LIMIT_UPPER32 + PCI_CFG_IO_BASE_UPPER16 = 0x30, // IO_BASE_UPPER16.w, IO_LIMIT_UPPER16.w + // PCI_CFG_CAP_PTR + PCI_CFG_BRIDGE_ROM_ADDRESS = 0x38, // BRIDGE_ROM_ADDRESS + PCI_CFG_INTERRUPT_LINE = 0x3C, // INTERRUPT_LINE.b, INTERRUPT_PIN.b, BRIDGE_CONTROL.w +}; + +class PCIBridge : public PCIHost, public PCIDevice { + friend class PCIHost; +public: + PCIBridge(std::string name); + ~PCIBridge() = default; + + // PCIHost methods + virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); + virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); + + // PCIDevice methods + virtual uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details); + virtual void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details); + + virtual bool pci_io_read(uint32_t offset, uint32_t size, uint32_t* res); + virtual bool pci_io_write(uint32_t offset, uint32_t value, uint32_t size); + + // MMIODevice methods + virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { + return 0; + }; + virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { }; + + // plugin interface for using in the derived classes + std::function pci_rd_primary_bus; + std::function pci_wr_primary_bus; + std::function pci_rd_secondary_bus; + std::function pci_wr_secondary_bus; + std::function pci_rd_subordinate_bus; + std::function pci_wr_subordinate_bus; + std::function pci_rd_sec_latency_timer; + std::function pci_wr_sec_latency_timer; + std::function pci_rd_sec_status; + std::function pci_wr_sec_status; + std::function pci_rd_io_base; + std::function pci_wr_io_base; + std::function pci_rd_io_limit; + std::function pci_wr_io_limit; + std::function pci_rd_memory_base; + std::function pci_wr_memory_base; + std::function pci_rd_memory_limit; + std::function pci_wr_memory_limit; + std::function pci_rd_pref_mem_base; + std::function pci_wr_pref_mem_base; + std::function pci_rd_pref_mem_limit; + std::function pci_wr_pref_mem_limit; + std::function pci_rd_pref_base_upper32; + std::function pci_wr_pref_base_upper32; + std::function pci_rd_pref_limit_upper32; + std::function pci_wr_pref_limit_upper32; + std::function pci_rd_io_base_upper16; + std::function pci_wr_io_base_upper16; + std::function pci_rd_io_limit_upper16; + std::function pci_wr_io_limit_upper16; + std::function pci_rd_bridge_control; + std::function pci_wr_bridge_control; + +protected: + // PCI configuration space state + uint8_t primary_bus = 0; + uint8_t secondary_bus = 0; + uint8_t subordinate_bus = 0; + uint8_t sec_latency_timer = 0; // if supportss r/w then must reset to 0 + uint16_t sec_status = 0; + uint8_t io_base = 0; + uint8_t io_limit = 0; + uint16_t memory_base = 0; + uint16_t memory_limit = 0; + uint16_t pref_mem_base = 0; + uint16_t pref_mem_limit = 0; + uint32_t pref_base_upper32 = 0; + uint32_t pref_limit_upper32 = 0; + uint16_t io_base_upper16 = 0; + uint16_t io_limit_upper16 = 0; + uint16_t bridge_control = 0; + + uint8_t sec_latency_timer_cfg = 0; // 0 = not writable, 0xf8 = limits the granularity to eight PCI clocks + uint8_t io_cfg = 0xf0; // 0 = not writable, 0xf0 = supports 16 bit io range, 0xf1 = supports 32 bit io range + uint16_t memory_cfg = 0xfff0; // 0 = not writable, 0xfff0 = supports 32 bit memory range + uint16_t pref_mem_cfg = 0xfff0; // 0 = not writable, 0xfff0 = supports 32 bit prefetchable memory range, 0xfff1 = supports 64 bit prefetchable memory range + + uint32_t io_base_32 = 0; + uint32_t io_limit_32 = 0; + uint64_t memory_base_32 = 0; + uint64_t memory_limit_32 = 0; + uint64_t pref_mem_base_64 = 0; + uint64_t pref_mem_limit_64 = 0; + +private: +}; + +#endif /* PCI_BRIDGE_H */ diff --git a/devices/common/pci/pcidevice.cpp b/devices/common/pci/pcidevice.cpp index 3a7624b..925a668 100644 --- a/devices/common/pci/pcidevice.cpp +++ b/devices/common/pci/pcidevice.cpp @@ -20,7 +20,6 @@ along with this program. If not, see . */ #include -#include #include #include #include @@ -103,17 +102,7 @@ void PCIDevice::pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails & this->set_bar_value((reg_offs - 0x10) >> 2, value); break; case PCI_CFG_ROM_BAR: - if ((value & this->exp_bar_cfg) == this->exp_bar_cfg) { - this->exp_rom_bar = (value & (this->exp_bar_cfg | 1)); - } else { - this->exp_rom_bar = (value & (this->exp_bar_cfg | 1)); - if (this->exp_rom_bar & 1) { - this->map_exp_rom_mem(); - } else { - LOG_F(WARNING, "%s: unmapping of expansion ROM not implemented yet", - this->pci_name.c_str()); - } - } + this->pci_wr_exp_rom_bar(value); break; case PCI_CFG_DWORD_15: this->irq_line = value >> 24; @@ -242,7 +231,7 @@ void PCIDevice::set_bar_value(int bar_num, uint32_t value) void PCIDevice::finish_config_bars() { - for (int bar_num = 0; bar_num < 6; bar_num++) { + for (int bar_num = 0; bar_num < this->num_bars; bar_num++) { uint32_t bar_cfg = this->bars_cfg[bar_num]; if (!bar_cfg) // skip unimplemented BARs @@ -262,7 +251,7 @@ void PCIDevice::finish_config_bars() bars_typ[bar_num] = PCIBarType::Mem_20_Bit; break; case 2: - if (bar_num >= 5) { + if (bar_num >= num_bars - 1) { ABORT_F("%s: BAR %d cannot be 64-bit", this->pci_name.c_str(), bar_num); } @@ -285,13 +274,45 @@ void PCIDevice::finish_config_bars() void PCIDevice::map_exp_rom_mem() { - uint32_t rom_addr, rom_size; - - rom_addr = this->exp_rom_bar & this->exp_bar_cfg; - rom_size = ~this->exp_bar_cfg + 1; - - if (!this->exp_rom_addr || this->exp_rom_addr != rom_addr) { + uint32_t rom_addr = this->exp_rom_bar & this->exp_bar_cfg; + if (rom_addr) { + if (this->exp_rom_addr != rom_addr) { + this->unmap_exp_rom_mem(); + uint32_t rom_size = ~this->exp_bar_cfg + 1; this->host_instance->pci_register_mmio_region(rom_addr, rom_size, this); this->exp_rom_addr = rom_addr; } +} + else { + this->unmap_exp_rom_mem(); + } +} + +void PCIDevice::unmap_exp_rom_mem() +{ + if (this->exp_rom_addr) { + uint32_t rom_size = ~this->exp_bar_cfg + 1; + this->host_instance->pci_unregister_mmio_region(exp_rom_addr, rom_size, this); + this->exp_rom_addr = 0; + } +} + +void PCIDevice::pci_wr_exp_rom_bar(uint32_t data) +{ + if (!this->exp_bar_cfg) { + return; + } + + if ((data & this->exp_bar_cfg) == this->exp_bar_cfg) { + // doing sizing + this->exp_rom_bar = (data & (this->exp_bar_cfg | 1)); + } else { + this->exp_rom_bar = (data & (this->exp_bar_cfg | 1)); + if (this->exp_rom_bar & 1) { + this->map_exp_rom_mem(); + } + else { + this->unmap_exp_rom_mem(); + } + } } diff --git a/devices/common/pci/pcidevice.h b/devices/common/pci/pcidevice.h index d1e48e9..bbfef5f 100644 --- a/devices/common/pci/pcidevice.h +++ b/devices/common/pci/pcidevice.h @@ -117,6 +117,9 @@ public: this->host_instance = host_instance; }; + virtual void set_multi_function(bool is_multi_function) { + this->hdr_type = is_multi_function ? (this->hdr_type | 0x80) : (this->hdr_type & 0x7f); + } // MMIODevice methods virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { return 0; } virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { } @@ -125,7 +128,9 @@ protected: void set_bar_value(int bar_num, uint32_t value); void setup_bars(std::vector cfg_data); void finish_config_bars(); + void pci_wr_exp_rom_bar(uint32_t data); void map_exp_rom_mem(); + void unmap_exp_rom_mem(); std::string pci_name; // human-readable device name PCIHost* host_instance; // host bridge instance to call back @@ -136,7 +141,7 @@ protected: uint32_t class_rev; // class code and revision id uint16_t status = 0; uint16_t command = 0; - uint8_t hdr_type = 0; // header type + uint8_t hdr_type = 0; // header type, single function uint8_t lat_timer = 0; // latency timer uint8_t cache_ln_sz = 0; // cache line size uint16_t subsys_id = 0; @@ -147,6 +152,7 @@ protected: uint8_t irq_pin = 0; uint8_t irq_line = 0; + int num_bars = 6; uint32_t bars[6] = { 0 }; // base address registers uint32_t bars_cfg[6] = { 0 }; // configuration values for base address registers PCIBarType bars_typ[6] = { PCIBarType::Unused }; // types for base address registers diff --git a/devices/common/pci/pcihost.cpp b/devices/common/pci/pcihost.cpp index d639a1f..d5e5796 100644 --- a/devices/common/pci/pcihost.cpp +++ b/devices/common/pci/pcihost.cpp @@ -20,28 +20,50 @@ along with this program. If not, see . */ #include -#include +#include #include #include #include +#include #include #include bool PCIHost::pci_register_device(int dev_fun_num, PCIDevice* dev_instance) { - // return false if dev_num already registered + // return false if dev_fun_num already registered if (this->dev_map.count(dev_fun_num)) return false; + int fun_num = dev_fun_num & 7; + int dev_num = (dev_fun_num >> 3) & 0x1f; + bool is_multi_function = fun_num != 0; + + for (int other_fun_num = 0; other_fun_num < 8; other_fun_num++) { + if (this->dev_map.count(DEV_FUN(dev_num, other_fun_num))) { + is_multi_function = true; + if (is_multi_function && other_fun_num == 0) { + this->dev_map[DEV_FUN(dev_num, other_fun_num)]->set_multi_function(true); + } + } + } + this->dev_map[dev_fun_num] = dev_instance; dev_instance->set_host(this); + if (is_multi_function && fun_num == 0) { + dev_instance->set_multi_function(true); + } if (dev_instance->supports_io_space()) { this->io_space_devs.push_back(dev_instance); } + PCIBridge *bridge = dynamic_cast(dev_instance); + if (bridge) { + this->bridge_devs.push_back(bridge); + } + return true; } @@ -84,7 +106,73 @@ void PCIHost::attach_pci_device(std::string& dev_name, int slot_id) slot_id, dynamic_cast(gMachineObj->get_comp_by_name(dev_name))); } +bool PCIHost::pci_io_read_loop(uint32_t offset, int size, uint32_t &res) +{ + for (auto& dev : this->io_space_devs) { + if (dev->pci_io_read(offset, size, &res)) { + return true; + } + } + return false; +} + +bool PCIHost::pci_io_write_loop(uint32_t offset, int size, uint32_t value) +{ + for (auto& dev : this->io_space_devs) { + if (dev->pci_io_write(offset, value, size)) { + return true; + } + } + return false; +} + +uint32_t PCIHost::pci_io_read_broadcast(uint32_t offset, int size) +{ + uint32_t res; + // broadcast I/O request to devices that support I/O space + // until a device returns true that means "request accepted" + if (pci_io_read_loop (offset, size, res)) { + return res; + } + HWComponent *hwc = dynamic_cast(this); + LOG_F( + ERROR, "%s: Attempt to read from unmapped PCI I/O space @%08x.%c", + hwc ? hwc->get_name().c_str() : "PCIHost", offset, + size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size + ); + // FIXME: add machine check exception (DEFAULT CATCH!, code=FFF00200) + return 0; +} + +void PCIHost::pci_io_write_broadcast(uint32_t offset, int size, uint32_t value) +{ + // broadcast I/O request to devices that support I/O space + // until a device returns true that means "request accepted" + if (pci_io_write_loop(offset, size, value)) { + return; + } + HWComponent *hwc = dynamic_cast(this); + LOG_F( + ERROR, "%s: Attempt to write to unmapped PCI I/O space @%08x.%c = %0*x", + hwc ? hwc->get_name().c_str() : "PCIHost", offset, + size == 4 ? 'l' : size == 2 ? 'w' : size == 1 ? 'b' : '0' + size, + size * 2, BYTESWAP_SIZED(value, size) + ); +} + PCIDevice *PCIHost::pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num) { + for (auto& bridge : this->bridge_devs) { + if (bridge->secondary_bus <= bus_num) { + if (bridge->secondary_bus == bus_num) { + if (bridge->dev_map.count(DEV_FUN(dev_num, fun_num))) { + return bridge->dev_map[DEV_FUN(dev_num, fun_num)]; + } + } + else if (bridge->subordinate_bus >= bus_num) { + return bridge->pci_find_device(bus_num, dev_num, fun_num); + } + } + } return NULL; } diff --git a/devices/common/pci/pcihost.h b/devices/common/pci/pcihost.h index 0566569..ca2756b 100644 --- a/devices/common/pci/pcihost.h +++ b/devices/common/pci/pcihost.h @@ -50,7 +50,8 @@ typedef struct AccessDetails { #define DEV_FUN(dev_num,fun_num) (((dev_num) << 3) | (fun_num)) -class PCIDevice; // forward declaration to prevent errors +class PCIDevice; +class PCIBridge; class PCIHost { public: @@ -67,11 +68,18 @@ public: virtual void attach_pci_device(std::string& dev_name, int slot_id); + virtual bool pci_io_read_loop (uint32_t offset, int size, uint32_t &res); + virtual bool pci_io_write_loop(uint32_t offset, int size, uint32_t value); + + virtual uint32_t pci_io_read_broadcast (uint32_t offset, int size); + virtual void pci_io_write_broadcast(uint32_t offset, int size, uint32_t value); + virtual PCIDevice *pci_find_device(uint8_t bus_num, uint8_t dev_num, uint8_t fun_num); protected: std::unordered_map dev_map; std::vector io_space_devs; + std::vector bridge_devs; }; // Helpers for data conversion in the PCI Configuration space. diff --git a/devices/memctrl/mpc106.cpp b/devices/memctrl/mpc106.cpp index 5e7ce0a..00abf28 100644 --- a/devices/memctrl/mpc106.cpp +++ b/devices/memctrl/mpc106.cpp @@ -75,105 +75,87 @@ int MPC106::device_postinit() } uint32_t MPC106::read(uint32_t rgn_start, uint32_t offset, int size) { - uint32_t result; - if (rgn_start == 0xFE000000) { - // broadcast I/O request to devices that support I/O space - // until a device returns true that means "request accepted" - for (auto& dev : this->io_space_devs) { - if (dev->pci_io_read(offset, size, &result)) { - return result; - } - } - LOG_F(ERROR, "Attempt to read from unmapped PCI I/O space, offset=0x%X", offset); - // FIXME: add machine check exception (DEFAULT CATCH!, code=FFF00200) - } else { - if (offset >= 0x200000) { - if (this->config_addr & 0x80) // process only if bit E (enable) is set - return pci_read(offset, size); - } else { + 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 + return pci_read(offset, size); } - return 0; } void MPC106::write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { if (rgn_start == 0xFE000000) { - // broadcast I/O request to devices that support I/O space - // until a device returns true that means "request accepted" - for (auto& dev : this->io_space_devs) { - if (dev->pci_io_write(offset, value, size)) { + pci_io_write_broadcast(offset, size, value); return; } - } - LOG_F(ERROR, "Attempt to write to unmapped PCI I/O space, offset=0x%X", offset); - } else { if (offset < 0x200000) { this->config_addr = value; - } else { - if (this->config_addr & 0x80) // process only if bit E (enable) is set + return; + } + if (this->config_addr & 0x80) { // process only if bit E (enable) is set return pci_write(offset, value, size); } } -} uint32_t MPC106::pci_read(uint32_t offset, uint32_t size) { - int bus_num = (this->config_addr >> 8) & 0xFF; - int dev_num = (this->config_addr >> 19) & 0x1F; - int fun_num = (this->config_addr >> 16) & 0x07; - int reg_offs = (this->config_addr >> 24) & 0xFC; - - if (bus_num) { - LOG_F(ERROR, "%s: read attempt from non-local PCI bus, %02x:%02x.%x @%02x", - this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3)); - return 0xFFFFFFFFUL; // PCI spec §6.1 + int bus_num, dev_num, fun_num; + uint8_t reg_offs; + AccessDetails details; + PCIDevice *device; + cfg_setup(offset, size, bus_num, dev_num, fun_num, reg_offs, details, device); + details.flags |= PCI_CONFIG_READ; + if (device) { + return pci_conv_rd_data(device->pci_cfg_read(reg_offs, details), details); } - - if (this->dev_map.count(DEV_FUN(dev_num,fun_num))) { - AccessDetails details; - details.offset = offset & 3; - details.size = size; - details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_READ; - uint32_t result = this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_read(reg_offs, details); - return pci_conv_rd_data(result, details); - } else { - LOG_F(ERROR, "%s: read attempt from non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3)); - } - + LOG_READ_NON_EXISTENT_PCI_DEVICE(); return 0xFFFFFFFFUL; // PCI spec §6.1 } void MPC106::pci_write(uint32_t offset, uint32_t value, uint32_t size) { - int bus_num = (this->config_addr >> 8) & 0xFF; - int dev_num = (this->config_addr >> 19) & 0x1F; - int fun_num = (this->config_addr >> 16) & 0x07; - int reg_offs = (this->config_addr >> 24) & 0xFC; - - if (bus_num) { - LOG_F(ERROR, "%s: write attempt to non-local PCI bus, %02x:%02x.%x @%02x", - this->name.c_str(), bus_num, dev_num, fun_num, reg_offs + (offset & 3)); + int bus_num, dev_num, fun_num; + uint8_t reg_offs; + AccessDetails details; + PCIDevice *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(); +} - if (this->dev_map.count(DEV_FUN(dev_num,fun_num))) { - AccessDetails details; - details.offset = offset & 3; - details.size = size; - details.flags = PCI_CONFIG_TYPE_0 | PCI_CONFIG_WRITE; +inline void MPC106::cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIDevice *&device) +{ + device = NULL; + details.size = size; + details.offset = offset & 3; - if (size == 4 && !details.offset) { // aligned DWORD writes -> fast path - this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_write(reg_offs, BYTESWAP_32(value), details); - } else { // otherwise perform necessary data transformations -> slow path - uint32_t old_val = this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_read(reg_offs, details); - uint32_t new_val = pci_conv_wr_data(old_val, value, details); - this->dev_map[DEV_FUN(dev_num,fun_num)]->pci_cfg_write(reg_offs, new_val, details); + 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; + if (this->dev_map.count(DEV_FUN(dev_num, fun_num))) { + device = this->dev_map[DEV_FUN(dev_num, fun_num)]; } - } else { - LOG_F(ERROR, "%s: write attempt to non-existing PCI device ??:%02x.%x @%02x", - this->name.c_str(), dev_num, fun_num, reg_offs + (offset & 3)); } } diff --git a/devices/memctrl/mpc106.h b/devices/memctrl/mpc106.h index bbfeb7e..c666721 100644 --- a/devices/memctrl/mpc106.h +++ b/devices/memctrl/mpc106.h @@ -99,6 +99,8 @@ protected: void setup_ram(void); private: + inline void cfg_setup(uint32_t offset, int size, int &bus_num, int &dev_num, int &fun_num, uint8_t ®_offs, AccessDetails &details, PCIDevice *&device); + uint32_t config_addr; uint16_t pmcr1 = 0; // power management config 1 From 99eb93f9e121d5a917034a7c96837d91a292069c Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 00:39:37 -0800 Subject: [PATCH 5/8] Add set_irq_pin method. A multi-function device may have functions of the same class (e.g. a USB device with two OHCI functions) but each function should be initialized with a different interrupt pin. --- devices/common/pci/pcidevice.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/devices/common/pci/pcidevice.h b/devices/common/pci/pcidevice.h index bbfef5f..6061e67 100644 --- a/devices/common/pci/pcidevice.h +++ b/devices/common/pci/pcidevice.h @@ -120,6 +120,10 @@ public: virtual void set_multi_function(bool is_multi_function) { this->hdr_type = is_multi_function ? (this->hdr_type | 0x80) : (this->hdr_type & 0x7f); } + virtual void set_irq_pin(uint8_t irq_pin) { + this->irq_pin = irq_pin; + } + // MMIODevice methods virtual uint32_t read(uint32_t rgn_start, uint32_t offset, int size) { return 0; } virtual void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size) { } From f61854a0aec4d390510cd6f9babc5207b9bb9e53 Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 01:03:26 -0800 Subject: [PATCH 6/8] Add has_io_space flag. PCIDevice - supports_io_space method now uses a flag has_io_space which is automatically set for PCI bridges or PCI devices that have an I/O BAR. atirage - Devices that have I/O BARs don't need a supports_io_space method. mpc106 - Devices that don't have I/O methods don't need a supports_io_space method. --- devices/common/pci/pcidevice.cpp | 1 + devices/common/pci/pcidevice.h | 3 ++- devices/memctrl/mpc106.h | 4 ---- devices/video/atirage.h | 4 ---- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/devices/common/pci/pcidevice.cpp b/devices/common/pci/pcidevice.cpp index 925a668..af9d0b9 100644 --- a/devices/common/pci/pcidevice.cpp +++ b/devices/common/pci/pcidevice.cpp @@ -240,6 +240,7 @@ void PCIDevice::finish_config_bars() if (bar_cfg & 1) { bars_typ[bar_num] = (bar_cfg & 0xffff0000) ? PCIBarType::Io_32_Bit : PCIBarType::Io_16_Bit; + has_io_space = true; } else { int pci_space_type = (bar_cfg >> 1) & 3; diff --git a/devices/common/pci/pcidevice.h b/devices/common/pci/pcidevice.h index 6061e67..44ea5e9 100644 --- a/devices/common/pci/pcidevice.h +++ b/devices/common/pci/pcidevice.h @@ -81,7 +81,7 @@ public: virtual ~PCIDevice() = default; virtual bool supports_io_space() { - return false; + return has_io_space; }; /* I/O space access methods */ @@ -156,6 +156,7 @@ protected: uint8_t irq_pin = 0; uint8_t irq_line = 0; + bool has_io_space = false; int num_bars = 6; uint32_t bars[6] = { 0 }; // base address registers uint32_t bars_cfg[6] = { 0 }; // configuration values for base address registers diff --git a/devices/memctrl/mpc106.h b/devices/memctrl/mpc106.h index c666721..077c30f 100644 --- a/devices/memctrl/mpc106.h +++ b/devices/memctrl/mpc106.h @@ -92,10 +92,6 @@ protected: uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details); void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details); - bool supports_io_space(void) { - return true; - }; - void setup_ram(void); private: diff --git a/devices/video/atirage.h b/devices/video/atirage.h index 62dac2e..7c7f9b8 100644 --- a/devices/video/atirage.h +++ b/devices/video/atirage.h @@ -60,10 +60,6 @@ public: void write(uint32_t rgn_start, uint32_t offset, uint32_t value, int size); /* PCI device methods */ - bool supports_io_space(void) { - return true; - }; - uint32_t pci_cfg_read(uint32_t reg_offs, AccessDetails &details); void pci_cfg_write(uint32_t reg_offs, uint32_t value, AccessDetails &details); From bee24b166d1ad45b575f1e995155e833a0302aca Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 01:12:50 -0800 Subject: [PATCH 7/8] Add method to attach PCI device with new name. - Added an overloaded version of attach_pci_device which takes a suffix string that can be used to make a device name unique so that multiple devices of the same class can be added to a machine. The method returns a PCIDevice which can be easily used to attach more PCI devices if it is a PCIHost. --- devices/common/pci/pcihost.cpp | 33 +++++++++++++++++++++++++-------- devices/common/pci/pcihost.h | 3 ++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/devices/common/pci/pcihost.cpp b/devices/common/pci/pcihost.cpp index d5e5796..295c2d2 100644 --- a/devices/common/pci/pcihost.cpp +++ b/devices/common/pci/pcihost.cpp @@ -83,27 +83,44 @@ bool PCIHost::pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCI return mem_ctrl->remove_mmio_region(start_addr, size, obj); } -void PCIHost::attach_pci_device(std::string& dev_name, int slot_id) +void PCIHost::attach_pci_device(const std::string& dev_name, int slot_id) +{ + this->attach_pci_device(dev_name, slot_id, ""); +} + +PCIDevice *PCIHost::attach_pci_device(const std::string& dev_name, int slot_id, const std::string& dev_suffix) { if (!DeviceRegistry::device_registered(dev_name)) { - LOG_F(WARNING, "PCIHost: specified PCI device %s doesn't exist", dev_name.c_str()); - return; + HWComponent *hwc = dynamic_cast(this); + LOG_F( + WARNING, "%s: specified PCI device %s doesn't exist", + hwc ? hwc->get_name().c_str() : "PCIHost", dev_name.c_str() + ); + return NULL; } // attempt to create device object auto dev_obj = DeviceRegistry::get_descriptor(dev_name).m_create_func(); if (!dev_obj->supports_type(HWCompType::PCI_DEV)) { - LOG_F(WARNING, "PCIHost: cannot attach non-PCI device %s", dev_name.c_str()); - return; + HWComponent *hwc = dynamic_cast(this); + LOG_F( + WARNING, "%s: cannot attach non-PCI device %s", + hwc ? hwc->get_name().c_str() : "PCIHost", dev_name.c_str() + ); + + return NULL; } // add device to the machine object - gMachineObj->add_device(dev_name, std::move(dev_obj)); + gMachineObj->add_device(dev_name + dev_suffix, std::move(dev_obj)); + + PCIDevice *dev = dynamic_cast(gMachineObj->get_comp_by_name(dev_name + dev_suffix)); // register device with the PCI host - this->pci_register_device( - slot_id, dynamic_cast(gMachineObj->get_comp_by_name(dev_name))); + this->pci_register_device(slot_id, dev); + + return dev; } bool PCIHost::pci_io_read_loop(uint32_t offset, int size, uint32_t &res) diff --git a/devices/common/pci/pcihost.h b/devices/common/pci/pcihost.h index ca2756b..1ad9ce2 100644 --- a/devices/common/pci/pcihost.h +++ b/devices/common/pci/pcihost.h @@ -66,7 +66,8 @@ public: virtual bool pci_register_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); virtual bool pci_unregister_mmio_region(uint32_t start_addr, uint32_t size, PCIDevice* obj); - virtual void attach_pci_device(std::string& dev_name, int slot_id); + virtual void attach_pci_device(const std::string& dev_name, int slot_id); + PCIDevice *attach_pci_device(const std::string& dev_name, int slot_id, const std::string& dev_suffix); virtual bool pci_io_read_loop (uint32_t offset, int size, uint32_t &res); virtual bool pci_io_write_loop(uint32_t offset, int size, uint32_t value); From 1d33ea2beb060f556695304c1a8cd3bb017fae1c Mon Sep 17 00:00:00 2001 From: joevt Date: Sun, 5 Feb 2023 01:17:26 -0800 Subject: [PATCH 8/8] Grackle: Update readme. --- zdocs/grackle.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zdocs/grackle.md b/zdocs/grackle.md index 5349e08..1feac17 100644 --- a/zdocs/grackle.md +++ b/zdocs/grackle.md @@ -27,7 +27,7 @@ It also spans for 0x7F000000 bytes starting from 0x80000000. The dingusppc CLI can add known PCI devices to a PCI slot (A1, B1, C1). -Only the following device numbers are probed by Open Firmware: +Only the following device numbers connected to grackle are probed by Open Firmware: - @0 used by pci [grackle] - @c slot PERCH interrupt 0x1c @@ -37,6 +37,8 @@ Only the following device numbers are probed by Open Firmware: - @10 used by mac-io [heathrow] which includes many devices with different interrupts - @12 slot F1 interrupt 0x16 used by AtiRageGT or AtiRagePro -With minor additions to source code, dingusppc can add a known PCI device to any device number between @1 and @1f except for @10 and @12. +With minor additions to source code, dingusppc can add a known PCI device to any device number between @1 and @1f except for @10 and @12. A nvramrc patch can make Open Firmware probe the other device numbers. An OS might be able to probe these other device numbers even if they are not probed by Open Firmware. -A nvramrc patch can make Open Firmware probe the other device numbers. An OS might be able to probe these other devices numbers even if they are not probed by Open Firmware. +A better approach may be to attach a PCI bridge to one of the supported slots. Then additional devices can be attached to the PCI bridge. For a PCI bridge, Open Firmware will probe device numbers between @0 and @f. Some nvramrc patches could maybe make Open Firmware probe the PCI bridge devices up to device number @1f. PCI bridges can be nested. In any case, each device number can have up to 8 functions numbered from 0 to 7. + +The dingusppc CLI doesn't yet support adding devices to PCI bridges or adding function numbers 1 to 7.