Fundamentals 22 min read

Exploring Linux Bus, Device, and Driver Model and a Deep Dive into Device Tree

This article systematically explains the Linux driver ecosystem by covering the bus‑device‑driver model, the role of device trees in decoupling hardware description from driver code, and step‑by‑step guidance for creating a custom development board with full DTS and kernel integration.

IT Services Circle
IT Services Circle
IT Services Circle
Exploring Linux Bus, Device, and Driver Model and a Deep Dive into Device Tree

Linux Bus, Device, and Driver Model Exploration

Learning resources about drivers are abundant online, but most are fragmented; this article aims to provide a systematic overview of the Linux driver model, starting with the kernel's design for buses, devices, and drivers, then introducing device trees to bridge hardware and software, and finally walking through a hands‑on board bring‑up.

Requirements of the Device Driver Model

Think of a wall‑mounted power socket that stays unchanged regardless of whether a laptop or phone is plugged in—this illustrates high cohesion and low coupling, a core software‑engineering principle applied to device‑driver relationships.

High cohesion means tightly related elements stay together inside a module, while low coupling keeps modules independent. Applying this to drivers ensures that devices and drivers can evolve separately without breaking each other.

Consider a hypothetical network card GITCHAT that needs a base address and an interrupt line. If the driver hard‑codes these values, every board variant would require a separate driver, violating the high‑cohesion/low‑coupling goal.

To avoid rewriting drivers for each board, the kernel uses a bus abstraction that separates board‑specific information from generic driver code.

#define GITCHAT_BASE 0x0001
#define GITCHAT_INTERRUPT 2

int gitchat_send() {
    writel(GITCHAT_BASE + REG, 1);
    ...
}

int gitchat_init() {
    request_init(GITCHAT_INTERRUPT, ...);
    ...
}

When the underlying board changes, only the definitions of GITCHAT_BASE and GITCHAT_INTERRUPT need updating, preserving driver stability.

Implementation of the Device‑Driver Model

The kernel introduces an adapter‑like layer called a "bus" that binds devices to drivers. In the Linux kernel, this layer is not called an adapter but a bus, which maintains the association between a device and its driver.

The three core entities—bus, device, and driver—have distinct responsibilities, as illustrated in the following diagram:

To register a device, its hardware resources are described in a platform_device structure and added to the platform bus:

static struct resource gitchat_resource[] = {
    { .start = ..., .end = ..., .flags = IORESOURCE_MEM },
    { .start = IRQ_PF, .end = IRQ_PF, .flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE }
};

static struct platform_device gitchat_device = {
    .name = "gitchat",
    .id = 0,
    .num_resources = ARRAY_SIZE(gitchat_resource),
    .resource = gitchat_resource,
};

static struct platform_device *ip0x_device __initdata = { &gitchat_device, ... };

static int __init ip0x_init(void) {
    platform_add_devices(ip0x_device, ARRAY_SIZE(ip0x_device));
}

The driver obtains those resources via the bus API platform_get_resource :

static int gitchat_probe(struct platform_device *pdev) {
    ...
    db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 2);
    ...
}

Matching between a device and a driver is performed primarily by name or by an ID table, as shown in the platform match function:

static int platform_match(struct device *dev, struct device_driver *drv) {
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    if (of_driver_match_device(dev, drv))
        return 1;
    if (acpi_driver_match_device(dev, drv))
        return 1;
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;
    return (strcmp(pdev->name, drv->name) == 0);
}

Improvements with Device Tree (DTS)

Historically, board‑specific information lived in C files like board‑xxx.c , leading to massive duplication. Device Tree (DTS) provides a declarative, XML‑like format to describe hardware, first appearing in PowerPC Linux in 2005 and later adopted by ARM, MIPS, and x86.

A typical DTS fragment for the GITCHAT Ethernet controller looks like:

eth:eth@4,c00000 {
    compatible = "csdn, gitchat";
    reg = <4 0x00c00000 0x2 4 0x00c00002 0x2>;
    interrupt-parent = <&gpio 2>;
    interrupts = <14 IRQ_TYPE_LEVEL_LOW>;
    ...
};

Using DTS, the same driver can be reused across multiple boards (A, B, C) without code changes, because the hardware description is externalized.

Device Tree File Structure and Compilation

DTS files are compiled into binary DTB blobs using the Device Tree Compiler (dtc). The compilation command is:

$ dtc -I dts -O dtb csdn-gitchat-evb.dts -o csdn-gitchat-evb.dtb

After adding the board definition to arch/arm/boot/dts/Makefile , the kernel build system produces the corresponding .dtb image.

Hands‑On: Custom Board Case Study

The article walks through creating a fictitious SOC named gitchat , adding a new architecture directory, writing a minimal Kconfig , Makefile , and a simple driver skeleton using DT_MACHINE_START . Then it shows the corresponding DTS ( csdn-gitchat.dtsi ) and board‑specific DTS ( csdn-gitchat-evb.dts ) with definitions for CPUs, memory, buses, Ethernet, timer, clocks, GPIO, UART, SPI, and I²C.

/ {
    compatible = "csdn,gitchat";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = <&intc>;
    ...
    axi@40000000 {
        simple-bus;
        #address-cells = <1>;
        #size-cells = <1>;
        ranges = <0x40000000 0x40000000 0x80000000>;
        ...
        peri-iobg@b0000000 {
            simple-bus;
            #address-cells = <1>;
            #size-cells = <1>;
            ranges = <0 0xb0000000 0x180000>;
            ethernet@10000 { ... };
            timer@20000 { ... };
            clks: clock-controller@30000 { ... };
            gpio0: gpio@40000 { ... };
            uart0: uart@50000 { status = "disabled"; };
            spi0: spi@d0000 { status = "okay"; };
            i2c0: i2c@e0000 { status = "okay"; };
        };
    };
};

Finally, the article demonstrates how to compile the DTS into a DTB and integrate it into the kernel build, completing the custom board bring‑up process.

Through this systematic tutorial, readers gain a solid understanding of Linux's bus‑device‑driver architecture, the advantages of Device Tree, and practical steps to create and compile a custom development board.

KernelLinuxembeddedDevice TreeDevice Driver
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.