RT-Thread RTOS
An open source embedded real-time operating system
PCI enumeration and drivers

Enumeration and device setup (<tt>probe.c</tt>, <tt>pci.c</tt>)

End-to-end host bring-up → bus scan → BAR assignment → driver probe. Sources: **probe.c** (scan/host), **pci.c** (regions, register, bus), **ofw.c** (DT windows).


Full initialization flow

Phase A — Host controller (platform driver)

Typical ECAM host (**pci_host_common_probe** in host/pci-host-common.c):

platform probe (e.g. pci-host-ecam-generic)
|
v
rt_pci_host_bridge_alloc()
|
v
rt_dm_dev_iomap(dev, 0) /* ECAM config window */
|
v
rt_pci_host_bridge_init()
+-- rt_pci_ofw_host_bridge_init() [RT_USING_OFW]
| bus-range, pci-domain
| rt_pci_ofw_parse_ranges() -> bus_regions[], dma_regions[]
| rt_pci_region_setup() /* allocator pools ready */
| irq_slot = rt_pci_irq_slot
| irq_map = rt_pci_ofw_irq_parse_and_map
|
v
pci_ecam_create(host, ops) /* host_bridge->ops = ECAM read/write */
conf_win->win = ECAM base
|
v
rt_pci_host_bridge_probe() /* starts enumeration — Phase B */

DesignWare and other hosts follow the same pattern after link/DBI setup (PCI host controllers).

Phase B — Root bus and enumeration

**rt_pci_host_bridge_probe** → **rt_pci_scan_root_bus_bridge**:

rt_pci_host_bridge_register(host)
| pci_alloc_bus(NULL) -> root_bus
| root_bus->number = bus_range[0]
| root_bus->ops = host_bridge->ops
|
v
rt_pci_scan_child_bus(root_bus)
|
v
rt_pci_scan_child_buses(bus, buses=0)
|
+-- for slot 0..30 (step 8 devfn):
| rt_pci_scan_slot(bus, devfn)
| for func 0..7 (ARI / multifunction):
| rt_pci_scan_single_device() /* Phase C */
|
+-- for each bridge on bus (rt_pci_foreach_bridge):
pci_scan_bridge_extend()
pcie_fixup_link() [if PCIe cap]
pci_alloc_bus(parent) -> child bus
pci_child_bus_init(bus_no, bridge pdev)
program PCIR_PRIBUS_1 (primary/secondary/subordinate)
rt_pci_scan_child_buses(child, ...) /* recurse */
update subordinate bus number

Depth-first: all devices on bus N are scanned, then each bridge opens bus N+1 and recurses.

Phase C — Single function (<tt>rt_pci_scan_single_device</tt>)

read config PCIR_VENDOR / PCIR_DEVICE
| (0xffff / 0x0000 -> empty slot, skip)
v
rt_pci_alloc_device(bus) /* link into bus->devices_nodes */
|
v
rt_pci_setup_device(pdev) /* Phase D — must succeed */
|
v
pci_procfs_attach(pdev) [optional debug]
|
v
rt_pci_device_register(pdev)
-> rt_bus_add_device(pci_bus, &pdev->parent)
-> pci bus match + pci_probe() /* Phase E */

If **rt_pci_setup_device** fails, the **rt_pci_device** is freed and the slot is skipped.

Phase D — Per-device setup (<tt>rt_pci_setup_device</tt>)

Order inside **probe.c** / **pci.c**:

Step Action
1 **rt_pci_ofw_device_init** — bind OFW node if any
2 Read class, hdr_type, clear STATUS errors
3 **pci_read_irq** — read INTPIN / INTLINE from config only (not mapped yet)
4 **rt_pci_device_alloc_resource** — size BARs, **rt_pci_region_alloc**, write BAR registers, enable bridge windows
5 **pci_init_capabilities** — **rt_pci_pme_init**, **rt_pci_msi_init/msix_init** (disabled), PCIe cap, cfg_size, ARI

Device name set to **domain:bus:slot.func**.

Phase E — Bus driver probe (<tt>pci.c</tt>)

When **rt_pci_device_register** adds the device to the PCI bus:

