4. Linux内核PCIe子系统:pci_bus、pci_dev数据结构,内核PCIe驱动框架概述
PCIe子系统是Linux内核中负责管理PCI/PCIe设备的核心基础设施。理解其核心数据结构pci_bus与pci_dev,以及驱动框架的层次关系,是进行PCIe驱动开发和调试的前提。本章将深入剖析这两个关键结构体,并概述内核PCIe驱动的整体架构。
4.1 核心数据结构:pci_bus 与 pci_dev
内核使用两个核心结构体来抽象PCIe总线与设备:struct pci_bus 表示一条PCIe总线,struct pci_dev 表示一个PCIe设备(包括桥设备)。两者通过链表和指针形成树状拓扑。
4.1.1 struct pci_bus — 总线节点
pci_bus 结构体描述了一条PCIe总线的属性,包括其父总线、子总线、挂接的设备链表、总线号范围、桥设备指针等。其关键字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
parent |
struct pci_bus * |
指向父总线(上游总线) |
children |
struct list_head |
子总线链表头 |
devices |
struct list_head |
挂接在此总线上的设备链表头 |
number |
unsigned char |
本总线号(主号) |
primary |
unsigned char |
桥设备上游总线号 |
secondary |
unsigned char |
桥设备下游总线号(本总线号) |
subordinate |
unsigned char |
桥设备可达的最大子总线号 |
self |
struct pci_dev * |
指向代表此总线桥设备的pci_dev(根总线为NULL) |
resource |
struct resource * |
总线上的资源(桥窗口)数组 |
ops |
struct pci_ops * |
总线访问操作函数集(配置空间读写) |
调试要点: 在调试PCIe枚举问题时,常通过
/sys/devices/pci0000:00/ 下的目录结构观察总线拓扑,或使用 lspci -t 查看树形关系。内核中可通过 list_for_each_entry 遍历 pci_bus->devices 链表来枚举设备。
4.1.2 struct pci_dev — 设备节点
pci_dev 是内核中代表一个PCIe设备的核心结构体,包含了设备的配置空间信息、资源、驱动绑定状态、电源管理等全部属性。其关键字段分组如下:
| 分类 | 字段 | 说明 |
|---|---|---|
| 标识 | vendor / device |
厂商ID / 设备ID |
subsystem_vendor / subsystem_device |
子系统厂商ID / 子系统设备ID | |
class |
类别寄存器(基类+子类+编程接口) | |
devfn |
设备号(总线上的设备+功能号) | |
| 拓扑 | bus |
指向所在 pci_bus |
slot |
指向 pci_slot(物理槽位) |
|
dev |
内嵌的通用设备结构体(用于驱动模型) | |
| 资源 | resource[DEVICE_COUNT_RESOURCE] |
设备资源数组(BAR、ROM等) |
irq |
中断号(INTx或MSI/MSI-X) | |
msi_enabled / msix_enabled |
MSI/MSI-X使能标志 | |
| 驱动 | driver |
指向绑定的 pci_driver |
pci_driver |
驱动自定义数据指针 | |
| 电源 | current_state |
当前电源状态(D0-D3hot) |
关键关系: 每个
pci_dev 通过 dev->kobj 暴露在 /sys/bus/pci/devices/ 下。调试时可通过 cat /sys/bus/pci/devices/0000:01:00.0/config 读取原始配置空间。
4.1.3 总线-设备拓扑关系SVG
下图展示了 pci_bus 与 pci_dev 之间的树状拓扑关系,以及桥设备如何连接上下游总线:
4.2 内核PCIe驱动框架概述
Linux内核的PCIe驱动框架采用分层设计,从上到下依次为:设备驱动层、PCI核心层、总线访问层。这种分层结构使得驱动开发者只需关注设备特定的逻辑,而无需关心底层配置空间访问的细节。
4.2.1 框架层次结构
- 设备驱动层 (pci_driver):由开发者实现,通过
pci_register_driver()注册。核心回调包括probe()、remove()、suspend()、resume()。 - PCI核心层 (PCI Core):管理设备枚举、资源分配、电源管理、
sysfs接口、pci_dev生命周期。提供pci_read_config_*()/pci_write_config_*()等API。 - 总线访问层 (pci_ops):与硬件平台相关的配置空间访问方法,通过
pci_bus->ops调用。x86使用pci_conf1/pci_conf2或MMCONFIG;ARM64使用ECAM。
4.2.2 驱动注册与匹配流程
PCIe驱动的标准注册流程如下:
// 定义 pci_device_id 表(用于匹配设备)
static const struct pci_device_id my_pci_ids[] = {
{ PCI_DEVICE(0x10EE, 0x1234) }, // 厂商ID 0x10EE, 设备ID 0x1234
{ 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);
// probe 回调:设备匹配成功时调用
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
// 1. 使能设备
ret = pci_enable_device(pdev);
if (ret)
return ret;
// 2. 请求MMIO/I/O资源
ret = pci_request_regions(pdev, "my_driver");
if (ret)
goto err_disable;
// 3. 设置DMA掩码
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
goto err_release;
// 4. 读取BAR0地址并ioremap
resource_size_t bar0_start = pci_resource_start(pdev, 0);
resource_size_t bar0_len = pci_resource_len(pdev, 0);
void __iomem *regs = ioremap(bar0_start, bar0_len);
// ... 初始化硬件 ...
return 0;
err_release:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
// remove 回调:设备移除或驱动卸载时调用
static void my_pci_remove(struct pci_dev *pdev)
{
// ... 清理资源 ...
pci_release_regions(pdev);
pci_disable_device(pdev);
}
// 定义 pci_driver 结构体
static struct pci_driver my_pci_driver = {
.name = "my_pcie_driver",
.id_table = my_pci_ids,
.probe = my_pci_probe,
.remove = my_pci_remove,
};
// 模块入口/出口
static int __init my_init(void)
{
return pci_register_driver(&my_pci_driver);
}
static void __exit my_exit(void)
{
pci_unregister_driver(&my_pci_driver);
}
module_init(my_init);
module_exit(my_exit);
常见错误: 在
probe() 中忘记调用 pci_enable_device() 会导致后续配置空间读写失败;忘记调用 pci_request_regions() 会导致资源冲突。调试时可通过 /proc/iomem 检查BAR地址是否已被正确分配。
4.2.3 PCIe驱动框架架构SVG
下图展示了PCIe驱动框架的三层架构及数据流:
4.3 关键API与调试接口
下表总结了PCIe驱动开发中最常用的内核API及其用途:
| API | 所属层次 | 用途 |
|---|---|---|
pci_register_driver() |
PCI Core | 注册pci_driver到内核 |
pci_enable_device() |
PCI Core | 使能设备(设置Command寄存器) |
pci_request_regions() |
PCI Core | 请求设备的I/O和MMIO资源 |
pci_iomap() |
PCI Core | 将BAR映射到CPU虚拟地址空间 |
pci_read_config_dword() |
PCI Core | 读取32位配置空间寄存器 |
pci_write_config_dword() |
PCI Core | 写入32位配置空间寄存器 |
pci_set_master() |
PCI Core | 设置设备为总线主控(Bus Master) |
dma_set_mask() |
DMA API | 设置DMA地址掩码 |
调试技巧: 使用
lspci -vvv -s 01:00.0 查看设备完整配置空间;使用 setpci 工具直接读写配置空间寄存器进行验证。内核中可通过 pci_find_device() 或 pci_get_device() 按VID/DID查找设备。
4.4 本章小结
- pci_bus 是总线节点,通过
parent/children/devices链表形成树状拓扑,self指向桥设备。 - pci_dev 是设备节点,包含完整的配置空间信息、资源数组、中断和电源状态。
- PCIe驱动框架分为三层:设备驱动层(
pci_driver)、PCI核心层(枚举/资源/API)、总线访问层(pci_ops硬件相关)。 - 标准驱动流程:注册驱动 → 匹配设备 → probe()中使能设备、请求资源、ioremap → remove()中释放资源。
- 调试时善用
lspci、setpci、/proc/iomem、/sys/bus/pci/devices/等接口。