4. PCIe设备枚举与识别

PCIe设备从“物理插上”到“驱动可用”,中间要经过一条完整的识别链路:总线枚举建立拓扑、固件描述提供平台信息、配置空间读取身份、内核匹配驱动、最后执行probe完成初始化。任何一环断裂,设备都会表现为“lspci能看到但没驱动”或“驱动加载了但设备不可用”。理解这条链路,是后续兼容性调试的基础。

PCIe 设备枚举与驱动绑定全链路 ① 总线枚举 Root Complex 扫描 Bus/Dev/Func 编号 分配 BAR 地址空间 ② 固件描述 ACPI _PRT / _DSD 设备树 PCIe 节点 中断路由与电源 ③ 身份读取 Vendor ID / Device ID Class Code / Revision Subsystem ID ④ 驱动匹配 pci_device_id 表 modalias 比对 绑定 pci_driver ⑤ probe 流程(驱动初始化) pci_enable_device → 映射 BAR → 申请中断 → 注册字符/网络设备 读取配置空间校验 → 初始化硬件寄存器 → 注册子系统接口 成功返回 0;失败返回负错误码,设备进入未绑定状态 关键配置空间字段(偏移) 0x00 Vendor ID 0x02 Device ID 0x08 Revision ID 0x09 Class Code 0x2C Subsystem ID 0x10~0x24 BAR0~BAR5 0x3C Interrupt Line/Pin 0x34 Capabilities Pointer 匹配优先级:Vendor+Device+SubVendor+SubDevice > Vendor+Device > Class

一、枚举流程:内核如何“看见”设备

PCIe枚举由内核PCI子系统在启动时发起,从Root Complex开始逐级扫描。每个PCIe设备在配置空间中都有唯一的Bus/Device/Function三元组地址。枚举过程主要完成三件事:发现设备、分配资源、注册设备对象。

# 查看枚举结果
lspci -nn
# 输出示例
01:00.0 Ethernet controller [0200]: Intel Corporation I210 Gigabit Network Connection [8086:1533] (rev 03)

# 查看详细配置空间
lspci -vvv -s 01:00.0
# 关键字段
# Control: I/O+ Mem+ BusMaster+ ...
# Region 0: Memory at f7c00000 (32-bit, non-prefetchable)
# Capabilities: [40] Power Management version 3
# Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
# Capabilities: [70] Express Endpoint, MSI 00

枚举时内核会为设备的BAR分配地址空间。如果BAR请求的空间无法满足,设备会保持未配置状态,表现为lspci可见但资源未分配。此时需要检查BIOS/UEFI的PCIe资源分配策略,或内核启动参数如pci=realloc。

二、设备树与ACPI描述:固件如何告诉内核

在ARM/嵌入式平台,设备树(Device Tree)描述PCIe控制器、中断映射、电源域和复位GPIO。在x86/服务器平台,ACPI通过_PRT描述中断路由,通过_DSD传递设备属性。两者都是“固件向内核描述硬件”的机制。

// 设备树中PCIe节点示例
pcie@f8000000 {
    compatible = "vendor,pcie-host";
    reg = <0x0 0xf8000000 0x0 0x100000>;
    #address-cells = <3>;
    #size-cells = <2>;
    device_type = "pci";
    ranges = <0x82000000 0x0 0x80000000 0x0 0x80000000 0x0 0x10000000>;
    interrupt-map-mask = <0x0 0x0 0x0 0x7>;
    interrupt-map = <
        0x0 0x0 0x0 0x1 &gic 0x0 0x20 0x4
        0x0 0x0 0x0 0x2 &gic 0x0 0x21 0x4
    >;
};

如果设备树中缺少interrupt-map,设备枚举正常但中断无法路由,驱动probe时申请中断会失败。ACPI平台则要检查_PRT方法是否返回正确的GSI/APIC中断号。

三、Vendor/Device ID匹配与驱动绑定

内核通过配置空间读取Vendor ID和Device ID,与驱动注册的pci_device_id表进行比对。匹配成功后,内核将设备与驱动绑定,调用驱动的probe函数。

// 驱动中的ID表
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(0x8086, 0x1533) },  // Vendor+Device
    { PCI_DEVICE_SUB(0x8086, 0x1533, 0x8086, 0xA01) }, // 含Subsystem
    { PCI_DEVICE_CLASS(0x020000) },  // 按Class匹配
    { 0, }
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

// probe函数
static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret;
    ret = pci_enable_device(pdev);
    if (ret) return ret;
    ret = pci_request_regions(pdev, "my_driver");
    if (ret) goto err_disable;
    // 映射BAR
    void __iomem *base = pci_iomap(pdev, 0, 0);
    // 申请中断
    ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
    if (ret < 0) goto err_unmap;
    return 0;
err_unmap:
    pci_iounmap(pdev, base);
err_disable:
    pci_disable_device(pdev);
    return ret;
}
匹配方式 匹配字段 优先级 适用场景
精确匹配 Vendor+Device+SubVendor+SubDevice 最高 OEM定制卡,需区分同一芯片的不同板型
标准匹配 Vendor+Device 高 通用驱动,覆盖同一芯片所有板卡
类匹配 Class Code 低 通用类驱动,如NVMe、AHCI
动态ID new_id写入 运行时 调试阶段临时绑定未收录设备

四、probe流程与常见失败点

probe是驱动真正“接管”设备的入口。典型probe顺序为:使能设备→申请BAR区域→映射寄存器→申请中断→初始化硬件→注册子系统接口。每一步失败都会返回负错误码,内核记录日志并保持设备未绑定。

常见probe失败原因:

  • BAR映射失败:资源冲突或BIOS未分配,检查/proc/iomem。
  • 中断申请失败:MSI/MSI-X未使能或ACPI _PRT错误,检查dmesg | grep -i msi。
  • ID表未匹配:驱动未加载或modalias不匹配,检查/sys/bus/pci/devices/*/modalias。
  • 固件描述缺失:设备树节点不完整或ACPI方法返回错误。

调试命令速查:

# 查看设备绑定状态
ls -l /sys/bus/pci/devices/0000:01:00.0/driver
# 查看驱动匹配的modalias
cat /sys/bus/pci/devices/0000:01:00.0/modalias
# 手动触发驱动绑定
echo 0000:01:00.0 > /sys/bus/pci/drivers/my_driver/bind
# 查看probe日志
dmesg | grep -i "my_driver\|01:00.0"

五、案例复盘:设备可见但驱动未绑定

某服务器插入自研PCIe加速卡,lspci能看到设备,但lspci -k显示“Kernel driver in use: none”。排查步骤:先确认Vendor/Device ID是否在驱动ID表中;再检查modalias与驱动modalias是否一致;最后查看dmesg是否有probe失败记录。若ID表缺失,可通过new_id临时绑定验证:

echo "8086 1533" > /sys/bus/pci/drivers/my_driver/new_id
# 若绑定成功,说明ID表需补充;若仍失败,检查probe内部错误

该案例说明:枚举成功不等于驱动可用,ID匹配和probe初始化是两个独立关卡。调试时应按“枚举→描述→ID→probe”的顺序逐段验证,避免在错误环节反复尝试。