The eCos PCI Library

To Contents

To previous page

To next page

 




The eCos PCI Library

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

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.

Initialising the bus

The PCI bus needs to be initialized before it can be used. This only needs to be done once - some HALs may do it as part of the platform initialization procedure, other HALs may leave it to the application to do it. The following function will do the initialization only once, so it's safe to call from multiple drivers:

 
void cyg_pci_init( void );

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 this function:

 
cyg_bool cyg_pci_configure_device( cyg_pci_device *dev_info );

This function handles all IO and memory regions that need configuration on the device. Each region is represented in the PCI device's config space by one of six 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 be 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.

Interrupts

A device may generate interrupts. The HAL vector associated with a given device on the bus is platform specific. This function allows a driver to find the actual interrupt vector for a given device:

 
cyg_bool cyg_pci_translate_interrupt( cyg_pci_device *dev_info,
                                       CYG_ADDRWORD *vec );

If the function returns false, no interrupts will be generated by the device. If it returns true, the CYG_ADDRWORD pointed to by vec is updated with the HAL interrupt vector the device will be using. This is how the function is used in the pci1 test:

 
            if (cyg_pci_translate_interrupt(&dev_info, &irq))
                diag_printf(" Wired to HAL vector %d\n", irq);
            else
 
                diag_printf(" Does not generate interrupts.\n");

The application/drive should attach an interrupt handler to a device's interrupt before activating the device.

Activating a device

When the device has been allocated memory space it can be activated. This is not done by the library since a driver may have to initialize more state on the device before it can be safely activated.

Activating the device is done by enabling flags in its command word. As an example, see the pci1 test which can be configured to enable the devices it finds. This allows these to be accessed from GDB (if a breakpoint is set on cyg_test_exit):

 
#ifdef ENABLE_PCI_DEVICES
                {
                    cyg_uint16 cmd;
 
                    // Don't use cyg_pci_set_device_info since it clears
                    // some of the fields we want to print out below.
                    cyg_pci_read_config_uint16(dev_info.devid,
                                               CYG_PCI_CFG_COMMAND, &cmd);
                    cmd |=
CYG_PCI_CFG_COMMAND_IO|CYG_PCI_CFG_COMMAND_MEMORY;
                    cyg_pci_write_config_uint16(dev_info.devid,
                                                CYG_PCI_CFG_COMMAND, cmd);
                }
                diag_printf(" **** Device IO and MEM access enabled\n");
#endif

Note: that the best way to activate a device is actually through cyg_pci_set_device_info(), but in this particular case the cyg_pci_device structure contents from before the activation is required for printout further down in the code.

Links

See these links for more information about PCI.

1) See http://www.pcisig.com (information on the PCI specifications)
2) See http://www.yourvote.com/pci (list of vendor and device IDs)
3) See http://www.picmg.org (PCI Industrial Computer Manufacturers Group)

PCI Library reference

This document defines the PCI Support Library for eCos.

The PCI support library provides a set of routines for accessing the PCI bus configuration space in a portable manner. This is provided by two APIs. The high level API is used by device drivers, or other code, to access the PCI configuration space portably. The low level API is used by the PCI library itself to access the hardware in a platform-specific manner, and may also be used by device drivers to access the PCI configuration space directly.

Underlying the low-level API is HAL support for the basic Configuration space operations. These should not generally be used by any code other than the PCI library, and are present in the HAL to allow low level initialization of the PCI bus and devices to take place if necessary.

PCI Library API

The PCI library provides the following routines and types for accessing the PCI configuration space.

The API for the PCI library is found in the header file <cyg/io/pci.h>.

Definitions

The header file contains definitions for the common configuration structure offsets and specimin values for device, vendor and class code.

Types and data structures

The following types are defined:

 
typedef CYG_WORD32 cyg_pci_device_id;

