什么是 PCIe 中的基地址寄存器 (BAR)?

What is the Base Address Register (BAR) in PCIe?

在浏览了一些基础文档后我了解到,Base Address Register 是 Address space 可以被 PCIe IP 访问。 PCIe IP 可以在基地址寄存器中传输数据,也可以将接收到的数据写入其中。

我说的对吗?或者遗漏了什么?

我认为这是一个非常基本的问题,我建议阅读:

基地址寄存器 (BAR) 用于:
- 指定设备要映射到主内存的内存量,以及
- 在设备枚举之后,它保存(基)地址,即映射内存块开始的位置。

一个设备最多可以有六个 32 位 BAR,或者将两个 BAR 组合成一个 64 位 BAR。

Linux内核观点

学习某物的一个好方法是与之互动,所以让我们为此使用 Linux 内核。

这是 QEMU 模拟设备上的最小 PCI 示例:https://github.com/cirosantilli/linux-kernel-module-cheat/blob/366b1c1af269f56d6a7e6464f2862ba2bc368062/kernel_module/pci.c

PCI 配置的前 64 个字节标准化为:

图片来自 LDD3.

所以我们可以看到有 6 个 BAR。 wiki page 然后显示每个 BAR 的内容:

区域宽度需要魔术写但是:How is a PCI / PCIe BAR size determined?

此内存由 PCI 设备设置,并向内核提供信息。

每个 BAR 对应一个地址范围,作为与 PCI 设备的独立通信通道。

每个区域的长度由硬件定义,并通过配置寄存器与软件通信。

除了长度之外,每个区域还有更多硬件定义的属性,特别是内存类型:

  • IORESOURCE_IO:必须用inXoutX
  • 访问
  • IORESOURCE_MEM:必须用ioreadXiowriteX
  • 访问

几个 Linux 内核 PCI 函数将 BAR 作为参数来标识要使用的通信通道,例如:

mmio = pci_iomap(pdev, BAR, pci_resource_len(pdev, BAR));
pci_resource_flags(dev, BAR);
pci_resource_start(pdev, BAR);
pci_resource_end(pdev, BAR);

通过查看 QEMU 设备源代码,我们看到 QEMU 设备将这些区域注册为:

memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
                "edu-mmio", 1 << 20);
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);

很明显 BAR 的属性是硬件定义的,例如BAR 编号 0,内存类型 PCI_BASE_ADDRESS_SPACE_MEMORY,内存区域长 1MiB 1 << 20

另见:http://wiki.osdev.org/PCI#Base_Address_Registers当然。

BAR是从内存开始的设备地址记录。

root@Ubuntu:~$ lspci -s 00:04.0 -x
00:04.0 USB controller: Intel Corporation 82801DB/DBM (ICH4/ICH4-M) USB2 EHCI Controller (rev 10)
00: 86 80 cd 24 06 00 00 00 10 20 03 0c 10 00 00 00
10: 00 10 02 f3 00 00 00 00 00 00 00 00 00 00 00 00
20: 00 00 00 00 00 00 00 00 00 00 00 00 f4 1a 00 11
30: 00 00 00 00 00 00 00 00 00 00 00 00 05 04 00 00

root@Ubuntu:~$ lspci -s 00:04.0 -v
00:04.0 USB controller: Intel Corporation 82801DB/DBM (ICH4/ICH4-M) USB2 EHCI Controller (rev 10) (prog-if 20 [EHCI])
        Subsystem: Red Hat, Inc QEMU Virtual Machine
        Physical Slot: 4
        Flags: bus master, fast devsel, latency 0, IRQ 35
        Memory at f3021000 (32-bit, non-prefetchable) [size=4K]
        Kernel driver in use: ehci-pci
root@Ubuntu:~$ grep  00:04.0 /proc/iomem
  f3021000-f3021fff : 0000:00:04.0

0xfff等于4095,也就是4K。内存从 0xf3021000 开始是 CPU 看到的这个 USB 设备。此地址在 BIOS 期间初始化,在本例中它位于 BAR0 上。为什么是 BAR0?

