VxWorks Device Driver Guide: Build a Simple Character Driver
Device drivers are the bridge between application code and hardware in VxWorks. While modern systems often rely on VxBus for complex hardware integration, the classic I/O system-based character driver remains a critical foundation—especially for understanding driver design, testing, and lightweight device abstractions.
This guide walks through a minimal yet practical implementation of a character device driver, showing how to integrate with the VxWorks I/O system and expose file-like interfaces to user tasks.
🚀 Introduction to VxWorks Device Drivers #
In VxWorks, device drivers provide a standardized interface for interacting with hardware or virtual devices.
Core Characteristics #
- Integrated into the I/O system
- Expose file-like APIs (
open,read,write,close) - Allow user tasks to interact with devices using familiar POSIX-style calls
This abstraction enables consistent interaction across real hardware and simulated devices.
🧩 I/O System and Driver Model #
The VxWorks I/O system manages device drivers through a registration mechanism.
Key Components #
- Driver Table: Registered via
iosDrvInstall() - Device Node: Created using
iosDevAdd() - Driver Routines: Implement operations such as open, read, write
Execution Flow #
- Driver is installed into the system
- Device node is registered (e.g.,
/myDev/) - Applications open the device using standard APIs
- Driver routines handle the underlying operations
This model is lightweight and ideal for simple drivers or early prototyping.
💻 Example: Simple Character Device Driver #
The following implementation creates a memory-backed character device /myDev/ that supports basic read and write operations.
#include <vxWorks.h>
#include <iosLib.h>
#include <errnoLib.h>
#include <stdio.h>
#include <string.h>
#define BUFFER_SIZE 128
static char deviceBuffer[BUFFER_SIZE];
static int dataLength = 0;
int myOpen(DEV_HDR *pDev, const char *name, int flags, int mode);
int myClose(DEV_HDR *pDev);
ssize_t myRead(DEV_HDR *pDev, char *buffer, size_t maxBytes);
ssize_t myWrite(DEV_HDR *pDev, const char *buffer, size_t nBytes);
typedef struct {
DEV_HDR devHdr;
} MY_DEV;
MY_DEV myDevice;
int myOpen(DEV_HDR *pDev, const char *name, int flags, int mode)
{
printf("Device opened: %s\n", name);
return (int)pDev;
}
int myClose(DEV_HDR *pDev)
{
printf("Device closed\n");
return 0;
}
ssize_t myRead(DEV_HDR *pDev, char *buffer, size_t maxBytes)
{
int bytesToCopy = (dataLength < maxBytes) ? dataLength : maxBytes;
memcpy(buffer, deviceBuffer, bytesToCopy);
printf("Device read: %d bytes\n", bytesToCopy);
return bytesToCopy;
}
ssize_t myWrite(DEV_HDR *pDev, const char *buffer, size_t nBytes)
{
int bytesToCopy = (nBytes < BUFFER_SIZE) ? nBytes : BUFFER_SIZE;
memcpy(deviceBuffer, buffer, bytesToCopy);
dataLength = bytesToCopy;
printf("Device write: %d bytes\n", bytesToCopy);
return bytesToCopy;
}
void myDevCreate()
{
iosDrvInstall((FUNCPTR)myOpen, (FUNCPTR)myClose,
(FUNCPTR)myOpen, (FUNCPTR)myClose,
(FUNCPTR)myRead, (FUNCPTR)myWrite, NULL);
iosDevAdd(&myDevice.devHdr, "/myDev/", 0);
printf("Device /myDev/ created\n");
}
📝 Code Breakdown #
Driver Registration #
iosDrvInstall()registers function pointers for driver operations- Maps system calls to driver-specific implementations
Device Creation #
iosDevAdd()creates a device entry in the I/O system/myDev/becomes accessible to user applications
Core Operations #
myWrite()copies user data into a kernel buffermyRead()retrieves stored datamyOpen()andmyClose()manage lifecycle hooks
This pattern mirrors real hardware drivers, replacing memory buffers with actual device access.
🔄 Using the Driver #
Once initialized, the device behaves like a standard file:
int fd = open("/myDev/", O_RDWR, 0);
write(fd, "Hello Driver", 12);
char buffer[32];
read(fd, buffer, sizeof(buffer));
close(fd);
Expected Output #
Device /myDev/ created
Device opened: /myDev/
Device write: 12 bytes
Device read: 12 bytes
Device closed
⚠️ Practical Considerations #
Even for simple drivers, several factors should be addressed in production code:
Concurrency #
- Protect shared buffers with semaphores or mutexes
- Prevent race conditions across tasks
Error Handling #
- Validate input parameters
- Return appropriate error codes (
errno)
Scalability #
- Avoid static buffers for multi-instance devices
- Consider dynamic allocation per device instance
✅ Best Practices #
- Keep driver logic modular and maintainable
- Separate hardware access from interface logic
- Use consistent naming and clear API boundaries
- Design for extensibility (e.g., add
ioctlsupport later)
📌 Conclusion #
A simple character driver is the fastest way to understand how VxWorks bridges applications and devices. By integrating with the I/O system and exposing file-like operations, developers can quickly prototype and validate driver behavior.
This foundational model scales naturally into more advanced designs, including VxBus-based drivers and interrupt-driven architectures, making it an essential building block for embedded development in VxWorks.
Reference: VxWorks Device Driver Guide: Build a Simple Character Driver