Skip to main content
The Linux Device Model (LDM) is the unified framework that represents every hardware and virtual device in the system as a tree of struct device objects rooted at /sys/devices. It enforces a strict bus → device → driver hierarchy, enables automatic power management, and generates consistent uevent notifications for userspace.

The hierarchy: bus, device, driver

Three abstract entities form the backbone of the LDM:
EntityKernel structRole
Busstruct bus_typeTransport layer connecting CPU to devices (PCI, USB, platform, I2C, SPI). Matches devices to drivers.
Devicestruct deviceRepresents one hardware instance. Embedded in bus-specific structs like struct pci_dev.
Driverstruct device_driverStatically-allocated descriptor for a driver. One driver may bind to many devices.
When a device appears on a bus, the bus’s match() callback iterates registered drivers looking for a compatible one. When a match is found, the bus calls the driver’s probe() function.

struct device

struct device (defined in include/linux/device.h) is the fundamental building block. Every device in the system contains one, usually embedded inside a bus-specific structure:
struct device {
    struct kobject          kobj;         /* sysfs representation */
    struct device          *parent;       /* parent in device tree */
    struct device_private  *p;            /* private driver core data */
    const char             *init_name;    /* initial device name */
    const struct device_type *type;

    const struct bus_type  *bus;          /* type of bus device is on */
    struct device_driver   *driver;       /* bound driver */
    void                   *platform_data;/* board-specific data */
    void                   *driver_data;  /* set/get with dev_set/get_drvdata() */

    struct dev_links_info   links;        /* supplier/consumer links */
    struct dev_pm_info      power;        /* power management state */
    struct dev_pm_domain   *pm_domain;    /* PM domain callbacks */

    u64                    *dma_mask;     /* DMA addressing mask */
    u64                     coherent_dma_mask;
    struct device_dma_parameters *dma_parms;

    struct device_node     *of_node;      /* device tree node */
    struct fwnode_handle   *fwnode;       /* firmware node (DT or ACPI) */

    dev_t                   devt;         /* for /sys/dev */
    const struct attribute_group **groups;
    void (*release)(struct device *dev);  /* last-reference destructor */
};
Devices are reference-counted via kobject. Never free a struct device directly — always use put_device() to release a reference and let the reference count drop to zero trigger release().

struct device_driver

struct device_driver describes a driver to the core. It is statically allocated — one instance per driver, not per device:
struct device_driver {
    const char              *name;
    const struct bus_type   *bus;

    struct module           *owner;
    const char              *mod_name;   /* used for built-in modules */

    bool suppress_bind_attrs;            /* disables bind/unbind via sysfs */
    enum probe_type probe_type;          /* synchronous or asynchronous */

    const struct of_device_id   *of_match_table;
    const struct acpi_device_id *acpi_match_table;

    int  (*probe)       (struct device *dev);
    void (*sync_state)  (struct device *dev);
    int  (*remove)      (struct device *dev);
    void (*shutdown)    (struct device *dev);
    int  (*suspend)     (struct device *dev, pm_message_t state);
    int  (*resume)      (struct device *dev);

    const struct attribute_group **groups;
    const struct attribute_group **dev_groups;
    const struct dev_pm_ops *pm;
};
Bus-specific drivers embed struct device_driver and extend it with a device-ID table:
struct pci_driver {
    const struct pci_device_id *id_table;  /* bus-specific matching */
    struct device_driver        driver;    /* embedded generic driver */
    /* ... */
};

struct bus_type

Each bus subsystem declares one struct bus_type and registers it with bus_register():
struct bus_type pci_bus_type = {
    .name  = "pci",
    .match = pci_bus_match,      /* compares device IDs */
    .probe = pci_device_probe,   /* calls driver->probe */
    .remove = pci_device_remove,
    .pm    = &pci_bus_pm_ops,
};
The core provides helpers to iterate all devices or drivers on a bus:
/* Iterate all devices on a bus */
int bus_for_each_dev(const struct bus_type *bus, struct device *start,
                     void *data, int (*fn)(struct device *, void *));

/* Iterate all drivers registered on a bus */
int bus_for_each_drv(const struct bus_type *bus, struct device_driver *start,
                     void *data, int (*fn)(struct device_driver *, void *));

Platform devices and drivers

