Linux Device Tree Explained Simply- Free Linux Device Drivers Course

Linux Device Tree Explained Simply

Free Linux Kernel Development Course — Chapter 6, Part 1

Kernel 6.x Ready
Original Driver Code
Hands-On Lab

PREV_LEC  |  NEXT_LEC

If you are learning Linux kernel device driver development, understanding the
Linux device tree is unavoidable. Almost every modern embedded Linux board — from a
Raspberry Pi to an industrial ARM SoC — describes its hardware to the kernel using a device tree
instead of hard-coded C files. This lecture is part of our free linux kernel development course
and introduces the device tree from scratch: what it is, why it replaced board files, how the
kernel enables it, and how a real driver reads values out of it. Every code sample here is
written fresh for this free linux device drivers course and tested against a
current 6.x kernel — nothing is copied from any book.

Topics Covered In This Free Embedded Linux Course Lecture

device tree basics
CONFIG_OF
of_property_read_u32
of_property_read_string
device tree data types
devicetree overlay

What You Will Learn

  • What a device tree is and the problem it solves for embedded Linux
  • The three device tree data types and how they map to C
  • How to enable device tree support in a kernel build
  • How the bootloader, kernel, and driver each use the device tree at boot
  • How to write an original driver that reads real values from a device tree node
  • How to build and load a device tree overlay to test the driver on real hardware

Prerequisites

  • Comfortable writing a basic kernel module (module_init/module_exit)
  • Completed our platform device driver chapter, or already know probe()/remove()
  • Access to an ARM board that boots from a device tree (Raspberry Pi OS works well) or a QEMU ARM target
  • The device-tree-compiler package installed (provides the dtc tool)

What Is a Device Tree?

A device tree is a plain-text hardware description file. It lists every piece of hardware on a
board — I2C sensors, SPI controllers, GPIO lines, memory-mapped peripherals — as a tree of named
nodes, and each node carries a set of properties that describe it. It reads a bit
like a simplified JSON file, except properties are typed as strings, 32-bit cells, byte arrays, or
simply present/absent for booleans.

The format did not start with Linux. It comes from Open Firmware (OF), a firmware standard that
several workstation and server vendors used decades ago to describe hardware to firmware and the
OS. The Linux kernel adopted the same tree format because it solved a real problem: on
non-discoverable buses (I2C, SPI, memory-mapped platform devices), there is no way for the kernel
to probe hardware automatically the way it can on PCI or USB. Something has to tell the kernel
“there is an accelerometer at I2C address 0x53” — and the device tree is that something.

Why Modern Kernels Use Device Tree Instead of Board Files

Before device tree became standard, every ARM board shipped its own C file (a “board file”) that
hard-coded every peripheral, address, and IRQ number directly into the kernel source. Every new
board meant a new board file, and the kernel source tree grew unmanageably large. Device tree
moves that description out of C code entirely: the same kernel binary can boot on different boards
simply by handing it a different compiled device tree blob. This is exactly why, even in 2026,
virtually every ARM and RISC-V board — and the platform driver model you learned in the previous
chapter — depends on device tree to supply the addresses, IRQ numbers, and configuration data that
probe() functions need.

Device Tree Boot Flow

Bootloader
loads DTB into RAM
Kernel
unflattens DTB into live tree
platform_match()
compatible string vs driver
Your Driver
probe() reads properties

Device Tree Data Types

Every property in a device tree node has one of three basic shapes. Understanding these up front
saves you from confusing errors later, since the wrong read function on the wrong type simply
fails silently or returns garbage.

Type Syntax In .dts Kernel Read API Typical Use
String label = "front-sensor"; of_property_read_string() Names, compatible strings
Cell (32-bit) sample-rate = <100>; of_property_read_u32() Frequencies, addresses, counts
Boolean ep,enable-filter; of_property_read_bool() Feature flags — present means true

A raw device tree node looks like this before you write a single line of driver code:

ep_sensor: sensor@53 {
    compatible = "ep,demo-sensor";
    reg = <0x53>;
    label = "front-sensor";
    sample-rate = <100>;
    ep,enable-filter;
};

Notice that cells are wrapped in angle brackets and can hold more than one 32-bit value — a
property like int-list = <10 20 30>; is a list of three cells, not one number.
A boolean property never has a value at all; its mere presence in the node is the “true”.

Enabling Device Tree Support In The Kernel

Device tree parsing is gated behind a single kernel config option. Almost every ARM/ARM64/RISC-V
defconfig ships with it on by default, but it is worth knowing explicitly:

CONFIG_OF=y

To pull the device tree API into your own driver, include these two headers:

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

Hands-On: Reading Device Tree Properties In A Real Driver

Let’s build an original platform driver, ep_dt_demo, that matches the sensor node shown
above and reads all three of its properties in probe(). This is not copied from any book —
it is written specifically for this free linux device drivers course lecture.

// ep_dt_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>

struct ep_dt_demo_priv {
    const char *label;
    u32 sample_rate;
    bool filter_enabled;
};

static int ep_dt_demo_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct ep_dt_demo_priv *priv;
    int ret;

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

    ret = of_property_read_string(dev->of_node, "label", &priv->label);
    if (ret) {
        dev_err(dev, "missing 'label' property\n");
        return ret;
    }

    ret = of_property_read_u32(dev->of_node, "sample-rate",
                                &priv->sample_rate);
    if (ret) {
        dev_err(dev, "missing 'sample-rate' property\n");
        return ret;
    }

    priv->filter_enabled =
        of_property_read_bool(dev->of_node, "ep,enable-filter");

    platform_set_drvdata(pdev, priv);

    dev_info(dev, "ep_dt_demo: label=%s rate=%u filter=%s\n",
             priv->label, priv->sample_rate,
             priv->filter_enabled ? "on" : "off");

    return 0;
}

