3. 设备枚举与资源获取:pci_get_device、pci_enable_device、pci_request_regions、BAR空间解析

在PCIe驱动开发的初始化阶段,核心任务是从总线拓扑中定位目标设备、激活设备电源管理、申请I/O与内存资源,并解析基地址寄存器(BAR)所映射的地址空间。本章围绕这四个关键步骤展开,提供标准API的调用规范、参数含义及常见陷阱。

3.1 设备查找:pci_get_device

驱动首先需要通过 Vendor ID 和 Device ID 在 PCI 总线树上定位目标设备。内核提供 pci_get_device 系列函数完成深度优先遍历。

函数原型 说明
struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from) 从 from 之后开始查找匹配 vendor 和 device 的设备;from 为 NULL 时从头开始。
struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from) 额外匹配 Subsystem Vendor/Device ID,用于区分同一芯片的不同子卡。
struct pci_dev *pci_get_class(unsigned int class_mask, struct pci_dev *from) 按 Class Code 查找,适用于通用类驱动(如存储、网络)。
关键注意:每次调用 pci_get_device 都会增加返回的 pci_dev 引用计数。驱动必须在不再使用时调用 pci_dev_put(dev) 释放引用,否则会导致设备对象泄漏。
// 典型查找模式
struct pci_dev *dev = NULL;
while ((dev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x1234, dev)) != NULL) {
    if (pci_dev_is_present(dev)) {
        // 找到目标设备,保存 dev 指针
        break;
    }
}
// 使用完毕后释放引用
pci_dev_put(dev);

3.2 设备使能:pci_enable_device

在访问设备任何资源之前,驱动必须调用 pci_enable_device 使设备脱离 D3cold 状态,开启 I/O 和内存地址解码,并分配必要的电源资源。

常见错误:跳过 pci_enable_device 直接读取 BAR 寄存器,返回的物理地址可能为 0 或无效值,导致后续 ioremap 失败。
int ret;
ret = pci_enable_device(pdev);
if (ret) {
    dev_err(&pdev->dev, "Failed to enable PCI device: %d\n", ret);
    return ret;
}

使能后,设备配置空间中的 Command 寄存器(偏移 0x04)的 Bit 0(I/O Space)和 Bit 1(Memory Space)会被置位,允许总线访问 BAR 映射的地址区域。

3.3 资源申请:pci_request_regions

为防止多个驱动冲突访问同一物理地址范围,内核要求驱动通过 pci_request_regions 声明对设备所有 BAR 区域的独占使用权。

API 行为
pci_request_regions(struct pci_dev *pdev, const char *res_name) 一次性请求设备所有 BAR 对应的 I/O 或内存区域,res_name 将显示在 /proc/iomem 或 /proc/ioports 中。
pci_request_region(struct pci_dev *pdev, int bar, const char *res_name) 仅请求单个 BAR 区域,适用于驱动只使用部分 BAR 的场景。
pci_release_regions(struct pci_dev *pdev) 在驱动卸载或错误路径中释放所有已请求的区域。
if (pci_request_regions(pdev, "my_pcie_driver")) {
    dev_err(&pdev->dev, "Failed to request PCI regions\n");
    pci_disable_device(pdev);
    return -EBUSY;
}
调试技巧:执行 cat /proc/iomem 查看当前系统所有已注册的内存资源。如果驱动未调用 pci_request_regions,则对应 BAR 区域不会出现在该文件中,但驱动仍可访问——这是一种不规范的编程习惯。

3.4 BAR空间解析

每个 PCIe 设备最多支持 6 个 BAR(Base Address Register),位于配置空间偏移 0x10~0x24。BAR 寄存器存储了设备请求的地址空间基址、类型(Memory 或 I/O)、可预取属性及地址宽度(32-bit 或 64-bit)。

3.4.1 BAR 寄存器结构

Bit 范围 含义
Bit 0 0 = Memory Space;1 = I/O Space
Bit 2:1 (Memory) 00 = 32-bit;10 = 64-bit;01 = 保留
Bit 3 (Memory) 可预取(Prefetchable):1 表示允许 CPU 缓存合并写入
Bit 31:4 (Memory) 基地址高位,低 4 位为属性位
Bit 31:2 (I/O) 基地址高位,低 2 位为属性位

3.4.2 内核解析 API

内核提供 pci_resource_start、pci_resource_end、pci_resource_flags 和 pci_resource_len 宏来安全读取 BAR 信息,无需直接操作配置空间。

// 遍历所有 BAR,打印资源信息
int i;
for (i = 0; i < 6; i++) {
    if (pci_resource_len(pdev, i) == 0)
        continue;  // 未实现的 BAR

    unsigned long start  = pci_resource_start(pdev, i);
    unsigned long end    = pci_resource_end(pdev, i);
    unsigned long flags  = pci_resource_flags(pdev, i);
    unsigned long length = pci_resource_len(pdev, i);

    dev_info(&pdev->dev, "BAR%d: start=0x%lx end=0x%lx len=%lu flags=0x%lx\n",
             i, start, end, length, flags);

    if (flags & IORESOURCE_MEM) {
        // 内存映射 BAR,后续调用 ioremap
        void __iomem *addr = ioremap(start, length);
        if (!addr) {
            dev_err(&pdev->dev, "ioremap failed for BAR%d\n", i);
        }
    } else if (flags & IORESOURCE_IO) {
        // I/O 端口 BAR,使用 inb/outb 系列函数
    }
}

3.5 完整初始化流程 SVG

下图展示了从设备查找到 BAR 映射的完整数据流与函数调用关系:

PCIe设备枚举与资源获取流程 PCIe设备枚举与资源获取流程 pci_get_device pci_enable_device pci_request_regions pci_resource_start/end/flags ioremap / ioport_map 关键说明 • 每次 pci_get_device 后 必须 pci_dev_put • pci_enable_device 使能 Command 寄存器 Bit0/1 • pci_request_regions 防 止资源冲突 • BAR 低4位为属性位 基址需对齐掩码 • ioremap 返回虚拟地址 用于 CPU 访问 • 64-bit BAR 占用连续 两个 BAR 槽位 • 卸载时逆序释放: iounmap → release_regions → disable_device → put

3.6 常见陷阱与最佳实践

  • 64-bit BAR 处理:当 pci_resource_flags 包含 IORESOURCE_MEM_64 时,当前 BAR 与下一个 BAR 共同组成 64-bit 地址。遍历时需跳过下一个 BAR,否则会重复映射。
  • BAR 未实现检测:通过 pci_resource_len(pdev, bar) == 0 判断 BAR 是否有效,避免对无效 BAR 执行 ioremap。
  • 错误路径清理:在 pci_enable_device 失败后,不应调用 pci_release_regions;在 pci_request_regions 失败后,必须调用 pci_disable_device 回滚使能状态。
  • 电源管理交互:如果设备支持 D3cold,pci_enable_device 可能触发电源状态切换,耗时较长。在原子上下文中不可调用。
推荐初始化顺序:
1. pci_get_device → 获取 pci_dev 指针
2. pci_enable_device → 激活设备
3. pci_request_regions → 声明资源所有权
4. pci_resource_start/len/flags → 解析 BAR 属性
5. ioremap → 建立虚拟地址映射
6. 注册中断、创建 DMA 描述符池等后续操作