Install the Device Tree Compiler (DTC)

Title: Demystifying Device Trees: A Complete Guide for Embedded Developers and Linux Enthusiasts

Introduction – Why the Device Tree Matters More Than Ever

If you’ve ever wrestled with a “kernel panic” on a new single‑board computer, or spent hours hunting down why a peripheral never shows up in `/dev`, you’ve already felt the pain that a missing or mis‑configured hardware description can cause. In the world of embedded Linux, the Device Tree is the silent negotiator between the hardware you’ve built and the software that wants to run on it.

Think of the Device Tree as the blueprint that tells the Linux kernel exactly what components are present on your board, how they’re wired, and what resources they need—without hard‑coding that information into the kernel source. This separation of concerns has become a cornerstone of modern ARM development, enabling the same kernel image to boot on a myriad of devices simply by swapping a small text file (or its compiled binary, the DTB).

In this post we’ll unpack everything you need to know to master Device Trees: from the fundamentals of the Device Tree Source (DTS) format, through practical steps for creating and debugging your own DTB files, to advanced tips for customizing the tree for complex peripherals. By the end, you’ll have a solid, actionable roadmap that lets you confidently integrate new hardware into any Linux‑based system.

1. Device Tree Basics – What It Is, How It Works, and Why It Exists

1.1 The Problem Before Device Trees

Historically, board‑specific code lived directly in the kernel source tree. Every new SoC or evaluation board required a dedicated set of `arch/arm/mach-xyz/` files, custom `board.c` initialization, and a host of compile‑time macros. This approach had two major drawbacks:

1. Scalability – Adding a new board meant recompiling the entire kernel, even if the only change was a different GPIO layout.
2. Portability – The same kernel binary could not be reused across multiple hardware variants, forcing vendors to maintain separate kernel builds for each product.

Both issues slowed development cycles and made long‑term maintenance a nightmare.

1.2 The Device Tree Solution

Enter the Device Tree (DT). Originating from the Open Firmware world, DT provides a hardware description language that lives outside the kernel source. The kernel reads the tree at boot time (via the bootloader) and builds its internal device model based on the data it finds.

Key concepts:

| Term | Meaning |
|——|———|
| DTS (Device Tree Source) | Human‑readable `.dts` file written in a C‑like syntax. |
| DTC (Device Tree Compiler) | Tool that converts DTS into a binary DTB (Device Tree Blob). |
| DTB | Compact binary representation loaded into memory by the bootloader. |
| Overlay | Small DTS fragment applied on top of a base DTB to add or modify nodes at runtime. |

Because the DT is parsed after the kernel has been loaded, you can reuse the same kernel image across many boards—just supply a different DTB. This is why modern ARM platforms (Raspberry Pi, BeagleBoard, NXP i.MX, Qualcomm Snapdragon, etc.) ship with a generic kernel and a set of DTBs for each board variant.

1.3 How the Kernel Consumes the Device Tree

When the bootloader (U‑Boot, Barebox, or even a minimal ROM loader) hands control to the kernel, it passes the address of the DTB via the `bootargs` or a dedicated register (e.g., `r2` on ARM). The kernel’s early initialization code (`offdt.c`) parses the DTB, creates an ofnode structure for each node, and registers devices with the appropriate bus drivers (I²C, SPI, GPIO, etc.).

