Guide to PCI Device Driver Design and Programming in VxWorks

Overview

VxWorks is a real-time operating system (RTOS) developed by Wind River, providing robust support for hardware interfaces, including Peripheral Component Interconnect (PCI) devices. Writing PCI device drivers in VxWorks involves interacting with the PCI bus, configuring devices, managing interrupts, and providing an access interface for applications. This guide will walk you through the process of designing and implementing a PCI driver for a hypothetical device (such as a network or storage controller).

Prerequisites

  • <span>VxWorks Development Environment</span>: Workbench or command-line tools installed.
  • <span>PCI Device Information</span>: Vendor ID, Device ID, and hardware documentation (e.g., register mapping, interrupt behavior).
  • <span>Hardware Access</span>: Test target system with PCI devices.
  • <span>VxWorks BSP</span>: Board Support Package (BSP) configured for your hardware, enabling PCI support.

Step-by-Step Guide

  1. 1. Understand PCI Devices and VxWorks PCI Support

PCI devices are identified in the configuration space by their Vendor ID and Device ID. VxWorks provides a PCI library (pciConfigLib) to scan the bus, read and write configuration registers, and map device memory. Refer to the device datasheet to understand:

  • • Configuration space layout (e.g., Base Address Registers or BAR).
  • • Interrupt allocation.
  • • Memory-mapped I/O (MMIO) or port I/O requirements.

VxWorks initializes the PCI bus using the sysBusPci.c file in the BSP. Ensure your BSP supports PCI by checking for a call to pciConfigLibInit().

  1. 2. Initialize PCI Driver

First, define a structure to hold the driver state and write an initialization routine to find 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;
}
  1. 3. Map Device MemoryPCI devices expose memory regions through BAR. Use <span>pciDevMemMap()</span> 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;
}
  1. 4. Handle Interrupts

PCI devices typically use interrupts to signal events. In VxWorks, use <span>intConnect()</span> to connect an interrupt service routine (ISR).

#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;
}
  1. 5. Provide Driver Interface

Expose functions for applications to interact with the device (e.g., read and 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);
}
  1. 6. Testing and Debugging
  • • Load Driver: Compile the code as a VxWorks kernel module (.out file) and load it in the VxWorks shell using <span>ld < myDriver.out</span>.
  • • Test Command: Add shell command (e.g., myPciTest()) to validate 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(), logMsg(), or Workbench debugger to trace execution.
  1. 7. Optimize and Refine
  • • Error Handling: Add robust checks for failure cases (e.g., unmapped memory, device not found).
  • • Multitasking Support: If multiple tasks access the driver, use semaphores (semMCreate()) to ensure thread safety.
  • • Power Management: Implement suspend/resume hooks as required by the BSP.

Key Considerations

  • • Byte Order: PCI devices may use little-endian format; ensure compatibility with VxWorks byte order settings.
  • • Performance: Minimize register accesses and optimize interrupt handling to meet real-time constraints.
  • • Device-Specific Logic: Adjust register access and interrupt handling according to device specifications.

Example Integration

Call in the sysLib.c of the BSP or a custom initialization file:

void sysAppInit(void) {
    if (myPciDriverInit() == OK) {
        myPciInterruptSetup();
        myPciTest();
    }
}

Resources

  • • VxWorks Documentation: Refer to the VxWorks Kernel Programmer’s Guide and API reference for <span>pciConfigLib</span> and <span>vxbLib</span>.
  • • Device Datasheet: Key information for register-level programming.
  • • Wind River Support: For BSP-specific issues or advanced debugging.

This guide lays the foundation for PCI driver development in VxWorks. Please adjust the code according to your specific device, such as replacing placeholder values (vendor/device IDs, register offsets, etc.) with actual values from the hardware documentation.

Leave a Comment