static void ep_dt_demo_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_dt_demo: removed\n");
}

static const struct of_device_id ep_dt_demo_of_match[] = {
    { .compatible = "ep,demo-sensor" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_dt_demo_of_match);

static struct platform_driver ep_dt_demo_driver = {
    .probe  = ep_dt_demo_probe,
    .remove = ep_dt_demo_remove,
    .driver = {
        .name           = "ep_dt_demo",
        .of_match_table = ep_dt_demo_of_match,
    },
};
module_platform_driver(ep_dt_demo_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original device tree property read demo");

Everything the kernel needs to bind this driver to hardware is the compatible string
match — no addresses or IRQ numbers are hard-coded anywhere in the C file. That is the entire point
of device tree.

Testing It: Build A Device Tree Overlay And Load It

You do not need to rebuild your whole board’s DTB to test this driver. Modern kernels support
device tree overlays, small compiled fragments that get merged into the live tree at
runtime — the fastest way to test a new node on real hardware such as a Raspberry Pi.

Create the overlay source:

// ep-demo-overlay.dts
/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835";

    fragment@0 {
        target-path = "/";
        __overlay__ {
            ep_sensor: sensor@53 {
                compatible = "ep,demo-sensor";
                reg = <0x53>;
                label = "front-sensor";
                sample-rate = <100>;
                ep,enable-filter;
            };
        };
    };
};

Compile it with the device tree compiler and load it through configfs:

$ dtc -@ -I dts -O dtb -o ep-demo.dtbo ep-demo-overlay.dts
$ sudo dtoverlay ep-demo.dtbo
$ sudo insmod ep_dt_demo.ko
$ dmesg | tail -3

Expected output:

[  102.441823] ep_dt_demo sensor@53: ep_dt_demo: label=front-sensor rate=100 filter=on

Common Mistakes And Troubleshooting

Symptom Likely Cause Fix
probe() never runs compatible string mismatch between node and of_match_table Compare strings character-for-character, including the vendor prefix
of_property_read_u32() returns -EINVAL Property is missing or written as a string instead of a cell Confirm angle brackets are used: prop = <10>;
Boolean always reads false Property was given a value instead of being left empty Boolean properties must have no = value at all
Overlay fails to apply Wrong target-path or missing base compatible string Match the overlay’s top-level compatible to your board’s base DTB

Best Practices

  • Always check the return value of every of_property_read_*() call — never assume a property exists
  • Use a vendor prefix (like ep,) for any custom, non-standard property name
  • Prefer devm_ managed allocations in probe() so cleanup happens automatically on removal
  • Keep the compatible string stable — changing it breaks every board’s DTB that already uses it

Real-World Use Case

This exact pattern — compatible match plus of_property_read_*() — is how real kernel
drivers pick up calibration values, GPIO assignments, and feature flags for sensors, regulators,
and display panels shipped on production ARM boards. Once you are comfortable with it, reading any
mainline driver’s device tree binding documentation becomes far easier.

Summary And Key Takeaways

  • Device tree is a text-based hardware description format inherited from Open Firmware
  • It replaced hard-coded board files so one kernel binary can support many boards
  • Properties come in three types: strings, 32-bit cells, and booleans
  • CONFIG_OF plus <linux/of.h> gives your driver the API to read them
  • Device tree overlays let you test new nodes without rebuilding the board’s base DTB

Conclusion

You now understand what a Linux device tree is, why it exists, and how a real driver reads values
out of it. This is the foundation the rest of this device tree chapter builds on. In the next
lecture of this free linux kernel development course, we go deeper into device tree
naming convention — exactly how node names and unit addresses are formed and why it
matters for matching.

Frequently Asked Questions

What is a Linux device tree used for?

It describes non-discoverable hardware — I2C/SPI devices, memory-mapped peripherals, GPIOs — to the kernel so drivers know what exists and where, without hard-coding it in C.

Is device tree only used on ARM?

It is most associated with ARM and RISC-V, but the OF-derived format is architecture-independent; x86 systems generally use ACPI instead for the same purpose.

What does CONFIG_OF actually enable?

It compiles in the open firmware/device tree core: the code that unflattens the DTB blob into the kernel’s live tree and exposes the of_* API to drivers.

What is the difference between a device tree and a device tree overlay?

A full device tree describes an entire board and is loaded once at boot. An overlay is a small fragment merged into the live tree afterward, useful for testing or optional add-on hardware.

Why did of_property_read_u32() fail even though the property exists?

Usually the property was written without angle brackets, so the DT compiler stored it as a different type than a 32-bit cell.

Do I need a physical board to learn device tree?

No — QEMU’s ARM vexpress or virt machine boots from a device tree and lets you experiment without real hardware.

What replaced board files in the Linux kernel?

Device tree replaced per-board C board files starting with the ARM device tree conversion effort, letting one kernel image support many boards via different DTBs.

Continue This Free Linux Device Drivers Course

Next: Device tree naming convention — node names, unit addresses, and labels explained.

PREV_LEC  |  NEXT_LEC

 

Leave a Reply

Your email address will not be published. Required fields are marked *