4. Linux PCIe驱动框架:内核PCI子系统架构与驱动模型
Linux内核的PCI子系统为PCIe设备驱动开发提供了完整的软件抽象层。理解其架构层次、核心数据结构以及驱动与设备的匹配机制,是编写正确、高效PCIe驱动的基石。本章将深入剖析PCI子系统的分层设计、pci_driver结构体、pci_device_id匹配表以及模块的生命周期管理。
4.1 Linux内核PCI子系统架构
PCI子系统位于硬件与设备驱动之间,采用分层架构设计,从上至下依次为:设备驱动层、PCI核心层(PCI Core)、PCI总线层(PCI Bus)以及硬件抽象层(HAL)。
- 设备驱动层:调用PCI核心API进行资源申请、中断注册、DMA映射等操作。
- PCI核心层:管理总线号、配置空间访问、资源分配(IO/MMIO/BAR)、电源管理、热插拔事件。
- PCI总线层:实现Linux设备模型中的总线逻辑,负责驱动与设备的枚举与匹配。
- 硬件抽象层:封装对底层配置空间(通过CFG cycles或ECAM)的访问,隔离平台差异。
4.2 pci_driver结构体:驱动的核心载体
在Linux内核中,每个PCIe设备驱动都必须定义一个struct pci_driver实例。该结构体是驱动与PCI子系统交互的核心接口,包含了驱动的名称、支持的设备ID列表、探测与移除回调函数、电源管理回调等关键成员。
| 成员名称 | 类型 | 描述 |
|---|---|---|
name |
const char * |
驱动名称,用于sysfs标识和日志输出 |
id_table |
const struct pci_device_id * |
指向设备ID匹配表的指针,驱动据此判断是否支持某设备 |
probe |
int (*probe)(struct pci_dev *, const struct pci_device_id *) |
当设备与驱动匹配成功时调用,完成设备初始化 |
remove |
void (*remove)(struct pci_dev *) |
设备移除或驱动卸载时调用,释放资源 |
suspend / resume |
电源管理回调 | 系统休眠/唤醒时调用,可选实现 |
shutdown |
void (*shutdown)(struct pci_dev *) |
系统关机时调用,执行优雅关闭 |
driver |
struct device_driver |
内嵌的通用设备驱动结构体,用于Linux设备模型 |
- probe() 是驱动初始化的入口,在此函数中完成BAR映射、中断注册、DMA池创建等操作。
- remove() 必须与probe()对称,释放所有已申请的资源,否则可能导致内核泄漏。
- 如果驱动不需要电源管理,可以将suspend/resume设置为
NULL。
4.3 pci_device_id匹配表:驱动与设备的桥梁
struct pci_device_id用于声明驱动所支持的PCIe设备。PCI子系统在枚举总线时,会遍历所有已注册驱动的id_table,通过比对Vendor ID、Device ID、Subsystem ID等字段来决定是否调用驱动的probe()函数。
/* pci_device_id 结构体定义 (include/linux/mod_devicetable.h) */
struct pci_device_id {
__u32 vendor, device; /* 主 Vendor ID 和 Device ID */
__u32 subvendor, subdevice; /* Subsystem Vendor ID 和 Device ID */
__u32 class, class_mask; /* 设备类别与掩码 */
kernel_ulong_t driver_data; /* 驱动私有数据,可传递设备变体信息 */
};
| 字段 | 匹配规则 | 常用值示例 |
|---|---|---|
vendor |
必须精确匹配 | 0x10EE (Xilinx), 0x8086 (Intel) |
device |
必须精确匹配 | 0x7021 (AXI DMA), 0x100E (Ethernet) |
subvendor / subdevice |
可指定为PCI_ANY_ID通配 |
PCI_ANY_ID 表示忽略子系统ID |
class / class_mask |
按类别匹配,掩码控制比较位 | 0x020000 (网络控制器) |
driver_data |
不参与匹配,仅用于传递信息 | 设备版本号、变体标识等 |
4.4 模块加载与卸载:驱动的生命周期管理
PCIe驱动通常以内核模块的形式存在,通过module_init()和module_exit()宏来注册入口和出口函数。在模块加载时,驱动调用pci_register_driver()将自身注册到PCI子系统;卸载时调用pci_unregister_driver()完成注销。
4.4.1 标准模块模板
#include <linux/module.h>
#include <linux/pci.h>
/* 设备ID匹配表 */
static const struct pci_device_id my_pci_ids[] = {
{ PCI_DEVICE(0x10EE, 0x7021) }, /* Xilinx AXI DMA */
{ 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;
pr_info("my_pci: probe device %04x:%04x\n", pdev->vendor, pdev->device);
/* 启用设备 */
ret = pci_enable_device(pdev);
if (ret)
return ret;
/* 申请BAR资源 */
ret = pci_request_regions(pdev, "my_pci");
if (ret) {
pci_disable_device(pdev);
return ret;
}
/* 设置DMA掩码 */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
/* 注册中断、创建字符设备等后续操作... */
return 0;
}
/* remove 回调 */
static void my_pci_remove(struct pci_dev *pdev)
{
pr_info("my_pci: remove device\n");
/* 释放中断、销毁设备、释放BAR资源 */
pci_release_regions(pdev);
pci_disable_device(pdev);
}
/* 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_pci_init(void)
{
return pci_register_driver(&my_pci_driver);
}
/* 模块出口 */
static void __exit my_pci_exit(void)
{
pci_unregister_driver(&my_pci_driver);
}
module_init(my_pci_init);
module_exit(my_pci_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PCIe Developer");
MODULE_DESCRIPTION("Sample PCIe Driver");
4.4.2 加载与卸载流程
| 阶段 | 操作 | 内核行为 |
|---|---|---|
| 模块加载 | insmod my_pci.ko |
执行module_init() → pci_register_driver() → 遍历已枚举设备 → 匹配ID表 → 调用probe() |
| 设备热插拔 | 设备插入 | PCI核心枚举新设备 → 匹配已注册驱动 → 调用probe() |
| 模块卸载 | rmmod my_pci |
执行module_exit() → pci_unregister_driver() → 遍历已绑定设备 → 调用remove() |
| 设备热移除 | 设备拔出 | PCI核心检测移除事件 → 调用remove() → 释放设备数据结构 |
- 始终使用
MODULE_DEVICE_TABLE(pci, ...)导出设备ID表,以便内核在模块未加载时也能识别设备并自动加载模块。 - 在
probe()中启用设备后,必须确保在remove()中对称禁用,否则可能导致系统不稳定。 - 使用
pci_set_drvdata()和pci_get_drvdata()在probe/remove之间传递驱动私有数据。
4.5 本章小结
Linux PCIe驱动框架以pci_driver为核心,通过pci_device_id匹配表实现驱动与设备的精准绑定。PCI子系统的分层架构将硬件访问、资源管理、设备模型解耦,使驱动开发者能够专注于设备逻辑。模块的加载与卸载流程由pci_register_driver()和pci_unregister_driver()驱动,配合probe/remove回调完成设备的全生命周期管理。掌握这些基础框架,是后续深入配置空间枚举与中断注册的前提。