The Internals of Linux 2.2′s PCI Interface

Peripheral Component Interconnect (PCI) is a widely used bus standard that provides several advantages over other bus standards, such as EISA. Standard on most Pentium motherboards, PCI is a fast, wide (32-bit and 64-bit), and processor-independent bus. When PCI support was first added to Linux, the kernel interface was a wrapper to the PCI BIOS32 functions. There were several problems with this approach:

Peripheral Component Interconnect (PCI) is a widely used bus standard that provides several advantages over other bus standards, such as EISA. Standard on most Pentium motherboards, PCI is a fast, wide (32-bit and 64-bit), and processor-independent bus. When PCI support was first added to Linux, the kernel interface was a wrapper to the PCI BIOS32 functions. There were several problems with this approach:

* PCI BIOS is found only on PC-class machines.

* The PCI BIOS itself represents only certain constructs, and there are non-PC machines that have PCI setups that cannot be described by the PCI BIOS.

* One or two machines have 32-bit PCI BIOS functions that don’t quite do what they are supposed to.

Linux 2.2 provides a generic PCI interface. The Linux x86 kernel actually tries to drive the hardware directly, but if it finds something it doesn’t understand, it may call PC BIOS32 functions.

Drivers can continue to use the old PCI interface but will need updating for future kernels. If the driver is intended to work cross platform, then it really should be updated.

Most of the functions map very simply from the old format to the new one. The PCI BIOS is based on the idea of bus, device, and function numbers. The new code uses pci_ bus and pci_dev structures. The first new PCI function is:


This function checks whether one or more PCI buses were found on the machine. The older kernels had a pcibios_present() function, whose usage is identical.

Having confirmed that PCI is actually present, you can scan the PCI buses for devices. PCI devices are identified by several configuration bytes. The main pair of these are the vendor and device identity.

Vendors are issued a unique identity and are supposed to issue unique device identifiers for the boards that they make. Another advantage of PCI is that it provides version and programming-interface information so that board variants can be discovered.

In Linux 2.2 a scan for a PCI device normally uses the pci_ find_device() function. A typical example would be:

struct pci_dev *pdev=NULL;

/* Found a device */

The pci_find_device takes three arguments. The first is the vendor identification, the second the board identity, and the third is the last return from the function, or NULL to indicate you wish to start scanning from the beginning. This example calls setup_device() for each instance of the device found.

Another nice thing that PCI does is that it handles all the resource configuration for you. Normally the PCI BIOS handles this, but on other platforms, the firmware- or architecture-specific Linux code will do this. By the time your driver comes to look for cards, they have been assigned system resources. Not only have they been assigned, but they can be retrieved without the user having to provide the information.

Linux provides the core information in the pci_dev structure. It also allows the per-card PCI configuration space to be read and written. Care should be taken whenever you are looking at resource data directly. On many systems, the values configured on the card will not match the values that the kernel provides. This is because many non-PC machines have multiple PCI buses, and the PCI buses are mapped into the system in ways that the cards on the bus cannot see.

Linux provides the IRQ and the PCI BARs (Base Address Registers) directly. You should always use the copies directly provided to avoid unpleasant non-x86 surprises. Listing One shows a setup_device() function.

Listing One: The setup_device () Function

void setup_device(struct pci_dev *dev)
int io_addr = dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK;
int irq = dev->irq;
u8 rev;

pci_read_config_byte(dev, PCI_REVISION_ID, &rev);

printk(“Found a WonderWidget 500 at I/O 0x%04X, IRQ %d.\n”, io_addr, irq);
printk(“Found a WonderWidget 600 at I/O 0x%04X, IRQ %d.\n”, io_addr, irq);

/* Check for a common BIOS problem – if you expect an IRQ you might not get it */
printk(KERN_ERR “BIOS has not assigned the WonderWidget an interrupt.\n”);

/* Now do the board initialization knowing the resources */
init_device(io_addr, irq, rev<64?0:1);


When your card is configured by the BIOS, certain features can be left disabled. In particular, most BIOSs leave the “master” bit cleared. This prevents boards from copying data into main memory of their own volition, for example if they don’t notice a reboot. If is the job of the bus-mastering drivers to enable this. Linux 2.2 provides a handy helper function:

