如何获得Windows中的PCI区域大小?

我需要扫描我的PCI总线,并从特定供应商获取特定设备的信息。 我的目标是findAMD显卡的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_idbase_addr但是应该包含此设备的PCI区域大小的大小始终是0.我期望至less有一个来自循环显示大小> 0。

我的代码基于一个使用相同代码的Linux应用程序,尽pipe它使用了Linux提供的pci.h头文件(pciutils也有相同的API)。 显然,Windows(在我的情况是Windows 7 x64)不显示这个信息,或者至less不会暴露给PCIUtils。

你如何build议我获得这些信息? 如果有Windows的pciutils的替代品,并提供这些信息,我很乐意获得一个链接。

编辑:我仍然没有find解决办法。 如果有任何解决scheme,我的问题,也适用于32位Windows,它将深表谢意。

那么whamma给出了一个很好的答案,但是有一件事他错了,这就是区域大小。 地区的大小很容易找到,在这里我将展示两种方式,第一种是通过从酒吧地址解密它,第二种是通过Windows用户界面。

假设E2000000是基址寄存器的地址。 如果我们将其转换为二进制,我们得到:11100010000000000000000000000000

现在这里共有32位,如果必须的话,你可以计算它们。 现在,如果您不熟悉BAR中的位如何布局,请查看 – > http://wiki.osdev.org/PCI ,具体来说就是“基址寄存器”,更具体地说,是“Memory Space BAR布局”。 现在让我们开始读取从右端到左端的位,并使用上面指向您的链接中的图像作为指导。

所以从右边开始的第一个比特(比特0)是0,表示这是一个内存地址BAR。 位(1-2)是0,表示它是一个32位(注意这不是大小)内存条。 位3是0,表示它不是可预取的内存。 现在我们消除了无用的位,让我们继续看看4-31位。如果你看一下位数4 -31,你会注意到第一位是“1”是位24.这里是一些数学玩。 首先,我们必须找到位24的16进制加权值16777216,也就是16777216字节,这是16MB,告诉我们BAR分配的内存大小是16MB。 如果你想知道如何得到位24的二进制加权值,它是这样的:2(二进制是基数2)乘以24(24位)次,或2乘以24的幂,或2 ^ 24。

另一种方法是使用设备管理器:开始 – >“设备管理器” – >显示适配器 – >右键单击您的视频卡 – >属性 – >资源。 标记为“内存范围”的每种资源类型都应该是一个内存条,并且您可以看到它显示了[起始地址]到[结束地址]。 例如,假设它读取[00000000E2000000 – 00000000E2FFFFFF],从[结束地址]中获取[起始地址]的大小:00000000E2FFFFFF – 00000000E2000000 = FFFFFF,十进制中的FFFFFF = 16777215 = 16777215字节= 16MB。

这是如何工作是相当复杂的。 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文档)