The Linux Driver Model
The Linux Driver Model is the elegant object-oriented spine that lets thousands of drivers speak a single language to buses, devices, and the kernel itself.
Overview
Before the Linux Driver Model existed, every bus subsystem — PCI, USB, platform, I2C — invented its own way of registering devices, matching drivers, and exposing information to userspace. The result was a fragmented zoo: code was duplicated everywhere, power management was bolted on as an afterthought, and tools like udev had no principled way to discover what hardware was present. The Driver Model, introduced in Linux 2.5 and refined continually since, unified all of this behind a small set of core abstractions.
At its heart the model is object-oriented C. It borrows the idea of a class hierarchy using embedded structs: every device in the system is represented by a struct device, every driver by a struct device_driver, and every bus type by a struct bus_type. These three types know how to talk to each other through well-defined callbacks — probe, remove, suspend, resume — without any of them needing to know the concrete type of the others. The kernel acts as the matchmaker, and sysfs acts as the window through which userspace can observe the whole arrangement.
The model is built on an even more fundamental primitive called kobject. A kobject is a reference-counted, named object that can be embedded in anything the kernel wants to expose to userspace as a directory in /sys. From kobjects come ksets (collections of kobjects), and from ksets comes the entire /sys tree. Understanding kobjects is understanding the skeleton on which everything else hangs.
By the end of this module you will be able to look at any driver in the kernel tree, understand why each struct field exists, trace exactly how the kernel matches a driver to a newly-plugged device, and write a minimal platform driver that probes, claims a resource, and removes cleanly. More than that, you will see why this design — separating what a device is from how a driver handles it from what bus it lives on — is one of the most instructive pieces of systems software architecture you will ever study.
Every device driver you will ever write for Linux slots into this model. Whether you are writing a GPIO expander driver for an embedded ARM board, a new PCIe NIC driver, or a USB HID gadget, the kernel expects you to fill in a struct device_driver (or one of its bus-specific wrappers like struct platform_driver or struct pci_driver), register it with a bus, and implement probe/remove. If you do not understand the model, you are copy-pasting incantations you cannot debug. When a device fails to probe, when a resource leaks on removal, when sysfs shows the wrong attributes — all of these bugs trace back to misunderstanding the model.
Beyond debugging, the Driver Model is a masterclass in how to design extensible C systems without a language-level type system. The techniques — embedding structs for inheritance, storing function pointers for polymorphism, using reference counting for lifetime management — appear everywhere in the kernel: in the VFS, in the network stack, in the scheduler. Internalizing this module gives you a transferable mental framework for reading and contributing to any subsystem in the kernel tree.
Linux Driver Model: From DT Node to probe() Call
The full chain from a Device Tree node to a successful probe() call, including the MODULE_DEVICE_TABLE autoload path and the deferred probe retry mechanism.
The concepts
01The Three Pillars: device, driver, bus
The entire Driver Model rests on three cooperating abstractions.
struct device represents one physical or virtual piece of hardware. It carries a name, a parent pointer (forming a tree), a reference count, a pointer to the bus it lives on, and a pointer to the driver currently bound to it. Crucially, it is almost never used bare — it is embedded inside a more specific structure like struct pci_dev or struct platform_device. This is the kernel's idiom for inheritance: instead of struct pci_dev * inheriting from struct device *, the pci_dev struct contains a struct device dev field, and you get back to the generic form with &pdev->dev.
struct device_driver represents a piece of code that knows how to handle one or more kinds of devices. It declares a name, which bus it belongs to, and at minimum a probe and remove function pointer. Again it is almost never used bare — you embed it inside struct platform_driver, struct pci_driver, etc.
struct bus_type is the matchmaker. It owns lists of all registered devices and all registered drivers for that bus. When a new device appears (hardware inserted, DT node parsed, ACPI table walked) or a new driver is loaded (insmod), the bus calls its match function against every driver/device it knows about until a binding is found, then calls the driver's probe.
Think of it like a job fair: the bus is the venue, devices are job seekers, drivers are employers. The venue (bus) checks each job seeker's ID card against each employer's requirements. When there is a match, the employer (driver) gets introduced to the job seeker (device) via probe.
/* Simplified view of the three structs — real kernel headers have more fields */
struct bus_type {
const char *name; /* e.g. "platform", "pci", "i2c" */
int (*match)(struct device *dev,
struct device_driver *drv); /* does drv handle dev? */
int (*probe)(struct device *dev); /* optional bus-level probe */
/* ... suspend, resume, uevent, etc. ... */
struct subsys_private *p; /* internal bus state */
};
struct device_driver {
const char *name;
struct bus_type *bus;
int (*probe) (struct device *dev);
void (*remove)(struct device *dev);
/* ... shutdown, suspend, resume, pm ... */
struct driver_private *p;
};
struct device {
struct device *parent; /* NULL = top-level device */
struct kobject kobj; /* sysfs node */
const char *init_name;
struct bus_type *bus;
struct device_driver *driver; /* bound driver, or NULL */
void *platform_data;
struct device_node *of_node; /* Device Tree node, if any */
/* ... power, dma, iommu ... */
};
02kobject: The Atomic Unit of the Driver Model
Everything visible under /sys is backed by a kobject. It is the smallest buildable unit of the driver model, and understanding it unlocks everything else.
A kobject has three jobs:
- Reference counting —
kobject_get()/kobject_put(). When the reference count drops to zero, the kernel calls thereleasefunction of the associatedktype. This is how kernel objects are safely freed without dangling pointers. - sysfs representation — each kobject corresponds to exactly one directory under
/sys. Itsnamefield becomes the directory name. - Hierarchy — a kobject has a
parentpointer. The chain of parents defines the path in/sys.
A ktype (kernel object type) groups kobjects that share the same attribute set and the same release function. When you read or write a sysfs file, the kernel looks up the ktype of the kobject that owns that directory, finds the matching sysfs_ops, and calls show or store.
A kset is a collection of kobjects of the same ktype, and is itself a kobject. /sys/bus, /sys/devices, /sys/class are all ksets.
You almost never create a bare kobject yourself. You create a struct device (which embeds a kobject), and the device core handles the kobject lifecycle for you. But knowing the layer exists lets you read error messages, trace reference-count bugs, and understand why device_register() creates a sysfs directory.
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/module.h>
/* A standalone kobject example — rare in production but great for learning */
static struct kobject *my_kobj;
static int my_value = 42;
static ssize_t value_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sysfs_emit(buf, "%d\n", my_value);
}
static ssize_t value_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
int ret = kstrtoint(buf, 10, &my_value);
return ret ? ret : count;
}
static struct kobj_attribute value_attr =
__ATTR(value, 0664, value_show, value_store);
static int __init mymod_init(void)
{
/* Creates /sys/kernel/my_kobj/ directory */
my_kobj = kobject_create_and_add("my_kobj", kernel_kobj);
if (!my_kobj)
return -ENOMEM;
return sysfs_create_file(my_kobj, &value_attr.attr);
/* Now: echo 99 > /sys/kernel/my_kobj/value works */
}
static void __exit mymod_exit(void)
{
kobject_put(my_kobj); /* drops refcount; frees when it hits 0 */
}
module_init(mymod_init);
module_exit(mymod_exit);
MODULE_LICENSE("GPL");
03The sysfs Filesystem: Hardware as a Directory Tree
sysfs is a pseudo-filesystem (like /proc) mounted at /sys. It is the userspace mirror of the kernel's internal kobject hierarchy. Every directory under /sys corresponds to one kobject; every regular file corresponds to one attribute (a struct attribute with a show/store pair).
The layout is not random. Key subtrees:
/sys/devices/— the canonical tree of all devices, organized by parent-child relationships reflecting physical topology (e.g., a USB device is nested under its hub, which is nested under the USB controller)./sys/bus/<bus>/devices/— symlinks into/sys/devices/for every device on that bus./sys/bus/<bus>/drivers/— one directory per registered driver on the bus./sys/class/<class>/— symlinks organized by functional class (e.g., all block devices appear under/sys/class/block/regardless of which bus they live on).
This multi-view design means you can find a device by topology, by bus, or by function, and they all point to the same underlying kobject. Tools like udevadm info walk these symlinks to present coherent device information.
When you write device_register(&dev), the device core:
- Initializes
dev->kobjand sets its parent to the parent device's kobject. - Calls
kobject_add()which callssysfs_create_dir(), creating the directory. - Creates default attribute files (like
uevent,power/). - Sends a uevent (
KOBJ_ADD) thatudevdreceives over a netlink socket — this is howudevrules fire and/devnodes get created.
# On any Linux system with a USB device:
$ ls /sys/bus/usb/devices/
1-0:1.0 1-1 1-1:1.0 usb1 usb2 ...
$ ls /sys/bus/usb/devices/1-1/
authorized bConfigurationValue bDeviceClass devnum
driver -> ../../../../bus/usb/drivers/usb
idProduct idVendor manufacturer power/ uevent ...
# The 'driver' entry is a symlink to the bound driver directory.
# Unloading the driver removes this symlink and calls ->remove().
# Writing to 'bind'/'unbind' manually rebinds a driver:
$ echo '1-1' > /sys/bus/usb/drivers/usb/unbind
$ echo '1-1' > /sys/bus/usb/drivers/usb/bind
04The Platform Bus: Devices Without Autodiscovery
PCI and USB are discoverable buses — the hardware can be enumerated at runtime. But a huge class of devices — especially on embedded systems (ARM SoCs, RISC-V boards) — are wired directly to the SoC and cannot announce themselves. There is no plug-and-play mechanism for an I2C sensor soldered to a board.
The kernel solves this with the platform bus (bus_type platform_bus_type). A platform device is registered either:
- Statically: from board files (old,
arch/arm/mach-*/), callingplatform_device_register(). - From the Device Tree (DT): the Open Firmware / Device Tree code parses the
.dtsfile describing the board and callsof_platform_populate(), which createsplatform_deviceobjects for every compatible DT node. - From ACPI: similar, using ACPI namespace objects.
The platform bus match function checks the driver's of_match_table (for DT) or id_table (for legacy) against the device's compatible string or name. This is why every platform driver declares:
static const struct of_device_id mydrv_of_match[] = {
{ .compatible = "vendor,mydevice" },
{ }
};
The string "vendor,mydevice" must match what the DT node says compatible = "vendor,mydevice". This two-sided contract is the platform bus's match function.
The platform bus is the most important bus for embedded Linux development. Every GPIO controller, UART, SPI master, and PWM block on your BeagleBone or Raspberry Pi is a platform device.
/* Minimal platform driver skeleton */
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
struct mydrv_priv {
void __iomem *base; /* MMIO base, mapped with devm_ioremap_resource */
};
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv;
struct resource *res;
/* devm_ functions: memory freed automatically on device removal */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "probed at %pa\n", &res->start);
return 0;
}
static int mydrv_remove(struct platform_device *pdev)
{
/* devm resources freed automatically; just undo manual registrations */
dev_info(&pdev->dev, "removed\n");
return 0;
}
static const struct of_device_id mydrv_of_match[] = {
{ .compatible = "example,mydevice-v1" },
{ } /* sentinel */
};
MODULE_DEVICE_TABLE(of, mydrv_of_match);
static struct platform_driver mydrv = {
.probe = mydrv_probe,
.remove = mydrv_remove,
.driver = {
.name = "mydevice",
.of_match_table = mydrv_of_match,
},
};
module_platform_driver(mydrv); /* expands to module_init/exit boilerplate */
MODULE_LICENSE("GPL");
05probe() and remove(): The Driver Lifecycle
When the bus finds a match between a device and a driver, it calls the driver's probe function. This is the most important function in any driver, and getting its semantics right is what separates robust drivers from buggy ones.
probe must:
- Claim and map hardware resources (MMIO regions, IRQs, clocks, regulators) — preferably using
devm_variants that are automatically released on failure or removal. - Set up any internal data structures and store a pointer with
platform_set_drvdata()(or bus equivalent). - Register with any higher-level framework (e.g.,
input_register_device(),netdev_register(),iio_device_register()). - Return 0 on success, or a negative errno on failure. Returning a negative value tells the bus to undo the binding — the driver is not loaded for this device.
A crucial rule: if probe fails halfway, all resources acquired so far must be released. devm_ functions automate this: on any non-zero return from probe, the devres (device resource) framework unwinds all devm_ allocations in reverse order. This is why modern drivers almost exclusively use devm_kmalloc, devm_ioremap_resource, devm_request_irq, etc.
remove must:
- Unregister from higher-level frameworks.
- Free any resources NOT managed by devres.
- Return cleanly; it cannot fail (on newer kernels it returns void).
A common mistake is forgetting that remove can be called even if probe partially succeeded (this is handled by devres), or that probe may be called multiple times for multiple matching devices (one call per device — the function must be re-entrant).
/* Showing the devm_ unwinding guarantee */
static int buggy_probe(struct platform_device *pdev)
{
void *buf;
int irq, ret;
/* Step 1: allocate memory */
buf = devm_kmalloc(&pdev->dev, 4096, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Step 2: request IRQ */
irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, my_isr, 0, "mydev", pdev);
if (ret)
return ret; /* devm_kmalloc above is freed automatically! */
/* Step 3: register a misc device -- NOT a devm variant here */
ret = misc_register(&my_misc);
if (ret) {
/* devm_ resources freed automatically, but we could also
* explicitly devm_free_irq here if we wanted -- not needed */
return ret;
}
return 0;
}
/*
* If misc_register fails:
* - devm_request_irq is released
* - devm_kmalloc is freed
* All automatically, in reverse order of registration.
*/
06Driver Binding: How the Kernel Matches Device to Driver
Binding is the process of associating a struct device with a struct device_driver. It can be triggered by two events:
- A new driver is registered (
driver_register()/module_init): the bus iterates all unbound devices on that bus and callsbus->match(dev, drv)for each. - A new device is registered (
device_register()): the bus iterates all drivers registered on that bus and callsbus->match(dev, drv)for each.
When match returns true (non-zero), the bus calls device_bind_driver() which:
- Sets
dev->driver = drv. - Creates a symlink
dev -> driverin sysfs. - Calls
drv->probe(dev)(or the bus-specific wrapper, e.g.platform_drv_probe()). - If
probereturns non-zero: clearsdev->driver, removes the symlink — the device is left unbound.
The match function for the platform bus checks (in order): of_match_device (Device Tree compatible strings), acpi_match_device, then the legacy id_table name match, and finally a plain name-string comparison. This is why of_match_table in the driver and compatible in the DT node must be identical strings — the match fails silently if there is a typo.
Binding can also be done manually from userspace via:
echo <devname> > /sys/bus/<bus>/drivers/<drvname>/bind
echo <devname> > /sys/bus/<bus>/drivers/<drvname>/unbind
This is invaluable for driver development: you can unbind a driver, reload a new version, and rebind without rebooting.
/* Tracing a bind event in the kernel (simplified from drivers/base/dd.c) */
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret;
/* 1. Does this driver claim to handle this device? */
if (drv->bus->match && !drv->bus->match(dev, drv))
return 0; /* no match, keep searching */
/* 2. Set the binding */
dev->driver = drv;
/* 3. Call probe (bus wrapper may add locking, power mgmt, etc.) */
if (dev->bus->probe)
ret = dev->bus->probe(dev); /* bus-level probe hook */
else if (drv->probe)
ret = drv->probe(dev); /* driver probe directly */
else
ret = 0;
if (ret) {
dev->driver = NULL; /* unbind on failure */
return ret;
}
/* 4. Expose binding in sysfs */
driver_bound(dev); /* creates symlinks, sends uevent */
return 0;
}
07Device Tree and of_match_table: Describing Hardware in Firmware
On embedded systems (ARM, RISC-V, MIPS, PowerPC), the Device Tree is how the bootloader tells the kernel what hardware exists on this specific board. The kernel binary is generic — the same Image can boot on a Raspberry Pi 4, a BeagleBone Black, or a custom industrial SoC. The Device Tree Blob (.dtb) file is board-specific and describes the hardware topology.
A .dts (Device Tree Source) node for a UART might look like:
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap2-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
clocks = <&dpll_per_m2_div4_wkupdm_ck>;
status = "okay";
};
The compatible property is a list of strings, most-specific first. The kernel's of_match_device() tries each string in the driver's of_match_table against each string in the node's compatible list. The first hit wins.
The reg property gives the MMIO base address and size. platform_get_resource() / devm_ioremap_resource() in probe reads these. The interrupts property gives the IRQ number that platform_get_irq() returns.
This contract — DT describes what is there, driver describes what it can handle — is why you can write a single omap-serial driver that works on dozens of TI boards: the DT changes per board, the driver does not.
For RISC-V development (HiFive Unleashed, QEMU virt machine), the exact same mechanism is used. QEMU's -machine virt generates a DT on the fly, the kernel's of_platform_populate() walks it, and platform devices are created for each node with status = "okay".
/* Matching multiple compatible strings, with per-match driver data */
struct myuart_config {
unsigned int fifo_size;
bool has_auto_rts;
};
static const struct myuart_config config_v1 = { .fifo_size = 16 };
static const struct myuart_config config_v2 = { .fifo_size = 64,
.has_auto_rts = true };
static const struct of_device_id myuart_of_match[] = {
{ .compatible = "example,uart-v2", .data = &config_v2 },
{ .compatible = "example,uart-v1", .data = &config_v1 },
{ } /* sentinel — always required */
};
MODULE_DEVICE_TABLE(of, myuart_of_match);
static int myuart_probe(struct platform_device *pdev)
{
const struct myuart_config *cfg;
const struct of_device_id *match;
match = of_match_device(myuart_of_match, &pdev->dev);
if (!match)
return -ENODEV;
cfg = match->data;
dev_info(&pdev->dev, "FIFO size: %u\n", cfg->fifo_size);
/* ... rest of probe ... */
return 0;
}
08devm_ and the Device Resource Management Framework
One of the most painful problems in early Linux driver development was resource leaks on error paths. Imagine a probe function that allocates memory, maps an MMIO region, requests an IRQ, and registers a clock. If the clock registration fails, you need to undo the IRQ, the MMIO mapping, and the memory allocation — in reverse order. With 5-6 resources and multiple error paths, this produces deeply nested goto err_* cleanup code that is easy to get wrong.
The devres framework solves this elegantly. Every devm_ function (device-managed variant) records the allocation in a list attached to struct device. When the device is removed (device_del()) — or when probe returns a non-zero error — the kernel iterates this list in reverse and calls each resource's release function.
Common devm_ functions:
devm_kzalloc(dev, size, gfp)— allocated memory, freed on device removal.devm_ioremap_resource(dev, res)— maps MMIO, releases the region and unmaps on removal.devm_request_irq(dev, irq, handler, ...)— requests IRQ, freed on removal.devm_clk_get(dev, name)— gets a clock, put on removal.devm_regulator_get(dev, name)— gets a regulator, put on removal.devm_add_action(dev, fn, data)— registers an arbitrary cleanup function.
The rule of thumb for modern kernel drivers: use devm_ for everything in probe. Your remove function then only needs to undo non-devres registrations (like misc_deregister(), netdev_unregister() — though many frameworks now also have devm_ variants).
This pattern is so effective that reviewers will flag a probe that uses the bare kzalloc without a corresponding kfree in every error path as a bug.
/* The old way vs the devm way */
/* OLD: fragile goto chain */
static int old_probe(struct platform_device *pdev)
{
struct priv *p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p) return -ENOMEM;
p->base = ioremap(0x44e09000, 0x2000);
if (!p->base) { kfree(p); return -ENOMEM; }
if (request_irq(72, isr, 0, "dev", p)) {
iounmap(p->base);
kfree(p);
return -EBUSY;
}
return 0;
/* remove() must undo all three */
}
/* NEW: devm makes every error path safe automatically */
static int new_probe(struct platform_device *pdev)
{
struct priv *p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
if (!p) return -ENOMEM;
p->base = devm_ioremap(&pdev->dev, 0x44e09000, 0x2000);
if (!p->base) return -ENOMEM; /* devm_kzalloc freed automatically */
if (devm_request_irq(&pdev->dev, 72, isr, 0, "dev", p))
return -EBUSY; /* devm_kzalloc + devm_ioremap freed automatically */
return 0;
/* remove() needs to do nothing for these three resources */
}
09The Device Hierarchy and Power Management
The struct device parent pointer creates a tree rooted at virtual devices near the top of /sys/devices/. This hierarchy is not just for aesthetics — it drives power management.
When the system suspends, the kernel walks the device tree bottom-up (leaves first), calling each bound driver's ->suspend() (or the dev_pm_ops equivalent). On resume it walks top-down. This guarantees a parent (e.g., a PCI bridge) is never suspended while a child (e.g., a NIC behind that bridge) is still active.
Modern drivers use dev_pm_ops instead of the old suspend/resume pointers:
static const struct dev_pm_ops mydrv_pm = {
SET_SYSTEM_SLEEP_PM_OPS(mydrv_suspend, mydrv_resume)
SET_RUNTIME_PM_OPS(mydrv_runtime_suspend,
mydrv_runtime_resume, NULL)
};
SET_SYSTEM_SLEEP_PM_OPS handles hibernate/suspend-to-RAM. SET_RUNTIME_PM_OPS handles runtime PM — the ability for a device to be suspended independently while the system is running (e.g., an idle USB device powering down its PHY).
The device hierarchy also affects uevent propagation. When a child device generates a KOBJ_ADD or KOBJ_REMOVE event, its parents can inject environment variables. This is how udev knows that a device at /sys/bus/usb/devices/1-2 is also a block device that needs a /dev/sdb node.
For embedded drivers, you often need pm_runtime_enable(&pdev->dev) in probe and pm_runtime_disable() in remove to opt the device into the runtime PM framework.
/* Enabling runtime PM in a platform driver */
static int mydrv_probe(struct platform_device *pdev)
{
/* ... resource acquisition ... */
pm_runtime_set_active(&pdev->dev); /* device is currently on */
pm_runtime_enable(&pdev->dev); /* allow the PM core to idle it */
pm_runtime_set_autosuspend_delay(&pdev->dev, 50); /* 50 ms idle before suspend */
pm_runtime_use_autosuspend(&pdev->dev);
return 0;
}
static int mydrv_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev);
return 0;
}
/* In the driver's operational code, bracket hardware access: */
static irqreturn_t mydrv_isr(int irq, void *data)
{
struct platform_device *pdev = data;
pm_runtime_get_sync(&pdev->dev); /* ensure device is awake */
/* ... read hardware registers ... */
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
return IRQ_HANDLED;
}
10MODULE_DEVICE_TABLE and Autoloading
When you plug in a USB drive or a PCI NIC, the kernel needs to know which kernel module should handle it — and load that module automatically, without the user running modprobe. This magic is MODULE_DEVICE_TABLE.
MODULE_DEVICE_TABLE(of, mydrv_of_match) causes the build system to run scripts/mod/file2alias.c on the compiled object file. This tool extracts the match table entries and generates MODULE_ALIAS() strings that are embedded into the .ko file's module metadata.
For a USB driver with USB_DEVICE(0x046d, 0xc52b) (a Logitech mouse), the alias is usb:v046DpC52B*. When the USB subsystem enumerates this device, it sends a uevent with MODALIAS=usb:v046DpC52B*. udevd receives this, runs modprobe usb:v046DpC52B*, and the kernel searches /lib/modules/<version>/modules.alias (generated by depmod) to find which .ko file provides that alias — and loads it.
For platform/DT drivers, the alias is of:NunknownTexample,mydevice-v1C*. When of_platform_populate() creates a platform device from a DT node, it sets the device's MODALIAS to match this format.
The entire autoloading chain is: DT node parsed → platform device created → uevent fired → udev calls modprobe → modprobe consults modules.alias → correct .ko loaded → driver registered → bus binds driver to device → probe() called. MODULE_DEVICE_TABLE is the glue that makes steps 6-7 possible automatically.
# See the aliases embedded in a module:
$ modinfo drivers/tty/serial/8250/8250_omap.ko | grep alias
alias: of:N*T*Cti,omap2-uartC*
alias: of:N*T*Cti,omap2-uart
alias: of:N*T*Cti,am3352-uartC*
alias: of:N*T*Cti,am3352-uart
# See the full alias database generated by depmod:
$ grep omap2-uart /lib/modules/$(uname -r)/modules.alias
alias of:N*T*Cti,omap2-uartC* 8250_omap
alias of:N*T*Cti,omap2-uart 8250_omap
# Simulate what happens on device hotplug:
$ modprobe --dry-run --verbose 'of:N*T*Cti,omap2-uart'
insmod /lib/modules/.../8250_omap.ko
11Registering Custom Bus Types (Advanced)
While you will spend most of your driver-writing career working with existing bus types (platform, PCI, USB, I2C, SPI), understanding how to register a new bus type reveals the full architecture of the driver model.
A struct bus_type requires at minimum a name and a match function. The bus lives in /sys/bus/<name>/ with devices/ and drivers/ subdirectories. Calling bus_register(&my_bus) creates this structure in sysfs.
Real examples of buses registered this way in the kernel:
platform_bus_type— the platform bus we use above.i2c_bus_type— I2C devices.matchchecksid_tablename orof_match_table.mdio_bus_type— for Ethernet PHYs on an MDIO bus.virtio_bus— for virtio devices in QEMU/KVM.
The key insight is that the bus match function defines the language by which devices and drivers find each other on that bus. On USB, match compares vendor/product IDs and device class. On I2C, match compares device name strings from id_table. On the platform bus, match uses DT compatible strings. Each bus speaks its own dialect for discovery, but the kernel's binding machinery is universal.
When writing a driver for a new virtual bus (e.g., a hypervisor-specific device channel), you would register a new bus_type, define match based on how your hypervisor identifies device types, and have your bus driver call device_register() for each device channel it discovers at probe time.
/* Skeleton of a minimal new bus type */
#include <linux/bus.h>
#include <linux/device.h>
static int mybus_match(struct device *dev,
struct device_driver *drv)
{
/* Simple name match: device name must equal driver name */
return strcmp(dev_name(dev), drv->name) == 0;
}
static int mybus_probe(struct device *dev)
{
/* Called by the bus before delegating to the driver's probe */
struct device_driver *drv = dev->driver;
return drv->probe ? drv->probe(dev) : 0;
}
struct bus_type mybus_type = {
.name = "mybus",
.match = mybus_match,
.probe = mybus_probe,
};
EXPORT_SYMBOL_GPL(mybus_type);
static int __init mybus_init(void)
{
return bus_register(&mybus_type);
/* Creates /sys/bus/mybus/devices/ and /sys/bus/mybus/drivers/ */
}
static void __exit mybus_exit(void)
{
bus_unregister(&mybus_type);
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
Key insights
- ✦The driver model is object-oriented C: embedding structs achieves inheritance, function pointers achieve polymorphism — no C++ needed.
- ✦kobject is the atom: every sysfs directory is one kobject; every attribute file is one show/store pair. Everything you see under /sys traces to this.
- ✦The bus is the matchmaker, not the driver. Drivers declare what they handle; the bus runs the introductions. This separation is why you can load drivers in any order.
- ✦devm_ functions are not optional niceties — they are the correct modern idiom. Using bare kzalloc/kfree in probe is a code smell reviewers will flag.
- ✦MODULE_DEVICE_TABLE is what makes hot-plug autoloading work. The compatible string in your driver must exactly match the DT node or nothing binds — silently.
- ✦probe() returning a non-zero value is not a crash — it is a clean 'I cannot handle this device right now'. The bus leaves the device unbound and may retry (e.g., -EPROBE_DEFER for dependency ordering).
- ✦The device tree is board-specific, the kernel image is not. The same compiled driver handles dozens of boards because of_match_table maps compatible strings to behavior.
- ✦The device hierarchy (parent pointers) is not aesthetic: it drives correct suspend/resume ordering and uevent propagation to udevd.
Common pitfalls
- ⚠Typo in the compatible string: 'vendor,mydevice-v1' in the driver vs 'vendor,my-device-v1' in the DT. The bus match silently fails, probe is never called, and dmesg shows nothing. Always double-check with: grep -r 'compatible' your.dts.
- ⚠Using bare kzalloc/ioremap in probe without matching cleanup on every error path. Use devm_ variants everywhere in probe.
- ⚠Forgetting the sentinel entry { } at the end of of_match_table or id_table. The kernel iterates until it finds a zero-filled entry; without it you get memory corruption or a hang.
- ⚠Calling probe-only registration functions (like misc_register) inside a loop or thinking probe is called once globally. Probe is called once per matching device — if three devices match, probe runs three times concurrently.
- ⚠Returning -ENODEV from probe when a dependency (clock, regulator, GPIO) is not yet available. The correct return is -EPROBE_DEFER, which tells the kernel to retry probe after more drivers have loaded.
- ⚠Not calling pm_runtime_disable() in remove() when pm_runtime_enable() was called in probe(). This leaves the PM reference count unbalanced and can prevent the device from being removed cleanly.
- ⚠Storing a raw pointer to pdev->dev in a global/static variable. The pdev is owned by the bus and freed after remove(); your pointer becomes dangling. Store your own priv struct (retrieved via platform_get_drvdata) instead.
- ⚠Forgetting MODULE_LICENSE("GPL"). Many kernel symbols (EXPORT_SYMBOL_GPL) are only accessible to GPL-licensed modules. A missing or non-GPL license causes probe to fail with 'Unknown symbol in module'.
Practice
- 01easy
Write a minimal platform driver that registers itself for compatible string 'learner,hello-v1', prints 'hello from probe' with dev_info, stores the MMIO base address from the first MEM resource in a devm_kzalloc'd private struct, and prints 'goodbye from remove'. Test it against a QEMU virt machine by adding a DT overlay node with matching compatible and a dummy reg range. Confirm probe fires by checking dmesg.
- 02easy
Create a kobject under /sys/kernel/ called 'mylearner' with two sysfs attributes: 'counter' (read/write integer) and 'reset' (write-only, sets counter to 0). Verify with 'cat /sys/kernel/mylearner/counter' and 'echo 0 > /sys/kernel/mylearner/reset'. Pay attention to the kobj_attribute __ATTR macros and the sysfs_emit function (not sprintf).
- 03medium
Write a platform driver that deliberately returns -EPROBE_DEFER on its first probe call (use a static bool first_call flag), then succeeds on the second call. Use dev_info to log each attempt. Observe in dmesg how the kernel retries deferred probes after other drivers load. This simulates waiting for a clock or regulator provider.
- 04medium
Register a new bus type called 'toybus'. Write a bus_type with a match function that compares device names to driver names as plain strings. Register one device and one driver on this bus (device name == driver name) and verify that probe is called, and that /sys/bus/toybus/devices/ and /sys/bus/toybus/drivers/ are populated. Use two separate modules: one for the bus, one for the driver.
- 05hard
Instrument an existing kernel driver (pick any simple one, e.g. drivers/leds/leds-gpio.c) with a kprobe on its probe function. Use the kprobe to log the device name and parent name every time probe is called. This requires understanding struct platform_device, dev_name(), and the kprobe pre_handler. Compare your output to 'udevadm monitor --kernel' to see the correlation between kernel events and your probe log.
Resources
The classic reference; chapters 13-14 cover the driver model. Free PDF from LWN. Some sections are dated (2.6 era) but the model chapter is still accurate.
The canonical in-tree docs for bus.rst, device.rst, driver.rst, binding.rst — read these after LDD3 to see what changed.
Free slide decks covering the driver model, platform devices, DT, and devm_ with excellent diagrams. Among the best free training materials in existence.
Best current book for hands-on platform driver development with modern kernel APIs.
Good starting point for understanding the DT ecosystem and how it integrates with platform drivers.
Jonathan Corbet's definitive explanation of platform devices, resources, and the DT integration. Written when the unified model was solidifying.
Covers the devres framework in depth, including devm_add_action and how to audit drivers for missing devm_ conversions.
Browse drivers/base/ live with cross-referenced symbols. Essential for reading device.c, dd.c (device-driver binding), and bus.c with full context.
Checkpoints — you can now…
- ✓You can describe the roles of struct device, struct device_driver, and struct bus_type and explain how they interact during driver binding.
- ✓You can explain what a kobject is, why it has a reference count, and how it maps to a directory in /sys.
- ✓You can write a minimal platform driver with a correct of_match_table, probe, and remove, using only devm_ resource functions.
- ✓You can explain why MODULE_DEVICE_TABLE is needed and trace the full chain from a DT node to a probe() call including the modprobe step.
- ✓You can read /sys/bus/<bus>/devices/ and /sys/bus/<bus>/drivers/ and explain what each symlink means and how it got there.
- ✓You can identify the difference between -ENODEV and -EPROBE_DEFER as probe return values and know when to use each.
- ✓You can explain why the device hierarchy (parent pointers) matters for correct suspend/resume ordering.