Platform devices represent SoC-integrated peripherals that are not enumerable at runtime. They are described statically in the device tree or ACPI tables and matched by name or compatible string.

struct platform_device

struct platform_device {
    const char              *name;         /* device name for matching */
    int                      id;           /* instance ID, or PLATFORM_DEVID_AUTO */
    bool                     id_auto;
    struct device            dev;          /* embedded generic device */
    u64                      platform_dma_mask;
    struct device_dma_parameters dma_parms;
    u32                      num_resources;
    struct resource         *resource;     /* I/O, MEM, IRQ resources */
    const struct platform_device_id *id_entry;
};
Use these helpers to retrieve resources from a platform device:
/* Get a resource by type and index */
struct resource *platform_get_resource(struct platform_device *pdev,
                                       unsigned int type,
                                       unsigned int num);

/* Get an IRQ number */
int platform_get_irq(struct platform_device *pdev, unsigned int num);

/* Map an MMIO resource (devm-managed) */
void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev,
                                             unsigned int index);

Declaring a platform driver

#include <linux/platform_device.h>
#include <linux/of.h>

struct mydev_priv {
    void __iomem  *base;
    int            irq;
    struct device *dev;
};

static int mydev_probe(struct platform_device *pdev)
{
    struct mydev_priv *priv;
    struct resource *res;

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

    priv->dev  = &pdev->dev;

    /* Map the first MMIO region — automatically unmapped on detach */
    priv->base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    priv->irq = platform_get_irq(pdev, 0);
    if (priv->irq < 0)
        return priv->irq;

    /* Request IRQ — automatically freed on detach */
    int ret = devm_request_irq(&pdev->dev, priv->irq, mydev_isr,
                               0, dev_name(&pdev->dev), priv);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, priv);
    dev_info(&pdev->dev, "probed successfully\n");
    return 0;
}

static int mydev_remove(struct platform_device *pdev)
{
    /* devm resources freed automatically */
    dev_info(&pdev->dev, "removed\n");
    return 0;
}

static const struct of_device_id mydev_of_match[] = {
    { .compatible = "vendor,mydevice-v1" },
    { }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);

static struct platform_driver mydev_driver = {
    .probe  = mydev_probe,
    .remove = mydev_remove,
    .driver = {
        .name           = "mydevice",
        .of_match_table = mydev_of_match,
        .pm             = &mydev_pm_ops,  /* optional */
    },
};
module_platform_driver(mydev_driver);

Probe and remove lifecycle

probe()

The probe() callback is called in task context with the bus read-write semaphore held. Its responsibilities:
  • Verify the device is present and its hardware revision is supported.
  • Allocate and initialize per-device state (use devm_kzalloc()).
  • Map hardware resources (registers, DMA buffers).
  • Request IRQs, clocks, regulators, GPIOs.
  • Register sub-devices (e.g., iio_device_register(), rtc_register_device()).
  • Call dev_set_drvdata() to store the private state pointer.
Return 0 on success. Return a negative error code to refuse binding. Return -EPROBE_DEFER if a dependency (clock, regulator, GPIO) is not yet available — the core will retry probe later.
Do not return -EPROBE_DEFER after registering child devices. A later retry with child devices still registered causes an infinite probe loop.

sync_state()

Called exactly once, after all consumer devices of this device have successfully probed. A typical use case is for a regulator or IOMMU driver to transition from a “leave everything at boot state” policy to the software-requested state only after all consumers are online.

remove()

Called when the device is unbound from the driver — due to physical removal, explicit unbind via sysfs, or module unload. With devm_ helpers, most resource cleanup is automatic. The driver should:
  • Stop any ongoing hardware activity (cancel DMA, disable IRQs).
  • Unregister sub-devices in reverse order of registration.
  • Quiesce the device and place it in a low-power state if still present.

Device resource management (devm_)

The devm_ API binds resource lifetimes to a struct device. Resources are released automatically in reverse-allocation order when the device is unbound or the driver module is removed. This eliminates entire classes of resource-leak bugs in error paths.
/* Memory */
void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
void *devm_kmalloc(struct device *dev, size_t size, gfp_t gfp);
void *devm_kmemdup(struct device *dev, const void *src, size_t len, gfp_t gfp);