pci_set_master(struct pci_dev *)

This will check if the flag needs setting, and, if so, set it and provide a convenient kernel message for the user.

The example setup_device function also uses pci_read_ config_byte to read data from the configuration space. The kernel provides a full set of functions relating to the configuration space. pci_read_ config_byte,pci_read_config_word, and pci_read_config_dword retrieve 8, 16, or 32 bits respectively from the configuration space. pci_write_config_byte, pci_write_config_word, and pci_write_config_dword write 8, 16, and 32 bits to the configuration space. The PCI configuration space is separate to both the I/O and memory resources on a PC and thus can be accessed only via these functions.

The final set of PCI functions that are sometimes useful scan the PCI bus in different ways. pci_find_class looks for devices that match a given class. The PCI specification categorizes devices into classes and allows you to scan by class for devices. Some specifications such as those shared by many vendors specify only a PCI class and maybe a programming revision. To find UHCI-specification USB controllers, for example, the kernel uses

struct pci_dev *pdev = NULL;

u8 type;
/* FOUND IT */

Another example of a shared interface that uses this is I2O. In these cases, the vendor identification is useful only for reporting the actual card type and occasionally for working around board-specific bugs.

The final way to scan for a PCI device is to use pci_ find_slot, which lets you scan the PCI slots and functions in a specific order. It is rarely useful but can be handy if you need to control the order of the PCI scan for a specific type of device. This normally occurs when you need to duplicate the order in which the on-board BIOS reports the cards, or when you want to force Linux and non-Linux drivers to report the controllers in the same order. pci_find_ slot() is passed the bus number and the device-function value (slot<<3 | function).

PCI Interrupts and Other Perils

An important PCI-bus concept not normally found in ISA bus devices is shared interrupt handling. PCI-bus interrupts are also level-triggered, which means that an interrupt is constantly indicated until the card decides to clear it. This combination of features puts some important constraints on the way the driver handles interrupts.

The driver should always register PCI interrupts with the SA_SHIRQ flag in order to indicate that the interrupt line can be shared. If it fails to do this, other PCI devices on the system may not be able to function, and the user will probably get peeved.

Since the interrupt is shared, both the PCI driver and the kernel need ways to talk about each handler for the interrupt. You must register interrupts for shared devices with a non-NULL dev_id; otherwise the kernel cannot tell different interrupt handlers apart when you need to free one with free_irq(). The dev_id is passed to the interrupt handler, so it is very useful anyway. For example, you might want to do:

if(request_irq(dev->irq, dev_interrupt,
SA_SHIRQ, “wonderwidget”,
return -EAGAIN;

and when finished, use

free_irq(dev->irq, dev)

to ensure that the correct interrupt using this IRQ line is freed.

This interrupt handler is invoked with the dev field that was passed. This makes life very simple. Instead of searching for devices using this interrupt, you can do something like Listing Two.

Listing Two: Using the dev_id

static void dev_interrupt(int irq, void *dev_id, struct pt_regs *regs)
struct wonderwidget *dev = dev_id;
u32 status;

/* It is important to exit interrupt handlers that are not for us as fast as possible */

if((status=inl(dev->port))==0) /* Not our interrupt */


The final thing to understand about PCI interrupts being shared is that you must be very careful that you always handle the interrupt. You also need to be sure you never generate an interrupt before you install a handler. Because the PCI bus is level-triggered, if you cause an interrupt you cannot handle, you will hang the machine. This means you should be particularly careful when writing initialization code that you register the interrupt handler before you enable interrupts on the device. Similarly, at shutdown, you need to be careful to disable interrupts on the card before you unregister the interrupt handler.

Supporting PCI bus on Linux requires the use of a few more functions than ISA bus, and a little care in the interrupt handling. In return, you get all the configuration without user intervention.

Alan Cox is a well-known Linux kernel hacker currently working on writing drivers, security auditing, Linux/SGI porting, and modular sound. He can be reached at alan@lxorguk.ukuu.org.uk.

Comments are closed.