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

55

经过阅读一些基础文档,我的理解是,基址寄存器是可以被PCIe IP访问的地址空间。PCIe IP可以在基址寄存器中传输数据,也可以将收到的数据写入其中。

我理解正确吗?还是有遗漏的地方?

4个回答

52

Linux内核的观点

学习某个东西的好方法是与之交互,因此让我们使用Linux内核来实现。

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

PCI配置的前64个字节是标准化的,如下所示:

enter image description here

LDD3中的图像。

因此,我们可以看到有6个BAR。然后维基页面显示了每个BAR的内容:

enter image description here

然而,区域宽度需要进行魔术写入:如何确定PCI / PCIe BAR大小?

该内存由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


18

我认为这是一个非常基础的问题,建议阅读以下内容:

基址寄存器(Base Address Register,简称BAR)用于:
- 指定设备希望映射到主内存的内存大小;
- 在设备枚举后,它保存映射内存块开始的地址(即基地址)。

一个设备最多可以有六个32位的BAR或将两个BAR合并为一个64位的BAR。


3
BARs在端点中。每个端点可以映射多达6个内存区域。 - Paebbels
2
这意味着 PCIe 接收到的数据(或要传输的数据)存储在基址寄存器中指定的内存位置中?如果是这样,为什么会有多个基址寄存器呢? - tollin jose
5
一方面,64位BAR需要2个BAR(否则无法将该设备映射到4GB边界之外),另一方面,可以考虑配置BAR和数据交换BAR,或者两个数据BAR,一个用于输入,一个用于输出。 - Paebbels
4
BAR映射的内存区域位于PCIe设备内部(数据存储在设备存储器中,对吗?)。映射后,软件(例如驱动程序)可以通过映射的内存区域读/写设备存储器。 - 4va1anch3
3
是的。CPU中的内存控制器、PCIe Root-Complex和PCIe设备树将会把内存访问指向该设备,而非主内存。 - Paebbels
显示剩余5条评论

16

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规范,特别是下面的类型0和类型1:

enter image description here

enter image description here

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

有人可能会想知道为什么这不完全是f3021000,因为lspci采用小端字节序。什么是字节序?你可能需要读一下《格列佛游记》。

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

0xf3021000
====>>>>
11110011000000100001000000000000

从此我们可以知道这个设备是32位的、不可预取的、内存请求。未初始化地址为32~12,因为2 ^ 12 = 4K。

想要了解更多设备和供应商信息,可以通过https://pcilookup.com/查询。


5
粗略地说,根复合体(也称为主机计算机)充当“经销商”,并通过称为枚举的过程与每个端点设备进行通信,其中每个设备都有自己的配置寄存器集。它使用配置空间而不是普通内存空间进行访问。除非根复合体设置并映射了bar寄存器,否则pci设备的内存空间不存在。
使用配置空间,根复合体按顺序向每个PCI设备的bar寄存器写入所有1,并将其读回以确定分配给每个设备的bar地址空间的大小。如果根复合体在位于位4上方的低阶位中看到零,则意味着这些是可寻址空间,然后选择一个物理内存地址并将其分配给bar寄存器中的非零位...
对于具有32位条的PCIe设备,配置空间具有以下32位DWORDS:
    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位DWORD拼接成一个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号栏以查找非对齐的情况...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接