Skip to main content

Developing a Dual Serial Port Driver for VxWorks Using XR16L788

·672 words·4 mins
VxWorks Device Driver UART Embedded Systems Serial Communication
Table of Contents

๐Ÿ”Œ Overview
#

Embedded platforms such as industrial controllers and communication gateways often outgrow the limited UARTs provided by on-chip peripherals. A 2011 paper documents a practical VxWorks device driver for the XR16L788 dual serial port chip. By integrating two XR16L788 devices with a Samsung S3C2410 ARM processor, the system expands to 16 reliable serial ports.

Although the work targets legacy ARM9 hardware, its driver architecture and implementation strategy remain directly applicable to modern VxWorks serial driver development.

๐Ÿงฑ VxWorks Serial Driver Model
#

VxWorks classifies serial ports as character devices managed through the I/O system. Applications interact with UARTs via standard APIs, while hardware details are encapsulated inside the driver.

The driver exposes a standard SIO (Serial I/O) interface, enabling:

  • Hardware-independent access
  • Dynamic driver loading
  • Clean separation between application and hardware logic

This modular model allows serial drivers to evolve without kernel recompilation.

๐Ÿงฉ Hardware Configuration
#

The reference design consists of:

  • CPU: Samsung S3C2410 (ARM9)
  • UART expansion: Two Exar XR16L788 chips
  • Total UARTs: 16 serial channels

Each XR16L788 provides eight high-speed UARTs with 64-byte TX/RX FIFOs and supports shared interrupts. The chips connect to the CPU via memory-mapped registers, minimizing additional glue logic.

๐Ÿ› ๏ธ Driver Data Structures
#

The driver adopts a two-level structure: one for the device and one for each UART channel.

#define MAX_XR16788_DEVS 2  // Number of XR16L788 devices
#define MAX_XR16788_CHANS 8 // Channels per device

typedef struct XR16L788_DEV {
    int devNum;         // Device index (0,1...)
    int devRegBase;     // Base register address
    int oscFreq;        // Input clock frequency
    int intNum;         // CPU interrupt number
    int intMask;        // Interrupt mask
    char *devNamePrefix; // Device name prefix
    void *pChanArray;   // Array of channels
} XR16L788_DEV;

typedef struct XR16L788_CHAN {
    SIO_CHAN sio;       // System SIO struct
    STATUS (*getTxChar)(); // TX callback
    STATUS (*putRcvChar)(); // RX callback
    void *getTxArg;
    void *putRcvArg;
    int chNum;          // Channel index
    int chRegBase;      // Channel register base
    int baudRate;       // Baud rate
    int options;        // Feature flags
    int mode;           // Interrupt or poll mode
    XR_CH_REG *pXrChReg; // Channel registers
    struct XR16L788_DEV *pXrDev; // Parent device
} XR16L788_CHAN;

This layout cleanly separates shared device resources from per-channel state, simplifying scaling and maintenance.

โš™๏ธ Hardware Initialization
#

Initialization is split into two phases. The first configures chip-level signals and timing, while the second initializes individual channels.

void sysSerialHwInit_16788(void) {
    xr16788Init();  // Init chip signals and timing
    
    for (devNum = 0; devNum < MAX_XR16788_DEVS; devNum++) {
        pDev = &xr16788Dev[devNum];
        pDev->pChanArray = &xr16788Chan[devNum];
        if (ERROR == xrInitDev(pDev)) continue;
        
        for (chanNum = 0; chanNum < MAX_XR16788_CHANS; chanNum++) {
            pChan = &xr16788Chan[devNum][chanNum];
            xrInitChan(pChan);  // Channel-specific init
        }
    }
}

A second-stage routine connects interrupts and registers the channels with the VxWorks I/O system.

โšก Interrupt Handling
#

All UART channels on a device share a single interrupt. The ISR reads the interrupt identification register and dispatches processing accordingly.

void xr16788Int(XR16L788_DEV *pDev) {
    UINT8 iir = XR_READ(pDev, IIR);
    if (iir & XR_IIR_NO_INT) return;
    
    switch (iir & XR_IIR_ID_MASK) {
        case XR_IIR_RX_RDY:
            xrRxInt(pDev->pChanArray[i]);
            break;
        case XR_IIR_TX_RDY:
            xrTxInt(pDev->pChanArray[i]);
            break;
    }
}

This centralized interrupt strategy reduces overhead while maintaining deterministic response times.

๐Ÿ”„ SIO Operations and Baud Configuration
#

The driver implements standard SIO callbacks, including open, close, read, write, and ioctl operations. Baud rate configuration follows the classic UART divisor model.

STATUS xr16788Ioctl(XR16L788_CHAN *pChan, int request, void *arg) {
    switch (request) {
        case SIO_BAUD_SET:
            baud = *(int *)arg;
            divisor = pChan->pXrDev->oscFreq / (16 * baud);
            // Program DLL/DLM via DLAB
            break;
    }
    return OK;
}

Strict compliance with the SIO interface ensures compatibility with existing VxWorks applications.

๐Ÿงช Validation Results
#

Testing confirmed stable full-duplex communication across all 16 ports at multiple baud rates, including long-duration stress tests. No data loss or interrupt anomalies were observed, validating the design for real-time use.

๐Ÿ“Œ Relevance in 2025
#

While the XR16L788 and S3C2410 are now considered legacy components, the driver structure, interrupt model, and SIO integration remain directly applicable to newer UART expansion chips and VxWorks 7-based systems. For engineers extending serial I/O on ARM, FPGA, or PCIe platforms, this design remains a solid reference implementation.

Related

PCI-RapidIO Bridge Driver Design on VxWorks
·731 words·4 mins
VxWorks RapidIO PCI Device Driver Embedded Systems
Interrupt Handling in VxWorks Device Drivers
·497 words·3 mins
VxWorks RTOS Interrupts Device Driver Embedded Systems Programming Tutorial
Writing a Simple Device Driver in VxWorks
·547 words·3 mins
VxWorks RTOS Device Driver Embedded Systems Programming Tutorial