pci — PCI Bus and Device Framework
The pci sub-module implements the complete PCI subsystem for gem5: a configurable host bridge with CAM/ECAM configuration space, PCI bus crossbar, Base Address Register (BAR) management for I/O and memory mapping, MSI/MSI-X interrupt support, and the class hierarchy for endpoint and bridge devices.
Config Space BAR Mapping MSI/MSI-X Bus Topology
📚 What is this & why does it exist?
The PCI sub-module simulates the complete PCI bus protocol: host bridges, device enumeration, configuration space, BAR mapping, and MSI/MSI-X interrupts. It lets guest operating systems discover and configure simulated hardware devices exactly as they would on real silicon.
💡 Real-world analogy
Think of the PCI bus like a sophisticated USB hub. The PciHost is the hub controller sitting between the CPU and all peripheral devices. Each device has a unique address (bus:device:function) and a set of mailbox slots (BARs) where it receives commands. The hub translates between the CPU address space and each device local address space.
📚 What is PCI in gem5?
PCI (Peripheral Component Interconnect) is the standard bus for connecting high-speed devices like network cards, GPUs, and storage controllers to the CPU. In gem5, the pci module simulates the entire PCI discovery and configuration protocol so that a guest OS can enumerate devices, assign memory regions, configure interrupts, and communicate with simulated hardware — just like on a real machine.
💡 Analogy: PCI as a USB Hub
Think of the PCI bus like a sophisticated USB hub. The PciHost is the hub itself — it sits between the CPU (your computer) and all the peripheral devices (USB devices). Each device plugged into the hub has a unique address (bus:device:function) and a set of "mailbox slots" (BARs) where it receives commands. The hub translates between the CPU's physical address space and each device's local address space, just as a USB hub routes packets to the right device. When a device needs attention, it sends an "interrupt" — like a USB device signaling it has data ready.
📜 Table of Contents
Class Architecture
The PCI subsystem is built on a layered class hierarchy. PciDevice inherits from DmaDevice (which provides both PIO and DMA ports), and specializes into Type 0 endpoints and Type 1 bridges:
classDiagram
class ClockedObject {
+clockPeriod()
+curCycle()
}
class PioDevice {
#PioPort pioPort
+read(pkt)* Tick
+write(pkt)* Tick
+getAddrRanges()* AddrRangeList
}
class DmaDevice {
#DmaPort dmaPort
+dmaRead(addr, size, event, data)
+dmaWrite(addr, size, event, data)
+dmaPending() bool
}
class PciDevice {
#PCIConfig _config
#vector~PciBar*~ BARs
#PciUpstream::DeviceInterface upstreamInterface
+read(pkt) Tick
+write(pkt) Tick
+readConfig(pkt)* Tick
+writeConfig(pkt)* Tick
+readDevice(pkt)* Tick
+writeDevice(pkt)* Tick
+intrPost()
+intrClear()
+pciToDma(pci_addr) Addr
+getAddrRanges() AddrRangeList
}
class PciEndpoint {
#PCIConfigType0 config
+writeConfig(pkt) Tick
}
class PciType1Device {
#PCIConfigType1 config
+writeConfig(pkt) Tick
}
class PciBar {
#Addr _addr
#Addr _size
+write(interface, val)* uint32_t
+range() AddrRange
+isMem()* bool
+isIo()* bool
}
class PciBarNone {
+write() uint32_t
}
class PciIoBar {
+write() uint32_t
+isIo() bool
}
class PciMemBar {
-bool _wide
-uint64_t _lower
-uint64_t _upper
+write() uint32_t
+isMem() bool
+wide() bool
}
class PciMemUpperBar {
-PciMemBar* _lower
+write() uint32_t
}
class PciHost {
+getConfigAddrRange()
}
class GenericPciHost {
#Platform platform
#Addr confBase
#Addr pciPioBase
#Addr pciMemBase
+mapPciInterrupt()
}
class PciBus {
#PortID configErrorPortID
#AddrRange configRange
+findPort() PortID
}
ClockedObject <|-- PioDevice
PioDevice <|-- DmaDevice
DmaDevice <|-- PciDevice
PciDevice <|-- PciEndpoint
PciDevice <|-- PciType1Device
PciDevice o-- PciBar
PciBar <|-- PciBarNone
PciBar <|-- PciIoBar
PciBar <|-- PciMemBar
PciBar <|-- PciMemUpperBar
PciIoBar <|-- PciLegacyIoBar
PciHost <|-- GenericPciHost
GenericPciHost --> PciBus : manages
PciDevice --> PciHost : registers with
PCI Configuration Space
Every PCI device has a 256-byte configuration header. The first 64 bytes are standardized (Type 0 for endpoints, Type 1 for bridges). gem5 implements this via PCIConfig, PCIConfigType0, and PCIConfigType1 unions in pcireg.h.
Configuration Access Flow
sequenceDiagram
participant OS as Guest OS
participant HOST as GenericPciHost
participant BUS as PciBus
participant DEV as PciDevice
Note over OS,DEV: Config Read — OS enumerates device
OS->>HOST: MMIO read to ECAM region (confBase + BDF offset)
HOST->>HOST: Decode bus/device/function from address
HOST->>BUS: Forward to config address range
BUS->>DEV: Route to device's config port
DEV->>DEV: readConfig(pkt) — return from _config
DEV-->>BUS: Config data response
BUS-->>HOST: Forward response
HOST-->>OS: Return vendor ID, device ID, etc.
Note over OS,DEV: BAR Programming — OS assigns addresses
OS->>HOST: MMIO write to BAR register offset
HOST->>BUS: Forward write
BUS->>DEV: writeConfig(pkt)
DEV->>DEV: PciBar::write() — update address mapping
DEV-->>OS: BAR programmed, device responds at new address
BAR Types
| Class | Type | Description |
|---|---|---|
PciBarNone | Disabled | Placeholder BAR that ignores all writes and returns 0 |
PciIoBar | I/O Space | Maps device registers into PCI I/O address space (bit 0 = 1) |
PciLegacyIoBar | Legacy I/O | Fixed-address I/O BAR for legacy devices (e.g., COM ports at 0x3F8) |
PciMemBar | Memory Space | Maps device memory into physical address space; supports 32-bit and 64-bit |
PciMemUpperBar | Upper 32 bits | Companion to a 64-bit PciMemBar; holds the upper 32 bits of address |
PCI Host Bridge
GenericPciHost is the configurable root complex that bridges between the system memory bus and the PCI subsystem. It provides three key mappings:
| Space | Parameter | Description |
|---|---|---|
| Configuration | conf_base, conf_size, conf_device_bits | ECAM (12 bits/device) or CAM (8 bits/device) config space layout |
| PIO / Legacy I/O | pci_pio_base | Base offset added to PCI I/O addresses to get physical addresses |
| Memory | pci_mem_base | Base offset added to PCI memory addresses for physical mapping |
| DMA | pci_dma_base | Offset for translating PCI bus addresses to system DMA addresses |
Interrupt Routing
The host bridge maps PCI interrupt pins (INTA#–INTD#) to platform-level interrupt numbers. The default mapPciInterrupt() uses the device's interrupt line register, but subclasses can override this for platform-specific swizzling (e.g., AArch32, AArch64, TrustZone security extensions, SVE/SVE2 vector processing, PMU, and hardware transactional memory." href="../../arch_doc/arm/index.html">ARM GIC SPI mapping).
// GenericPciHost interrupt delivery
void GenericPciHost::interfacePostInt(const PciDevAddr &addr, PciIntPin pin)
{
platform.postPciInt(mapPciInterrupt(addr, pin));
}
void GenericPciHost::interfaceClearInt(const PciDevAddr &addr, PciIntPin pin)
{
platform.clearPciInt(mapPciInterrupt(addr, pin));
}
PCI Bus
PciBus extends NoncoherentXBar to implement PCI bus topology. It adds a special config error port that catches configuration accesses to non-existent devices (returning 0xFFFFFFFF, the standard PCI "no device" response).
// PciBus — a non-coherent crossbar with config error handling
class PciBus : public NoncoherentXBar
{
public:
PARAMS(PciBus);
PciBus(const Params &p);
Port &getPort(const std::string &if_name,
PortID idx = InvalidPortID) override;
protected:
// Override to handle config space routing
PortID findPort(AddrRange addr_range, PacketPtr pkt = nullptr) override;
PortID configErrorPortID;
AddrRange configRange;
};
Key Classes Reference
| Class | File | Inherits | Role |
|---|---|---|---|
PciDevice | pci/device.hh | DmaDevice | Base PCI device: config space, BARs, MSI/MSI-X, read/write dispatch |
PciEndpoint | pci/device.hh | PciDevice | PCI Type 0 device (endpoints: NICs, GPUs, etc.) |
PciType1Device | pci/device.hh | PciDevice | PCI Type 1 device (bridges extending bus hierarchy) |
PciBar | pci/device.hh | SimObject | Abstract BAR base: address, size, range query |
PciBarNone | pci/device.hh | PciBar | Disabled/unused BAR slot |
PciIoBar | pci/device.hh | PciBar | I/O space BAR with address translation via host |
PciLegacyIoBar | pci/device.hh | PciIoBar | Fixed-address legacy I/O BAR (ignores runtime writes) |
PciMemBar | pci/device.hh | PciBar | Memory-space BAR; supports 32/64-bit addressing |
PciMemUpperBar | pci/device.hh | PciBar | Upper 32 bits of a 64-bit memory BAR |
PciHost | pci/host.hh | PciUpstream | Abstract PCI host bridge base |
GenericPciHost | pci/host.hh | PciHost | Configurable host: ECAM/CAM, address mapping, interrupt routing |
PciBus | pci/bus.hh | NoncoherentXBar | PCI bus crossbar with config error handling |
PciUpstream | pci/upstream.hh | — | Interface between PCI devices and their upstream bus/bridge |
CopyEngine | pci/copy_engine.hh | PciEndpoint | Intel I/O AT DMA copy engine (PCI device example) |
PciUpDownBridge | pci/up_down_bridge.hh | — | Bridge connecting downstream PCI bus to upstream |
Device Lifecycle
Registration and Discovery
When a PciDevice is constructed, it receives a PciUpstream::DeviceInterface from the PCI host. This interface provides methods for address translation and interrupt delivery:
// PciUpstream::DeviceInterface methods used by PciDevice
Addr pioAddr(Addr pci_addr); // translate PCI I/O addr → physical
Addr memAddr(Addr pci_addr); // translate PCI mem addr → physical
Addr dmaAddr(Addr pci_addr); // translate PCI bus addr → DMA addr
void postInt(); // post interrupt via host bridge
void clearInt(); // clear interrupt via host bridge
Read/Write Dispatch
PciDevice::read() and write() are final — they dispatch to config space or device space based on the address:
flowchart TD
A["Incoming Packet"] --> B{Address in config range?}
B -->|Yes| C["readConfig() / writeConfig()"]
B -->|No| D{Address matches a BAR?}
D -->|Yes| E["readDevice() / writeDevice()"]
D -->|No| F["Error response"]
C --> G["Return config register data"]
E --> H["Device-specific logic"]
MSI and MSI-X Support
gem5's PCI implementation supports both MSI (Message Signaled Interrupts) and MSI-X. The capability structures are stored in the device's config space:
| Capability | Config Offset | Key Fields |
|---|---|---|
| Power Management (PMCAP) | PMCAP_BASE | Power state control and status |
| MSI | MSICAP_BASE | Message address, message data, control |
| MSI-X | MSIXCAP_BASE | Table offset/size, PBA offset, per-vector control |
| PCI Express (PXCAP) | PXCAP_BASE | PCIe capability register, device capabilities |
MSI-X Table Structure
// MSI-X table entry in PciDevice
struct MSIXTable {
uint32_t addr_lo; // Lower 32 bits of message address
uint32_t addr_hi; // Upper 32 bits of message address
uint32_t msg_data; // Message data value
uint32_t vec_ctrl; // Vector control (bit 0 = mask)
};
// MSI-X PBA (Pending Bit Array)
struct MSIXPbaEntry {
uint64_t bits; // 64 pending bits per entry
};
Copy Engine — A PCI Device Example
The CopyEngine in pci/copy_engine.hh is a concrete example of a PCI endpoint device. It models the Intel I/O Acceleration Technology (I/O AT) DMA copy engine, which the guest OS can use to offload memory copies:
// Simplified usage in gem5 Python config
from m5.objects import CopyEngine, PciMemBar
copy_engine = CopyEngine(
pci_bus=0, pci_dev=4, pci_func=0,
InterruptLine=0x20,
BAR0=PciMemBar(size='4KiB') # Control registers
)
system.pci_host.attach(copy_engine)
Configuration Reference
| Parameter | Python Class | Default | Description |
|---|---|---|---|
pci_bus | PciDevice | 0 | PCI bus number |
pci_dev | PciDevice | 0 | PCI device number (0–31) |
pci_func | PciDevice | 0 | PCI function number (0–7) |
VendorID | PciDevice | Device-specific | PCI Vendor ID (16-bit) |
DeviceID | PciDevice | Device-specific | PCI Device ID (16-bit) |
InterruptLine | PciDevice | 0x1f | Interrupt line for legacy INTx routing |
InterruptPin | PciDevice | 0x01 | Interrupt pin (1=INTA, 2=INTB, 3=INTC, 4=INTD) |
BAR0–BAR5 | PciDevice | PciBarNone | BAR objects defining device address regions |
conf_base | GenericPciHost | 0x30000000 | Physical base address of PCI config space |
conf_size | GenericPciHost | 256MB | Size of configuration space region |
conf_device_bits | GenericPciHost | 12 | Bits per device: 12=ECAM, 8=CAM |
pci_pio_base | GenericPciHost | 0x0 | Base offset for PCI I/O space mapping |
pci_mem_base | GenericPciHost | 0x0 | Base offset for PCI memory space mapping |
pci_dma_base | GenericPciHost | 0x0 | Base offset for DMA address translation |
File Map
pci/ directory structure
src/dev/pci/
├── device.hh / device.cc ← PciDevice, PciEndpoint, PciType1Device, all PciBar variants
├── host.hh / host.cc ← PciHost, GenericPciHost (root complex)
├── bus.hh / bus.cc ← PciBus (NoncoherentXBar with config routing)
├── upstream.hh / upstream.cc ← PciUpstream interface and DeviceInterface
├── up_down_bridge.hh/cc ← PciUpDownBridge (bus-to-bus bridge)
├── types.hh ← PciDevAddr, PciBusNum, PciIntPin types
├── pcireg.h ← PCI config register layouts (PCIConfigType0/1)
├── copy_engine.hh / copy_engine.cc ← Intel I/O AT copy engine device
├── copy_engine_defs.hh ← Copy engine register definitions
├── PciDevice.py ← Python config for PciDevice, PciBar, PciEndpoint
├── PciHost.py ← Python config for PciHost, GenericPciHost
├── CopyEngine.py ← Python config for CopyEngine
├── PciUpstream.py ← Python config for PciUpstream
└── SConscript ← Build definitions
Creating a New PCI Device
To implement a custom PCI endpoint device in gem5, inherit from PciEndpoint and override readDevice() and writeDevice():
// my_pci_device.hh
#include "dev/pci/device.hh"
#include "params/MyPciDevice.hh"
namespace gem5 {
class MyPciDevice : public PciEndpoint
{
public:
PARAMS(MyPciDevice);
MyPciDevice(const Params &p);
protected:
// Handle reads to BAR-mapped addresses
Tick readDevice(PacketPtr pkt) override
{
int bar_num;
Addr offset;
getBAR(pkt->getAddr(), bar_num, offset);
// Decode register at offset within BAR
switch (offset) {
case 0x00: // STATUS register
pkt->setLE<uint32_t>(statusReg);
break;
case 0x04: // DATA register
pkt->setLE<uint32_t>(dataReg);
break;
default:
pkt->setLE<uint32_t>(0);
}
pkt->makeAtomicResponse();
return pioDelay;
}
// Handle writes to BAR-mapped addresses
Tick writeDevice(PacketPtr pkt) override
{
int bar_num;
Addr offset;
getBAR(pkt->getAddr(), bar_num, offset);
uint32_t val = pkt->getLE<uint32_t>();
switch (offset) {
case 0x08: // COMMAND register
processCommand(val);
break;
}
pkt->makeAtomicResponse();
return pioDelay;
}
private:
uint32_t statusReg = 0;
uint32_t dataReg = 0;
void processCommand(uint32_t cmd);
};
} // namespace gem5
Glossary
| Term | Definition |
|---|---|
| BAR | Base Address Register — maps device memory/IO into CPU physical address space |
| BDF | Bus:Device:Function — unique address identifying a PCI device |
| CAM | Configuration Access Mechanism — legacy PCI config space (256 bytes/device, 8 bits/device) |
| ECAM | Enhanced Configuration Access Mechanism — PCIe config space (4KB/device, 12 bits/device) |
| MSI | Message Signaled Interrupt — interrupt delivered as a memory write to a specific address |
| MSI-X | Extended MSI — supports per-vector masking and more interrupt vectors via table |
| Type 0 | PCI endpoint device config header layout (PciEndpoint) |
| Type 1 | PCI-to-PCI bridge config header layout (PciType1Device) |
| INTx | Legacy PCI interrupt pins (INTA#, INTB#, INTC#, INTD#) |