2. Linux PCI子系统架构:pci_driver结构体、pci_device_id匹配表、模块加载与卸载流程

Linux PCI子系统是内核中设备模型与总线驱动框架的典范实现。理解其核心数据结构与生命周期管理,是编写稳定、可移植PCIe驱动的基石。本节将深入剖析pci_driver结构体、pci_device_id匹配表以及模块加载/卸载的标准流程。

2.1 pci_driver结构体:驱动核心

struct pci_driver是PCI设备驱动的“身份证”与“操作手册”,它向内核声明驱动的能力、支持的设备以及回调函数。定义于<linux/pci.h>。

成员 类型 说明
name const char * 驱动名称,用于/sys/bus/pci/drivers/目录标识
id_table const struct pci_device_id * 指向设备ID匹配表,决定驱动支持哪些PCI设备
probe int (*)(struct pci_dev *, const struct pci_device_id *) 设备发现时调用,完成硬件初始化、资源分配、注册中断等
remove void (*)(struct pci_dev *) 设备移除或驱动卸载时调用,释放资源、注销中断
suspend int (*)(struct pci_dev *, pm_message_t) 电源管理:挂起设备(可选)
resume int (*)(struct pci_dev *) 电源管理:恢复设备(可选)
shutdown void (*)(struct pci_dev *) 系统关机时调用(可选)
driver struct device_driver 内嵌通用设备驱动结构体,用于设备模型框架
关键点: probe 和 remove 是必须实现的两个回调。驱动的大部分初始化逻辑(如使能DMA、映射BAR空间、申请中断)都在 probe 中完成,而 remove 负责逆序清理。

2.2 pci_device_id匹配表:驱动与设备的桥梁

内核通过pci_device_id表判断哪个驱动可以接管某个PCI设备。匹配字段包括Vendor ID、Device ID、Subsystem Vendor ID、Subsystem Device ID以及Class等。

/* pci_device_id 结构体定义 (简化) */
struct pci_device_id {
    __u32 vendor;           /* 厂商ID,PCI_VENDOR_ID_xxx 或 PCI_ANY_ID */
    __u32 device;           /* 设备ID,PCI_DEVICE_ID_xxx 或 PCI_ANY_ID */
    __u32 subvendor;        /* 子系统厂商ID,通常 PCI_ANY_ID */
    __u32 subdevice;        /* 子系统设备ID,通常 PCI_ANY_ID */
    __u32 class;            /* 设备类别 */
    __u32 class_mask;       /* 类别掩码 */
    kernel_ulong_t driver_data; /* 驱动私有数据,可传递设备特定信息 */
};
匹配技巧: 使用 PCI_ANY_ID (值为0xFFFF) 可以匹配任意厂商或设备ID,但应谨慎使用,避免驱动错误绑定不兼容硬件。推荐精确匹配 Vendor ID + Device ID。

匹配表必须以MODULE_DEVICE_TABLE(pci, my_id_table)宏导出,以便内核模块工具(如modprobe)生成别名,实现自动加载。

/* 示例:匹配表定义 */
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x10EE, 0x7014) },             /* 精确匹配厂商0x10EE, 设备0x7014 */
    { PCI_DEVICE_SUB(0x10EE, 0x7015, 0x10EE, 0x0001) }, /* 匹配子系统 */
    { PCI_DEVICE_CLASS(0x020000, 0xFFFFFF) },   /* 匹配以太网控制器类 */
    { 0, }                                      /* 终止条目 */
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

2.3 模块加载与卸载流程

PCI驱动通常以内核模块形式存在。加载与卸载流程涉及模块入口/出口函数、PCI子系统注册以及设备生命周期回调。

2.3.1 标准流程步骤

  1. 模块加载:module_init(my_init) 调用驱动初始化函数。
  2. 注册驱动:调用 pci_register_driver(&my_driver),将驱动加入PCI总线。
  3. 设备匹配:内核遍历已枚举的PCI设备,与驱动的 id_table 进行匹配。
  4. probe回调:匹配成功,调用驱动的 probe 函数,完成硬件初始化。
  5. 设备运行:驱动正常工作,处理I/O、中断、DMA等。
  6. 模块卸载:module_exit(my_exit) 调用驱动清理函数。
  7. 注销驱动:调用 pci_unregister_driver(&my_driver),触发所有已绑定设备的 remove 回调。
  8. 资源释放:remove 回调释放硬件资源、注销中断、禁用DMA等。
常见错误: 在 probe 中分配资源后,如果后续步骤失败,必须回滚已分配的所有资源,否则卸载模块时会导致内存泄漏或内核崩溃。推荐使用 goto 错误处理模式。

2.3.2 完整代码模板

#include <linux/module.h>
#include <linux/pci.h>

/* 设备ID表 */
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x10EE, 0x7014) },
    { }
};
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;

    pr_info("my_pci: probe device %04x:%04x\n", pdev->vendor, pdev->device);

    /* 1. 使能PCI设备 */
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    /* 2. 请求BAR资源 */
    ret = pci_request_regions(pdev, "my_pci");
    if (ret)
        goto err_disable;

    /* 3. 设置DMA掩码 (后续章节详解) */
    ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
    if (ret)
        goto err_release;

    /* 4. 申请中断 (后续章节详解) */
    /* ... */

    pci_set_drvdata(pdev, /* 私有数据结构指针 */);
    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)
{
    /* 逆序释放probe中分配的资源 */
    /* 注销中断 */
    /* 释放私有数据 */
    pci_release_regions(pdev);
    pci_disable_device(pdev);
    pr_info("my_pci: device removed\n");
}

/* pci_driver 结构体 */
static struct pci_driver my_pci_driver = {
    .name       = "my_pci_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);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux PCIe Driver Developer");
MODULE_DESCRIPTION("Sample PCI driver for subsystem architecture demo");

2.4 PCI子系统架构与驱动生命周期SVG图

下图展示了PCI子系统核心组件、数据结构关系以及驱动从加载到卸载的完整生命周期。

Linux PCI子系统架构与驱动生命周期 Linux PCI子系统架构与驱动生命周期 核心数据结构关系 struct pci_driver name, id_table, probe, remove pci_device_id[] vendor, device, driver_data struct pci_dev vendor, device, irq, bar[] probe() 绑定 PCI 核心 (pci-core) 枚举、配置空间、电源管理 驱动通过 id_table 声明支持设备,内核PCI核心负责匹配与回调 驱动生命周期流程 ① module_init() → pci_register_driver() ② 内核遍历设备,匹配 id_table ③ 匹配成功 → 调用 probe() 回调 ④ probe: 使能设备、申请BAR、设置DMA、注册中断 ⑤ 设备正常运行,处理I/O/中断/DMA ⑥ module_exit() → pci_unregister_driver() ⑦ 触发所有绑定设备的 remove() 回调 ⑧ remove: 注销中断、释放BAR、禁用设备

2.5 关键避坑指南

场景 常见错误 正确做法
probe失败 直接返回错误,未释放已使能的设备 使用 goto 模式逐级回滚
id_table未导出 忘记 MODULE_DEVICE_TABLE,导致无法自动加载 始终在id_table定义后添加宏导出
remove遗漏清理 只注销中断,未释放BAR或禁用设备 严格按照probe的逆序清理所有资源
并发访问 probe/remove与中断处理函数竞态 使用自旋锁或互斥锁保护共享数据
调试建议: 在 probe 和 remove 中添加 dev_info() 或 dev_dbg() 打印,配合 dmesg 观察驱动生命周期是否按预期执行。使用 lspci -k 可查看当前设备绑定的驱动。