Overview #
VxWorks, a real-time operating system (RTOS) from Wind River, provides robust support for hardware interfacing, including Peripheral Component Interconnect (PCI) devices. Writing a PCI device driver in VxWorks involves interacting with the PCI bus, configuring the device, managing interrupts, and providing an interface for application-level access. This guide walks you through the process of designing and implementing a PCI driver for a hypothetical device (e.g., a network or storage controller).
Prerequisites #
VxWorks Development Environment
: Installed with Workbench or command-line tools.PCI Device Details
: Vendor ID, Device ID, and hardware documentation (e.g., register map, interrupt behavior).Hardware Access
: A target system with a PCI device for testing.VxWorks BSP
: A Board Support Package configured for your hardware, with PCI support enabled.
Step-by-Step Guide #
- Understand the PCI Device and VxWorks PCI Support
PCI devices are identified by a Vendor ID and Device ID, stored in the device’s configuration space. VxWorks provides a PCI library (
pciConfigLib
) to scan the bus, read/write configuration registers, and map device memory. Review your device’s datasheet for:
- Configuration space layout (e.g., Base Address Registers or BARs).
- Interrupt assignments.
- Memory-mapped I/O (MMIO) or port I/O requirements.
VxWorks uses the sysBusPci.c
file in the BSP to initialize the PCI bus. Ensure your BSP supports PCI by checking for calls like pciConfigLibInit()
.
- Initialize the PCI Driver
Start by defining a structure to hold your driver’s state and writing an initialization routine to locate and configure the PCI device.
#include <vxWorks.h>
#include <hwif/vxBusLib.h>
#include <hwif/buslib/pciConfigLib.h>
#define MY_VENDOR_ID 0x1234 /* Replace with your device's Vendor ID */
#define MY_DEVICE_ID 0x5678 /* Replace with your device's Device ID */
typedef struct {
UINT32 bar0Addr; /* Base Address Register 0 (example) */
UINT32 irqLine; /* Interrupt line */
BOOL initialized; /* Driver state */
} MyPciDevice;
MyPciDevice myDevice = {0};
/* Initialization function */
STATUS myPciDriverInit(void) {
int busNo, devNo, funcNo;
UINT32 devVendor;
/* Scan PCI bus for the device */
if (pciFindDevice(MY_DEVICE_ID, MY_VENDOR_ID, 0, &busNo, &devNo, &funcNo) == ERROR) {
printf("Device not found!\n");
return ERROR;
}
/* Read Vendor/Device ID to confirm */
pciConfigInLong(busNo, devNo, funcNo, PCI_CFG_VENDOR_ID, &devVendor);
printf("Found device: Vendor=0x%04X, Device=0x%04X\n", devVendor & 0xFFFF, devVendor >> 16);
/* Get BAR0 (example memory region) */
pciConfigInLong(busNo, devNo, funcNo, PCI_CFG_BASE_ADDRESS_0, &myDevice.bar0Addr);
myDevice.bar0Addr &= PCI_BAR_MEM_ADDR_MASK; /* Mask off flags to get base address */
/* Get IRQ line */
pciConfigInByte(busNo, devNo, funcNo, PCI_CFG_INTERRUPT_LINE, (UINT8*)&myDevice.irqLine);
myDevice.initialized = TRUE;
return OK;
}
- Map Device Memory
PCI devices expose memory regions via BARs. Use pciDevMemMap()
or VxWorks’ memory mapping functions to access these regions.
#include <vmLib.h>
void* myPciMapMemory(void) {
void* mappedAddr = NULL;
if (!myDevice.initialized) {
printf("Device not initialized!\n");
return NULL;
}
/* Map the BAR0 memory region (assuming 4KB size as an example) */
mappedAddr = (void*)vxbPciDevMemMap(myDevice.bar0Addr, 0x1000, VM_STATE_MASK_VALID | VM_STATE_MASK_WRITABLE);
if (mappedAddr == NULL) {
printf("Failed to map PCI memory!\n");
} else {
printf("Mapped BAR0 at 0x%08X\n", (UINT32)mappedAddr);
}
return mappedAddr;
}
- Handle Interrupts
PCI devices typically use interrupts to signal events. In VxWorks, connect an Interrupt Service Routine (ISR) using intConnect()
.
#include <intLib.h>
void myPciIsr(void* arg) {
MyPciDevice* dev = (MyPciDevice*)arg;
/* Example: Clear interrupt flag in device register (device-specific) */
printf("Interrupt triggered on IRQ %d!\n", dev->irqLine);
}
STATUS myPciInterruptSetup(void) {
if (!myDevice.initialized) return ERROR;
/* Connect ISR to IRQ */
if (intConnect(INUM_TO_IVEC(myDevice.irqLine), myPciIsr, (int)&myDevice) == ERROR) {
printf("Failed to connect ISR!\n");
return ERROR;
}
/* Enable interrupts (device-specific register write might be needed) */
intEnable(myDevice.irqLine);
return OK;
}
- Provide Driver Interface
Expose functions for applications to interact with the device (e.g., read/write data).
STATUS myPciWrite(UINT32 offset, UINT32 value, void* baseAddr) {
if (!myDevice.initialized || baseAddr == NULL) return ERROR;
*(volatile UINT32*)((UINT32)baseAddr + offset) = value;
return OK;
}
UINT32 myPciRead(UINT32 offset, void* baseAddr) {
if (!myDevice.initialized || baseAddr == NULL) return 0;
return *(volatile UINT32*)((UINT32)baseAddr + offset);
}
- Test and Debug
Load the Driver
: Compile your code into a VxWorks kernel module (.out file) and load it usingld < myDriver.out
in the VxWorks shell.Test Commands
: Add shell commands (e.g.,myPciTest()
) to verify functionality:
void myPciTest(void) {
void* base = myPciMapMemory();
if (base) {
myPciWrite(0x10, 0xDEADBEEF, base); /* Example write */
printf("Read back: 0x%08X\n", myPciRead(0x10, base));
}
}
- Debugging: Use
printf()
, VxWorks’logMsg()
, or Workbench’s debugger to trace execution.
- Optimize and Finalize
- Error Handling: Add robust checks for failures (e.g., unmapped memory, device not found).
- Multitasking: Ensure thread safety with semaphores (
semMCreate()
) if the driver is accessed by multiple tasks. - Power Management: Implement suspend/resume hooks if required by your BSP.
Key Considerations #
Endianness
: PCI devices may use little-endian format; ensure compatibility with VxWorks’ endian settings.Performance
: Minimize register accesses and optimize interrupt handling for real-time constraints.Device-Specific Logic
: Tailor register accesses and interrupt handling to your device’s specification.
Example Integration #
In your BSP’s sysLib.c or a custom initialization file, call:
void sysAppInit(void) {
if (myPciDriverInit() == OK) {
myPciInterruptSetup();
myPciTest();
}
}
Resources #
- VxWorks Documentation: Refer to the VxWorks Kernel Programmer’s Guide and API Reference for pciConfigLib and vxbLib.
- Device Datasheet: Critical for register-level programming.
- Wind River Support: For BSP-specific quirks or advanced debugging.
This guide provides a foundation for PCI driver development in VxWorks. Adapt the code to your specific device by replacing placeholder values (e.g., Vendor/Device IDs, register offsets) with those from your hardware documentation.