Embedded Linux Device Drivers: Roles, Runtime Insights, and Development Guidance
Editor’s Note: Embedded Linux consistently ranks among the most popular operating systems for embedded system design. With the explosive growth of the Internet of Things, its versatility across all layers of IoT applications is indispensable. Mastery of embedded Linux enables engineers to develop sophisticated, reliable systems rapidly. In *Mastering Embedded Linux Programming – Second Edition*, Chris Simmonds guides readers through this powerful OS with clear, practical examples.
In this excerpt from Chapter 9, the author explains how kernel device drivers interface with hardware, how developers can write and utilize these drivers, and why this knowledge matters for embedded engineers. The following sections break down the key concepts:
- Understanding their role
- Reading driver state at runtime
- Device drivers in user space
- Writing a kernel device driver
- Discovering the hardware configuration
Adapted from *Mastering Embedded Linux Programming – Second Edition*, by Chris Simmonds.
Chapter 9. Interfacing with Device Drivers
Kernel device drivers expose the underlying hardware to the rest of the system. As an embedded system developer, you must understand how these drivers fit into the overall architecture and how to access them from user‑space applications. Most systems include drivers for common peripherals, allowing you to manipulate hardware—such as GPIO pins, LEDs, SPI, and I²C—without writing kernel code. However, when you encounter custom hardware, you’ll need to write or adapt a driver.
This chapter covers:
- The role of device drivers
- Character devices
- Block devices
- Network devices
- Finding out about drivers at runtime
- Finding the right device driver
- Device drivers in user space
- Writing a kernel device driver
- Discovering the hardware configuration
The role of device drivers
As described in Chapter 4, the kernel encapsulates a system’s myriad hardware interfaces, presenting them to user‑space in a consistent manner. Device drivers mediate between this kernel layer and the physical hardware below. They can control real devices (e.g., UART, MMC) or represent virtual devices (e.g., /dev/null, ramdisk). A single driver may manage multiple instances of the same device type.
Kernel drivers run with full privilege, accessing processor registers, handling interrupts, and performing DMA. They leverage the kernel’s robust synchronization and memory‑management infrastructure. Yet this power comes with risk: a buggy driver can crash the entire system. Therefore, drivers should be kept simple, providing data and operations while leaving policy decisions to user space. For example, loading modules in response to plug‑in events is handled by the user‑space daemon udev, not the kernel.
Linux classifies drivers into three main types:
- Character – Unbuffered I/O with a thin interface; the default choice for custom drivers.
- Block – Designed for block I/O to mass storage, featuring extensive buffering; unsuitable for other purposes.
- Network – Similar to block devices but handles packet transmission and reception.
A fourth category involves pseudo‑filesystems, exposing driver functionality through a group of files (e.g., GPIO accessed via /sys/class/gpio). This chapter begins by exploring the first three types in depth.
Character devices
Character devices appear in user space as special files called device nodes. Each node maps to a driver via a major number and identifies a specific interface via a minor number. For example, on an ARM Versatile PB, the first serial port is /dev/ttyAMA0 with major 204 and minor 64; the second port shares the same major but uses minor 65. The complete list for the four ports is shown below:
# ls -l /dev/ttyAMA*
crw-rw---- 1 root root 204, 64 Jan 1 1970 /dev/ttyAMA0
crw-rw---- 1 root root 204, 65 Jan 1 1970 /dev/ttyAMA1
crw-rw---- 1 root root 204, 66 Jan 1 1970 /dev/ttyAMA2
crw-rw---- 1 root root 204, 67 Jan 1 1970 /dev/ttyAMA3
Standard major/minor numbers are listed in Documentation/devices.txt, though custom devices may use values defined in the kernel source. For instance, the PL011 UART driver declares:
#define SERIAL_AMBA_MAJOR 204
#define SERIAL_AMBA_MINOR 64
When a driver supports multiple instances, the device node name typically appends an instance number to a base name (e.g., ttyAMA + 0‑3).
Device nodes can be created automatically or manually:
- devtmpfs – The driver registers an interface and a daemon creates the node.
- udev / mdev – User‑space daemon extracts the name from sysfs and creates the node.
- mknod – Static nodes created manually during system build.
From Linux 2.6 onward, the major number is 12 bits (1–4095) and the minor number is 20 bits (0–1,048,575). This expanded range supports a vast array of devices.
Embedded
- How Embedded Systems Drive Modern Vehicle Innovation
- Embedded Linux Device Drivers: Mastering Hardware Configuration
- Writing a Kernel Device Driver for Embedded Linux: A Practical Guide
- Embedded Linux Device Drivers: Managing Drivers in User Space
- Embedded Linux Device Drivers: Runtime State Monitoring
- Embedded C Structures: A Practical Guide to Definition, Usage, and Memory Optimization
- How a Maintenance Storeroom Drives Reliability and Efficiency
- From IoT to Cryptojacking: A Guide to Emerging Mobile Device Threats
- Mastering Your Automation Journey: A Roadmap to RPA Success
- How AI Is Revolutionizing Robotic Process Automation