在浏览了一些我理解的基础文档后,基地址寄存器是可以通过PCIe IP访问的地址空间。 PCIe IP既可以在基地址寄存器中传输数据,也可以将接收到的数据写入其中。
我是对的吗?或者遗漏了什么?
答案 0 :(得分:11)
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
:必须使用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);
通过查看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
。
答案 1 :(得分:10)
我认为这是一个非常基本的问题,我建议阅读:
基地址寄存器(BAR)用于:
- 指定设备想要映射到主存储器的内存量,以及
- 在器件枚举之后,它保存(基址)地址,映射的存储器块开始。
设备最多可以有6个32位BAR,或者将两个BAR组合到64位BAR。
答案 2 :(得分:2)
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期间是init,在本例中为BAR0。为什么是BAR0?
在此之前,需要了解PCI规范,尤其是以下内容,输入0和1:
请注意,标头类型均在第三个字段0x0c处定义,这就是BAR的区别。在此示例中,它是00,表示它是类型0。因此BAR0存储了地址00 10 02 f3
。
一个人可能想知道为什么这不完全是f3021000
,这是因为lspci与Little Endian一起使用。什么是Endian?可能需要阅读“格列佛游记”。
BAR0通常具有三种状态,即未初始化,全1和已写入地址。自设备已经启动以来,我们现在排名第三。未初始化时,位11〜4设置为0;当设置为0时,位3表示NP;设置为1时,P表示P;位2〜1表示设置为00时为32位,设置为10时为64位;位0表示内存请求设置为0,IO请求设置为1。
0xf3021000
====>>>>
11110011000000100001000000000000
由此,我们可以知道此设备是32位不可预取的内存请求。未初始化的地址是32〜12,因为2 ^ 12 = 4K。
要获取更多设备和供应商,可以通过https://pcilookup.com/
查找答案 3 :(得分:0)
粗略地说,根复合体(也称为主机)充当“经销商”,并在称为枚举的过程中与每个端点设备对话,其中每个设备都有自己的一组配置寄存器。它使用配置空间而不是普通的内存空间来执行此访问。在根复合体设置和映射 bar 寄存器之前,pci 设备的内存空间不存在。 使用配置空间,根复合体在每个 PCI 设备中顺序写入全 1 的 bar 寄存器,然后读回它们以确定分配给每个设备的 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 位 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 的顺序检查条以找到未对齐的情况……< /p>