Writing a Driver for a PCI Bridge

Writing a driver for a PCI bridge is not a simple task. Most PCI bridges do not need any special drivers, and in general, if you are designing the hardware, you are urged to use bridge parts that comply with PCI specifications and thus do not need special drivers. This is not always possible in certain edge cases, however. This is generally because either the hardware already exists or because a transparent bridge does not provide the needed functionality.

This section explains how to write drivers for the most common types of programmable PCI bridges and explains some of the differences between those types of bridges from both a hardware perspective and from a programming perspective.

Types of PCI Bridges

There are three basic types of PCI bridges: PCI host controllers, transparent PCI bus bridges, and nontransparent PCI bus bridges. They each have their own special issues.

Writing Drivers for Host Controllers

Many of the details of writing a driver for a PCI host controller are device-specific. In many respects, it is similar to writing any other device driver for a device on the main logic board. It is different, however, in that with a PCI host controller you must configure the controller in such a way that the rest of the PCI subsystem can talk to devices behind it. The issues of interrupt assignment and address range assignment are particularly significant in this regard.

The basic process is as follows. The host controller has certain Open Firmware properties (or for Darwin/x86, the platform expert presents certain properties) that identify the physical address and size of its register set. You should map this range into the kernel’s virtual address space using an IOMemoryMap object.

You then create a PCI address range for the bridge and set the registers on the device appropriately based on the range you obtained. You also need to provide a standard set of routines for communicating with devices on the PCI bus. The system then uses these routines to finish configuring the devices on the bus.

The following methods should be implemented in a PCI host controller driver:

start

Gets information about the bridge (as needed) and maps the bridge hardware into the kernel’s address space.

configure

Configures the controller with the base address and size of the PCI bus.

free

Releases any data structures created by the driver, including the mapping of the controller into kernel memory, as well as any locks, queues, and other dynamically allocated structures that the driver may have created during operation or within the start routine.

ioDeviceMemory

Returns a pointer of type IODeviceMemory to a class instance that contains information about the base address and size of the bus’s memory space. This pointer is created in the start routine.

firstBusNum

Returns the first PCI bus number that lives beyond this controller.

lastBusNum

Returns the last PCI bus number that lives beyond this controller.

getBridgeSpace

Returns the IOPCIAddressSpace entry representing the bridge. This entry contains the bus number, device number, and configuration space bitfield for the bridge itself. The bits field, which holds the bitfield, should be set to 0 since it is not relevant except for devices that reside on the PCI bus.

setConfigSpace

Sets a device’s configuration bits (bus master enable, memory space enable, and so on) using a write to PCI configuration space, based on information contained in an IOPCIAddressSpace entry (bus number, device number, and bitfield).

configRead32, configRead16, and configRead8

Reads 32, 16, or 8 bytes from an address in PCI configuration space.

configWrite32, configWrite16, and configWrite8

Writes 32, 16, or 8 bytes to an address in PCI configuration space.

callPlatformFunction

Call a method in the platform expert. This method is defined in the IOPCIBridge base class, but it may be overridden in an individual PCI bridge driver to allow you to add additional calls specific to your bridge or to override the behavior of existing calls.

For more information, you should study the AppleMacRiscPCI driver and the other PCI drivers available from Apple’s Open Source website, which can be found at http://www.opensource.apple.com, or contact Apple Developer Technical Support.

Writing Drivers for Transparent Bridges

In general, transparent PCI bridges do not require special drivers, because they obey a very strict specification. However, if a transparent bridge fails to follow the PCI specification, you may need to change the behavior of the transparent bridge driver to support your particular device.

To easily create a driver for a non-compliant transparent bridge, you should subclass the standard Apple transparent bridge driver and modify or extend the basic initialization code. If you do this, be certain to set appropriate passive matching parameters or use active matching so that your modified driver matches against only the particular bridge in question. Otherwise, your driver could break the normal operation of other PCI bridges in the system. For details on passive and active matching, see Open Firmware Matching or read the appropriate parts of IOKit Fundamentals.

Writing Drivers for Nontransparent Bridges

Fundamentally, nontransparent bridges, which are also often called embedded bridges, are not what most people would describe as bridges, in that they do not generally make devices on one side of the bridge accessible to devices on the other side, nor do they pass traditional interrupts from one side to the other. What makes them bridges is that they are connected to and provide limited communication between two busses.

Drivers for nontransparent bridges are highly device-specific. They appear to each side as a single PCI device, not a bridge. Neither side can see devices on the opposite side of the bridge. They can, however, see address ranges that are explicitly mapped and perform basic interrupt-based communication between the two sides. Thus, it is possible, to a limited degree, to control devices across such a bridge, though they are not generally used in this way.

These devices generally have a series of BATs (block address translation registers) that map a large range onto another large range. In most cases, the client must actually know how to control these BATs whether it is a driver that controls a physical device behind the bridge or a pseudo-device that maps memory across the bridge for backplane networking. You can make the BATs accessible to the client through a user client if the client is in user space, or by simply publishing a nub if the client is another driver within the kernel.

Similarly, these devices often have “doorbell” interrupts for basic communication. These interrupts are triggered by the computer on one side of the bridge and are seen by the computer on the other. Your client interface should reflect this and should provide access as appropriate.

In all other respects, however, nontransparent bridges behave like normal PCI devices from a software perspective. Thus, if you need to write a driver for a nontransparent bridge, you should read Writing a Driver for a PCI Device for more information.

Other Bridges

Many other types of PCI bridges exist. Most of them fall into one of these categories:

With the exception of special features like ejection and device detection, CardBus devices are just PCI devices, and CardBus bridges behave like transparent bridges. Thus, you should read Writing Drivers for Transparent Bridges and Writing a Driver for a PCI Device for general information, then look in the IOPCCardFamily from Apple’s Open Source website for CardBus-specific extensions.

Other bridges, such as those involving ISA or other busses, are beyond the scope of this document. For help with such bridges, you should contact Apple Developer Technical Support.

Debugging a PCI Bridge

Because most Ethernet hardware is found on the PCI bus, debugging a PCI host bridge using traditional Ethernet-based (gdb) debugging can pose difficulties. For this reason, you should consider building a custom kernel with ddb (serial debugging) support enabled. With this option, you will still be able to debug if the address mappings in your host bridge get inadvertently changed and attaching by Ethernet becomes impossible.

For more information on building a kernel with ddb support, and for instructions and advice on debugging drivers using gdb and ddb, see Kernel Programming Guide, available from the Darwin section of Apple’s developer documentation website, http://developer.apple.com/Documentation.