From a developer’s perspective, this means:

  • No more board files – All hardware details are in the DT.
  • Dynamic driver binding – Drivers declare `compatible` strings that the kernel matches against DT nodes.
  • Runtime configurability – Overlays allow you to enable or disable peripherals on the fly (think of adding a USB‑to‑UART dongle to a running system).
  • 2. Crafting Your First Device Tree – From DTS to DTB

    2.1 Setting Up the Toolchain

    Before you dive into editing `.dts` files, make sure you have the essential tools:

    “`bash

    sudo apt-get install device-tree-compiler

    Verify version (>= 1.4 is recommended)

    dtc -v
    “`

    Most Linux distributions ship a recent DTC, but for cutting‑edge features (e.g., new property types) you may need to build from source:

    “`bash
    git clone https://git.kernel.org/pub/scm/utils/dtc/dtc.git
    cd dtc
    make && sudo make install
    “`

    2.2 Understanding the DTS Syntax

    A typical DTS file looks like a structured C header:

    “`dts
    /dts-v1/;

    / {
    model = “MyBoard v1.0”;
    compatible = “myvendor,myboard”;

    / Memory map /
    memory@80000000 {
    device_type = “memory”;
    reg = ; // 512 MiB RAM
    };

    / UART0 /
    uart0: serial@101f1000 {
    compatible = “arm,pl011”;
    reg = ;
    interrupts = ;
    clock-frequency = ;
    status = “okay”;
    };
    };
    “`

    Key syntax elements:

    | Symbol | Meaning |
    |——–|———|
    | `/` | Root node. |
    | `node@address` | Node name with optional unit address (used for `reg` property). |
    | `property = “value”;` | String property. |
    | `property = ;` | Integer array (big‑endian). |
    | `&label` | Reference to another node (used for phandles). |
    | `status = “disabled”` | Prevents driver binding; useful for optional hardware. |

    2.3 Building a DTB

    Assume you have `myboard.dts` in your project directory. Compile it with:

    “`bash
    dtc -I dts -O dtb -o myboard.dtb myboard.dts
    “`

    Add `-@` to preserve phandles for overlays, and `-Wno-unitaddressvs_reg` to silence warnings about mismatched addresses.

    You can verify the binary with:

    “`bash
    dtc -I dtb -O dts -o – myboard.dtb | less
    “`

    The output is a human‑readable representation of the compiled tree, handy for quick sanity checks.

    2.4 Integrating the DTB into Your Build System

    Most Yocto or Buildroot environments already have a `device-tree` directory. Add your `.dts` file there and extend the `Makefile`:

    “`make
    dtb-$(CONFIG_MYBOARD) += myboard.dtb
    “`

    Then enable the board in the configuration (`make menuconfig` → `System Type → MyBoard`). The build system will automatically compile the DTS and place the DTB in `arch/arm/boot/dts/`.

    2.5 Testing on Real Hardware

    Copy the DTB to the boot partition (e.g., FAT32 on a Raspberry Pi) and tell the bootloader which file to use:

    “`bash

    In U‑Boot

    setenv fdtfile myboard.dtb
    saveenv
    boot
    “`

    If the kernel boots and you see your UART console output, you’ve successfully linked the Device Tree to the hardware. Use `dmesg | grep -i uart` to confirm that the driver recognized the node.

    3. Advanced Device Tree Techniques – Overlays, Property Types, and Debugging

    3.1 Using Device Tree Overlays for Runtime Flexibility

    Overlays let you extend or modify a base DTB without rebuilding the whole tree. This is ideal for plug‑and‑play modules (e.g., a cape on a BeagleBone) or for enabling optional features in the field.

    Creating an overlay (`mymodule-overlay.dts`):

    “`dts
    /dts-v1/;
    /plugin/;

    / {
    fragment@0 {
    target = ;
    overlay {
    status = “okay”;
    pinctrl-0 = ;
    };
    };

    fragment@1 {
    target-path = “/aliases”;
    overlay {
    mymodule = &my_peripheral;
    };
    };

    symbols {
    uart0 = “/soc/serial@101f1000”;
    };
    };
    “`

    Compile with `-@` to keep phandles:

    “`bash
    dtc -I dts -O dtb -@ -o mymodule.dtbo mymodule-overlay.dts
    “`

    Load the overlay at runtime (U‑Boot or via the `configfs` interface on modern kernels):

    “`bash

    U‑Boot

    fdt apply mymodule.dtbo
    boot
    “`

    Or on a running system:

    “`bash
    echo mymodule.dtbo > /sys/kernel/config/device-tree/overlays/myoverlay/overlay
    “`

    If the overlay fails, the kernel logs will contain a helpful error message (`dmesg | grep -i overlay`).

    3.2 New Property Types – `phandle`, `stringlist`, and `u32_array`

    The DT spec has evolved to support richer data structures:

  • `phandle` – Unique identifier for a node, used for cross‑references (e.g., an I²C device pointing to its controller).
  • `stringlist` – Space‑separated strings in a single property (`compatible = “vendor,dev1”, “vendor,dev2”;`).
  • `prop-encoded-array` – Allows mixed types (e.g., “).

When writing a new node, prefer the canonical property names defined in the Linux Device Tree Bindings repository (`Documentation/devicetree/bindings/`). For example, a GPIO‑controlled LED should use:

“`dts
leds {
compatible = “gpio-leds”;
status = “okay”;

led0 {
label = “myboard:green:status”;
gpios = <&gpio1 12 GPIOACTIVEHIGH>;
default-state = “on”;
};
};
“`

3.3 Debugging Common DT Issues

Even seasoned engineers hit roadblocks. Here’s a quick checklist:

| Symptom | Likely Cause | Debug Step |
|———|————–|————|
| Driver not bound (`offindcompatible_node` returns NULL) | Missing or mismatched `compatible` string | `grep -R “compatible” /proc/device-tree` and compare to driver source. |
| Peripheral not visible (`i2c‑dev` missing) | Wrong `reg` address or missing `#address-cells`/`#size-cells` in parent node | Verify the I²C bus node defines `#address-cells = ; #size-cells = ;`. |
| Kernel panics early (`Failed to unpack DTB`) | Corrupted DTB (wrong endianness) | Re‑compile with `dtc -I dts -O dtb -o myboard.dtb myboard.dts`. |
| Overlay fails (`overlay: error -22`) | Invalid target path or missing `overlay` block | Check `dmesg` for “overlay: failed to apply” and ensure `target-path` matches the base DT. |

The `/proc/device-tree` pseudo‑filesystem is an invaluable live view. For example:

“`bash
cd /proc/device-tree
tree -L 2
“`

You can also use `dtc -I dtb -O dts -o – /boot/myboard.dtb` to dump the on‑disk DTB for offline inspection.

3.4 Porting a Device Tree to a New SoC

When moving a board design from one SoC family to another (e.g., from an i.MX6 to an i.MX8), follow these steps:

1. Identify common peripherals – Keep nodes for UART, I²C, and GPIO that share the same register layout.
2. Update `compatible` strings – Each SoC has a unique root compatible (e.g., `”fsl,imx6ull”` → `”fsl,imx8mm”`).
3. Adjust address cells – New SoCs may use 64‑bit addresses (`#address-cells = <

Leave a Comment