This is comprised of the bus number, device number and functional unit number packed into a single word. The macro CYG_PCI_DEV_MAKE_ID() may be used to construct a device id from the bus, device and functional unit numbers of a device. Similarly the macros CYG_PCI_DEV_GET_BUS() and CYG_PCI_DEV_GET_DEVFN() may be used to extract them. It should not be necessary to use these macros under normal circumstances.

 
typedef struct cyg_pci_device;

This structure is used to contain data read from a PCI device's configuration header by cyg_pci_get_device_info(). It is also used to record the resource allocations made to the device.

 
typedef CYG_WORD64 CYG_PCI_ADDRESS64;
typedef CYG_WORD32 CYG_PCI_ADDRESS32;

Pointers in the PCI address space are 32 bit (IO space) or 32/64 bit (memory space). In most platform and device configurations all of PCI memory will be linearly addressable using only 32 bit pointers as read from base_map[].

The 64 bit type is used to allow handling 64 bit devices in the future, should it be necessary, without changing the library's API.

Functions

 
void cyg_pci_init(void);

Initialize the PCI library and establish contact with the hardware. This function is idempotent and can be called either by all drivers in the system, or just from an application initialization function.

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

Searches the PCI bus configuration space for a device with the given vendor and device ids. The search starts at the device pointed to by devid, or at the first slot if it contains CYG_PCI_NULL_DEVID. *devid will be updated with the ID of the next device found. Returns true if one is found and false if not.

 
cyg_bool cyg_pci_find_class( cyg_uint32 dev_class,
			     cyg_pci_device_id *devid );

Searches the PCI bus configuration space for a device with the given class code. The search starts at the device pointed to by devid, or at the first slot if it contains CYG_PCI_NULL_DEVID.

*devid will be updated with the ID of the next device found. Returns true if one is found and false if not.

 
cyg_bool cyg_pci_find_next( cyg_pci_device_id cur_devid,
			    cyg_pci_device_id *next_devid );

Searches the PCI configuration space for the next valid device after cur_devid. If cur_devid is given the value CYG_PCI_NULL_DEVID, then the search starts at the first slot. It is permitted for next_devid to point to cur_devid. Returns true if another device is found and false if not.

 
void cyg_pci_get_device_info ( cyg_pci_device_id devid,
			       cyg_pci_device *dev_info );

This function gets the PCI configuration information for the device indicated in devid. The common fields of the cyg_pci_device structure, and the appropriate fields of the relevant header union member are filled in from the device's configuration space. If the device has not been enabled, then this function will also fetch the size and type information from the base address registers and place it in the base_size[] array.

 
void cyg_pci_set_device_info ( cyg_pci_device_id devid,
			       cyg_pci_device *dev_info );

This function sets the PCI configuration information for the device indicated in devid. Only the configuration space registers that are writable are actually written. Once all the fields have been written, the device info will be read back into *dev_info, so that it reflects the true state of the hardware.

 
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 );

These functions read registers of the appropriate size from the configuration space of the given device. They should mainly be used to access registers that are device specific. General PCI registers are best accessed through cyg_pci_get_device_info().

 
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 );

These functions write registers of the appropriate size to the configuration space of the given device. They should mainly be used to access registers that are device specific. General PCI registers are best accessed through cyg_pci_get_device_info(). Writing the general registers this way may render the contents of a cyg_pci_device structure invalid.

Resource allocation

These routines allocate memory and IO space to PCI devices.

 
cyg_bool cyg_pci_configure_device( cyg_pci_device *dev_info )

Allocate memory and IO space to all base address registers using the current memory and IO base addresses in the library. The allocated base addresses, translated into directly usable values, will be put into the matching base_map[] entries in *dev_info. If *dev_info does not contain valid base_size[] entries, then the result is false. This function will also call cyg_pci_translate_interrupt() to put the interrupt vector into the hal_vector entry.

 
cyg_bool cyg_pci_translate_interrupt( cyg_pci_device *dev_info, CYG_ADDRWORD *vec );