/* I/O memory */
void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
                           resource_size_t size);
void __iomem *devm_ioremap_resource(struct device *dev,
                                    const struct resource *res);

/* IRQ */
int devm_request_irq(struct device *dev, unsigned int irq,
                     irq_handler_t handler, unsigned long flags,
                     const char *name, void *dev_id);

/* Clocks */
struct clk *devm_clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get_enabled(struct device *dev, const char *id);

/* Regulators */
struct regulator *devm_regulator_get(struct device *dev, const char *id);

/* GPIO */
struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                 enum gpiod_flags flags);
Prefer devm_ variants for every resource type that offers one. Reserve manual cleanup only for resources that must outlive the device binding.

Device tree bindings

Platform, I2C, and SPI devices are described in device tree source (.dts) files. A minimal binding for a hypothetical peripheral:
/* In the board .dts */
&i2c1 {
    status = "okay";

    sensor@48 {
        compatible = "vendor,mysensor";
        reg = <0x48>;              /* I2C address */
        interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
        interrupt-parent = <&gpio1>;
        vdd-supply = <&reg_3v3>;   /* regulator reference */
    };
};
The driver accesses device-tree properties using the of_ and device_property_ APIs:
#include <linux/of.h>
#include <linux/property.h>

static int mydrv_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    u32 clock_freq;
    bool active_high;

    /* Read a u32 property */
    if (device_property_read_u32(dev, "clock-frequency", &clock_freq))
        clock_freq = 1000000;  /* default */

    /* Read a boolean property */
    active_high = device_property_read_bool(dev, "active-high");

    return 0;
}

sysfs representation

The device model maps directly onto the sysfs filesystem:
/sys/
├── bus/
│   ├── pci/
│   │   ├── devices/            # symlinks to physical device dirs
│   │   │   ├── 0000:00:1f.0 -> ../../../devices/pci0000:00/0000:00:1f.0
│   │   └── drivers/
│   │       └── e1000e/         # driver directory
│   └── platform/
│       ├── devices/
│       └── drivers/
│           └── mydevice/
│               ├── bind        # write device name to bind
│               └── unbind      # write device name to unbind
└── devices/
    └── platform/
        └── mydevice.0/
            ├── driver -> ../../../../bus/platform/drivers/mydevice
            ├── power/
            │   ├── control     # "auto" or "on"
            │   └── wakeup      # "enabled" or "disabled"
            └── uevent

sysfs attributes

Drivers export per-device attributes using the DEVICE_ATTR family of macros:
/* Read-write attribute: /sys/devices/.../myattr */
static ssize_t myattr_show(struct device *dev,
                           struct device_attribute *attr, char *buf)
{
    struct mydev_priv *priv = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%u\n", priv->value);
}

static ssize_t myattr_store(struct device *dev,
                            struct device_attribute *attr,
                            const char *buf, size_t count)
{
    struct mydev_priv *priv = dev_get_drvdata(dev);
    unsigned int val;
    int ret;

    ret = kstrtouint(buf, 0, &val);
    if (ret)
        return ret;
    priv->value = val;
    return count;
}
static DEVICE_ATTR_RW(myattr);

/* Export via attribute group (preferred over manual create/remove) */
static struct attribute *mydev_attrs[] = {
    &dev_attr_myattr.attr,
    NULL
};
ATTRIBUTE_GROUPS(mydev);  /* defines mydev_groups[] */

/* Reference in driver struct */
static struct platform_driver mydev_driver = {
    .driver = {
        .name       = "mydevice",
        .dev_groups = mydev_groups,  /* auto created/removed with device */
    },
};
Driver-global attributes (not per-device) use DRIVER_ATTR_RW and driver_create_file() / driver_remove_file(). Device links express supplier/consumer dependencies between devices. The PM core uses these links to order suspend and resume and to implement runtime PM reference counting across devices.
#include <linux/device.h>

/* Express that 'consumer' depends on 'supplier' */
struct device_link *device_link_add(struct device *consumer,
                                    struct device *supplier,
                                    u32 flags);

/* Common flag combinations */
// Runtime PM tracking only:
DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE

// Managed link removed automatically when either driver unbinds:
DL_FLAG_AUTOREMOVE_CONSUMER

Build docs developers (and LLMs) love