4. Linux内核PCI子系统架构:pci_bus、pci_dev数据结构、pci_driver结构体、设备与驱动的匹配过程
Linux内核的PCI子系统是连接硬件与驱动程序的桥梁,其核心架构围绕三个关键数据结构构建:pci_bus(总线)、pci_dev(设备)和pci_driver(驱动)。理解这三者的关系以及设备与驱动的匹配机制,是编写PCIe驱动的基石。
4.1 PCI子系统三层架构概览
PCI子系统采用分层设计,从物理总线到具体设备,再到驱动功能,形成清晰的树状结构。下图展示了核心组件之间的组织关系:
4.2 pci_bus:PCI总线结构体
pci_bus 是内核中描述一条PCI总线的核心数据结构,它维护了总线上的设备链表、桥接关系以及资源分配信息。每个PCI域(Domain)中的每一条总线都对应一个 pci_bus 实例。
| 字段名 | 类型 | 描述 |
|---|---|---|
number |
unsigned char | 总线号(Bus Number),在域内唯一 |
primary |
unsigned char | 主总线号(用于PCI桥) |
secondary |
unsigned char | 从总线号(用于PCI桥) |
subordinate |
unsigned char | 最大从总线号(用于PCI桥) |
devices |
struct list_head | 挂载在此总线上的所有pci_dev链表 |
children |
struct list_head | 子总线链表(通过PCI桥连接) |
resource |
struct resource * | 总线上的资源(IO、MEM、BUS号码) |
ops |
struct pci_ops * | 总线访问操作函数指针(配置空间读写) |
关键理解: pci_bus 的 ops 字段指向 pci_ops 结构体,其中封装了 read() 和 write() 函数指针。这是内核访问PCI配置空间的底层接口,不同平台(x86、ARM、PowerPC)通过实现不同的 pci_ops 来适配各自的硬件访问方式。
4.3 pci_dev:PCI设备结构体
pci_dev 代表一个具体的PCI或PCIe功能(Function)。每个物理设备上的每个功能都对应一个独立的 pci_dev 实例。该结构体包含了设备的完整身份信息、资源分配以及运行时状态。
struct pci_dev {
struct pci_bus *bus; /* 所属总线 */
struct pci_driver *driver; /* 绑定的驱动(匹配后赋值) */
unsigned int devfn; /* 设备号(高5位) + 功能号(低3位) */
unsigned short vendor; /* 厂商ID */
unsigned short device; /* 设备ID */
unsigned short subsystem_vendor; /* 子系统厂商ID */
unsigned short subsystem_device; /* 子系统设备ID */
unsigned int class; /* 类别代码 */
u8 revision; /* 修订版本 */
u8 hdr_type; /* 配置空间头部类型 */
struct resource resource[DEVICE_COUNT_RESOURCE]; /* BAR资源数组 */
unsigned int irq; /* 中断号 */
/* ... 其他字段 */
};
pci_dev 中的 resource 数组是驱动获取设备基地址(BAR)和中断号的关键入口。内核在枚举PCI总线时,会读取每个设备的配置空间,自动填充这些字段。
4.4 pci_driver:PCI驱动结构体
pci_driver 是驱动开发者需要实现的核心结构体。它定义了驱动的身份标识、支持的设备列表以及生命周期回调函数。
struct pci_driver {
const char *name; /* 驱动名称 */
const struct pci_device_id *id_table; /* 支持的设备ID表 */
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id);
void (*remove) (struct pci_dev *dev);
int (*suspend)(struct pci_dev *dev, pm_message_t state);
int (*resume) (struct pci_dev *dev);
/* ... 其他回调 */
};
| 回调函数 | 触发时机 | 驱动应执行的操作 |
|---|---|---|
probe() |
设备与驱动匹配成功时 | 启用设备、分配资源、注册中断、初始化DMA |
remove() |
设备拔出或驱动卸载时 | 释放资源、注销中断、禁用设备 |
suspend() |
系统进入低功耗状态时 | 保存设备状态、停止DMA传输 |
resume() |
系统从低功耗状态恢复时 | 恢复设备状态、重新初始化DMA |
驱动开发要点: id_table 是匹配的关键。驱动必须声明一个 pci_device_id 数组,列出所有支持的设备。内核通过比对 pci_dev 中的 vendor/device/class 等信息与 id_table 中的条目来决定是否调用 probe()。
4.5 设备与驱动的匹配过程
PCI设备与驱动的匹配是内核PCI核心层自动完成的,整个过程遵循“发现-注册-匹配-绑定”的流程。下图展示了完整的匹配生命周期:
4.5.1 匹配的核心机制:pci_device_id
匹配过程的核心是比较 pci_dev 的硬件信息与 pci_driver 的 id_table。内核提供了多种匹配模式:
struct pci_device_id {
__u32 vendor, device; /* 厂商ID和设备ID */
__u32 subvendor, subdevice; /* 子系统厂商ID和设备ID */
__u32 class, class_mask; /* 类别代码及掩码 */
unsigned long driver_data; /* 驱动私有数据 */
};
| 匹配模式 | 匹配规则 | 使用场景 |
|---|---|---|
| 精确匹配 | vendor + device 完全相等 | 特定型号设备,如网卡、显卡 |
| 子系统匹配 | vendor + device + subvendor + subdevice 完全相等 | 同一芯片不同板卡厂商 |
| 类别匹配 | class & class_mask 匹配 | 通用驱动,如所有存储控制器 |
| 通配符匹配 | PCI_ANY_ID 表示任意值 | 实验或调试驱动 |
匹配优先级: 内核按照 id_table 中条目的顺序依次比对。精确匹配优先于类别匹配。如果多个驱动都支持同一个设备,则先注册的驱动获得绑定权。实际开发中,建议将最具体的设备ID放在 id_table 的前面。
4.5.2 probe() 回调的典型实现
当匹配成功后,内核调用驱动的 probe() 函数。这是驱动初始化的入口点,典型的实现步骤如下:
static int my_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
/* 1. 启用PCI设备(包括DMA主控能力) */
ret = pci_enable_device(pdev);
if (ret)
return ret;
/* 2. 请求BAR资源(获取设备地址空间) */
ret = pci_request_regions(pdev, "my_driver");
if (ret)
goto err_disable;
/* 3. 设置DMA掩码(决定DMA地址宽度) */
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
if (ret)
goto err_release;
}
/* 4. 读取配置空间中的设备特定信息 */
u16 cap_offset;
pci_read_config_word(pdev, PCI_CAPABILITY_LIST, &cap_offset);
/* 5. 注册中断(MSI-X优先) */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSIX | PCI_IRQ_MSI);
if (ret < 0)
goto err_release;
/* 6. 初始化硬件(DMA描述符环、FIFO等) */
my_device_hw_init(pdev);
return 0;
err_release:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
常见错误: 在 probe() 中忘记调用 pci_enable_device() 就直接访问BAR空间或配置DMA,会导致系统崩溃或设备无响应。同样,remove() 中必须对称地调用 pci_disable_device() 和 pci_release_regions()。
4.6 数据结构关系总结
下表总结了三个核心结构体在PCI子系统中的角色与交互关系:
| 结构体 | 角色 | 关键字段 | 与上下层关系 |
|---|---|---|---|
| pci_bus | 总线容器 | devices链表、children链表、ops | 包含多个pci_dev;通过ops访问配置空间 |
| pci_dev | 设备实体 | vendor/device、resource[]、irq | 属于一个pci_bus;绑定一个pci_driver |
| pci_driver | 驱动功能 | id_table、probe/remove回调 | 通过id_table匹配pci_dev;调用probe初始化 |
理解这三者的关系后,驱动开发者只需专注于实现 pci_driver 结构体,并正确填充 id_table 和回调函数。内核PCI核心层会自动完成设备枚举、匹配和生命周期管理,这也是Linux内核“驱动模型”设计思想的体现。