Chapter 32: Driver Model
- Why the kernel needs a unified driver model
- How devices are discovered and matched to drivers
- The device tree (DT) and platform device model
- Driver initialization and registration
- How drivers interact with the rest of the kernel
- Our driver model implementation
32.1 What is a Driver Model?
A driver model is a framework that organizes device drivers into a consistent structure. It defines how drivers are registered, how devices are discovered, how drivers and devices are matched, and how drivers provide services to the rest of the kernel.
Without a driver model, the kernel would have a mess of ad-hoc initialization code: each driver would probe hardware in a different way, conflict with other drivers, and be hard to maintain.
32.2 Core Structures
/* A device on the system */
struct device {
const char *name;
struct device_node *of_node; /* Device tree node */
uint64_t mmio_base; /* MMIO base address */
uint64_t mmio_size;
int irq; /* Interrupt number (SPI) */
struct device_driver *driver; /* Bound driver */
struct list_head device_node; /* For device list */
void *priv_data; /* Driver-private data */
};
/* A device driver */
struct device_driver {
const char *name;
const char **compatible; /* Device tree compatible strings */
int (*probe)(struct device *dev); /* Called when device matches */
int (*remove)(struct device *dev);
struct list_head driver_node;
};
/* Match a device to a driver using device tree compatible string */
int driver_match(struct device *dev, struct device_driver *drv) {
const char **compat = drv->compatible;
const char *device_compat = of_get_property(dev->of_node, "compatible");
while (*compat) {
if (strcmp(device_compat, *compat) == 0) return 1;
compat++;
}
return 0;
}
/* Bind a driver to a device */
int driver_bind(struct device *dev) {
struct device_driver *drv;
/* Iterate over registered drivers */
list_for_each(drv, driver_list, driver_node) {
if (driver_match(dev, drv)) {
dev->driver = drv;
return drv->probe(dev);
}
}
return -1; /* No driver found */
}
32.3 Device Discovery
On ARM64, devices are described by the Device Tree (DT), passed to the kernel by the bootloader. The kernel parses the DT blob (Chapter 7) and creates struct device objects for each node with a compatible property:
/* Parse device tree and create platform devices */
void of_platform_populate(struct device_node *root) {
struct device_node *child;
for_each_child_of_node(root, child) {
if (of_get_property(child, "compatible")) {
struct device *dev = kmalloc(sizeof(struct device));
dev->name = child->name;
dev->of_node = child;
dev->mmio_base = of_read_reg(child, 0);
dev->mmio_size = of_read_reg(child, 1);
dev->irq = of_read_irq(child, 0);
dev->driver = NULL;
device_list_add(dev);
driver_bind(dev); /* Try to bind a driver */
}
/* Recurse into child nodes */
of_platform_populate(child);
}
}
/* Example: UART device from QEMU virt device tree */
// /uart@9000000 {
// compatible = "arm,pl011", "arm,primecell";
// reg = <0x0 0x09000000 0x0 0x1000>;
// interrupts = <0x0 0x29 0x4>;
// };
32.4 Driver Initialization
Drivers register themselves at system startup. Our kernel uses a simple init system: each driver declares an init function in a special section:
/* Driver initialization: called during boot */
#define DRIVER_INIT(func) \
static void __attribute__((used, section(".drvinit"))) *__drv_##func = &func;
/* Each driver calls this macro */
DRIVER_INIT(pl011_init);
DRIVER_INIT(gic_init);
DRIVER_INIT(framebuffer_init);
/* At boot, iterate the .drvinit section */
extern void *__drvinit_start[];
extern void *__drvinit_end[];
void driver_init_all(void) {
for (void ***p = (void ***)&__drvinit_start;
p < (void ***)&__drvinit_end; p++) {
void (*init_fn)(void) = (void (*)(void))*p;
init_fn();
}
}
/* PL011 UART driver registration */
void pl011_init(void) {
struct device_driver *drv = kmalloc(sizeof(struct device_driver));
drv->name = "pl011_uart";
drv->compatible = (const char *[]){ "arm,pl011", "arm,primecell", NULL };
drv->probe = pl011_probe;
drv->remove = pl011_remove;
driver_register(drv);
}
/* Called when a matching device is found */
int pl011_probe(struct device *dev) {
/* Initialize UART hardware */
pl011_init_hw(dev->mmio_base);
/* Register this device with the serial subsystem */
serial_register(dev);
return 0;
}
32.5 Our Implementation
Our kernel's driver model follows the Linux platform device model. Key components:
- Device tree parsing: located in
drivers/of/of_tree.c, parses the DT blob - Platform devices: created from DT nodes with
compatibleproperties - Driver registration: drivers register via
driver_register()during boot - Match and probe: matching by compatible string,
probe()initializes hardware - Subsystem registration: drivers register with subsystems (serial, net, fb, input)
- Power management: drivers implement suspend/resume callbacks
Drivers access hardware through MMIO using the per-device mmio_base. Interrupt handlers are registered via request_irq(dev->irq, handler). The driver model ensures that each device has exactly one driver and each driver can handle multiple devices.
32.6 Exercises
Exercise 1: Add a New Driver
Write a driver template for a GPIO controller. Define the device tree binding, implement probe, and register with the driver model.
Exercise 2: Driver Hotplug
Implement a notification system so that when a new device is added (e.g., a USB device or virtio device), the kernel automatically binds a driver.
32.7 Summary
The driver model provides a structured framework for device drivers. Devices are discovered from the device tree, and drivers register with the kernel. Matching uses compatible strings. When a device matches a driver, the probe function initializes the hardware and registers the device with the appropriate subsystem. Our kernel implements this model with platform devices, DT parsing, driver sections, and subsystem registration.