1
votes

I am writing a toy kernel on x86_64 platform. I am planning to elevate my kernel to use VGA and other devices. Till this point I was interfacing my toy kernel with just keyboard and UART. Now I think it is mature enough to start using some basic devices.

However, I am finding it hard to find documentation on PCI interface at early boot time. I am finding documentation on linux PCI drivers but not something which I really need. I must have to do the walk on my own. I am still confused if BIOS maps PCI space to somewhere.

Can someone throw some light on it or point me to some documentation.

Thanks

1

1 Answers

0
votes

The first thing you are going to want to do is perform PCI enumeration. There are basically two ways to do PCI enumeration, either port based or memory mapped based. The port based is the older way and the memory mapped PCI configuration space was introduced with PCI express. The port based approach still works with newer machines and also works with emulators/virtual machines for backwards compatibility. Therefore I will use this approach in the rest of my explanation. This also works as it is a little more complicated and if you understand how to do port based enumeration the memory mapped enumeration is pretty simple. I should add that some non-PC compatible x86 boards do not support port based PCI enumeration.

To access the PCI configuration space (in order to perform enumeration) via ports you need to know about two specific ports:

  1. CONFIG_ADDRESS - port 0xCF8

  2. CONFIG_DATA - port 0xCFC

Basically what you do is you write an address to CONFIG_ADDRESS and read or write the value to that address using CONFIG_DATA. The address written to CONFIG_ADDRESS is of the form:

31          30 - 24     23 - 16     15 - 11         10 - 8          7 - 2           1 - 0
Enable Bit  Reserved    Bus Number  Device Number   Function Number Register Number 00

The enable bit should basically always be set to 1 assuming you are going to use CONFIG_DATA to read or write a value.

So say you wish to read the first register for the first device in the configuration, you would do something like:

outl(CONFIG_ADDRESS, 1 << 31);
uint32_t result = inl(CONFIG_DATA)

To determine what each register is you can look at this table:

PCI Configuration Space Table

So in the previous example the first register contains both the device ID and vendor ID.

Now you that you know how to read a register from the PCI configuration space you need to know when you have found a device. Basically if the first register is 0xFFFFFFFF then no device is connected to that bus at that device number. The brute force way is to scan all the buses and devices and then scan the functions if you find a device. More sophisticated ways will look at the header type of the first device on the first bus to determine which buses need to be checked. You can find more information about how to do more sophisticated enumeration here.

To do the memory mapped based approach you will need to find the MCFG table specified in ACPI (also included in the simpler SFI). This will give you the physical address that you can use for the PCI enumeration. All memory addresses will be an offset from this address. Also one of the key differences is that the configuration space has been extended so the address bit fields do not match the port based enumeration bit fields. Addresses for PCI express use the following format:

31 - 28   27 - 20     19 - 15        14 - 12          11 - 8                    7 - 2            1 - 0
ZEROS     bus number  device number  function number  extended register number  register number  offset

This is the offset from the physical addressed obtained from the MCFG table. You can find more info about PCI express here.

Finally, while it is device specific, the most useful information you can get from the PCI configuration space is typically the base address registers. These are typically the physical addresses that the device is memory mapped to assuming it is memory mapped or are the port address assuming it is port based. Of course the interrupt pin and line are also useful.