我需要扫描我的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 %x\n", dev->size[i], dev->base_addr[i], dev->device_id);
}
}
}
pci_cleanup(pci);
return 0;
}
正如您在我的printf行中看到的,我尝试打印一些数据,我正在成功打印device_id
和base_addr
但是size
应该包含此设备的PCI区域大小总是0.我预计,至少有一个循环从循环中显示一个大小&gt; 0
我的代码基于一个使用相同代码的Linux应用程序,尽管它使用Linux附带的pci.h头文件(pciutils看起来有相同的API)。 显然,Windows(在我的情况下是Windows 7 x64)没有显示此信息,或者至少没有暴露给PCIUtils。
您如何建议我获取此信息?如果有替代pciutils for Windows并提供此信息,我很乐意获得它们的链接。
编辑:我仍然没有找到解决办法。如果我的问题有任何解决方案,也适用于32位Windows,我们将非常感激。答案 0 :(得分:4)
这是如何工作的非常复杂。 PCI设备使用Base Address Registers
让BIOS和操作系统决定在哪里找到他们的内存区域。允许每个PCI设备指定它想要的几个内存或IO区域,并让BIOS / OS决定放置它的位置。使问题复杂化的是,只有一个寄存器用于指定大小和地址。这是如何工作的?
当卡首次上电时,它的32位地址寄存器中会有0xFFFF0000。任何二进制1表示“操作系统可以更改此”,任何二进制0表示“必须保持为零”。所以这告诉操作系统,前16位中的任何一位都可以设置为操作系统想要的任何值,但最后16位必须保持为零。这也意味着该存储区占用16位地址空间或64k。因此,内存区域必须与其大小对齐。如果一张卡需要64K的地址空间,操作系统只能将其放在64K倍数的内存地址上。当OS决定在哪里找到该卡的64K存储空间时,它会将其写回该寄存器,覆盖其中的初始0xFFFF0000。
换句话说,卡告诉操作系统内存需要什么尺寸/对齐,然后操作系统用内存地址覆盖相同的寄存器/变量。完成此操作后,如果不重置地址,则无法从寄存器中取出大小。
这意味着没有便携式的方式来询问卡片的区域有多大,所有你可以问的是区域在哪里。
那么为什么这在Linux中有用呢?因为它要求内核提供这些信息。内核有一个API来提供这些东西,就像lspci一样。我不是Windows专家,但我不知道应用程序向Windows内核询问此信息的任何方式。可能有一个API以某种方式执行此操作,或者您可能需要编写在内核端运行的内容以将此信息传递给您。如果查看libpci源代码,对于windows,它会调用pci_fill_info()的“通用”版本,它返回:
return flags & ~PCI_FILL_SIZES;
这基本上意味着“我要归还你要求的所有东西,但尺寸。”
但是,无论如何这可能无关紧要。如果你所做的只是想读/写I2C寄存器,它们通常(总是?)在控制/配置区域的第一个4K中。您可以只映射4K(一页)并忽略可能有更多的事实。另外要注意的是,您可能需要采取其他措施来阻止此卡的真正驱动程序进行读/写操作。如果您手动 bit-banging I2C总线,并且驱动程序同时尝试,则可能会导致总线混乱。
也可能有一种现有方法要求radeon驱动程序为您执行I2C请求,这可能会避免所有这些。
(另请注意,我正在简化和掩盖有关BAR如何工作的许多细节,包括64位地址,I / O空间等,如果您想了解更多信息,请阅读PCI文档)
答案 1 :(得分:2)
好的whamma给出了一个非常好的答案[但]有一件事他错了,这是区域大小。区域大小很容易找到,在这里我将展示两种方式,第一种方法是从条形码的地址解密,第二种方式是通过Windows用户界面。
假设E2000000是基址寄存器的地址。如果我们将其转换为二进制,我们得到: 11100010000000000000000000000000
现在总共有32位,如果必须,可以计算它们。现在,如果你不熟悉这些位 BAR被打好了,看这里 - &gt; http://wiki.osdev.org/PCI,特别是“基地址寄存器”,更具体地说 读取“Memory Space BAR Layout”的图像。现在让我们开始读取从右端到左端的位并使用 链接中的图像我在上面指出了你作为指南。
因此从右边开始的第一位(位0)为0,表示这是一个存储器地址BAR。 位(1-2)为0,表示它是32位(注意这不是大小)内存BAR。 位3为0,表示它不是Prefetchable内存。 现在我们消除了无用的位,让我们继续保持良好的位置,位4-31。如果你看看位的4 -31,你会 请注意,第一位是“1”是位24.这是一些数学发挥作用的地方。首先我们必须找到 Bit 24的二进制加权值,即16777216,也是16777216字节,即16 MB,告诉我们 BAR分配的内存大小为16 MB。如果你想知道我如何得到第24位的二进制加权值,它就像这样:2(二进制是基数2)乘以 本身24(24位),或2到24的幂,或2 ^ 24。
另一种方法是使用设备管理器: 开始 - &gt;“设备管理器” - &gt;显示适配器 - &gt;右键单击您的视频卡 - >属性 - >资源。标记的每种资源类型 “Memory Range”应该是一个内存BAR,你可以看到[start address]到[end address]。例如,让我们说 它读取[00000000E2000000 - 00000000E2FFFFFF],以获取[end address]中[起始地址]的大小: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF,十进制FFFFFF = 16777215 = 16777215字节= 16MB。