Plug-and-Play HOWTO: devices, drivers, udev and firmware on Linux

Updated November 2026

On a current Linux system, plug-and-play just happens: the kernel finds the device, udev loads the matching driver module, the driver loads its firmware, and a device node appears. This page explains that chain and shows how to inspect it, change a driver's options, stop a driver from loading, and write your own udev rule.

The classic Plug-and-Play-HOWTO (last revised in 2007) is kept by the Linux Documentation Project at tldp.org. It deals with ISA cards, isapnp and hand-assigned IRQs and I/O addresses. On PCI Express, USB and ACPI hardware, the firmware and the kernel assign those resources, and you almost never touch them.

From plug-in to working device

Kernel bus driverfinds device, uevent systemd-udevdrules, kmod load Driver moduleprobe, bind Firmware/lib/firmware udev: /dev node, permissions, symlinks
The hotplug chain. Each step can be inspected with the commands below.
  1. A bus driver (PCI, USB, ACPI, platform) detects the device and the kernel sends a uevent. Most uevents carry a MODALIAS string built from the device's IDs, for example pci:v00008086d... or usb:v0BDAp8153....
  2. systemd-udevd receives the event and runs its rules. One of the default rules (80-drivers.rules) says: if the event has a MODALIAS, run the built-in kmod load. kmod looks the alias up in the index that depmod built from each module's ID table, and loads the match.
  3. The driver probes the device and binds to it. Many drivers then request a firmware file.
  4. Other udev rules create the device node in /dev, set its owner, group and mode, and add stable symlinks such as /dev/disk/by-id/ and /dev/serial/by-id/.

The same happens at boot: udev replays ("triggers") events for every device that already exists.

See what is there and who drives it

lspci -nnk              # PCI devices, [vendor:device] IDs, driver in use, possible modules
lsusb                   # USB devices
lsusb -t                # USB tree, with the driver bound to each interface
lsmod                   # loaded modules and who uses them
modinfo e1000e          # a module's file, firmware, parameters and aliases
cat /sys/bus/pci/devices/0000:00:1f.6/modalias
udevadm info /dev/sda   # udev properties of a device
udevadm info --tree     # sysfs tree (systemd 251 or later)

lspci -k shows "Kernel driver in use" and "Kernel modules" for each device. A device with modules listed but no driver in use is the usual sign of a failed probe or missing firmware; the kernel log says which:

sudo dmesg -w           # watch live while you plug the device in
journalctl -k -b        # this boot's kernel messages

To watch the events themselves, run udevadm monitor --kernel --udev --property and plug in the device. You see the kernel uevent, then the udev event after rules ran, with all properties.

Load, unload and configure modules

sudo modprobe btusb                   # load, with its dependencies
sudo modprobe -r btusb                # unload (fails if in use)
modprobe -n -v btusb                  # dry run: show what would happen
modprobe --show-depends btusb
sudo modprobe dummy numdummies=2      # with a parameter, this time only

Use modprobe, not insmod: it resolves dependencies and applies the configuration files. To make a setting permanent, put it in a .conf file in /etc/modprobe.d/ (files there override files of the same name in /run/modprobe.d/ and /lib/modprobe.d/ or /usr/lib/modprobe.d/):

# /etc/modprobe.d/iwlwifi.conf
options iwlwifi power_save=1

The current values of a loaded module's parameters are under /sys/module/<name>/parameters/. If the module is loaded from the initramfs (storage and early graphics drivers often are), rebuild it after changing modprobe.d: sudo update-initramfs -u on Debian and Ubuntu, sudo dracut -f on Fedora and RHEL, sudo mkinitcpio -P on Arch.

Stop a driver from loading

# /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
install nouveau /bin/false

blacklist tells modprobe to ignore the module's aliases, which is what stops automatic loading by udev. It does not stop modprobe nouveau typed by hand, or loading as a dependency of another module. The install line closes that gap: modprobe runs the given command instead of loading the module, and /bin/false does nothing. Rebuild the initramfs afterwards. For a one-off test, the kernel parameter modprobe.blacklist=nouveau does the same for a single boot (see the BootPrompt HOWTO).

softdep lines handle the other common case, a module that must load before or after another: softdep mydriver pre: crc32c.

Write your own udev rule

Typical reasons: a stable name for one of several identical USB serial adapters, permissions for a device your user should access, or running a script on plug-in. Rules go in /etc/udev/rules.d/, must end in .rules, and are read in lexical order together with the packaged rules in /usr/lib/udev/rules.d/; a file in /etc overrides a packaged file of the same name.

First find attributes to match on. udevadm info -a walks up from the device through its parents and prints every attribute:

udevadm info -a /dev/ttyUSB0 | grep -E 'idVendor|idProduct|serial' | head

Then write the rule. Keys with == match, keys with = or += assign. ATTR matches the device itself, ATTRS may match any parent, but all ATTRS in one rule must come from the same parent:

# /etc/udev/rules.d/70-gps.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", ATTRS{serial}=="A1B2C3", SYMLINK+="gps0", GROUP="dialout", MODE="0660"

Use your own IDs and serial number; the ones above are only an example. Check, load and apply:

sudo udevadm verify /etc/udev/rules.d/70-gps.rules   # systemd 254 or later
sudo udevadm control --reload
sudo udevadm trigger --action=add --subsystem-match=tty
ls -l /dev/gps0

Reloading alone does not change devices that are already present, hence the trigger (or unplug and replug). sudo udevadm test $(udevadm info -q path /dev/ttyUSB0) shows which rules match without changing anything.

No long-running programs in RUN. udev kills processes started by RUN{program} once event handling is over, and starting daemons from rules is not allowed. To start a service on plug-in, tag the device with TAG+="systemd", ENV{SYSTEMD_WANTS}="myservice.service" and let systemd run it.

Firmware

Many devices (Wi-Fi, Bluetooth, GPUs, some network cards) need a firmware file that the driver loads at probe time. The kernel looks in /lib/firmware/updates/<kernel version>/, /lib/firmware/updates/, /lib/firmware/<kernel version>/ and /lib/firmware/, in that order, plus any path given with the firmware_class.path= parameter. Kernels built with compression support also try the same name with .zst or .xz added. When a file is missing, the log says so:

sudo dmesg | grep -i firmware
# ... Direct firmware load for iwlwifi-....ucode failed with error -2

Error -2 means "file not found". Install your distribution's firmware package: linux-firmware on Fedora, RHEL, Arch and Ubuntu; on Debian the firmware-* packages from the non-free-firmware component (for example firmware-iwlwifi, firmware-realtek, firmware-amd-graphics). Then reload the driver or reboot. Device firmware that lives in flash on the device itself (docks, SSDs, system firmware) is a different thing, updated with fwupd; see the Hardware HOWTO.

What happened to IRQs and I/O ports?

They still exist, and you can look at them in /proc/interrupts, /proc/ioports and /proc/iomem. But on PCI Express devices, interrupts are mostly MSI or MSI-X messages rather than shared lines, and the firmware and kernel assign address ranges. Manual resource settings now matter only for old ISA hardware and some embedded boards; for talking to hardware registers from a program, see the I/O port programming mini-HOWTO.

Sources