Translate the device's PCI interrupt (INTA#-INTD#) to the associated HAL vector. This may also depend on which slot the device occupies. If the device may generate interrupts, the translated vector number will be stored in vec and the result is true. Otherwise the result is false.

 
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 );

These routines allocate memory or IO space to the base address register indicated by bar. The base address in *base will be correctly aligned and the address of the next free location will be written back into it if the allocation succeeds. If the base address register is of the wrong type for this allocation, or dev_info does not contain valid base_size[] entries, the result is false. These functions allow a device driver to set up its own mappings if it wants. Most devices should probably use cyg_pci_configure_device().

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

These routines set the base addresses for memory and IO mappings to be used by the memory allocation routines. Normally these base addresses will be set to default values based on the platform. These routines allow these to be changed by application code if necessary.

PCI Library Hardware API

This API is used by the PCI library to access the PCI bus configuration space. Although it should not normally be necessary, this API may also be used by device driver or application code to perform PCI bus operations not supported by the PCI library.

 
void cyg_pcihw_init(void);

Initialize the PCI hardware so that the configuration space may be accessed.

 
void cyg_pcihw_read_config_uint8( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint8 *val);
void cyg_pcihw_read_config_uint16( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint16 *val);
void cyg_pcihw_read_config_uint32( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint32 *val);

These functions read a register of the appropriate size from the PCI configuration space at an address composed from the bus, devfn and offset arguments.

 
void cyg_pcihw_write_config_uint8( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint8 val);
void cyg_pcihw_write_config_uint16( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint16 val);
void cyg_pcihw_write_config_uint32( cyg_uint8 bus, cyg_uint8 devfn, cyg_uint8 offset, cyg_uint32 val);

These functions write a register of the appropriate size to the PCI configuration space at an address composed from the bus, devfn and offset arguments.

 
cyg_bool cyg_pcihw_translate_interrupt( cyg_uint8 bus, cyg_uint8 devfn, CYG_ADDRWORD *vec);

This function interrogates the device and determines which HAL interrupt vector it is connected to.

HAL PCI support

HAL support consists of a set of C macros that provide the implementation of the low level PCI API.

 
HAL_PCI_INIT()

Initialize the PCI bus.

 
HAL_PCI_READ_UINT8( bus, devfn, offset, val )
HAL_PCI_READ_UINT16( bus, devfn, offset, val )
HAL_PCI_READ_UINT32( bus, devfn, offset, val )

Read a value from the PCI configuration space of the appropriate size at an address composed from the bus, devfn and offset.

 
HAL_PCI_WRITE_UINT8( bus, devfn, offset, val )
HAL_PCI_WRITE_UINT16( bus, devfn, offset, val )
HAL_PCI_WRITE_UINT32( bus, devfn, offset, val )

Write a value to the PCI configuration space of the appropriate size at an address composed from the bus, devfn and offset.

 
HAL_PCI_TRANSLATE_INTERRUPT( bus, devfn, *vec, valid )

Translate the device's interrupt line into a HAL interrupt vector.

 
HAL_PCI_ALLOC_BASE_MEMORY
HAL_PCI_ALLOC_BASE_IO

These macros define the default base addresses used to initialize the memory and IO allocation pointers.

 
HAL_PCI_PHYSICAL_MEMORY_BASE
HAL_PCI_PHYSICAL_IO_BASE

PCI memory and IO range do not always correspond directly to physical memory or IO addresses. Frequently the PCI address spaces are windowed into the processor's address range at some offset. These macros define offsets to be added to the PCI base addresses to translate PCI bus addresses into physical memory addresses that can be used to access the allocated memory or IO space.

NOTE

The chunk of PCI memory space directly addressable though the window by the CPU may be smaller than the amount of PCI memory actually provided. In that case drivers will have to access PCI memory space in segments. Doing this will be platform specific and is currently beyond the scope of the HAL.


The eCos PCI Library

To Contents

To previous page

To next page