Detailed Development of VxWorks 7 BSP

VxWorks 7 is a modern real-time operating system (RTOS) launched by Wind River, with significant improvements in the modularity and tool support of its BSP (Board Support Package) development process. The BSP serves as a bridge between hardware and the operating system, responsible for hardware initialization, device drivers, and system configuration. This article will detail the steps for developing a BSP on VxWorks 7, providing technical details and code examples.

Core Components of BSP

A complete BSP in VxWorks 7 typically includes the following files and functionalities:

  • • romInit.s: Startup code written in assembly language for the lowest level of hardware initialization.
  • • sysLib.c: System library providing core functions related to hardware (such as clock and interrupt control).
  • • sysALib.s: Assembly utility functions, usually used in conjunction with sysLib.c.
  • • config.h: Hardware configuration header file defining compilation options and hardware parameters.
  • • Makefile: Compilation script controlling the BSP build process.

Preparing the Development Environment

  • • Install Wind River Workbench: Ensure the latest version of Workbench (e.g., 4.6 or higher) that supports VxWorks 7 is installed.
  • • Reference BSP: Start from templates provided by Wind River (such as wrSbcArmv8 or intel_x86_64) and copy them to the new project directory.
  • • Hardware Documentation: Obtain the chip manual for the target board (such as the NXP i.MX8 data sheet) to clarify CPU architecture, memory addresses, and peripheral registers.

Detailed Development Steps

Creating a BSP Project

Select “File > New > VxWorks Board Support Package” in Workbench, enter the project name (e.g., myBsp) and target architecture (e.g., ARMv8). The generated project contains the following basic files:

  • • romInit.s: Startup entry.
  • • sysLib.c: System functions.
  • • config.h: Configuration file.

Customizing Hardware Initialization

  1. 1. Modify the startup code (romInit.s)
    .section .text
    .globl romInit
romInit:
    /* Set the base address of the exception vector table */
    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 .
  • • Note: This code sets the exception vector table, initializes the stack, and configures the system clock (specific values should refer to the hardware manual). Finally, it jumps to the sysInit function.
  1. 2. Configure Memory Mapping (sysLib.c)

Define the memory layout in sysLib.c:

#include "vxWorks.h"
#include "sysLib.h"

LOCAL char *sysPhysMemTop = (char *)0x80000000; /* Assume DRAM starting 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;
}
  • • Note: sysHwInit initializes the hardware, and sysMemTop returns the top address of memory. Register addresses and values must be adjusted according to the hardware manual.
  1. 3. 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 */
}
  • • Note: Specific addresses and values must refer to the GIC manual (such as ARM GICv3 specification).

Implementing Serial Driver

Taking the UART driver as an example, assuming 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 Holding Register */
#define UART_RBR  (UART_BASE + 0x00) /* Receive Buffer Register */
#define UART_LSR  (UART_BASE + 0x14) /* Line Status Register */

void uartInit(void)
{
    /* Set baud rate 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;
}
  • • Note: This code initializes the UART and provides basic send and receive functions. Register offsets and configuration values must match the hardware.

Adjusting 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 support */
#define DEFAULT_BOOT_LINE "uart(0,115200)"

Compile and Debug

  • • Select “Build > Build Project” in Workbench to generate the vxWorks image.
  • • Use JTAG (such as Segger J-Link) to program the image to the target board.
  • • Use a serial terminal (Tera Term or Minicom) to observe the boot log:
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 functionality (e.g., serial sending “Hello, VxWorks!”).
  • • Use System Viewer to analyze performance bottlenecks, optimizing interrupt handling or memory access.

Considerations for Serial Driver Development

  • • Exception Handling: Correctly set the exception vector in romInit.s to avoid system crashes.
  • • Driver Reusability: Encapsulate the driver as a VxWorks component (e.g., INCLUDE_MYSERIAL) for cross-project use.
  • • Hardware Debugging: If issues arise, use an oscilloscope or logic analyzer to check signal integrity.
  • • Documentation: Record the configuration basis for each register to facilitate team collaboration.

Summary

The BSP development for VxWorks 7 combines powerful tool support with flexible modular design. By customizing startup code, implementing drivers, and configuring the system, developers can seamlessly adapt the operating system to target hardware. The code examples above are based on the ARMv8 architecture, but the principles apply to other platforms (such as PowerPC or x86). With the debugging capabilities of Workbench and Wind River’s documentation, this process is both efficient and controllable.

Implementing Network Driver

Network driver development is a high-level task in the BSP, usually based on VxWorks’ **END (Enhanced Network Driver)** framework. The following steps illustrate the implementation using the ENET controller of the NXP i.MX8.

  1. 1. Overview of Network Driver Framework

VxWorks 7 uses the END framework to interact with the network stack (such as TCP/IP). END drivers need to implement the following core functions:

  • • xxxInit: Initialize hardware.
  • • xxxSend: Send data packets.
  • • xxxRecv: Receive data packets.
  • • xxxIoctl: Control interface (such as setting MAC address).
  • • xxxStart/xxxStop: Start/stop the device.
  1. 2. Define Driver Data Structure

Define private data for the driver 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;
  1. 3. 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;
}
  • • Note: Initialization includes setting the MAC address, enabling the controller, and configuring DMA descriptors. Register addresses should refer to the hardware manual.
  1. 4. Sending Data 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 send */
    *(volatile UINT32 *)(ENET_BASE + 0x28) = 0x1;

    /* Free M_BLK */
    netMblkFree(pMblk);
    return OK;
}
  • • Note: Extract data from M_BLK, write it to the hardware buffer, and trigger the send.
  1. 5. Receiving Data 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;
    }
}
  • • Note: Detect receive status through interrupts or polling, encapsulate data into M_BLK, and pass it to the network stack.
  1. 6. Starting the Device (myEnetStart)
STATUS myEnetStart(MY_ENET_DEV *pDev)
{
    if (pDev->running) return OK;

    /* Enable interrupts */
    *(volatile UINT32 *)(ENET_BASE + 0x40) = 0x3; /* Enable transmit/receive interrupts */
    intEnable(IRQ_ENET); /* Enable IRQ, assuming interrupt number is IRQ_ENET */

    pDev->running = TRUE;
    return OK;
}
  1. 7. Registering the Driver to the 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);
}
  • • Note: myEnetLoad registers the driver to the MUX (Multiplexor), allowing it to interface with the TCP/IP stack.

Adjusting 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 */

Compile and Test

  • • Compile the BSP to generate the vxWorks image.
  • • Program and start, connect the Ethernet cable.
  • • Test in the VxWorks shell:
-> ifconfig("myenet0", "192.168.1.100", "255.255.255.0")
-> ping("192.168.1.1")
  • • Check log output to ensure sending/receiving is normal.

Considerations for Network Driver Development

  • • DMA Management: Ensure the descriptor ring is correctly 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 the driver integrates seamlessly with VxWorks network stacks (such as LwIP or BSD stack).

Summary

Developing a network driver is a complex task within the BSP, but through the END framework and VxWorks’ modular design, it can be efficiently implemented. Ethernet driver development involves hardware initialization, DMA management, and data transmission and reception, requiring developers to have a deep understanding of hardware manuals and network protocols.

Leave a Comment