Skip to main content

Guide to PCI Device Driver Design and Programming in VxWorks

·870 words·5 mins
VxWorks PCI Device Driver
Table of Contents
BSP - This article is part of a series.
Part 7: This Article

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
#

  1. 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().

  1. 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;
}
  1. 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;
}
  1. 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;
}
  1. 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);
}
  1. Test and Debug
  • Load the Driver: Compile your code into a VxWorks kernel module (.out file) and load it using ld < 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.
  1. 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.

BSP - This article is part of a series.
Part 7: This Article

Related

VxWorks on Xen on Arm Cortex A53
·791 words·4 mins
Cortex A53 VxWorks Linux Xen Hypervisor
Using VxWorks BSP With Zynq 7000 Ap Soc
·4167 words·20 mins
VxWorks Zynq-7000 ARM
CAN Programming Under VxWorks
·642 words·4 mins
CAN VxWorks