12

Device Drivers

The kernel cannot talk to hardware directly. Drivers are the translators.

The kernel knows how to manage memory, schedule processes, and handle interrupts. But it does not inherently know how to talk to your specific network card, your particular SSD, or the exact GPU on your motherboard. Every piece of hardware has its own protocol -- its own set of registers, commands, and quirks. The software that translates between the kernel's generic interfaces and a specific device's protocol is called a device driver.

Without drivers, the kernel sees hardware as a collection of inscrutable chips on a circuit board. With drivers, it sees a network interface, a disk, a display. This article explains how drivers work, how they get loaded, and how the system creates the /dev entries that let userspace programs interact with hardware.

What a Driver Actually Does

Think of a driver as a translator at a diplomatic meeting. On one side is the kernel, which speaks a standard internal language: "read 4096 bytes from this block device" or "send this packet out this network interface." On the other side is the hardware, which speaks its own proprietary language: "write value 0x42 to register offset 0x18, then poll bit 3 of the status register until it goes high."

The driver translates between these two languages. It exposes a standard interface to the kernel -- a set of function pointers that implement operations like read, write, open, and close. Inside those functions, it talks to the hardware using whatever register-level protocol the manufacturer designed.

Key term: Device driver A piece of software that knows how to communicate with a specific piece of hardware. It implements the kernel's standard interface for that class of device (block device, network device, character device, etc.) and translates generic requests into hardware-specific register operations.
Fig. 12a -- The driver as translator
Application: read(fd, buf, 4096) Kernel VFS: "read 4096 bytes from block dev" DRIVER: nvme_read_sectors() Translates to NVMe command queue entries NVMe SSD Controller Hardware registers, DMA, interrupts

userspace kernel hardware

A read request flows from the application through the kernel's generic layer to the driver, which translates it into hardware-specific operations. The driver is the only component that knows the details of the NVMe protocol.

This layered design means the kernel does not need to be rewritten every time a new piece of hardware appears. Someone writes a driver for the new device, the driver plugs into the kernel's standard interfaces, and everything above the driver works without modification.

Built-in vs. Loadable Modules

A driver can be compiled directly into the kernel binary (built-in) or compiled as a separate file that the kernel loads at runtime (a loadable module). The choice has practical consequences.

Built-in drivers are available immediately at boot. The kernel needs certain drivers to get through early initialization -- the driver for the boot disk, for example, must be available before the kernel can mount the root filesystem. You cannot load a module from a disk you cannot yet read.

Loadable modules live in files with a .ko extension (kernel object) on the root filesystem, typically under /lib/modules/<kernel-version>/. The kernel loads them on demand -- when it detects hardware that needs a particular driver, or when an administrator explicitly requests it with modprobe.

Key term: Loadable kernel module A compiled driver file (.ko) that can be loaded into or removed from a running kernel without rebooting. Modules let the kernel start small and add hardware support as needed, rather than carrying every possible driver in memory at all times.

Most Linux distributions compile a small set of essential drivers into the kernel and ship everything else as modules. A typical system has a few dozen built-in drivers and several thousand available as modules. Only the modules for hardware actually present in the system get loaded.

$ lsmod | head -5
Module                  Size  Used by
nvidia               2453504  42
snd_hda_intel          57344   4
iwlwifi               450560   1
nvme                   49152   3

The lsmod command shows currently loaded modules. Each module has a name, a size in memory, and a count of how many things are using it. A module with a use count of zero can be safely unloaded.

The Device Model

The kernel maintains an internal model of every device in the system. This model organizes devices into a hierarchy that reflects how they are physically connected: the PCI bus hosts a network controller, which has a specific vendor and device ID, which is bound to a specific driver.

This hierarchy is exposed to userspace through the /sys filesystem (sysfs). You can browse it to see every device the kernel knows about, which driver is bound to it, and what attributes it has.

Fig. 12b -- The device model hierarchy
/sys/devices pci0000:00 platform usb1 0000:02:00.0 NVMe SSD 0000:03:00.0 Ethernet 1-1 Keyboard 1-2 Mouse nvme.ko e1000e.ko usbhid.ko usbhid.ko Bus Device Driver

Each device is bound to a driver. The kernel matches devices to drivers using vendor/device ID tables.

