Host Bridge、枚举与 PCI 设备生命周期

Host Bridge、枚举与 PCI 设备生命周期

1. 三类核心对象

  • struct pci_host_bridge:一个 Root Complex 对 PCI Core 的入口,提供配置访问ops、总线号范围、CPU/PCI 地址窗口和固件节点。
  • struct pci_bus:一段 PCI 总线,保存numberprimary/secondary/subordinate、上游桥、设备及子总线链表。
  • struct pci_dev:一个 Function,而不是一张物理卡;0000:01:00.1会对应独立对象。

父子关系是:

1
2
3
4
5
6
pci_host_bridge
-> root pci_bus
-> pci_dev(endpoint)
-> pci_dev(bridge)
-> subordinate pci_bus
-> pci_dev(...)

2. Host 注册主链

平台控制器准备好配置空间访问和地址窗口后调用:

1
2
3
4
5
6
7
8
pci_host_probe()
-> pci_scan_root_bus_bridge()
-> pci_register_host_bridge()
-> pci_scan_child_bus()
-> claim firmware resources
或 size bridges + assign resources
-> pcie_bus_configure_settings()
-> pci_bus_add_devices()

关键入口在probe.c

  • pci_host_probe():3096 行;
  • pci_scan_root_bus_bridge():3190 行;
  • pci_scan_slot():2701 行。

Host Bridge 注册与设备扫描是两个阶段。前者把 Root Bus 纳入设备模型,后者才通过配置读发现 Function。

3. 单个 Function 如何被发现

扫描本质是配置空间访问,不是广播“请设备上报”:

1
2
3
4
5
6
7
8
pci_scan_child_bus()
-> pci_scan_slot(bus, devfn)
-> pci_scan_single_device()
-> pci_scan_device()
-> pci_bus_read_dev_vendor_id()
-> pci_alloc_dev()
-> pci_setup_device()
-> pci_device_add()

第一次关键读取是偏移PCI_VENDOR_ID的 32 位值。无设备通常返回0xffffffff;CRS 响应则按超时策略重试。随后读取 Header Type、Class、BAR、Subsystem ID 和 Capability。

access.c中的pci_bus_read_config_*()最终调用bus->ops->read()。因此 PCI Core 决定“读哪个 BDF/偏移”,RK3588 DWC Host 决定“如何把它变成 CFG0/CFG1 事务”。

4. 桥递归

发现 PCI-to-PCI Bridge 后,pci_scan_bridge()处理:

  1. 读取或分配 secondary/subordinate bus number;
  2. 创建 subordinate pci_bus
  3. 递归pci_scan_child_bus()
  4. 回填 subordinate 范围;
  5. 后续资源阶段为桥配置 I/O、MEM、Prefetchable MEM 窗口。

PCIe Root Port、Downstream Port 在 Linux 枚举层仍按桥处理。典型点到点 PCIe 链路只扫描 Device 0,但 quirk、ARI 或扫描标志可改变策略。

5. 加入设备模型与驱动绑定

pci_device_add()先将对象加入 PCI 拓扑;pci_bus_add_devices()再递归调用pci_bus_add_device()。设备进入 Linux Driver Core 后:

1
2
3
4
5
device_attach()
-> pci_bus_match()
-> pci_device_probe()
-> local_pci_probe()
-> pci_driver->probe()

__pci_register_driver()位于pci-driver.c:1447,把drv->driver.bus设为pci_bus_type并调用driver_register()。匹配依据静态pci_device_id、动态 ID 和driver_override,不是 DT compatible。

6. 引用、锁与并发

  • pci_dev_get()/pci_dev_put()保护pci_dev引用;
  • pci_bus_sem保护全局和总线设备链表的读写;
  • pci_lock_rescan_remove()串行化 rescan 与 remove;
  • 配置空间访问通常由pci_lock串行化;
  • 遍历期间若跨越可能睡眠或设备删除的操作,必须持有引用,不能只保存裸指针。

search.c中的pci_get_device()pci_get_slot()搜索的是已注册对象,并返回带引用的pci_dev。它们不会访问硬件。

7. 移除路径

remove.c的公开入口:

  • pci_stop_and_remove_bus_device():115 行;
  • 带全局 rescan/remove 锁版本:122 行。

逻辑分为 stop 和 remove:

1
2
3
4
5
6
7
8
pci_stop_bus_device()
-> 先递归停止下游
-> device_release_driver()

pci_remove_bus_device()
-> 先递归删除下游
-> sysfs/IRQ/resources/proc 清理
-> device_unregister()

必须先停止 DMA 和功能驱动,再拆资源与对象。物理拔卡、sysfs remove及错误恢复失败最终都可能进入该路径。

8. Rescan

rescan 在持pci_lock_rescan_remove()时重新扫描槽位或总线,并调用pci_bus_add_devices()。它只能发现“链路和供电已经可访问”的设备,不能替代控制器上电、PERST#释放或链路训练。

9. 生命周期不变量

  1. 配置访问可用后才能扫描;
  2. 桥下游对象不得晚于上游桥停止;
  3. BAR 与 IRQ 必须在设备注销前释放;
  4. pci_driver->remove()返回后驱动不能继续 DMA;
  5. sysfs rescan/remove 与热插拔必须使用同一全局锁避免重复对象和 use-after-free。

文章互动

阅读 --

留言

0 条留言

正在加载留言…