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
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
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.
# 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:
modprobe resolves dependencies automatically (use this)insmod loads a single .ko file directly, no dependency resolutionmodinfo 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:
alias: PCI/USB IDs this driver handles — this is how autoloading worksparm: Module parameters you can set at load timelicense: Must be GPL to use certain kernel APIsWhen 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.
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:
That registration is what creates the /dev/ttyUSB0 entry you can open from Python.
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
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
If no driver is found for a device:
lspci or lsusb but nothing in /dev is createdlspci -k shows no “Kernel driver in use” linedmesgSolutions:
sudo apt install linux-modules-extra-$(uname -r))sudo modprobe <module_name>)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