我需要扫描我的PCI总线并从特定供应商处获取特定设备的信息。 我的目标是找到 AMD 显卡的 PCI 区域大小,以便将该卡的 PCI 内存映射到用户空间,以便进行 i2c 传输并查看来自各种传感器的信息。
为了扫描PCI总线,大约一年前我下载并编译了Windows x64的pciutils 3.1.7。据说它使用DirectIO。
这是我的代码。
int scan_pci_bus()
{
struct pci_access *pci;
struct pci_dev *dev;
int i;
pci = pci_alloc();
pci_init(pci);
pci_scan_bus(pci);
for(dev = pci->devices; dev; dev = dev->next)
{
pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS | PCI_FILL_IRQ | PCI_FILL_BASES | PCI_FILL_ROM_BASE | PCI_FILL_SIZES | PCI_FILL_PHYS_SLOT);
if(dev->vendor_id == 0x1002 && dev->device_id == 0x6899)
{
//Vendor is AMD, Device ID is a AMD HD5850 GPU
for(i = 0; i < 6; i++)
{
printf("Region Size %d %x ID %xn", dev->size[i], dev->base_addr[i], dev->device_id);
}
}
}
pci_cleanup(pci);
return 0;
}
正如您在 printf 行中看到的,我尝试打印一些数据,我已成功打印device_id
并base_addr
但是size
应包含此设备的 PCI 区域大小始终为 0。我预计,至少有一个循环从循环中显示大小> 0。
我的代码基于一个使用相同的代码的Linux应用程序,尽管它使用Linux附带的pci.h标头(pciutils具有相同的API)。 显然,Windows(在我的情况下是Windows 7 x64)不显示此信息,或者至少不会向PCIUtils公开。
你建议我如何获得这些信息?如果有适用于Windows的pciutils的替代品并提供此信息,我很乐意获得它们的链接。
编辑:我仍然没有找到解决方案。如果有任何解决方案可以解决我的问题并且也适用于32位Windows,将不胜感激。
这是如何工作的非常复杂。PCI 设备使用Base Address Registers
让 BIOS 和操作系统决定其内存区域的位置。每个 PCI 设备都可以指定它想要的多个内存或 IO 区域,并让 BIOS/OS 决定将其放置在哪里。更复杂的是,只有一个寄存器用于指定大小和地址。这是如何工作的?
当卡首次通电时,它的 32 位地址寄存器中将包含类似 0xFFFF0000 的东西。任何二进制 1 表示"操作系统可以更改它",任何二进制 0 表示"必须保持零"。所以这是在告诉操作系统,前 16 位中的任何一个都可以设置为操作系统想要的任何内容,但底部的 16 位必须保持零。这也意味着此内存区域占用 16 位地址空间,即 64k。因此,内存区域必须与其大小对齐。如果卡需要 64K 的地址空间,操作系统只能将其放在 64K 的倍数的内存地址上。当操作系统决定要放置此卡的 64K 内存空间的位置时,它会将其写回此寄存器,覆盖其中的初始0xFFFF0000。
换句话说,卡告诉操作系统内存需要什么大小/对齐方式,然后操作系统用内存地址覆盖相同的寄存器/变量。完成此操作后,如果不重置地址,您将无法从寄存器中恢复大小。
这意味着没有便携式的方式来询问卡片的区域有多大,您只能询问它区域在哪里。
那么为什么这在Linux中有效呢?因为它要求内核提供此信息。内核有一个 API 来提供这些东西,就像 lspci 的工作方式一样。我不是 Windows 专家,但我不知道应用程序有什么方法可以向 Windows 内核询问此信息。可能有一个 API 可以以某种方式执行此操作,或者您可能需要编写在内核端运行的内容来将此信息传回给您。如果你查看libpci源代码,对于Windows,它调用pci_fill_info()的"通用"版本,它返回:
return flags & ~PCI_FILL_SIZES;
这基本上意味着"我退回您要求的所有内容,但尺寸。
但是,无论如何,这可能无关紧要。如果您所做的只是想要读/写I2C寄存器,它们通常(总是?)位于控制/配置区域的前4K中。您可能只映射4K(一页)而忽略可能还有更多的事实。另请注意,您可能需要采取其他步骤来阻止此卡的真正驱动程序在您时读取/写入。如果您手动敲击I2C 总线,并且驱动程序同时尝试这样做,则可能会导致总线一团糟。
也可能有一种现有方法可以要求 radeon 驱动程序为您执行 I2C 请求,这可能会避免所有这些。
(另请注意,我正在简化和掩盖 BAR 如何工作的很多细节,包括 64 位地址、I/O 空间等,如果您想了解更多信息,请阅读 PCI 文档)
Whamma给出了一个非常好的答案,但有一件事他错了,那就是区域大小。区域大小很容易找到,在这里我将展示两种方法,第一种方法是从栏的地址破译它,第二种是通过 Windows 用户界面。
假设E2000000是基本寄存器的地址。如果我们将其转换为二进制,我们会得到: 11100010000000000000000000000000
现在这里总共有 32 位,如果必须,您可以计算它们。现在,如果您不熟悉位如何 一个 BAR 被布置出来,看这里 -> http://wiki.osdev.org/PCI ,特别是"基址寄存器",更具体地说 显示"内存空间栏布局"的图像。现在让我们开始从右端到左端读取位并使用 我上面指向您的链接中的图像作为指南。
所以从右边开始的第一个位(位0)是0,表示这是一个内存地址BAR。 位(1-2)为0,表示它是一个32位(注意这不是大小)内存栏。 位 3 为 0,表示它不是可预取内存。 位 4-31 表示地址。
该页面记录了 PCI 批准的流程:
要确定 PCI 设备所需的地址空间量,请 必须保存 BAR 的原始值,将所有 1 的值写入 寄存器,然后读回去。然后,内存量可以 通过屏蔽信息位确定,执行按位 NOT (C 中的"~"),并将值递增 1。的原始值 然后应恢复 BAR。BAR 寄存器自然对齐,并且 因此,您只能修改设置的位。
另一种方法是使用设备管理器: 开始->"设备管理器"->显示适配器->右键单击您的视频卡->属性->资源。标记的每个资源类型 "内存范围"应该是一个内存栏,如您所见,它显示[开始地址]到[结束地址]。例如,让我们说 它读取 [00000000E2000000 - 00000000E2FFFFFF],以获取从 [结束地址] 获取 [开始地址] 的大小: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF,十进制 FFFFFF = 16777215 = 16777215 字节 = 16MB。