Chapter 3 — Device Drivers: The Translators

What Is a Device Driver?

A device driver is a piece of software that knows how to talk to a specific piece of hardware. It translates generic kernel operations (read, write, ioctl) into hardware-specific commands (write these bytes to this register, assert this GPIO pin, send this USB packet).

Without a driver, the kernel knows a device exists (it can see it on the PCI or USB bus) but cannot use it for anything useful.

The driver is the translator:

Kernel generic API: "give me 512 bytes from block 4"
        │
        ▼
    NVMe Driver
        │
        ▼
NVMe hardware command: "NVME_CMD_READ, LBA=4, len=512"
        │
        ▼
   Physical SSD

Where Drivers Live

Drivers exist in two places:

1. Built into the kernel — compiled directly into the kernel image (vmlinuz). Always available, no loading needed. Used for essential drivers (like the filesystem driver needed at boot).

2. Kernel modules — separate .ko files that can be loaded/unloaded at runtime. Most drivers are modules.

# Kernel module files live here
ls /lib/modules/$(uname -r)/kernel/drivers/

# Examples
/lib/modules/6.5.0/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko
/lib/modules/6.5.0/kernel/drivers/usb/serial/cp210x.ko
/lib/modules/6.5.0/kernel/drivers/gpu/drm/i915/i915.ko

Working with Kernel Modules

Listing Loaded Modules

lsmod

Output format: Module Size Used by

Module                  Size  Used by
e1000e                282624  0
i915                 3612672  9
drm                   606208  12 i915
usbcore               331776  5 xhci_hcd,xhci_pci

The “Used by” column shows dependencies — you can’t unload usbcore while xhci_hcd is loaded.

Loading and Unloading

# Load a module (and its dependencies automatically)
sudo modprobe cp210x

# Unload a module
sudo modprobe -r cp210x

# Load with parameters
sudo modprobe usbserial vendor=0x1234 product=0x5678

The difference between modprobe and insmod:

Getting Module Information

modinfo e1000e
filename:       /lib/modules/6.5.0/kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko
description:    Intel(R) PRO/1000 Network Driver
author:         Intel Corporation, <linux.nics@intel.com>
license:        GPL v2
alias:          pci:v00008086d00001533sv*sd*bc*sc*i*
parm:           debug:Debug level (0=none,...,16=all) (int)

Key fields:

Automatic Module Loading

When the kernel detects a new device (at boot or when plugged in), it reads the device ID and looks it up in the module alias database:

# The alias database (maps device IDs to module names)
cat /lib/modules/$(uname -r)/modules.alias | grep cp210x
# alias usb:v10C4p8A2Bd*dc*dsc*dp*ic*isc*ip*in* cp210x

This means: when a USB device with vendor 10C4 and product 8A2B is plugged in, load the cp210x module. This is why plugging in a USB serial adapter “just works” — udev reads the device ID, the kernel loads the matching module, and the driver registers /dev/ttyUSB0.

What a Driver Does Internally

You don’t need to write drivers, but understanding their structure helps you understand what’s happening when things go wrong.

A driver registers itself with the kernel for a specific bus and device type:

// Simplified C — this is what a driver looks like conceptually
static struct usb_driver my_driver = {
    .name       = "my_device",
    .probe      = my_probe,    // called when device is found
    .disconnect = my_disconnect, // called when device removed
    .id_table   = my_id_table,  // list of VID/PID this driver handles
};

When a matching device is found, the kernel calls probe(). The driver then:

  1. Configures the hardware (sets up registers, endpoints)
  2. Allocates buffers
  3. Registers a character device, network device, or block device with the kernel

That registration is what creates the /dev/ttyUSB0 entry you can open from Python.

The Driver Stack in Practice: USB Serial Example

Let’s trace what happens when you plug in a USB-to-serial adapter (e.g., CP2102 chip):

1. USB host controller detects new device on port
2. Kernel reads USB descriptor: VID=10C4, PID=EA60
3. uevent sent to userspace (udev sees it)
4. Kernel queries module alias DB: VID/PID → cp210x module
5. cp210x.ko is loaded
6. Driver's probe() is called
7. Driver configures the CP2102 chip via USB control transfers
8. Driver calls tty_register_device() — creates /dev/ttyUSB0
9. udev runs rules for the new device (sets permissions, symlinks)
10. You can now: open("/dev/ttyUSB0", ...)

You can observe steps 1–9 in real time:

# Watch kernel messages as you plug in a device
sudo dmesg -w

# Watch udev events
udevadm monitor

Finding Which Driver Is Handling a Device

For PCI devices:

lspci -k
# 00:1f.3 Audio device: Intel Corporation
#         Subsystem: Lenovo Device
#         Kernel driver in use: snd_hda_intel
#         Kernel modules: snd_hda_intel

For USB devices:

lsusb -v 2>/dev/null | grep -E "^Bus|iManufacturer|iProduct|Driver"

Via sysfs:

# For PCI device 0000:00:1f.3
cat /sys/bus/pci/devices/0000:00:1f.3/driver/module/drivers
readlink /sys/bus/pci/devices/0000:00:1f.3/driver

When a Driver Is Missing

If no driver is found for a device:

Solutions:

  1. Install the driver package (e.g., sudo apt install linux-modules-extra-$(uname -r))
  2. Load the module manually (sudo modprobe <module_name>)
  3. Build and install an out-of-tree driver (requires kernel headers)

Checking dmesg for Driver Events

dmesg is the kernel message buffer. It contains all hardware detection, driver loading, and error messages:

dmesg | grep -i usb      # USB events
dmesg | grep -i error    # errors
dmesg | tail -20         # most recent messages
sudo dmesg -w            # follow mode (like tail -f)

When a device misbehaves, dmesg is the first place to look.


Previous: Chapter 2 — Hardware Topology

Next: Chapter 4 — /dev: Where Hardware Meets the Filesystem

Back to Table of Contents