The kernel's device model is a tree. Buses contain devices, and each device is bound to a driver. The binding is based on hardware identifiers -- the kernel looks up the device's vendor and product ID in each driver's supported-device table.

When the kernel scans a bus (PCI, USB, platform) and discovers a device, it reads the device's identifier -- typically a vendor ID and device ID pair. It then searches all registered drivers for one that claims to support that identifier. If a match is found, the kernel calls the driver's probe function, which initializes the device and makes it available for use.

The kernel uses a match-and-bind model: devices advertise their identity, and drivers advertise which identities they support. The kernel's bus code matches them up automatically. This is why plugging in a USB device usually "just works" -- the kernel finds the matching driver and binds it without manual intervention.

/dev and Device Files

Unix systems represent hardware devices as files in the /dev directory. This is one of Unix's most powerful abstractions: programs that know how to read and write files can talk to hardware without knowing anything about how that hardware works internally.

There are two types of device files:

Block devices transfer data in fixed-size blocks. Disks and SSDs are block devices. Reading or writing any block is equally fast -- you can jump to any position without reading everything before it. Block devices are named things like /dev/sda (first SCSI/SATA disk), /dev/nvme0n1 (first NVMe SSD), and /dev/sda1 (first partition on the first disk).

Character devices transfer data as a stream of bytes, one at a time. Serial ports, terminals, and random number generators are character devices. You read from them sequentially. /dev/tty (the current terminal), /dev/null (discards everything written to it), and /dev/urandom (produces random bytes) are all character devices.

Key term: Device file (device node) A special file in /dev that represents a hardware device or virtual device. Programs interact with hardware by opening, reading, writing, and closing these files using standard file operations. The kernel maps these file operations to the appropriate driver functions.

Each device file has two numbers associated with it: a major number and a minor number. The major number identifies which driver handles the device. The minor number identifies which specific device that driver manages. You can see these numbers with ls -l:

$ ls -l /dev/sda
brw-rw---- 1 root disk 8, 0 Jan 14 10:00 /dev/sda

The 8, 0 means major 8 (the sd driver), minor 0 (the first disk). /dev/sda1 would be 8, 1 -- same driver, different minor number for the first partition.

udev: Populating /dev Dynamically

In early Linux systems, /dev was a static directory containing thousands of device files for every conceivable piece of hardware, whether or not it was actually present. This was wasteful and confusing.

Modern Linux uses udev, a userspace daemon that creates and removes device files dynamically as hardware appears and disappears. When the kernel detects a new device -- at boot or when you plug something in -- it sends an event called a uevent. udev receives this event and creates the appropriate /dev entry.

