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.
This commit is contained in:
joevt 2022-10-25 23:06:12 -07:00
parent b472123746
commit be45a6a020
10 changed files with 566 additions and 180 deletions

View File

@ -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 &reg_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)];
}
}

View File

@ -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 &reg_offs, AccessDetails &details, PCIDevice *&device);
};
/*

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcibridge.h>
#include <memaccess.h>
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);
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#ifndef PCI_BRIDGE_H
#define PCI_BRIDGE_H
#include <devices/deviceregistry.h>
#include <devices/common/pci/pcidevice.h>
#include <devices/common/pci/pcihost.h>
#include <cinttypes>
#include <string>
#include <unordered_map>
#include <vector>
/** 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<uint8_t()> pci_rd_primary_bus;
std::function<void(uint8_t)> pci_wr_primary_bus;
std::function<uint8_t()> pci_rd_secondary_bus;
std::function<void(uint8_t)> pci_wr_secondary_bus;
std::function<uint8_t()> pci_rd_subordinate_bus;
std::function<void(uint8_t)> pci_wr_subordinate_bus;
std::function<uint8_t()> pci_rd_sec_latency_timer;
std::function<void(uint8_t)> pci_wr_sec_latency_timer;
std::function<uint16_t()> pci_rd_sec_status;
std::function<void(uint16_t)> pci_wr_sec_status;
std::function<uint8_t()> pci_rd_io_base;
std::function<void(uint8_t)> pci_wr_io_base;
std::function<uint8_t()> pci_rd_io_limit;
std::function<void(uint8_t)> pci_wr_io_limit;
std::function<uint16_t()> pci_rd_memory_base;
std::function<void(uint16_t)> pci_wr_memory_base;
std::function<uint16_t()> pci_rd_memory_limit;
std::function<void(uint16_t)> pci_wr_memory_limit;
std::function<uint16_t()> pci_rd_pref_mem_base;
std::function<void(uint16_t)> pci_wr_pref_mem_base;
std::function<uint16_t()> pci_rd_pref_mem_limit;
std::function<void(uint16_t)> pci_wr_pref_mem_limit;
std::function<uint32_t()> pci_rd_pref_base_upper32;
std::function<void(uint32_t)> pci_wr_pref_base_upper32;
std::function<uint32_t()> pci_rd_pref_limit_upper32;
std::function<void(uint32_t)> pci_wr_pref_limit_upper32;
std::function<uint16_t()> pci_rd_io_base_upper16;
std::function<void(uint16_t)> pci_wr_io_base_upper16;
std::function<uint16_t()> pci_rd_io_limit_upper16;
std::function<void(uint16_t)> pci_wr_io_limit_upper16;
std::function<uint16_t()> pci_rd_bridge_control;
std::function<void(uint16_t)> 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 */

View File

@ -20,7 +20,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/pci/pcidevice.h>
#include <devices/common/viacuda.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <memaccess.h>
@ -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();
}
}
}

View File

@ -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<BarConfig> 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

View File

@ -20,28 +20,50 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <devices/common/hwcomponent.h>
#include <devices/common/pci/pcidevice.h>
#include <devices/common/pci/pcibridge.h>
#include <devices/common/pci/pcihost.h>
#include <devices/memctrl/memctrlbase.h>
#include <machines/machinebase.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <cinttypes>
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<PCIBridge*>(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<PCIDevice*>(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<HWComponent*>(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<HWComponent*>(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;
}

View File

@ -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<int, PCIDevice*> dev_map;
std::vector<PCIDevice*> io_space_devs;
std::vector<PCIBridge*> bridge_devs;
};
// Helpers for data conversion in the PCI Configuration space.

View File

@ -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 &reg_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));
}
}

View File

@ -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 &reg_offs, AccessDetails &details, PCIDevice *&device);
uint32_t config_addr;
uint16_t pmcr1 = 0; // power management config 1