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

tol*_*ose 15 base-address pci pci-e

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

我对吗?或遗漏任何东西?

Cir*_*四事件 16

Linux内核的观点

学习东西的好方法是与它进行交互,所以让我们使用Linux内核.

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

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

在此输入图像描述

图片来自LDD3.

所以我们可以看到有6个BAR.该wiki页面则显示了每个栏的内容:

在此输入图像描述

区域宽度需要魔术写入:如何确定PCI/PCIe BAR尺寸?

该存储器由PCI设备设置,并向内核提供信息.

每个BAR对应于一个地址范围,该地址范围用作到PCI设备的单独通信信道.

每个区域的长度由硬件定义,并通过配置寄存器传送给软件.

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

  • IORESOURCE_IO:必须使用inX和访问outX
  • IORESOURCE_MEM:必须使用ioreadX和访问iowriteX

几个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);
Run Code Online (Sandbox Code Playgroud)

通过查看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);
Run Code Online (Sandbox Code Playgroud)

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

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


Pae*_*els 12

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

基址寄存器(BAR)用于:
- 指定设备希望映射到主存储器的内存量,以及
- 在器件枚举之后,它保存映射存储器块开始的(基址)地址.

一个器件最多可以有六个32位BAR,或者将两个BAR组合到一个64位BAR.

  • 一方面,64位BAR需要2个BAR(否则不可能将该设备映射到4GB边界以外),另一方面考虑配置BAR和数据交换BAR或两个数据BAR用于输入和一个输出. (3认同)
  • BAR位于端点中.每个端点最多可映射6个内存区域. (2认同)
  • 这意味着 PCIe 接收(或要发送)的数据存储在基址寄存器中指定的内存位置?如果是这样,为什么会有不止一个 BAR? (2认同)
  • 那么,BAR 映射的内存区域是在 PCIe 设备内部(数据存储在设备存储中,对吗?)?映射后,软件(例如驱动程序)可以通过映射的内存区域读/写设备存储吗? (2认同)
  • @ 4va1anch3 是的。CPU 中的内存控制器、PCIe Root-Complex 和 PCIe 设备树会将内存访问定向到设备而不是主内存。 (2认同)
  • 这值得我们提出一个新问题。关键字是“PCI总线枚举”。由例如 BIOS 触发的枚举算法执行深度优先搜索 (DFS)。简而言之:RC 知道公交车上有一名参与者。它询问它是交换机还是端点。如果它是交换机,它会询问有多少端口以及哪些端口处于活动状态。然后它配置交换机。如果它是 EP,它会读取 BAR 并分配所请求的内存。稍后的路由基于枚举时收集的信息。数据不会被端点广播和过滤。 (2认同)

Lin*_*ive 8

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
Run Code Online (Sandbox Code Playgroud)

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

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

在此处输入图片说明

在此处输入图片说明

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

有人可能想知道为什么这不完全是f3021000,这是因为 lspci 与 Little Endian 一起使用。什么是字节序?人们可能需要阅读“格列佛游记”。

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
Run Code Online (Sandbox Code Playgroud)

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

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


Bil*_*ore 5

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

对于具有 32 位条的 PCIe 设备,配置空间具有以下 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);
Run Code Online (Sandbox Code Playgroud)

对于具有 64 位 bar 的 PCIe 设备,两个相邻的 32 位 DWORD 被连接起来形成一个 64 位 bar:

    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.
Run Code Online (Sandbox Code Playgroud)

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