Fig. 12c -- udev device creation flow
1. HARDWARE USB drive plugged in 2. KERNEL Binds usb-storage driver 3. UEVENT add@/block/sdb 4. UDEV DAEMON (userspace) Receives uevent via netlink socket 5. APPLY RULES /etc/udev/rules.d/*.rules 6. CREATE /dev/sdb mknod, set permissions Symlinks: /dev/disk/by-uuid/..., /dev/disk/by-label/...
When hardware appears, the kernel sends a uevent. udev receives it, applies rules to determine the device name and permissions, creates the /dev entry, and optionally creates symlinks for stable naming.

udev also applies rules that control the device name, its permissions, and any symlinks. For example, udev rules create the stable symlinks under /dev/disk/by-uuid/ and /dev/disk/by-id/ that let you refer to a disk by its unique identifier rather than a name like sda that might change between boots.

The rules files live in /etc/udev/rules.d/ and /lib/udev/rules.d/. A rule looks like this:

SUBSYSTEM=="block", ATTR{size}!="0", ENV{ID_SERIAL}=="?*", \
  SYMLINK+="disk/by-id/$env{ID_BUS}-$env{ID_SERIAL}"

This says: for any block device with a non-zero size and a serial number, create a symlink using the bus type and serial number. This is how /dev/disk/by-id/ata-Samsung_SSD_860_S3Z9NB0K123456 gets created.

How Drivers Talk to Hardware

At the lowest level, drivers communicate with hardware through two mechanisms: memory-mapped I/O and port I/O.

Memory-mapped I/O (MMIO) maps a device's registers into the system's address space. The driver reads and writes to specific memory addresses, but those addresses do not correspond to RAM -- they are wired to the device's control registers. Writing to address 0xFEA00004 might tell a network card to start transmitting. Reading from it might return the card's status.

Port I/O (PIO) uses special CPU instructions (in and out on x86) to communicate with devices through a separate I/O address space. This is an older mechanism, mostly used by legacy devices like PS/2 keyboards and serial ports.

Modern high-performance devices also use DMA -- Direct Memory Access. Instead of the CPU reading data from the device one word at a time, the device writes directly into RAM without CPU involvement. The CPU sets up a DMA transfer by telling the device where in memory to write, then does other work while the transfer happens. When the device finishes, it fires an interrupt to let the CPU know the data is ready.

Drivers bridge the gap between the kernel's abstract view of hardware (block devices, network interfaces, character devices) and the physical reality of register addresses, DMA buffers, and interrupt lines. The driver model lets the rest of the system treat all devices uniformly, regardless of how different they are at the hardware level.

The Class System

Devices are also organized into classes based on their function. All network interfaces -- whether Ethernet, Wi-Fi, or cellular -- belong to the net class. All block storage devices belong to the block class. All input devices belong to the input class.

Classes let userspace tools work with devices by function rather than by physical connection. The ip command does not care whether your network interface is PCI, USB, or virtual. It uses the net class interface. The mount command does not care whether your disk is NVMe, SATA, or a network block device. It uses the block class interface.

You can see device classes in /sys/class/:

$ ls /sys/class/
block   dmi     hwmon   misc    net     scsi_device   tty
dma     gpio    input   mmc     pci_bus scsi_host     usb

Each directory contains symlinks to every device of that class, regardless of how it is physically connected.

Drivers During Boot

During the boot sequence, driver loading happens in two waves.

Wave 1: Built-in drivers. As the kernel initializes, it runs the init functions of all built-in drivers. These include drivers for the CPU itself, the interrupt controllers, the system timer, and the memory controller. Without these, nothing else can function.

Wave 2: Module loading. Once the kernel has mounted enough of a filesystem to read module files (typically from initramfs, which we cover in the next article), it can load additional drivers on demand. The kernel detects hardware on PCI, USB, and other buses, matches device IDs to module alias strings, and calls modprobe to load the right module.

Fig. 12d -- Driver loading timeline during boot
0s ~0.5s ~1.5s ~3s BUILT-IN DRIVERS

CPU, IRQ, timer, memory controller, early console

initramfs mounted MODULE LOADING

Storage drivers, filesystem modules, network, GPU, USB

root mounted LATE MODULES

Bluetooth, audio, webcam, sensors, additional USB

udev processes events, creates /dev entries
Driver loading happens in waves. Built-in drivers run immediately. Modules load once the initramfs is available. Additional modules load after the real root filesystem is mounted.

This staged loading is a chicken-and-egg solution. You need a storage driver to read module files from disk, but the storage driver itself might be a module. The answer is initramfs -- a temporary root filesystem loaded into RAM by the bootloader that contains the essential modules needed to access the real root. We cover initramfs in the next article.

Debugging Driver Problems

When a device does not work, the first place to look is the kernel log:

$ dmesg | grep -i error
[    2.345] e1000e: probe of 0000:03:00.0 failed with error -5

The -5 is EIO -- an I/O error during probe. The kernel also provides diagnostic information through /sys. Every device directory in sysfs contains files like vendor, device, driver, and uevent that tell you exactly what the kernel sees and which driver (if any) is bound.

lspci -v and lsusb -v show detailed information about every PCI and USB device, including which driver is currently bound. If a device shows no driver, either the right module is not loaded or no driver exists for that hardware.

Key term: probe The function a driver calls to check whether it can handle a specific device and, if so, to initialize it. If probe succeeds, the driver is bound to the device. If it fails, the kernel tries other drivers or leaves the device unbound.

Drivers are the nervous system of the operating system. They bridge the abstract world of file operations and network sockets with the physical world of voltage levels, register writes, and DMA transfers. Every time you read a file, send a packet, or hear a sound, a driver is doing the translation.

Next: The Root Filesystem