PCI bus info and code from a programmer's perspective.
nov 2007 updated for PCIe!
aug 2010 updated again for PCIe!
There are 4 components to the PCI subsytem:
Bus Number
Device Number
Function Number
Register Number
There are up to 256 available Buses on a PCI system, most commonly all the cards and
chips will be located on Bus 0 and Bus 1.
When scanning for hardware, it's a good idea to scan all 256 buses as it won't take that
much additional time.
A Device is a physical thing on the PCI bus. It could be a video card, an ethernet
card, a Northbridge, anything. There is a software maximum of 32 devices that can
exist on each bus. The physical hardware limit is much lower than this due to
electrical loading issues, but we won't get into that.
Devices on a motherboard are typically scattered throughout the 32 devices, holes can and
will exist. Your software must scan all 32 devices on each bus.
All devices have at least 1 function, function #0. There are 8 possible functions per device, numbered 0-7. Any device that has more than 1 function is (hey clever!) called a multi-function device. Multi-function devices, such as a combination modem+soundcard will usually have 2 uniquely addressable functions, numbered 0 and 1.
Every function of a device has 256 eight-bit registers. Registers 0-3F are defined by the PCI specification and provide a wealth of information about the particular function. Registers 40-FF are vendor defined and control the properties of the function itself. Without vendor specific documentation, these registers should probably be left untouched.
A typical PCI function looks like this:
x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF 00000000: B7 10 55 90 17 01 10 02 30 00 00 02 08 50 00 00 00000010: 81 10 00 00 00 00 00 0C 00 00 00 00 00 00 00 00 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 B7 10 55 90 00000030: 00 00 00 00 DC 00 00 00 00 00 00 00 0B 01 0A 0A 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000050: 00 00 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000000C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000000D0: 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01 F6 000000E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000000F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Registers 0 and 1 are defined by the PCI spec as being the vendor ID. The vendor
ID is a 16bit value, in this case 10B7h
Register 2 and 3 are the device ID, 9055h in this example.
By looking up the vendor ID number 10b7 in the PCI vendor database
you can see this is a 3COM card. The 9055h is a 3COM
generated number which describes what model card this is.
Other registers can tell us it's an Ethernet controller, it's using IRQ 11, uses a base I/O address of 1080h and more. Refer to a pci specification to get all the details, as those are outside the scope of this document.
So, how do you get to all these registers in PCI space?
Accessing these registers is sorta like accessing CMOS memory in that you first send an index
value (a pointer to the register you want to access) to the index port, then you read or
write the data to/from the data port. The index is going to be a
combination of all 4 above mentioned components, (bus, device, function, register)
In the x86 world, the index port always located at 0CF8h and the data
port is always located at 0CFCh in I/O space. Most system BIOSes have software
interfaces to abstract the index/data port access from you. I describe both methods
below. Refer to Ralph Brown's interrupt list for details on the BIOS method of
reading/writing PCI
Before you can get to any PCI function's registers, you should probably make sure the PCI interface exists first!
Detecting the PCI bus
you can detect the PCI bus a couple different ways. the easist one is to call the
PCI BIOS interface directly and do an installation check.
note that you can also do a non-80386+ check by issuing this call and only check DX for
"CP" (4350h), but when have you ever seen a 286 with a PCI bus?
.386 mov ax, 0b101h ; interrupt 1a function b101 int 1ah ; will tell us if there is a PCI cmp edx," ICP" ; bus on the board. jz yup ; EDX=20494350h
; nope yup:
------------------------------------
You can also find the PCI bus using I/O by just doing a quick read to port CFCh.
mov dx, 0cfch ; config data port in eax, dx cmp eax, -1 ; FFFFFFFF? jz nope ; something must exist in PCI land
; yup
nope:
you will simply receive the data from the last PCI operation that was completed. This is by no means a foolproof method, but can do the job for quick-n-dirty type applications.
finding a device on the PCI bus:
there are 2 ways of finding a PCI device on the bus. You can either use the PCI
BIOS interface call, or direct hardware I/O.
Here's the BIOS way:
INTEL_VENDOR_ID EQU 8086h ; intel's unique sig # INTEL_EXP_NIC EQU 1227h ; sample PCI device etherexpress 10/100 NIC
.386 mov ax, 0b102h ; interrupt 1a function b102 mov dx, INTEL_VENDOR_ID mov cx, INTEL_EXP_NIC xor si, si ; 0=1st device, 1=2nd etc. int 1ah jc nope ; once returned from this call, BH=bus number, BL=device/function #
nope:
----------------------------------------------------------------
in the above example, SI will normally be zero, unless you are trying to locate the 2nd or
more instance of the same PCI device.
(ie, you had 3 PCI intel etherexpress cards installed)
an extremely handy utility for snooping through the PCI bus on your system is a DOS program called PCIVIEW.EXE. i was able to locate this utility on the net by simply searching for pciview.exe
--------------------------------------------------------------------------------------
the non-BIOS way:
locating a specific device on the PCI bus requires you to understand how the PCI
configuration cycle is broken down.
it's a 32bit value that looks like this:
bit 31=1 (bit is always set for a PCI access)
bits30:24=0 (reserved)
bit 23:16=bus number (0-255)
bits15:11=device # (0-31)
bits10:8=function # (0-7)
bits7:0=register number (0-255)
you send the above bit-encoded value out to the index port (cf8h) and then do a 32bit read
from the data port (cfch)
here's how to read the vendor and device ID from a device sitting at bus 0, device 7,
function 3.
BUS EQU 0 DEV EQU 7 FN EQU 3 VEN_ID EQU 0 ; vendor ID=PCI regs 0,1 PCI_INDEX EQU 0CF8h PCI_DATA EQU 0CFCh
.386 mov ax, 8000h ; set bit 31 (after shift) or al, BUS ; add in bus number shl eax, 16
mov ax, DEV shl ax, 11 ; slide device # up to bits 15:11 mov al, FN or ah, al ; add function into bits 10:8 mov al, VEN_ID
cli mov dx, PCI_INDEX out dx, eax ; send our request out
mov dx, PCI_DATA in eax, dx ; read back 32bit value. sti
Remember that PCI registers are 8 bit values. The above read from PCI_DATA reads
a 32bit value, or 4 PCI registers. In the above example, after the read, EAX =
device ID, AX = vendor ID.
Per the PCI specification, the vendor ID is always registers 0 and 1, and the device ID is
registers 2 and 3.
Thus, AL=register 0, AH=register 1, EAL=register 2, EAH=register 3.
PCIe update:
PCI express did some minor tweaks to the interface. Specifically, they changed the number
of config registers from 256 to 4096. The 1st 256 registers still look and feel just like
regular PCI registers-in fact, they are also readable and writable using the same methods
described above.
The additional registers however, are only available through a memory mapped subsystem, and
it's a total pain in the cheeks to get to them. **update, aug 2010, see below!**
For PCIe, it turns out that the *ENTIRE* PCI subsystem has been mapped into a 256MB chunk of
system memory. On the 2 machines that I had a chance to play with, this memory map started
at phsyical address 0xe0000000. That's way up near the top of 4Gig of memory. I suggest
you grab a memory browser/editor that allows you to poke around in memory out there and
explore the area for yourself.
At 0xe0000000, you'll see a copy of every PCI(e) register from Bus 0, Device 0, Function 0.
Function 1 follows at 0xe0001000, Function 2 at 0xe0002000, etc, all the way up to Bus 255,
Device 31, Function 7. Each PCI function, PCIe or not, is mapped to consume 0x1000 (4k) of space!
(thanks to Lihan Liang for the clarification!)
Any time there was no PCI device, such as BUS 89, Device 9, Function 3, the memory just
returns all FF's because nothing is there to respond to the read. To me, it seems like a HUGE
waste of memory space, considering that there is already a perfectly good mechanism for
accessing PCI, and all they'd need to do is use a couple of the reserved bits in the 32bit
index port to allow access to all the extended PCIe registers, but I digress...
Changing any register value here in memory is just like changing them via I/O ports the old way; the
memory map is just a mirror of whatever you see through the index and data ports and vice versa.
On your machine, your PCIe memory map might not be at 0xe0000000. How did I find this magic
value? This is where the pain in the cheeks starts, with ACPI.
Tucked away in the ACPI Root System Description Table (RSDT) is an entry for the
PCI Express memory mapped configuration space base address Description Table, or MCFG for
short.
In order to get there, you have to parse the ACPI tables. Here's how to do it:
1) search in memory (real mode) in the BIOS segments 0xE000 or 0xF000 for the byte sequence:
"RSD PTR "
1a) The RSD PTR may also be located in the EBDA area (typically between 638 and 640k).
This is clearly for people who have patched tables, who have moved the RSDT to a RAM area.
Presumably, the RAM area is searched first, although that is not specifically stated, it does come first in the list. (thanks Henry!)
2) add 0x10 to the address that "RSD PTR " is found, to get a 32bit pointer to where the
main ACPI RSDT tables are located.
3) You will need to be in protected mode, or unreal mode to access memory to do this next part.
Starting at the 32bit address of the RSDT tables (should be in high memory, my NVidia
board put them at 0x7FEF3040) search for the keyword "MCFG" or parse through all the tables
until you get to one labeled "MCFG". At offset 0x2c from "MCFG" will be another 32bit
pointer to the PCIe memory map.
My Nvidia board's "MCFG" table looks like this:
4D434647 3C000000 012C4E76 69646961 MCFG<....,Nvidia 4E564441 41435049 312E3042 4E564441 NVDAACPI1.0BNVDA 00000000 00000000 00000000 000000E0 ...............á 00000000 000000FF 00000000 00000000 .......ÿ........