pci_match(pdrv, pdev) /* vendor/device or class table */
|
v
pci_probe(dev)
+-- rt_pci_assign_irq(pdev) /* host irq_map or OFW INTx */
+-- rt_pci_enable_wake(pdev, D0)
+-- pdrv->probe(pdev) /* NVMe, NIC, ... */

Important: function driver **probe** runs after BARs are assigned and after **rt_pci_assign_irq**. Use **rt_pci_iomap** / **rt_pci_alloc_vector** here, not during scan.

IRQ timing summary

When What
**rt_pci_setup_device** **pci_read_irq** — raw config pin/line
**pci_probe (bus)** **rt_pci_assign_irq** — **irq_map** / OFW → **pdev->irq**, **intx_pic**
Function **pdrv->probe** MSI/MSI-X enable, **rt_pic_*** register

Flow diagram

flowchart TD
A[Platform host probe] --> B[host_bridge_alloc + iomap ECAM]
B --> C[host_bridge_init / OFW ranges]
C --> D[pci_ecam_create]
D --> E[host_bridge_probe]
E --> F[host_bridge_register root bus]
F --> G[scan_child_bus DFS]
G --> H[scan_slot / scan_single_device]
H --> I[setup_device BAR + caps]
I --> J[device_register]
J --> K[bus pci_probe assign_irq]
K --> L[function driver probe]
G --> M[scan bridge -> child bus]
M --> G

Host bridge lifecycle (API)

API Role
**rt_pci_host_bridge_alloc(priv_size)** Allocate host bridge + private tail
**rt_pci_host_bridge_init** OFW: **rt_pci_ofw_host_bridge_init**
**rt_pci_host_bridge_probe** **rt_pci_scan_root_bus_bridge**
**rt_pci_host_bridge_register** Create root **rt_pci_bus**
**rt_pci_host_bridge_remove** Enum remove all devices

Scan API

API Role
**rt_pci_scan_child_bus(bus)** Entry: scan one bus + bridges
**rt_pci_scan_child_buses** Slots 0–30, then bridge extend
**rt_pci_scan_slot** All functions on one devfn (MF/ARI)
**rt_pci_scan_single_device** One function if vendor valid
**rt_pci_scan_bridge** Bridge secondary bus setup
**rt_pci_alloc_device** Allocate **rt_pci_device**, link to bus

Bridges: **pdev->subbus** after **pci_scan_bridge_extend**. Endpoints: **subbus == NULL**.


<tt>rt_pci_setup_device</tt> detail

See Phase D above. Invalid header/class → error, device discarded.


Resources (<tt>pci.c</tt>)

API Role
**rt_pci_region_setup** After OFW **ranges**: log pools, **bus_start ≥ 0x1000**
**rt_pci_region_alloc** Carve BAR/bus address from **bus_regions**
**rt_pci_device_alloc_resource** Probe BAR sizes, assign, write config
**rt_pci_find_bar** Lookup assigned BAR by flags/index

Driver model

static const struct rt_pci_device_id my_ids[] = {
{ RT_PCI_DEVICE_ID(0x1234, 0xabcd) },
{ RT_PCI_DEVICE_CLASS(0x01080200, 0xffffff00) },
{ /* sentinel */ },
};
static struct rt_pci_driver my_driver = {
.name = "my_pci",
.ids = my_ids,
.probe = my_probe,
.remove = my_remove,
};
RT_PCI_DRIVER_EXPORT(my_driver);
API Role
**rt_pci_match_ids** Match during **pci_match**
**rt_pci_driver_register** Register on PCI bus (export macro at init)
**rt_pci_device_register** After scan — triggers bus **probe**
**rt_pci_enum_device** Walk tree callback

Helpers

API Role
**rt_pci_domain**, **rt_pci_find_host_bridge** Domain / host lookup
**rt_pci_is_bridge**, **rt_pci_is_pcie** Type checks
**rt_pci_find_capability** Capability walk

Pitfalls

Issue Mitigation
MMIO in scan before **device_register** Wait for function **probe**
BARs empty Fix DT **ranges** + **rt_pci_region_setup**
**irq == 0** after probe Check **interrupt-map** / use MSI
Bridge bus exhausted **bus-range** too small in DTS
Scan order vs driver **RT_PCI_DRIVER_EXPORT** drivers already registered before host probe

See also