侧边栏壁纸
  • 累计撰写 53 篇文章
  • 累计收到 3 条评论

P4-01 · PCIe Configuration Space 与 BAR 解码:写全 1 读回来,才知道设备多大

PacketForge
2026-08-18 / 0 评论 / 0 阅读 /
对应路线图:Phase 4 (Day 120-140) | 内核 v6.6 | 代码出处:source/linux-stable/drivers/pci/,引用均标 文件:行号
上一篇:P3-06 host_if | 下一篇:P4-02 MSI-X

一句话定位

Config Space 是设备与软件之间的"注册表":软件通过它知道设备是谁(Vendor/Device ID)、有什么能力(Capability 链)、占多大地址(BAR)。而 BAR 解码的玄机在于——BAR 寄存器同时承担"地址寄存器"和"尺寸寄存器"两个身份,内核用"写全 1 读回"让它现出原形。

真实源码精读

① 64 字节头部:偏移定义在 include/uapi/linux/pci_regs.h:38-125

Config Space 头部与 BAR 解码

#define PCI_VENDOR_ID        0x00    /* 16 bits */
#define PCI_DEVICE_ID        0x02    /* 16 bits */
#define PCI_COMMAND        0x04    /* 16 bits */
// ...省略(STATUS 0x06、REVISION_ID 0x08、CLASS_DEVICE 0x0a ...)
#define PCI_BASE_ADDRESS_0    0x10    /* 32 bits */
// ...省略(BASE_ADDRESS_1..5 依次 0x14/0x18/0x1c/0x20/0x24,pci_regs.h:96-100)
#define PCI_CAPABILITY_LIST    0x34    /* Offset of first capability list entry */
#define PCI_INTERRUPT_LINE    0x3c    /* 8 bits */
#define PCI_INTERRUPT_PIN    0x3d    /* 8 bits */

BAR 寄存器的位布局(pci_regs.h:101-110):bit0 区分空间(1=I/O, 0=Memory)、bit1-2 编码类型(PCI_BASE_ADDRESS_MEM_TYPE_64 = 0x04 表示 64 位 BAR 占两个寄存器)、bit3 是否 prefetchable,其余位才是地址。

② 枚举第一步:读 Vendor/Device ID 判断设备是否存在(probe.c:2494-2517)

static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;
    u32 l;

    if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
        return NULL;

    dev = pci_alloc_dev(bus);
    if (!dev)
        return NULL;

    dev->devfn = devfn;
    dev->vendor = l & 0xffff;
    dev->device = (l >> 16) & 0xffff;

    if (pci_setup_device(dev)) {
        // ...
        return NULL;
    }
    // ...
}

一次 dword 读偏移 0x00 就完成"设备在不在"判断:全 0xffffffff 表示该 devfn 悬空。存在则 pci_setup_device(probe.c:1867)继续读 header type、class 等并解析 BAR。

③ BAR 尺寸探测:写全 1 读回(probe.c:176-247,__pci_read_base 节选)

    mask = type ? PCI_ROM_ADDRESS_MASK : ~0;
    // ...
    pci_read_config_dword(dev, pos, &l);
    pci_write_config_dword(dev, pos, l | mask);
    pci_read_config_dword(dev, pos, &sz);
    pci_write_config_dword(dev, pos, l);

    /*
     * All bits set in sz means the device isn't working properly.
     * If the BAR isn't implemented, all bits must be 0.  If it's a
     * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit
     * 1 must be clear.
     */
    if (PCI_POSSIBLE_ERROR(sz))
        sz = 0;
    // ...
    if (res->flags & IORESOURCE_MEM_64) {
        pci_read_config_dword(dev, pos + 4, &l);
        pci_write_config_dword(dev, pos + 4, ~0);
        pci_read_config_dword(dev, pos + 4, &sz);
        pci_write_config_dword(dev, pos + 4, l);
        // ...
    }

