Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DeelerDev/linux/llms.txt

Use this file to discover all available pages before exploring further.

The Linux kernel driver model is a unified representation of all hardware in the system. Before it existed, each bus subsystem (PCI, USB, ISA, and so on) maintained its own ad-hoc data structures and device lists. The driver model introduced three common abstractions—struct device, struct device_driver, and struct bus_type—that every bus now builds on. This consistency makes power management, hotplug, and sysfs representation work uniformly regardless of the underlying bus.

The three core abstractions

struct device

struct device is the generic representation of one hardware instance. Bus-specific structures embed it:
struct pci_dev {
    /* PCI-specific fields */
    struct device dev;   /* generic device interface */
};
The embedded struct device carries the kobject (for sysfs), a pointer to the parent device, a pointer to the bus type, and PM state. Bus and driver code accesses the embedded device with container_of():
struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
Key fields and functions from include/linux/device.h:
/* Register a device with the driver core */
int device_register(struct device *dev);

/* Remove a device */
void device_unregister(struct device *dev);

/* Reference counting */
struct device *get_device(struct device *dev);
void put_device(struct device *dev);
The bus must initialize at minimum parent, name, and bus before calling device_register().

struct device_driver

struct device_driver represents the driver as a whole, not one device instance. It is statically allocated:
static struct device_driver eepro100_driver = {
    .name    = "eepro100",
    .bus     = &pci_bus_type,
    .probe   = eepro100_probe,
    .remove  = eepro100_remove,
    .suspend = eepro100_suspend,
    .resume  = eepro100_resume,
};
Bus-specific driver structures embed struct device_driver and add bus-specific fields such as device ID tables:
struct pci_driver {
    const struct pci_device_id *id_table;
    struct device_driver        driver;
};

struct bus_type

struct bus_type represents one bus technology. It is declared as a static object by the bus driver:
struct bus_type pci_bus_type = {
    .name  = "pci",
    .match = pci_bus_match,
};
The match callback is the bus’s way of deciding whether a given driver supports a given device. The kernel calls it during driver registration and during device discovery.

Driver registration

int driver_register(struct device_driver *drv);
Drivers with bus-specific structures use bus wrappers (pci_register_driver(), platform_driver_register(), etc.) that ultimately call driver_register() internally. Registration initializes the reference count, the lock, and the driver’s sysfs directory. Drivers should register early—these fields are accessed by the core at any time.

Bus matching: how the kernel pairs devices and drivers

When a new driver is registered, the bus walks its list of unbound devices and calls match() for each one. When a new device is discovered, the bus walks its list of registered drivers and calls match(). The bus-specific match() implementation compares the device’s identity (PCI vendor/device ID, USB product ID, Device Tree compatible string, etc.) against the driver’s ID table.
You iterate over all devices bound to a driver or all drivers on a bus using the helper functions driver_for_each_dev(), bus_for_each_dev(), and bus_for_each_drv(). These helpers acquire the bus lock and maintain proper reference counts on each object.

sysfs representation

Every registered bus, device, and driver gets a corresponding directory in sysfs. The layout reflects the physical hierarchy:
/sys/bus/pci/
├── devices
│   ├── 00:00.0 -> ../../../root/pci0/00:00.0
│   ├── 00:01.0 -> ../../../root/pci0/00:01.0
│   └── 00:02.0 -> ../../../root/pci0/00:02.0
└── drivers
    ├── Intel ICH
    ├── agpgart
    └── e100
Device entries under /sys/bus/<bus>/devices/ are symlinks into the physical hierarchy under /sys/devices/. Driver directories under /sys/bus/<bus>/drivers/ contain a devices/ subdirectory that holds symlinks to bound devices, plus any attributes the driver exports. Drivers export sysfs attributes using DRIVER_ATTR_RW and DRIVER_ATTR_RO macros, then add or remove them with:
int driver_create_file(struct device_driver *drv,
                       const struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv,
                        const struct driver_attribute *attr);

Platform devices and platform_driver

Platform devices are SoC-integrated peripherals (UARTs, I2C controllers, GPIO banks) that appear as autonomous entities with direct CPU-bus addressing. They use a pseudo-bus called the platform bus.
/* From include/linux/platform_device.h */
struct platform_device {
    const char       *name;
    int               id;
    bool              id_auto;
    struct device     dev;
    u32               num_resources;
    struct resource  *resource;
    const struct platform_device_id *id_entry;
};

struct platform_driver {
    int  (*probe)(struct platform_device *);
    void (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int  (*suspend)(struct platform_device *, pm_message_t state);
    int  (*resume)(struct platform_device *);
    struct device_driver          driver;
    const struct platform_device_id *id_table;
};
Register a platform driver with:
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
A convenience macro handles the common case where the driver has no bus-specific setup:
module_platform_driver(my_platform_driver);
This macro expands to module_init() / module_exit() wrappers that call platform_driver_register() and platform_driver_unregister().

probe() and remove() callbacks

probe() is called in task context when the kernel decides a driver should own a device. A typical probe function:
  1. Retrieves device resources (memory-mapped I/O regions, IRQs, clocks).
  2. Allocates and initializes driver-private data.
  3. Initializes the hardware.
  4. Registers the device with a higher-level subsystem (e.g., input_register_device()).
static int mydrv_probe(struct platform_device *pdev)
{
    struct mydrv_priv *priv;
    struct resource *res;
    void __iomem *base;

    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(base))
        return PTR_ERR(base);

    priv->base = base;
    platform_set_drvdata(pdev, priv);

    return 0;
}
Return 0 on success, a negative errno on failure. Return -EPROBE_DEFER if a dependency (clock, regulator, GPIO) is not yet available; the core will retry later.
remove() is called when the device is physically removed, the driver module is unloaded, or the system is shutting down. Free all resources that were not allocated with devm_* helpers.
static void mydrv_remove(struct platform_device *pdev)
{
    struct mydrv_priv *priv = platform_get_drvdata(pdev);

    /* devm_* resources are freed automatically after this returns */
    hardware_disable(priv->base);
}
sync_state() is called once, after all consumer devices of a given device have successfully probed. Its primary use case is letting the kernel cleanly take over management of devices configured by boot firmware (bootloaders, UEFI, ACPI) without disrupting consumers that haven’t finished initializing.
Do not return -EPROBE_DEFER after child devices have been registered. If a child device is registered and then -EPROBE_DEFER is returned from probe(), it can result in an infinite loop of probe attempts.

Device resources: devm_* managed allocators

The devm_* family of functions ties resource lifetime to a struct device. All resources acquired through devm_* are automatically released when the device is unbound from its driver—whether remove() returns normally, probe() fails partway through, or the driver module is unloaded. Common managed allocators:
FunctionReplaces
devm_kzalloc(dev, size, gfp)kzalloc() + manual kfree()
devm_ioremap_resource(dev, res)ioremap() + iounmap()
devm_platform_ioremap_resource(pdev, idx)platform_get_resource() + ioremap()
devm_request_irq(dev, irq, handler, ...)request_irq() + free_irq()
devm_clk_get(dev, id)clk_get() + clk_put()
devm_regulator_get(dev, id)regulator_get() + regulator_put()
Use devm_* for everything in probe(). It eliminates the need for cleanup labels and makes error paths trivially correct. The only time you need manual cleanup is for resources that must outlive driver unbinding, which is rare.

Build docs developers (and LLMs) love