Introduction
Device driver development is at the core of embedded systems. In the industry-leading real-time operating system (RTOS) VxWorks, hardware drivers are implemented using the VxBus framework.
This guide will provide a comprehensive overview of VxBus driver development, covering all aspects from architecture to practical implementation. Whether you are a newcomer to VxWorks 7 or migrating from an older BSP style, this article will help you master VxBus and build efficient, maintainable drivers.
Why VxBus is So Important in VxWorks
In earlier versions of VxWorks, drivers were often hard-coded into the BSP (Board Support Package). This approach made maintenance difficult and had limited portability when hardware changes occurred.
To address this issue, Wind River introduced VxBusβa standardized, scalable, and modular driver framework.
Advantages of VxBus:
- β’ π Modular β Drivers are decoupled from the BSP.
- β’ β‘ Efficient β Device discovery and initialization are automated.
- β’ π Reusable β The same driver can support multiple platforms.
- β’ π οΈ Maintainable β Code is clearer, reducing the complexity of the BSP.
- β’ π Standardized β Supports Device Tree, ensuring alignment with industry standards.
Overview of VxBus Architecture
The VxBus driver model draws from frameworks of modern operating systems like Linux. It revolves around a bus-driver-device hierarchy:
- 1. Bus β Represents a method of interconnection (e.g., PCI, IΒ²C, SPI, USB).
- 2. Device β A hardware instance connected to the bus, defined in the **Device Tree (DTS)**.
- 3. Driver β The software logic that manages the device.
At startup, VxBus performs the following steps:
- 1. Reads the Device Tree.
- 2. Enumerates devices.
- 3. Matches the
<span>compatible</span>string of each device with registered drivers. - 4. Calls the driverβs
<span>probe</span>and<span>attach</span>functions.
Step-by-Step Guide to VxBus Driver Development
1. Define Your Device in the Device Tree
The Device Tree Source (DTS) is the cornerstone of hardware description. For example, to define an IΒ²C GPIO expander, you can do it like this:
i2c@0x10000000 {
compatible = "nxp,pca9535";
reg = <0x10000000 0x1000>;
interrupts = <10 0>; // Applicable interrupt line
};
π Tip: Use vendor-standard <span>compatible</span> strings whenever possible. This ensures your driver is portable and consistent with upstream conventions.
2. Implement the Driver Skeleton
A VxBus driver typically includes probe, attach, and detach methods.
LOCAL STATUS myDriverProbe(VXB_DEV_ID pDev) {
// Check if this driver supports the device
return OK;
}
LOCAL STATUS myDriverAttach(VXB_DEV_ID pDev) {
// Initialize registers, memory, interrupts, etc.
return OK;
}
LOCAL STATUS myDriverDetach(VXB_DEV_ID pDev) {
// Release resources, disable interrupts
return OK;
}
LOCAL VXB_DRV_METHOD myDriverMethods[] = {
{ VXB_DEVMETHOD_CALL(vxbDevProbe), myDriverProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), myDriverAttach },
{ VXB_DEVMETHOD_CALL(vxbDevDetach), myDriverDetach },
VXB_DEVMETHOD_END
};
VXB_DRV myDriver = {
{ NULL },
"myDriver",
"Example VxBus Driver",
VXB_BUSID_PLB, // Bus type
0,
sizeof(VXB_DEV_ID),
myDriverMethods,
NULL,
NULL,
NULL
};
VXB_DRV_DEF(myDriver);
3. Register the Driver with VxBus
Add the driver during system initialization:
IMPORT VXB_DRV myDriver;
void sysHwInit(void) {
vxbDevRegister(&myDriver);
}
4. Handle Interrupts
Many devices rely on interrupts for efficient operation. In VxBus, you can request an IRQ interrupt line like this:
LOCAL void myIsr(void * arg) {
VXB_DEV_ID pDev = (VXB_DEV_ID) arg;
// Handle interrupt
}
LOCAL STATUS myDriverAttach(VXB_DEV_ID pDev) {
int irq = vxbIntConnect(pDev, 0, myIsr, pDev);
if (irq != OK) {
return ERROR;
}
vxbIntEnable(pDev, 0, myIsr, pDev);
return OK;
}
5. DMA and Buffer Management
For network cards or storage controllers, Direct Memory Access (DMA) is crucial.
VxBus provides APIs for DMA-safe memory allocation:
void * dmaBuf;
dmaBuf = cacheDmaMalloc(1024);
if (dmaBuf == NULL) {
return ERROR;
}
π Always use <span>cacheDmaMalloc()</span> or similar APIs to ensure cache coherence.
6. Testing and Debugging
Common VxWorks commands for driver testing:
- β’
<span>-> devs</span>β List all registered devices. - β’
<span>-> i2cShow</span>/<span>-> spiShow</span>β Display bus devices. - β’
<span>-> d (0xADDRESS, 16)</span>β Dump memory/register values.
Debugging methods:
- β’ Use
<span>printf</span>in early development stages. - β’ Enable Wind River Workbench or WDB debugger to set breakpoints.
- β’ Add
<span>errnoGet()</span>checks to diagnose errors.
Real Case: VxBus UART Driver
To make VxBus driver development more concrete, letβs create a simplified UART driver. This example assumes a memory-mapped UART device with basic transmit (TX) and receive (RX) registers.
1. Device Tree Entry
First, define the UART in the device tree:
uart0: serial@0x101f1000 {
compatible = "simple,uart";
reg = <0x101f1000 0x1000>;
interrupts = <5 0>;
clock-frequency = <48000000>;
};
Here:
- β’
<span>reg</span>defines the base address and size of the UART. - β’
<span>interrupts</span>specifies the IRQ interrupt line. - β’
<span>clock-frequency</span>provides the input clock frequency for baud rate calculations.
2. Register Mapping
This example assumes the UART has the following registers:
| Offset | Register | Description |
|---|---|---|
| 0x00 | UART_DR | Data Register (read/write) |
| 0x04 | UART_SR | Status Register |
| 0x08 | UART_CR | Control Register |
| 0x0C | UART_BAUD | Baud Rate Divisor Register |
3. Driver Implementation
#define UART_DR 0x00
#define UART_SR 0x04
#define UART_CR 0x08
#define UART_BAUD 0x0C
#define UART_SR_TX_READY (1 << 0)
#define UART_SR_RX_READY (1 << 1)
#define UART_CR_TX_EN (1 << 0)
#define UART_CR_RX_EN (1 << 1)
typedef struct {
VXB_DEV_ID dev;
void * base; // Mapped register base address
int irq;
} UART_DEV;
LOCAL void uartIsr(void * arg) {
UART_DEV * pDrv = (UART_DEV *) arg;
UINT32 sr = vxbRead32(pDrv->dev, (UINT32 *) (pDrv->base + UART_SR));
if (sr & UART_SR_RX_READY) {
char c = vxbRead8(pDrv->dev, (UINT8 *) (pDrv->base + UART_DR));
printf("UART RX: %c\n", c);
}
}
LOCAL STATUS uartProbe(VXB_DEV_ID pDev) {
return OK; // Always claim support in this demo
}
LOCAL STATUS uartAttach(VXB_DEV_ID pDev) {
UART_DEV * pDrv = (UART_DEV *) vxbMemAlloc(sizeof(UART_DEV));
if (!pDrv) return ERROR;
pDrv->dev = pDev;
pDrv->base = (void *) vxbRegMap(pDev, 0, 0);
pDrv->irq = vxbIntConnect(pDev, 0, uartIsr, pDrv);
// Enable TX/RX
vxbWrite32(pDev, (UINT32 *)(pDrv->base + UART_CR), UART_CR_TX_EN | UART_CR_RX_EN);
// Configure baud rate
vxbWrite32(pDev, (UINT32 *)(pDrv->base + UART_BAUD), 48000000 / 115200);
vxbDevSoftcSet(pDev, pDrv);
return OK;
}
LOCAL STATUS uartDetach(VXB_DEV_ID pDev) {
UART_DEV * pDrv = (UART_DEV *) vxbDevSoftcGet(pDev);
if (!pDrv) return ERROR;
vxbIntDisconnect(pDev, pDrv->irq, uartIsr, pDrv);
vxbMemFree(pDrv);
return OK;
}
LOCAL VXB_DRV_METHOD uartMethods[] = {
{ VXB_DEVMETHOD_CALL(vxbDevProbe), uartProbe },
{ VXB_DEVMETHOD_CALL(vxbDevAttach), uartAttach },
{ VXB_DEVMETHOD_CALL(vxbDevDetach), uartDetach },
VXB_DEVMETHOD_END
};
VXB_DRV uartDriver = {
{ NULL },
"uartDriver",
"Simple UART VxBus Driver",
VXB_BUSID_PLB,
0,
sizeof(UART_DEV),
uartMethods,
NULL,
NULL,
NULL
};
VXB_DRV_DEF(uartDriver);
4. Driver Registration
Add the driver to the BSP initialization:
IMPORT VXB_DRV uartDriver;
void sysHwInit(void) {
vxbDevRegister(&uartDriver);
}
5. Testing the UART Driver
- 1. Boot the system and run:
-> devs
You should see the UART device listed.
- 2. Send data by writing to the TX register:
UART_DEV * pDrv = (UART_DEV *) vxbDevSoftcGet(<device>);
vxbWrite8(pDrv->dev, (UINT8 *)(pDrv->base + UART_DR), 'A');
- 3. When the character arrives, the ISR will print it to the console.
Advanced Topics in VxBus Driver Development
Device Tree Overlays
You can add new hardware support by applying DTS overlays without rebuilding the BSPβideal for prototyping.
Power Management
Modern VxBus drivers should support suspend/resume hooks to accommodate power-sensitive devices.
Multi-Instance Devices
Ensure your driver can gracefully handle multiple instances (e.g., multiple IΒ²C controllers).
Error Recovery
Implement retry logic for transient failures (e.g., bus errors, timeouts).
Best Practices for VxBus Drivers
β Keep drivers generic β Avoid hardcoding board-specific details.β Use standard APIs β Follow VxBus and VxWorks kernel APIs.β Document interfaces β Help future developers integrate your driver.β Follow modularity β Separate hardware initialization, ISR, and data processing.β Benchmark performance β Test latency and throughput under load.
Conclusion
For VxWorks engineers, developing drivers using VxBus is an essential skill. By understanding its architecture, leveraging the Device Tree, and following best practices, you can build portable, efficient, and future-proof drivers.
From basic driver skeletons to advanced implementations supporting DMA, mastering VxBus will open the door to building stable, high-performance embedded systems.
For more VxWorks learning resources, please click “Read Original”