四步协议:读原值 → 写全 1 → 读回 → 还原。读回值中低 N 位被硬件"钉死"为 0,N 就是尺寸的对数——设备应答 0xfff00000 说明它是 1MB 的 BAR。注意写全 1 前内核先关掉 decode(probe.c:189-193),防止探测期间把配置写当成正常访问。64 位 BAR 占两个寄存器,第二个寄存器在 pos + 4 重复一遍(probe.c:239-243)。

④ 树形枚举:pci_scan_bus → pci_scan_slot → pci_device_add

pci_scan_bus(probe.c:3308)挂上 ioport/iomem/busn 三类资源后创建根总线,调 pci_scan_child_bus 深度扫描。pci_scan_slot(probe.c:2745-2770)按 function 递增探测:fn 0 不存在且不在 hypervisor 透传环境就 break——Fn 0 是一个 slot 的"存在性锚点"。每个设备最终进 pci_device_add(probe.c:2609),挂到 bus->devices 链表(:2642)后由 driver core 触发匹配和 probe。

🐧 内核机制对照:同一份 BAR 信息,两种消费方式

环节内核(drivers/pci/)DPDK(drivers/bus/pci/)
枚举入口pci_scan_bus probe.c:3308用户态读 /sys/bus/pci/devices/
存在性判断读 offset 0x00 dword,probe.c:2499sysfs vendor/device 文件
BAR 尺寸写全 1 读回,probe.c:198-201直接读 sysfs resource 文件
BAR 映射ioremap(内核态地址)mmap,pci_common_uio.c:178-179
空 BAR 跳过尺寸 0 则 fail,probe.c:253-254phys_addr == 0 continue,pci_common_uio.c:120-121

本质区别:内核替所有驱动把 BAR 解析好、放进 pci_dev.resource[](pci.h:421);DPDK 在用户态只能拿到已分配好的物理地址,用 mmap 直接映射设备寄存器——这也是 DPDK 不写内核驱动就能操作网卡的前提。

面试 3 问

  1. 内核怎么测 BAR 尺寸? → 写全 1 读回(probe.c:198-201),读回值取反加一即尺寸;写之前先关 decode 防误访问(probe.c:189-193);64 位 BAR 在 pos+4 再做一遍(probe.c:239-243)。
  2. BAR 低 4 位为什么不是地址? → bit0 空间类型(pci_regs.h:101)、bit1-2 类型编码含 64 位标志(pci_regs.h:104-107)、bit3 prefetch(pci_regs.h:108),掩码 PCI_BASE_ADDRESS_MEM_MASK = ~0x0f(pci_regs.h:109)。
  3. 扫描时如何避免误判"幽灵设备"? → 悬空 devfn 读回 0xffffffff,pci_scan_device 直接返回 NULL(probe.c:2499-2500);读回全 1 也用于识别坏设备(probe.c:203-210 注释)。

🔧 动手实验

用真实 NIC(如 ixgbe 网卡)做一次"手写 __pci_read_base":

lspci -nn -v -s 01:00.0              # 记录 BAR0 地址与尺寸(如 64-bit, 4MB)
sudo lspci -xxxx -s 01:00.0 | head   # 前 64 字节逐字节对表解码
sudo setpci -s 01:00.0 0x10.L        # 读 BAR0 原值
sudo setpci -s 01:00.0 0x10.L=0xffffffff
sudo setpci -s 01:00.0 0x10.L        # 读回:低 N 位为 0,验证尺寸
sudo setpci -s 01:00.0 0x10.L=<原值> # 必须还原!不还原设备可能失效

同时 insmod code/dpu_dev/01_pci/pci_scan.ko 看内核侧 pci_resource_start(pdev, 0) 给出的 BAR0 值是否与 lspci 一致。数据落点 perf-data/P4/01-config-space.txt(lspci 原文 + 手工解码过程 + setpci 三次读数)。


下一篇:P4-02 · MSI-X 中断机制:Table 在哪、怎么触发

0

评论 (0)

取消