VxWorks 7 is a modern real-time operating system (RTOS) launched by Wind River, and its BSP (Board Support Package) development process has seen significant improvements in modularity and tool support. The BSP serves as the bridge between hardware and the operating system, handling hardware initialization, device drivers, and system configuration. This article provides a detailed guide on developing a BSP for VxWorks 7, including technical details and code examples.
Core Components of a BSP #
In VxWorks 7, a complete BSP typically includes the following files and functionalities:
- romInit.s: Assembly-language boot code for the lowest-level hardware initialization.
- sysLib.c: System library providing hardware-related core functions (e.g., clock and interrupt control).
- sysALib.s: Assembly utility functions, typically used in conjunction with sysLib.c.
- config.h: Hardware configuration header file defining compilation options and hardware parameters.
- Makefile: Build script controlling the BSP compilation process.
Preparing the Development Environment #
- Install Wind River Workbench: Ensure the latest version of Workbench (e.g., 4.6 or higher) is installed, supporting VxWorks 7.
- Reference BSP: Start with a template provided by Wind River (e.g., wrSbcArmv8 or intel_x86_64) and copy it to a new project directory.
- Hardware Documentation: Obtain the chipset manual for the target board (e.g., NXP i.MX8 datasheet), clarifying CPU architecture, memory addresses, and peripheral registers.
Detailed Development Steps #
Creating a BSP Project #
In Workbench, select “File > New > VxWorks Board Support Package,” enter the project name (e.g., myBsp) and target architecture (e.g., ARMv8). The generated project includes the following base files:
- romInit.s: Boot entry point.
- sysLib.c: System functions.
- config.h: Configuration file.
Customizing Hardware Initialization #
- Modify Boot Code (romInit.s)
.section .text
.globl romInit
romInit:
/* Set the exception vector table base address */
ldr x0, =_vector_table
msr VBAR_EL1, x0
/* Initialize the stack pointer */
ldr x0, =__stack_top
mov sp, x0
/* Configure the clock (PLL) */
ldr x0, =0x40000000 /* Clock control register address */
ldr x1, =0x00001234 /* PLL configuration value */
str x1, [x0]
/* Jump to C code */
bl sysInit
b .
- Explanation: This code sets the exception vector table, initializes the stack, and configures the system clock (specific values must be referenced from the hardware manual). It then jumps to the sysInit function. Configure Memory Mapping (sysLib.c)
- Define the memory layout in sysLib.c:
#include "vxWorks.h"
#include "sysLib.h"
LOCAL char *sysPhysMemTop = (char *)0x80000000; /* Assumed DRAM start address */
LOCAL UINT32 sysMemSize = 0x10000000; /* 256MB memory */
void sysHwInit(void)
{
/* Initialize memory controller */
*(volatile UINT32 *)0x40001000 = 0x00000101; /* Memory control register */
}
char *sysMemTop(void)
{
return sysPhysMemTop;
}
- Explanation: sysHwInit initializes the hardware, and sysMemTop returns the top memory address. Register addresses and values must be adjusted based on the hardware manual.
- Interrupt Initialization
Configure interrupts for the ARM GIC (Generic Interrupt Controller):
void sysIntInit(void)
{
/* Enable GIC distributor */
*(volatile UINT32 *)0xF9000000 = 0x1; /* GICD_CTLR */
/* Configure IRQ priority */
*(volatile UINT32 *)0xF9001000 = 0xA0; /* GICD_IPRIORITYR */
}
- Explanation: Specific addresses and values should refer to the GIC manual (e.g., ARM GICv3 specification).
Implementing a Serial Port Driver #
Using a UART driver as an example, assume the target hardware uses a 16550-compatible serial port:
#include "drv/serial/serial.h"
#define UART_BASE 0xF8000000
#define UART_THR (UART_BASE + 0x00) /* Transmit register */
#define UART_RBR (UART_BASE + 0x00) /* Receive register */
#define UART_LSR (UART_BASE + 0x14) /* Status register */
void uartInit(void)
{
/* Set baud rate to 115200, 8N1 */
*(volatile UINT32 *)(UART_BASE + 0x0C) = 0x83; /* LCR */
*(volatile UINT32 *)(UART_BASE + 0x00) = 0x0C; /* DLL */
*(volatile UINT32 *)(UART_BASE + 0x04) = 0x00; /* DLM */
*(volatile UINT32 *)(UART_BASE + 0x0C) = 0x03; /* LCR */
}
int uartPutChar(char c)
{
while (!(*(volatile UINT32 *)UART_LSR & 0x20)); /* Wait for transmit buffer to be empty */
*(volatile UINT32 *)UART_THR = c;
return 1;
}
int uartGetChar(void)
{
if (*(volatile UINT32 *)UART_LSR & 0x01) /* Check if data is ready */
return *(volatile UINT32 *)UART_RBR;
return EOF;
}
- Explanation: This code initializes the UART and provides basic transmit/receive functions. Register offsets and configuration values must match the hardware.
Adjusting the Configuration File (config.h) #
Enable necessary components and define hardware parameters:
#define CPU _VX_ARMV8A /* ARMv8-A architecture */
#define SYS_CLK_RATE 1000000 /* System clock 1MHz */
#define INCLUDE_SERIAL /* Enable serial port support */
#define DEFAULT_BOOT_LINE "uart(0,115200)"
Compilation and Debugging #
- In Workbench, select “Build > Build Project” to generate the VxWorks image.
- Use a JTAG tool (e.g., Segger J-Link) to flash the image onto the target board.
- Observe the boot log via a serial terminal (e.g., Tera Term or Minicom):
VxWorks 7.0
BSP Version: 1.0
CPU: ARMv8-A
Memory Size: 256MB
- Use the Workbench debugger to set breakpoints and verify functions like uartPutChar.
Optimization and Testing #
- Test peripheral functions (e.g., send “Hello, VxWorks!” via the serial port).
- Use System Viewer to analyze performance bottlenecks and optimize interrupt handling or memory access.
Notes on Serial Driver Development #
- Exception Handling: Properly set exception vectors in romInit.s to avoid system crashes.
- Driver Reusability: Encapsulate the driver as a VxWorks component (e.g., INCLUDE_MYSERIAL) for reuse across projects.
- Hardware Debugging: If issues arise, use an oscilloscope or logic analyzer to check signal integrity.
- Documentation: Record the basis for each register configuration to facilitate team collaboration.
Summary #
BSP development in VxWorks 7 combines powerful tool support with a flexible modular design. By customizing boot code, implementing drivers, and configuring the system, developers can seamlessly adapt the OS to target hardware. The code examples above are based on the ARMv8 architecture, but the principles apply to other platforms (e.g., PowerPC or x86). With Workbench’s debugging features and Wind River’s documentation, this process is both efficient and manageable.
Implementing a Network Driver #
Network driver development is an advanced task in BSP, typically based on VxWorks’ END (Enhanced Network Driver) framework. The following uses the NXP i.MX8 ENET controller as an example, implemented step-by-step.
- Overview of the Network Driver Framework
VxWorks 7 uses the END framework to interface with the network stack (e.g., TCP/IP). An END driver must implement the following core functions:
- xxxInit: Initialize hardware.
- xxxSend: Send packets.
- xxxRecv: Receive packets.
- xxxIoctl: Control interface (e.g., set MAC address).
- xxxStart/xxxStop: Start/stop the device.
- Define Driver Data Structure
Define private driver data in myEnet.c:
#include "endLib.h"
#include "muxLib.h"
#define ENET_BASE 0x5B040000 /* Ethernet base address */
#define ENET_TX_DESC 0x5B041000 /* Transmit descriptor address */
#define ENET_RX_DESC 0x5B042000 /* Receive descriptor address */
typedef struct {
END_OBJ endObj; /* END object, must be first */
UINT32 baseAddr; /* Controller base address */
UINT8 macAddr[6]; /* MAC address */
BOOL running; /* Running state */
M_BLK_ID txQueue; /* Transmit queue */
M_BLK_ID rxQueue; /* Receive queue */
} MY_ENET_DEV;
- Initialize Network Hardware (myEnetInit)
LOCAL MY_ENET_DEV *pEnetDev = NULL;
STATUS myEnetInit(MY_ENET_DEV *pDev)
{
/* Allocate device structure */
pDev = (MY_ENET_DEV *)malloc(sizeof(MY_ENET_DEV));
if (!pDev) return ERROR;
pDev->baseAddr = ENET_BASE;
pDev->running = FALSE;
/* Set default MAC address */
pDev->macAddr[0] = 0x00; pDev->macAddr[1] = 0x1A;
pDev->macAddr[2] = 0x2B; pDev->macAddr[3] = 0x3C;
pDev->macAddr[4] = 0x4D; pDev->macAddr[5] = 0x5E;
/* Initialize hardware registers */
*(volatile UINT32 *)(ENET_BASE + 0x10) = 0x1; /* Enable controller */
*(volatile UINT32 *)(ENET_BASE + 0x14) = 0x3; /* 100Mbps, full duplex */
/* Initialize descriptor ring (DMA) */
*(volatile UINT32 *)ENET_TX_DESC = 0x80000000; /* Mark descriptor ready */
*(volatile UINT32 *)ENET_RX_DESC = 0x80000000;
return OK;
}
- Explanation: Initialization includes setting the MAC address, enabling the controller, and configuring DMA descriptors. Register addresses must be referenced from the hardware manual.
- Send Packets (myEnetSend)
STATUS myEnetSend(MY_ENET_DEV *pDev, M_BLK_ID pMblk)
{
if (!pDev->running) return ERROR;
/* Write data to transmit buffer */
char *data = netMblkToBufCopy(pMblk, NULL, NULL);
*(volatile UINT32 *)(ENET_BASE + 0x20) = (UINT32)data; /* Data address */
*(volatile UINT32 *)(ENET_BASE + 0x24) = pMblk->mBlkHdr.mLen; /* Data length */
/* Trigger transmission */
*(volatile UINT32 *)(ENET_BASE + 0x28) = 0x1;
/* Free M_BLK */
netMblkFree(pMblk);
return OK;
}
- Explanation: Extracts data from M_BLK, writes it to the hardware buffer, and triggers transmission.
- Receive Packets (myEnetRecv)
LOCAL void myEnetHandleRecv(MY_ENET_DEV *pDev)
{
M_BLK_ID pMblk;
/* Check receive status */
if (*(volatile UINT32 *)(ENET_BASE + 0x30) & 0x1) {
/* Allocate M_BLK */
pMblk = netMblkAlloc();
if (!pMblk) return;
/* Read data from hardware */
UINT32 len = *(volatile UINT32 *)(ENET_BASE + 0x34);
char *data = (char *)(*(volatile UINT32 *)(ENET_BASE + 0x38));
netMblkFromBufCopy(pMblk, data, len);
/* Report to network stack */
muxReceive(&pDev->endObj, pMblk);
/* Clear receive flag */
*(volatile UINT32 *)(ENET_BASE + 0x30) = 0x0;
}
}
- Explanation: Detects receive status via interrupt or polling, encapsulates data into M_BLK, and passes it to the network stack.
- Start Device (myEnetStart)
STATUS myEnetStart(MY_ENET_DEV *pDev)
{
if (pDev->running) return OK;
/* Enable interrupts */
*(volatile UINT32 *)(ENET_BASE + 0x40) = 0x3; /* Enable TX/RX interrupts */
intEnable(IRQ_ENET); /* Enable IRQ, assuming interrupt number is IRQ_ENET */
pDev->running = TRUE;
return OK;
}
- Register Driver with Network Stack
END_OBJ *myEnetLoad(char *initString, void *pArg)
{
MY_ENET_DEV *pDev;
if (myEnetInit(pDev) == ERROR) return NULL;
/* Bind to MUX */
if (endLoad(initString, &pDev->endObj, myEnetStart, myEnetStop,
myEnetSend, myEnetRecv, myEnetIoctl) == ERROR) {
free(pDev);
return NULL;
}
return &pDev->endObj;
}
void myEnetRegister(void)
{
muxDevLoad(0, myEnetLoad, "", FALSE, NULL);
muxDevStart(0);
}
- Explanation: myEnetLoad registers the driver with the MUX (Multiplexer) to interface with the TCP/IP stack.
Adjusting the Configuration File (config.h) #
Enable network support:
#define INCLUDE_END /* Enable END framework */
#define INCLUDE_MUX /* Enable MUX layer */
#define INCLUDE_IPV4 /* Enable IPv4 support */
#define INCLUDE_IFCONFIG /* Enable ifconfig command */
#define MY_ENET_UNIT 0 /* Network device unit number */
Compilation and Testing #
- Compile the BSP to generate the VxWorks image.
- Flash and boot, then connect an Ethernet cable.
- Test in the VxWorks shell:
-> ifconfig("myenet0", "192.168.1.100", "255.255.255.0")
-> ping("192.168.1.1")
- Check logs to ensure transmission and reception work correctly.
Notes on Network Driver Development #
- DMA Management: Ensure the descriptor ring is properly initialized to avoid data loss.
- Interrupt Handling: Optimize interrupt frequency for high-throughput scenarios, using NAPI-like polling if necessary.
- Performance Testing: Use iperf to test bandwidth and verify driver efficiency.
- Compatibility: Ensure seamless integration with the VxWorks network stack (e.g., LwIP or BSD stack).
Summary #
Network driver development is a complex task in BSPs, but the END framework and VxWorks’ modular design enable efficient implementation. Ethernet drivers involve hardware initialization, DMA management, and packet transmission/reception, requiring a deep understanding of hardware manuals and network protocols.