Previous - Up - Next

11.3   Writing a PCI Device in C/C++

11.3.1   API and Interfaces

The following APIs and interfaces can be used when writing PCI devices in Simics.

PCI Device API
Used for PCI devices, and optionally for host-to-PCI bridges.
Simics API
Since it is used for all Simics modules, it also applies to PCI devices.
Implemented by the pci-bus module in Simics. This interface shouldn't be used directly by PCI devices. The PCI Device API should be used instead. PCI bridges can use this interface to configure the pci-bus.
Simics internal interface that shouldn't be used directly. May change without notice. Developers should use the PCI Device API instead.
This is a dummy interface currently implemented by all PCI bridges. It will probably be removed in a future Simics version.
This interface should only be implemented by PCI devices that handle interrupts, i.e., when a device other than the bridge handles PCI interrupts on the PCI bus.

11.3.2   PCI Device Setup

When creating a PCI device using the PCI Device API, some common initialization must be done. First, typically in the init_local function of the module, the PCI device-specific attributes must be registered. This is done by calling PCI_register_device_attributes.

Then, when the object is created, the generic part of the PCI device must initialized. The PCI_device_init function is used for this, and is called from the device's new_instance function. It takes several function pointers as arguments. These functions are used to override the generic PCI device behavior. Most of them are optional, but some must be implemented by the new device.

Once the generic part is initialized, the configuration registers should be setup. Several configuration registers are device specific and have be assigned their values here. Example:

PCI_write_config_register(&bcm_ptr->pci, PCI_VENDOR_ID,   0x12ae);
PCI_write_config_register(&bcm_ptr->pci, PCI_DEVICE_ID,   0x1647);
PCI_write_config_register(&bcm_ptr->pci, PCI_STATUS,      0x02b0);
PCI_write_config_register(&bcm_ptr->pci, PCI_REVISION_ID, 0x00);
PCI_write_config_register(&bcm_ptr->pci, PCI_CLASS_CODE,  0x020000);
PCI_write_config_register(&bcm_ptr->pci, PCI_HEADER_TYPE, 0x00);

11.3.3   PCI Device Mappings and BARs

Among the configuration registers there are some base address registers (BARs) that specify the memory and I/O mappings that the device supports. The software uses the BARs to figure out how large the mappings are, where they can be placed in memory, and also to actually map the device in the corresponding memory spaces.

The PCI Device API has two functions that can be called from new_instance to tell the generic PCI code to handle accesses to a BAR. PCI_handle_mapping32 and PCI_handle_mapping64 are used for 32 and 64-bit mappings respectively. Note that 64-bit mappings use two base address registers.

There are also PCI Device API functions to modify the mappings for the device at runtime. This is usually not needed, but there are PCI devices that allow software to modify the mappings in more ways than with BARs, and in this case the functions in question are useful: PCI_set_map_base, PCI_get_map_base, PCI_set_map_size, PCI_get_map_base, PCI_set_map_enable, PCI_get_map_enable, PCI_set_map_offset and PCI_get_map_offset.

11.3.4   Accessing Memory

There are several functions in the PCI Device API that can be used to access the configuration, memory and I/O spaces. Calling them is equivalent to issuing PCI bus transactions in a real system. This should only be done by master capable devices.

PCI_data_from_memory and PCI_data_to_memory access raw data from a specified memory space. No endian conversion is performed. These functions are often used for DMA block transactions.

PCI_value_from_memory and PCI_value_to_memory use a value in host-endian order, and performs byte-swapping if needed. This is for example useful when the device reads data from memory that it should interpret.

11.3.5   Interrupts

The functions PCI_raise_interrupt and PCI_lower_interrupt changes the output value of the interrupt pin used by the device. The interrupt pin is specified in the INTERRUPT_PIN configuration register.

If the interrupt pin is already asserted, a call to PCI_raise_interrupt has no effect, and similar for PCI_lower_interrupt when the interrupt pin already is lowered. This is important to be able to model level triggered interrupts, where the interrupt target can count the raise and lower calls from different devices to determine the actual interrupt signal level.

Previous - Up - Next