4. PCIe设备枚举与驱动绑定:pci_driver结构体、probe/remove回调、设备树与ACPI
PCIe设备从物理插入到被Linux内核驱动接管,经历了硬件枚举、资源分配、驱动匹配和绑定四个阶段。本章深入剖析pci_driver结构体的核心字段、probe/remove回调的生命周期管理,以及设备树(Device Tree)与ACPI在PCIe设备描述中的差异与协同。
核心要点:驱动绑定的本质是内核PCI核心层将枚举出的
pci_dev设备对象,与驱动注册的pci_driver进行ID匹配,匹配成功后调用probe回调完成初始化。
4.1 pci_driver结构体深度解析
pci_driver是PCI设备驱动的核心数据结构,定义在include/linux/pci.h中。它封装了驱动与内核PCI子系统的所有交互接口。
struct pci_driver {
const char *name; // 驱动名称,用于sysfs标识
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);
void (*shutdown)(struct pci_dev *dev);
int (*sriov_configure)(struct pci_dev *dev, int num_vfs);
const struct pci_error_handlers *err_handler;
struct device_driver driver; // 内嵌通用驱动模型
struct pci_dynids dynids; // 动态ID支持
};
| 字段 | 作用 | 驱动开发者关注点 |
|---|---|---|
name |
驱动名称,在/sys/bus/pci/drivers/下可见 |
保持唯一,与模块名一致 |
id_table |
设备ID匹配表,决定驱动能绑定哪些设备 | 必须包含Vendor ID、Device ID、Subsystem ID等 |
probe |
设备匹配成功后的初始化入口 | 分配资源、映射BAR、注册中断、创建设备文件 |
remove |
设备移除或驱动卸载时的清理出口 | 释放所有probe中分配的资源,注销中断 |
suspend/resume |
电源管理回调 | 保存/恢复设备上下文,处理D3hot/D0状态切换 |
sriov_configure |
SR-IOV虚拟功能配置 | 启用/禁用VF,分配VF资源 |
err_handler |
PCIe错误处理回调(AER) | 处理可恢复/不可恢复错误 |
实战建议:在
id_table中使用PCI_DEVICE(VENDOR_ID, DEVICE_ID)宏定义匹配条目。对于需要匹配多个子系统的设备,使用PCI_DEVICE_SUB宏精确匹配。
4.2 probe/remove回调的生命周期
probe和remove是驱动与设备绑定的两个关键时间点,其调用时序与内核PCI枚举流程紧密相关。
4.3 设备树(Device Tree)与PCIe
在ARM/ARM64等嵌入式平台中,PCIe控制器和设备的拓扑信息通过设备树描述。设备树节点提供PCIe总线地址映射、中断路由、复位GPIO等硬件配置信息。
/* 设备树中PCIe控制器节点示例 */
pcie0: pcie@f0000000 {
compatible = "vendor,pcie-controller";
reg = <0x0 0xf0000000 0x0 0x10000000>; // 配置空间和寄存器基址
bus-range = <0x0 0xff>; // 总线号范围
device_type = "pci";
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x02000000 0x0 0x40000000 0x0 0x40000000 0x0 0x40000000>;
// 中断映射
interrupt-map-mask = <0xff00 0x0 0x0 0x7>;
interrupt-map = <0x0000 0x0 0x0 0x1 &gic 0x0 0x0 0x0 0x7e 0x4>,
<0x0000 0x0 0x0 0x2 &gic 0x0 0x0 0x0 0x7f 0x4>;
// MSI控制器
msi-parent = <&its>;
status = "okay";
};
| 设备树属性 | 含义 | 驱动获取方式 |
|---|---|---|
reg |
PCIe控制器寄存器基址和配置空间地址 | platform_get_resource() |
bus-range |
此控制器管理的PCI总线号范围 | 内核PCI核心自动解析 |
ranges |
PCI地址空间到CPU地址空间的映射 | 内核PCI核心自动建立 |
interrupt-map |
PCI INTx中断到中断控制器的路由 | of_irq_parse_pci() |
msi-parent |
指向MSI中断控制器(如GIC ITS) | msi_domain_of_msi_parent() |
常见陷阱:设备树中
msi-parent属性缺失或指向错误,会导致MSI中断无法分配。在调试时检查/proc/interrupts确认中断类型是否为PCI-MSI。
4.4 ACPI与PCIe枚举
在x86和部分ARM64服务器平台,PCIe设备枚举依赖ACPI(高级配置与电源管理接口)。ACPI提供_SB_.PCI0命名空间下的设备描述,包括MMIO资源、中断路由表(PRT)和MSI控制器信息。
// ACPI命名空间中的PCIe设备描述(ASL语言片段)
Device(PCI0) {
Name(_HID, EISAID("PNP0A08")) // PCI Express Root Bridge
Name(_CID, EISAID("PNP0A03")) // PCI Root Bridge兼容
Name(_ADR, 0x00000000)
// 中断路由表
Name(_PRT, Package() {
Package() { 0x0000FFFF, 0x00, 0x00, 0x10 }, // INTA -> IRQ 16
Package() { 0x0000FFFF, 0x01, 0x00, 0x11 }, // INTB -> IRQ 17
Package() { 0x0000FFFF, 0x02, 0x00, 0x12 }, // INTC -> IRQ 18
Package() { 0x0000FFFF, 0x03, 0x00, 0x13 } // INTD -> IRQ 19
})
// MSI控制器信息
Name(_DSD, Package() {
ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
Package() {
Package() { "msi-parent", Package() { \_SB_.ITS0 } }
}
})
}
4.5 驱动绑定完整示例
以下是一个完整的PCIe MSI中断驱动绑定示例,展示pci_driver注册、probe中分配MSI中断、remove中清理的完整流程。
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#define DRV_NAME "pcie_msi_sample"
#define VENDOR_ID 0x10EE // 示例Vendor ID
#define DEVICE_ID 0x7011 // 示例Device ID
static struct pci_device_id sample_ids[] = {
{ PCI_DEVICE(VENDOR_ID, DEVICE_ID) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, sample_ids);
static irqreturn_t sample_msi_handler(int irq, void *dev_id)
{
struct pci_dev *pdev = (struct pci_dev *)dev_id;
// 读取中断状态寄存器,处理中断事件
u32 status = ioread32(pci_iomap(pdev, 0, 0) + 0x1000);
// ... 中断处理逻辑 ...
return IRQ_HANDLED;
}
static int sample_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
int msirq;
// 1. 使能PCI设备
ret = pci_enable_device(pdev);
if (ret)
return ret;
// 2. 请求BAR0内存区域
ret = pci_request_region(pdev, 0, DRV_NAME);
if (ret)
goto err_disable;
// 3. 设置DMA掩码(64位)
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
goto err_release;
// 4. 分配MSI中断向量(请求1个MSI)
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
if (ret < 0)
goto err_release;
msirq = pci_irq_vector(pdev, 0);
// 5. 注册中断处理函数
ret = request_irq(msirq, sample_msi_handler, 0, DRV_NAME, pdev);
if (ret)
goto err_free_vectors;
// 6. 保存设备私有数据
pci_set_drvdata(pdev, pci_iomap(pdev, 0, 0));
dev_info(&pdev->dev, "probe success, MSI IRQ=%d\n", msirq);
return 0;
err_free_vectors:
pci_free_irq_vectors(pdev);
err_release:
pci_release_region(pdev, 0);
err_disable:
pci_disable_device(pdev);
return ret;
}
static void sample_remove(struct pci_dev *pdev)
{
int msirq = pci_irq_vector(pdev, 0);
// 1. 释放中断
free_irq(msirq, pdev);
// 2. 释放MSI向量
pci_free_irq_vectors(pdev);
// 3. 释放IO映射
pci_iounmap(pdev, pci_get_drvdata(pdev));
// 4. 释放PCI资源
pci_release_region(pdev, 0);
// 5. 禁用设备
pci_disable_device(pdev);
dev_info(&pdev->dev, "remove complete\n");
}
static struct pci_driver sample_driver = {
.name = DRV_NAME,
.id_table = sample_ids,
.probe = sample_probe,
.remove = sample_remove,
};
module_pci_driver(sample_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("PCIe MSI Course");
MODULE_DESCRIPTION("PCIe MSI Interrupt Driver Sample");
调试技巧:使用
modprobe pcie_msi_sample加载驱动后,检查/sys/bus/pci/drivers/pcie_msi_sample/目录下是否出现设备符号链接。使用lspci -vvv -s 01:00.0查看设备是否已绑定驱动,以及MSI中断是否启用。
4.6 设备树与ACPI的驱动适配策略
驱动开发者需要根据平台类型选择正确的资源获取方式。以下表格总结了两种平台下的关键差异:
| 操作 | 设备树平台 | ACPI平台 |
|---|---|---|
| 获取设备资源 | platform_get_resource() |
acpi_dev_resource() |
| 中断解析 | of_irq_parse_pci() |
acpi_pci_irq_lookup() |
| MSI域获取 | msi_domain_of_msi_parent() |
acpi_msi_domain_get() |
| 电源管理 | pm_runtime_of() |
acpi_device_set_power() |
| 设备匹配 | of_match_table |
acpi_match_table |
跨平台兼容方案:使用
device_property_read_*()系列API(如device_property_read_u32())替代直接调用OF或ACPI专用函数。这些API在底层自动判断当前平台并调用对应的实现,实现驱动代码的跨平台统一。
PCIe设备枚举与驱动绑定是MSI中断驱动开发的基础。理解pci_driver结构体的每个字段、掌握probe/remove的正确资源管理顺序、熟悉设备树与ACPI的差异,是编写稳定、高效PCIe驱动的必备技能。下一阶段将深入MSI中断的配置与传输优化。