How to get PCI area size in Windows?

I needed to scan my PCI bus and get information for specific devices from specific vendors. My goal is to find the PCI area size for an AMD graphics card, to map the PCI memory of this card to user space in order to transfer i2c and view information from various sensors.

To scan the PCI bus, I downloaded and compiled pciutils 3.1.7 for Windows x64 about a year ago. It supposedly uses DirectIO.

This is my code.

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; } 

As you see in my printf line, I am trying to print some data, I successfully print device_id and base_addr however the size , which should contain the size of the PCI area for this device, is always 0. I expected at least one of the loop cycles to display size> 0.

My code is based on a Linux application that uses the same code, although it uses the pci.h headers that come with Linux (pciutils obviously have the same APIs). Apparently, Windows (that is, Windows 7 x64 in my case) does not display this information, or at least is not subject to PCIUtils.

How do you suggest getting this information? If there are alternatives to pciutils for Windows and provide this information, I would be happy to receive a link to them.

EDIT: I still haven't found a solution. If there are any solutions to my problem, as well as working for 32-bit Windows, that would be deeply appreciated.

+8
c windows size pci
source share
2 answers

It is good that lime gave a very good answer [but] there was one thing about which he was not mistaken, that was the size of the region. The size of the area is quite easy to find, here I will show two ways: the first, decrypting it from the panel address, the second - through the Windows user interface.

Suppose E2000000 is a base register address. If we convert this to binary, we get: 11100010000000000000000000000000

Now there are only 32 bits, you can count them if necessary. Now, if you are not familiar with how the bits are laid out in the BAR, see here → http://wiki.osdev.org/PCI , in particular, “Basic Address Registers” and more specifically the image that reads “Layout of spatial memory space” . Now, let's start reading bits from the right end to the left end and use the image in the link I indicated to you above as a guide.

So, the first bit (bit 0), starting on the right, is 0, which indicates that it is a BAR memory address. Bits (1-2) are 0, which indicates a 32-bit (note that this is not the size) BAR of memory. Bit 3 is 0, which indicates that this is not a prefetch. Now we have destroyed the useless bits to continue the good part, bit 4-31. If you look at bit 4 -31, you will notice that the first bit, which is “1”, is bit 24. Here we use math. First, we need to find the binary weighted value of bit 24, which is 16777216, which is 16777216 bytes, which is 16 MB, which tells us the size of the memory allocated to the BAR is 16 MB. If you are interested in how I got the binary weighted value of bit 24, it will look like this: 2 (the binary code is base 2), multiplied by itself 24 (bit 24) times or 2 to a power of 24 or 2 ^ 24.

Another way is to use the device manager: Start → “Device Manager” → Display Adapters → Right-click the video card-> Properties-> Resources. Each type of resource marked with a “Memory Range” must be a memory BAR, and as you can see, it says [start address] at [end address]. For example, let's say it reads [00000000E2000000 - 00000000E2FFFFFF] to get the size that you would take [start address] from [end address]: 00000000E2FFFFFF - 00000000E2000000 = FFFFFF, FFFFFF in decimal = 16777215 = 16777215 bytes = 16 MB.

+2
source share

How it works is pretty tricky. PCI devices use Base Address Registers to let the BIOS and operating system decide where to find their memory areas. Each PCI device is allowed to specify several memory areas or IOs that it wants, and allows the BIOS / OS to decide where to put it. Complicating matters, there is only one register, which is used to indicate the size AND address. How it works?

When the card is first turned on, there will be something like 0xFFFF0000 in this 32-bit address register. Any binary 1 means "the OS can change this," any binary value 0 means "must remain zero." Thus, this tells the OS that any of the first 16 bits can be set according to what the OS wants, but the lower 16 bits should remain zero. It also means that this memory area occupies 16 bits of address space, or 64k. Because of this, memory areas must be aligned with their size. If the card requires 64 KB of address space, the OS can only store memory addresses that are multiples of 64 KB. When the OS decided where it wants to find this memory on 64 KB of memory, it writes it back to this register, overwriting the initial 0xFFFF0000 that was there.

In other words, the card tells the OS what size / alignment is needed for the memory, then the OS overwrites the same register / variable with an address for the memory. After that, you cannot get the size back from the register without resetting the address.

This means that there is no portable way to ask for a map, how large is its region, all you can ask is WHERE is the area.

So why does it work on Linux? Because it asks for a kernel for this information. The kernel has an API to provide this material, just as lspci works. I am not a Windows expert, but I do not know how an application can query the Windows kernel for this information. There may be an API to do this somehow, or you may need to write something that works on the kernel side to pass this information back to you. If you look in the libpci source, for windows it calls the "generic" version of pci_fill_info (), which returns:

 return flags & ~PCI_FILL_SIZES; 

which basically means "I am returning everything you requested, but the dimensions."

But it does not matter. If everything you do wants to read / write to I2C registers, they are usually (always?) In the first 4K control / configuration area. You can probably just match 4K (one page) and ignore the fact that there could be more. You should also be warned that you may need to take additional steps to stop the real driver for this card from reading / writing while you are at it. If you beat the I2C bus bit manually and the driver tries at the same time, it can cause a mess on the bus.

There may also be an existing way to ask the radeon driver to execute I2C requests for you, which can avoid all of this.

(also note that I simplify and hush up a lot of details about how BARs work, including 64-bit addresses, I / O space, etc., read the PCI documentation if you want to know more)

+4
source share

All Articles