PCI Subsystem¶
Responsibility¶
- Discover PCI devices on the system bus.
- Read configuration space (IDs, class codes, BARs, interrupt lines).
- Enable features like bus mastering on selected devices.
- Instantiate and register appropriate drivers (e.g., AMD am79c973 NIC) with
DriverManager.
Components¶
class PeripheralComponentInterconnectControllerstruct PeripheralComponentInterconnectDeviceDescriptorstruct BaseAddressRegister- Uses:
Port32Bit(viacommandPortat 0xCF8 anddataPortat 0xCFC).DriverManagerandDriverfor driver registration.InterruptManagerfor IRQ integration.- Specific device driver
amd_am79c973.
PCI Device Descriptor¶
PeripheralComponentInterconnectDeviceDescriptor¶
Represents key configuration data for a single PCI function:
- Fields (from implementation usage):
uint16_t bus;uint16_t device;uint16_t function;uint16_t vendor_id;uint16_t device_id;uint8_t class_id;uint8_t subclass_id;uint8_t interface_id;uint8_t revision;uint8_t interrupt;uint32_t portBase;(I/O base, set via BAR parsing when applicable).- Constructors/destructor are trivial (no special logic).
PCI Configuration Access¶
Ports and controller construction¶
PeripheralComponentInterconnectController::PeripheralComponentInterconnectController()
: dataPort(0xCFC), commandPort(0xCF8) {}
PeripheralComponentInterconnectController::~PeripheralComponentInterconnectController() {}
- Uses standard PCI configuration mechanism:
- Command port:
0xCF8 - Data port:
0xCFC
Read / Write API¶
uint32_t PeripheralComponentInterconnectController::Read(
uint16_t bus, uint16_t device, uint16_t function, uint32_t registerOffset);
void PeripheralComponentInterconnectController::Write(
uint16_t bus, uint16_t device, uint16_t function, uint32_t registerOffset, uint32_t value);
- Address construction (configuration address):
uint32_t id =
0x1 << 31
| ((bus & 0xFF) << 16)
| ((device & 0x1F) << 11)
| ((function & 0x07) << 8)
| (registerOffset & 0xFC);
- Read:
- Writes
idtocommandPort. - Reads 32 bits from
dataPort. - Returns the requested byte shifted out of the 32‑bit result:
result >> (8 * (registerOffset % 4)).
- Write:
- Writes
idtocommandPort. - Writes
valuetodataPort(full 32 bits).
Multi-function detection¶
bool PeripheralComponentInterconnectController::DeviceHasFunctions(uint16_t bus, uint16_t device) {
return Read(bus, device, 0, 0x0E) & (1 << 7);
}
- Checks bit 7 of the header type register (offset 0x0E).
- If set: device is multi-function (up to 8 functions).
- Else: assume a single function.
Device Enumeration and Driver Selection¶
SelectDrivers¶
void PeripheralComponentInterconnectController::SelectDrivers(
DriverManager* driverManager, InterruptManager* interrupts);
- Enumerates a subset of the PCI bus:
busin[0, 8)devicein[0, 32)functioncount:- 8 if
DeviceHasFunctions(bus, device)is true. - 1 otherwise.
- 8 if
- For each function:
- Obtain
PeripheralComponentInterconnectDeviceDescriptor dev = GetDeviceDescriptor(bus, device, function); - Skip if
dev.vendor_idis0x0000or0xFFFF(no device / invalid). - For each BAR index
barNumin[0, 6):BaseAddressRegister bar = GetBaseAddressRegister(bus, device, function, barNum);- If
bar.addressis non‑null andbar.type == InputOutput, set: dev.portBase = (uint32_t)bar.address;
-
Ask for a driver:
Driver* driver = GetDriver(dev, interrupts);- If non‑null, call
driverManager->AddDriver(driver);
-
Invariants:
- Only I/O BARs are used to populate
dev.portBase. - Device detection ignores vendor ID
0x0000/0xFFFF.
Debug printing¶
- Same enumeration as
SelectDrivers, but instead of instantiating drivers, prints:
printf("PCI BUS %04x, DEVICE %04x, VENDOR %04x\n",
(uint32_t)bus,
(uint32_t)dev.device_id,
(uint32_t)dev.vendor_id);
- Can be used as the kernel’s equivalent of
lspcito see available devices.
Base Address Registers (BARs)¶
GetBaseAddressRegister¶
BaseAddressRegister PeripheralComponentInterconnectController::GetBaseAddressRegister(
uint16_t bus, uint16_t device, uint16_t function, uint16_t bar);
- Determines how a function maps its resources (I/O vs memory) via BARs.
Process:
- Determine header type:
uint32_t headertype = Read(bus, device, function, 0x0E) & 0x7F;
int maxBARs = 6 - (4 * headertype);
if (bar >= maxBARs) return result; // returns default (address == null)
-
For non‑standard header types,
maxBARsis reduced accordingly. -
Read BAR value:
uint32_t bar_value = Read(bus, device, function, 0x10 + 4 * bar);
result.type = (bar_value & 0x1) ? InputOutput : MemoryMapping;
- If
InputOutput: result.address = (uint8_t*)(bar_value & ~0x3);- Mask off the low two bits (flags) to get the I/O base.
result.prefetchable = false;- If
MemoryMapping: - For now, only checks the address type:
-
No further memory mapping setup is performed here; memory‑mapped BARs are not fully handled yet.
-
Invariants:
- If
barindex is out of range for current header type, the returnedBaseAddressRegisterhas a null/zero address. - I/O BARs are interpreted as port ranges; memory BARs currently have no fully implemented mapping logic.
Device Descriptor Retrieval¶
GetDeviceDescriptor¶
PeripheralComponentInterconnectDeviceDescriptor
PeripheralComponentInterconnectController::GetDeviceDescriptor(
uint16_t bus, uint16_t device, uint16_t function);
- Populates a descriptor as follows:
result.bus = bus;
result.device = device;
result.function = function;
result.vendor_id = Read(bus, device, function, 0x00);
result.device_id = Read(bus, device, function, 0x02);
result.class_id = Read(bus, device, function, 0x0b);
result.subclass_id = Read(bus, device, function, 0x0a);
result.interface_id = Read(bus, device, function, 0x09);
result.revision = Read(bus, device, function, 0x08);
result.interrupt = Read(bus, device, function, 0x3c);
- This captures identity (vendor/device), type (class/subclass/interface), revision, and interrupt line.
Driver Instantiation¶
GetDriver¶
Driver* PeripheralComponentInterconnectController::GetDriver(
PeripheralComponentInterconnectDeviceDescriptor dev,
InterruptManager* interrupts);
- Chooses and constructs a driver based on
vendor_id,device_id, and class.
Current logic:
- AMD devices:
case 0x1022: // AMD
switch (dev.device_id) {
case 0x2000: // am79c973 NIC
EnableBusMastering(&dev);
driver = new amd_am79c973(&dev, interrupts);
if (driver != 0) {
new (driver) amd_am79c973(&dev, interrupts); // placement new (currently redundant)
}
return driver;
}
- Intel devices (placeholder):
- Class‑based handling (example):
switch (dev.class_id) {
case 0x03: // graphics
switch (dev.subclass_id) {
case 0x00: // VGA graphics
// TODO: VGA driver integration
break;
}
break;
}
- Returns:
- Pointer to a constructed
Driversubclass if recognized. 0if no driver is available for this device.
Invariants:
- Any driver created here is expected to be registered with
DriverManagerand activated later. EnableBusMasteringis invoked for the AMD NIC before driver construction.
Enabling Bus Mastering¶
By (bus, device, function)¶
void PeripheralComponentInterconnectController::EnableBusMastering(
uint16_t bus, uint16_t device, uint16_t function);
- Reads the PCI command register at offset
0x04:
uint32_t command = Read(bus, device, function, 0x04);
if (!(command & 0x4)) {
Write(bus, device, function, 0x04, command | 0x4);
}
- Sets bit 2 if not already set:
- Enables bus mastering (DMA), allowing the device to initiate memory transactions.
By device descriptor¶
void PeripheralComponentInterconnectController::EnableBusMastering(
PeripheralComponentInterconnectDeviceDescriptor* dev);
- Convenience wrapper that calls the above using
dev->bus,dev->device, anddev->function.
Invariants:
- Bus mastering must be enabled for devices like NICs that rely on DMA.
- Must only be called after configuration space is accessible and before the driver begins heavy use.
Invariants and Assumptions¶
- Only buses
0–7are scanned; systems with devices on higher bus numbers are not yet fully supported. - Devices with
vendor_id == 0x0000or0xFFFFare treated as non‑existent or invalid. - BAR parsing:
- I/O BARs are correctly parsed and used to set
dev.portBase. - Memory BARs are partially recognized but not fully mapped/used.
GetDrivercontains explicit knowledge of certain vendor/device IDs (e.g., AMD am79c973) and can be extended with more device support over time.- Bus mastering is enabled only when needed; other command register bits (I/O space, memory space, etc.) are not modified here.
Open Questions / TODO¶
- Extend bus enumeration beyond bus 7 if needed for more complex hardware setups.
- Fully implement handling for memory‑mapped BARs:
- Map device memory into the kernel’s virtual address space.
- Document alignment and caching behavior.
- Replace hard‑coded vendor/device IDs with a table‑driven registration mechanism for cleaner extensibility.
- Integrate
PrintPCIDrivers()output with the CLI (e.g.,lspci‑like command) for runtime inspection. - Clarify how the
interruptfield fromGetDeviceDescriptor()ties intoInterruptManagerand driver IRQ registration (document expected IRQ wiring per device). ```