在此之前,需要了解 PCI 规范,尤其是下面的 type 0 和 type 1:

注意 header 类型都定义在 0x0c 第三字段,这就是 BAR 的不同之处。在这个例子中,它是00,这意味着它是类型0。因此BAR0存储地址,即00 10 02 f3

有人可能想知道为什么这不完全是 f3021000,这是因为 lspci 使用 Little Endian。什么是字节序?可能需要阅读 "Gulliver's Travels".

BAR0一般有三种状态,未初始化、全1、写入地址。我们现在处于第三个位置,因为设备已经初始化。 Bit 11 ~ 4 在未初始化状态下设置为 0; Bit 3 为 0 表示 NP,为 1 表示 P; Bit 2~1 设置为00 时表示32 位,设置为10 时表示64 位; Bit 0 为 0 表示内存请求,为 1 表示 IO 请求。

0xf3021000
====>>>>
11110011000000100001000000000000

由此可知此设备为32位,non-prefetchable,内存请求。未初始化地址为32~12,因为2^12=4K.

更多设备和供应商,可以通过https://pcilookup.com/

查找

粗略地说,根复合体(又名主机)充当“经销商”并在称为枚举的过程中与每个端点设备对话,其中每个设备都有自己的一组配置寄存器。它使用配置 space 而不是普通内存 space 来执行此访问。 pci 设备的内存 space 在根复合体设置和映射条形寄存器之前不存在。 使用配置 space,root-complex 在每个 PCI 设备中顺序写入全 1 的 bar 寄存器,然后读回它们以确定分配给每个设备的 bar 地址 space 的大小。如果根复合体在第 4 位以上的低阶位中看到零,这意味着这些是可寻址的 space,然后它会选择一个物理内存地址并将其分配给条形寄存器中的非零位...

对于具有 32 位条的 PCIe 设备,配置 space 具有以下 32 位 DWORD:

    UInt32 PCIEBAR32_0, PCIEBAR32_1, PCIEBAR32_2, 
       PCIEBAR32_3, PCIEBAR32_4, PCIEBAR32_5;
    bool cond32_0 = (PCIeBAR32_0 & 0x7) == 0x00);
    bool cond32_1 = (PCIeBAR32_1 & 0x7) == 0x00);
    bool cond32_2 = (PCIeBAR32_2 & 0x7) == 0x00);
    bool cond32_3 = (PCIeBAR32_3 & 0x7) == 0x00);
    bool cond32_4 = (PCIeBAR32_4 & 0x7) == 0x00);
    bool cond32_5 = (PCIeBAR32_5 & 0x7) == 0x00);

对于64位条的PCIe设备,相邻的两个32位DWORDS串连起来形成一个64位条:

    UInt64 PCIEBAR64_0, PCIEBAR64_1, PCIEBAR64_2;
    bool cond64_0 = (PCIEBAR32_0 & 0x7) == 0x4);
    bool cond64_1 = (PCIEBAR32_2 & 0x7) == 0x4);
    bool cond64_2 = (PCIEBAR32_4 & 0x7) == 0x4);
    if (!(cond64_0 && cond64_1 && cond64_2)) {
        Console.Writeline("Whoops, we don't have 3 adjacent 64-bit bars");
        return -1;
    }
    PCIEBAR64_0 =  (UInt64)PCIEBAR32_1<<32 | (UInt64)PCIEBAR32_0; 
    PCIEBAR64_1 =  (UInt64)PCIEBAR32_3<<32 | (UInt64)PCIEBAR32_2; 
    PCIEBAR64_2 =  (UInt64)PCIEBAR32_5<<32 | (UInt64)PCIEBAR32_4; 
    //note that since lower 4-bits of Least significant 
    //bar indicate its a 64-bit bar, this means the 
    //next adjacent 32-bit bar doesn't knockout
    //the bottom 4-bits of the bar. so that it can be concatenated.

不太确定混合使用 32 位和 64 位条的系统会发生什么...也许您需要按从 0 到 5 的顺序检查条以找到未对齐的情况。 .