Chapter 30. The eCos PCI Library

Table of Contents
PCI Library
PCI Library reference

The PCI library is an optional part of eCos, and is only applicable to some platforms.

PCI Library

The eCos PCI library provides the following functionality:

  1. Scan the PCI bus for specific devices or devices of a certain class.

  2. Read and change generic PCI information.

  3. Read and change device-specific PCI information.

  4. Allocate PCI memory and IO space to devices.

  5. Translate a device's PCI interrupts to equivalent HAL vectors.

Example code fragments are from the pci1 test (see io/pci/<release>/tests/pci1.c).

All of the functions described below are declared in the header file <cyg/io/pci.h> which all clients of the PCI library should include.

Scanning for devices

After the bus has been initialized, it is possible to scan it for devices. This is done using the function:

cyg_bool cyg_pci_find_next(  cyg_pci_device_id cur_devid, 
	                     cyg_pci_device_id *next_devid );

It will scan the bus for devices starting at cur_devid. If a device is found, its devid is stored in next_devid and the function returns true.

The pci1 test's outer loop looks like:

    cyg_pci_init();
    if (cyg_pci_find_next(CYG_PCI_NULL_DEVID, &devid)) {
        do {
             <use devid>
        } while (cyg_pci_find_next(devid, &devid));
    }

What happens is that the bus gets initialized and a scan is started. CYG_PCI_NULL_DEVID causes cyg_pci_find_next() to restart its scan. If the bus does not contain any devices, the first call to cyg_pci_find_next() will return false.

If the call returns true, a loop is entered where the found devid is used. After devid processing has completed, the next device on the bus is searched for; cyg_pci_find_next() continues its scan from the current devid. The loop terminates when no more devices are found on the bus.

This is the generic way of scanning the bus, enumerating all the devices on the bus. But if the application is looking for a device of a given device class (e.g., a SCSI controller), or a specific vendor device, these functions simplify the task a bit:

cyg_bool cyg_pci_find_class(  cyg_uint32 dev_class,
                              cyg_pci_device_id *devid );
cyg_bool cyg_pci_find_device(  cyg_uint16 vendor, cyg_uint16 device,
                               cyg_pci_device_id *devid );

They work just like cyg_pci_find_next(), but only return true when the dev_class or vendor/device qualifiers match those of a device on the bus. The devid serves as both an input and an output operand: the scan starts at the given device, and if a device is found devid is updated with the value for the found device.

The <cyg/io/pci_cfg.h> header file (included by pci.h) contains definitions for PCI class, vendor and device codes which can be used as arguments to the find functions. The list of vendor and device codes is not complete: add new codes as necessary. If possible also register the codes at the PCI Code List (http://www.yourvote.com/pci) which is where the eCos definitions are generated from.

Generic config information

When a valid device ID (devid) is found using one of the above functions, the associated device can be queried and controlled using the functions:

void cyg_pci_get_device_info (  cyg_pci_device_id devid, 
                                cyg_pci_device *dev_info );
void cyg_pci_set_device_info (  cyg_pci_device_id devid, 
                                cyg_pci_device *dev_info );

The cyg_pci_device structure (defined in pci.h) primarily holds information as described by the PCI specification [1]. The pci1 test prints out some of this information:

            // Get device info
            cyg_pci_get_device_info(devid, &dev_info);
            diag_printf("\n Command   0x%04x, Status 0x%04x\n",
                        dev_info.command, dev_info.status);

The command register can also be written to, controlling (among other things) whether the device responds to IO and memory access from the bus.

Specific config information

The above functions only allow access to generic PCI config registers. A device can have extra config registers not specified by the PCI specification. These can be accessed with these functions:

void cyg_pci_read_config_uint8(  cyg_pci_device_id devid,
                                 cyg_uint8 offset, cyg_uint8 *val);
void cyg_pci_read_config_uint16(  cyg_pci_device_id devid,
                                  cyg_uint8 offset, cyg_uint16 *val);
void cyg_pci_read_config_uint32(  cyg_pci_device_id devid,
                                  cyg_uint8 offset, cyg_uint32 *val);
void cyg_pci_write_config_uint8(  cyg_pci_device_id devid,
                                  cyg_uint8 offset, cyg_uint8 val);
void cyg_pci_write_config_uint16(  cyg_pci_device_id devid,
                                   cyg_uint8 offset, cyg_uint16 val);
void cyg_pci_write_config_uint32(  cyg_pci_device_id devid,
                                   cyg_uint8 offset, cyg_uint32 val);

The write functions should only be used for device-specific config registers since using them on generic registers may invalidate the contents of a previously fetched cyg_pci_device structure.

Allocating memory

A PCI device ignores all IO and memory access from the PCI bus until it has been activated. Activation cannot happen until after device configuration. Configuration means telling the device where it should map its IO and memory resources. This is done with one of the following functions::

cyg_bool cyg_pci_configure_device( cyg_pci_device *dev_info );
cyg_bool cyg_pci_configure_bus( cyg_uint8 bus, cyg_uint8 *next_bus );

The cyg_pci_configure_device handles all IO and memory regions that need configuration on non-bridge devices. On platforms with multiple busses connected by bridges, the cyg_pci_configure_bus function should be used. It will recursively configure all devices on the given bus and all subordinate busses. cyg_pci_configure_bus will use cyg_pci_configure_device to configure individual non-bridge devices.

Each region is represented in the PCI device's config space by BARs (Base Address Registers) and is handled individually according to type using these functions:

cyg_bool cyg_pci_allocate_memory(  cyg_pci_device *dev_info,
                                   cyg_uint32 bar, 
                                   CYG_PCI_ADDRESS64 *base );
cyg_bool cyg_pci_allocate_io(  cyg_pci_device *dev_info,
                               cyg_uint32 bar, 
                               CYG_PCI_ADDRESS32 *base );

The memory bases (in two distinct address spaces) are increased as memory regions are allocated to devices. Allocation will fail (the function returns false) if the base exceeds the limits of the address space (IO is 1MB, memory is 2^32 or 2^64 bytes).

These functions can also be called directly by the application/driver if necessary, but this should not be necessary.

The bases are initialized with default values provided by the HAL. It is possible for an application to override these using the following functions:

void cyg_pci_set_memory_base(  CYG_PCI_ADDRESS64 base );
void cyg_pci_set_io_base( CYG_PCI_ADDRESS32 base );

When a device has been configured, the cyg_pci_device structure will contain the physical address in the CPU's address space where the device's memory regions can be accessed.

This information is provided in base_map[] - there is a 32 bit word for each of the device's BARs. For 32 bit PCI memory regions, each 32 bit word will be an actual pointer that can be used immediately by the driver: the memory space will normally be linearly addressable by the CPU.

However, for 64 bit PCI memory regions, some (or all) of the region may be outside of the CPUs address space. In this case the driver will need to know how to access the region in segments. This functionality may be adopted by the eCos HAL if deemed useful in the future. The 2GB available on many systems should suffice though.

Links

See these links for more information about PCI:

  1. http://www.pcisig.com/ - information on the PCI specifications

  2. http://www.yourvote.com/pci/ - list of vendor and device IDs

  3. http://www.picmg.org/ - PCI Industrial Computer Manufacturers Group