Back to Blog
TutorialsJanuary 28, 202613 min read

Introduction to Device Tree in Embedded Linux: A Practical Guide

Master Linux Device Tree from basics to advanced overlays. Learn DTS syntax, node structures, bindings, and how to describe your custom hardware board.

Introduction to Device Tree in Embedded Linux: A Practical Guide

The Device Tree (DT) is a data structure and language used in embedded Linux to describe the hardware topology of a system to the kernel at boot time, replacing the legacy approach of hardcoding board-specific information directly into the kernel source code. Written in Device Tree Source (DTS) format and compiled into Device Tree Blob (DTB) binary format by the Device Tree Compiler (dtc), a device tree describes processors, memory regions, buses, peripherals, interrupt controllers, clocks, GPIOs, and their interconnections in a hierarchical tree structure. The bootloader (U-Boot, for example) passes the DTB to the Linux kernel during boot, enabling a single kernel binary to support multiple hardware platforms simply by swapping the DTB file. Device Tree is mandatory for ARM and ARM64 Linux platforms and is also used by Zephyr RTOS. Understanding device tree is essential for any embedded Linux engineer developing custom board support packages (BSPs), adding peripheral support, or configuring pin multiplexing and power domains on System-on-Chip (SoC) platforms.

What Is the Structure of a Device Tree Source File?

/* Basic Device Tree Source (DTS) structure */
/dts-v1/;

/ {  /* Root node */
    model = "Custom IoT Board";
    compatible = "mycompany,iot-board", "ti,am335x";
    
    #address-cells = <1>;
    #size-cells = <1>;
    
    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
        stdout-path = &uart0;
    };
    
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>; /* 512MB DDR */
    };
    
    /* I2C bus with sensor */
    &i2c1 {
        status = "okay";
        clock-frequency = <400000>;
        
        temperature-sensor@48 {
            compatible = "ti,tmp102";
            reg = <0x48>;
            interrupt-parent = <&gpio1>;
            interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
        };
    };
    
    /* SPI bus with flash */
    &spi0 {
        status = "okay";
        
        flash@0 {
            compatible = "jedec,spi-nor";
            reg = <0>;
            spi-max-frequency = <40000000>;
            #address-cells = <1>;
            #size-cells = <1>;
            
            partition@0 {
                label = "bootloader";
                reg = <0x0 0x80000>;
            };
        };
    };
};

How Do Device Tree Bindings Work?

Device Tree bindings define the rules for describing a particular type of hardware in the device tree. Each binding specifies required and optional properties for a device node, including the "compatible" string that matches the device to its kernel driver. Bindings are documented in the Linux kernel under Documentation/devicetree/bindings/ in YAML format (dt-schema). When you write a device tree node for a sensor, LED controller, or any peripheral, you must follow the binding specification for that device. The "compatible" property is the most important—it tells the kernel which driver to load. Multiple compatible strings allow fallback: compatible = "myvendor,sensor-v2", "generic-sensor" means the kernel first looks for a driver matching "myvendor,sensor-v2" and falls back to "generic-sensor" if not found.

What Are Device Tree Overlays and When Should You Use Them?

Device Tree Overlays (DTO) allow runtime modification of the base device tree without recompiling it. Overlays are compiled into DTBO files and applied by the bootloader or at runtime (on platforms supporting ConfigFS). Common use cases include enabling or disabling peripherals, configuring expansion boards (like Raspberry Pi HATs or BeagleBone capes), and pin multiplexing changes for different hardware configurations. In production, overlays enable a single base DTB to support multiple hardware variants, reducing the number of firmware images that need to be maintained and tested.

/* Device Tree Overlay example - enable SPI display */
/dts-v1/;
/plugin/;

&spi1 {
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;
    
    display@0 {
        compatible = "sitronix,st7789v";
        reg = <0>;
        spi-max-frequency = <32000000>;
        dc-gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>;
        reset-gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
        width = <240>;
        height = <320>;
        rotation = <90>;
    };
};

What Are Common Device Tree Debugging Techniques?

Essential tools and techniques for debugging device tree issues:

  • dtc -I fs /proc/device-tree: Decompile the running device tree to verify what the kernel actually received.
  • ls /proc/device-tree/: Browse the live device tree nodes as a filesystem to verify node existence and properties.
  • dmesg | grep -i "device tree": Check kernel log for device tree parsing errors and warnings.
  • dt-validate: Validate your DTS against the YAML binding schema before compiling. Catches binding violations early.
  • fdtdump <dtb-file>: Dump a compiled DTB file in human-readable format for inspection.
  • CONFIG_OF_UNITTEST: Enable kernel device tree unit tests to validate overlay application and property parsing.

How Do You Write a Device Tree for a Custom Board?

Creating a device tree for a custom board starts with identifying the SoC and including its base DTSI file, which defines the processor cores, internal buses, and on-chip peripherals. Your board DTS then enables specific peripherals, defines pin multiplexing through the pinctrl subsystem, describes external devices connected via I2C, SPI, and UART buses, configures power regulators and clock sources, and sets up the memory map. The typical workflow is: study the SoC reference manual, review similar board DTS files in the kernel tree, create your board DTS with appropriate includes, compile with dtc, and iteratively test and refine. Pay special attention to pin multiplexing conflicts—the same physical pin may be routable to multiple peripheral functions, and incorrect configuration causes silent failures.

