4. Linux内核PCI子系统架构:pci_bus、pci_dev数据结构、pci_driver结构体、设备与驱动的匹配过程

Linux内核的PCI子系统是连接硬件与驱动程序的桥梁,其核心架构围绕三个关键数据结构构建:pci_bus(总线)、pci_dev(设备)和pci_driver(驱动)。理解这三者的关系以及设备与驱动的匹配机制,是编写PCIe驱动的基石。

4.1 PCI子系统三层架构概览

PCI子系统采用分层设计,从物理总线到具体设备,再到驱动功能,形成清晰的树状结构。下图展示了核心组件之间的组织关系:

Linux内核PCI子系统核心架构 pci_bus (PCI总线) pci_dev (PCI设备) pci_dev (PCI设备) pci_driver (PCI驱动) pci_driver (PCI驱动) 一个 pci_bus 可以挂载多个 pci_dev,每个 pci_dev 对应一个唯一的 pci_driver 匹配过程由内核 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核心层自动完成的,整个过程遵循“发现-注册-匹配-绑定”的流程。下图展示了完整的匹配生命周期:

PCI设备与驱动匹配流程 1. 设备枚举(PCI扫描) 2. 创建pci_dev并注册到总线 3. 驱动注册(pci_register_driver) 4. 遍历设备链表,比对id_table 5. 匹配成功 → 调用probe() 6. 绑定完成,设备可操作

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内核“驱动模型”设计思想的体现。