Key takeaway: Device Tree is a hierarchical data structure that describes hardware topology to the Linux kernel at boot time, replacing hardcoded board-specific code. Written in DTS format and compiled to DTB binary, device trees enable a single kernel binary to support multiple hardware platforms. Overlays allow runtime hardware configuration changes without recompilation.

How Did We Create a BSP Using Device Tree for a Custom Board?

At EmbedCrest, we developed a complete Board Support Package for a custom industrial gateway board based on the NXP i.MX8M Mini SoC. The board included 3 Ethernet ports (2x RGMII via AR8035 PHYs + 1x USB-Ethernet via LAN9514), an RS-485 transceiver on UART3, a Quectel EC25 LTE modem on USB, an ATECC608B secure element on I2C2, and a custom FPGA (Lattice MachXO3) on SPI1 for industrial protocol offload. Creating the device tree required 3 weeks of iterative development. We started from the NXP EVK DTS (imx8mm-evk.dts), created our board-specific DTS with the correct pinmux configurations using the i.MX Pins Tool, described each peripheral with the appropriate compatible strings and register addresses, and verified each node individually using dtc validation and boot testing. The most challenging aspect was correctly configuring the RGMII timing delays for the AR8035 PHYs, which required tuning the rgmii-rxc-dly-en and rgmii-txc-dly-en properties through iterative testing with iperf3 throughput measurements at different clock delay settings.

What Are the Most Common Device Tree Mistakes?

The most common device tree mistake is pin multiplexing conflicts: assigning the same physical pin to two different peripherals. The SoC will silently use whichever peripheral was configured last, causing the first peripheral to fail with no obvious error message. Use the SoC vendor's pin planning tool (NXP Pins Tool, TI SysConfig, STM32CubeMX) to detect conflicts before writing DTS. Second, incorrect interrupt-parent and interrupts properties cause devices to probe successfully but never generate interrupts. Verify interrupt configuration with cat /proc/interrupts and test with manual interrupt triggering. Third, clock configuration errors manifest as peripherals running at wrong frequencies or not running at all. Check assigned-clocks and assigned-clock-rates against the SoC reference manual clock tree. Fourth, forgetting to set status = "okay" leaves correctly described peripherals disabled. The convention is that SoC DTSI files describe all peripherals with status = "disabled", and board DTS files selectively enable used peripherals.

How Do You Use Device Tree Overlays for Production Flexibility?

Device tree overlays enable a single base firmware image to support multiple hardware variants, reducing manufacturing and maintenance complexity. In a production scenario, consider a product family with three variants: a basic model with RS-485 only, a mid-range model adding BLE via a UART-connected module, and a premium model adding LTE via a USB modem. Create a base DTS with all variant-specific peripherals disabled, then create three overlay DTBO files that enable the appropriate peripherals for each variant. During manufacturing, write the variant identifier to an EEPROM or OTP fuse, and configure U-Boot to select the corresponding overlay at boot time using the fdt apply command. This approach reduces the number of firmware images from 3 to 1 base image + 3 small overlays, simplifying your CI/CD pipeline, OTA update system, and production testing. Zephyr RTOS uses the same overlay mechanism (app.overlay files), so this approach applies equally to MCU projects targeting boards with hardware variants.

Device TreeEmbedded LinuxDTSBoard SupportLinux Kernel

Rajdatt

Lead Embedded Systems Engineer at EmbedCrest Technology

Delivering enterprise grade embedded systems, IoT, and Edge AI engineering solutions.

FAQ

Frequently Asked Questions

Is device tree only for ARM Linux?

Device tree is mandatory for ARM/ARM64 Linux and is used by RISC-V Linux as well. It is also adopted by Zephyr RTOS for hardware description. x86 Linux primarily uses ACPI instead, though device tree is supported on some x86 embedded platforms.

Can I modify the device tree without recompiling the kernel?

Yes, you only need to recompile the DTS file into a new DTB using dtc, then update the bootloader to load the new DTB. The kernel binary itself does not change. Device tree overlays can even be applied at runtime on supported platforms.

What happens if the device tree describes hardware that does not exist?

The kernel will attempt to initialize the described device and fail. Depending on the driver, this may result in a probe error in dmesg, a hung boot (if the device is on a critical path), or the driver simply reporting the device as unavailable. Always verify hardware connections match your DTS.

How is device tree different from ACPI?

Device tree is a static description of hardware passed to the kernel at boot. ACPI (used on x86/UEFI platforms) provides both hardware description and runtime methods (AML bytecode) that the OS can execute to configure hardware dynamically. ACPI is more complex but supports runtime power management and device enumeration.

Ready to Build Your Embedded Solution?

From Edge AI to industrial IoT, our engineering team delivers end to end embedded systems solutions. Let us discuss your project requirements.

Get in Touch