Devicetree
The Devicetree is a data structure and language for describing hardware in computer systems, providing a standardized way for operating systems to receive details about hardware components at boot time rather than relying on hardcoded configurations.[1] It represents hardware topology through a hierarchical tree of nodes and properties, where nodes correspond to devices or buses and properties define attributes such as addresses, interrupts, and compatible drivers.[2] This approach enables flexible, data-driven hardware initialization, decoupling device drivers from specific board layouts and supporting extensibility for new hardware via bindings that specify conventions for data representation.[1]
Originating from the Open Firmware standard in the 1990s for platforms like PowerPC and SPARC, the Devicetree was formalized to facilitate boot firmware communication with operating systems.[1] In the Linux kernel, its adoption began in 2005 with PowerPC support, introducing the Flattened Device Tree (FDT) format—a compact, binary representation passed directly to the kernel during boot.[1] Today, it is integral to Linux on multiple architectures, including ARM, MIPS, and x86, where it handles platform identification, runtime configuration, and device population for embedded and server systems.[1] The specification, maintained by the Devicetree Project, reached version 0.4 in June 2023, emphasizing best practices for compatibility and evolution.[2]
Introduction
Definition and Purpose
The Devicetree is a tree-structured data format used to describe the hardware components of a computer system, such as central processing units (CPUs), memory regions, buses, and peripherals, in a manner that avoids hardcoding these details directly into software.[3] This representation organizes hardware information hierarchically through nodes and properties, allowing firmware to convey a complete, runtime-readable description of the system's physical configuration to the operating system kernel during boot.[1] By providing this abstracted hardware description, Devicetree enables systems to operate without relying on platform-specific code embedded in the kernel, promoting flexibility across diverse architectures.[4]
The primary purpose of Devicetree is to facilitate portable and hardware-agnostic boot processes, where the firmware passes the device description to the OS kernel, which then uses it to initialize drivers and allocate resources dynamically.[3] This approach is particularly valuable for embedded and server systems where hardware cannot always be fully probed at runtime, ensuring that non-discoverable aspects—like interrupt mappings or device addresses—are explicitly defined.[1] In practice, Devicetree supports the selection of appropriate device drivers through compatible strings in node properties, allowing the kernel to match hardware variants without extensive reconfiguration.[3]
Key benefits of Devicetree include reducing kernel bloat by minimizing the need for architecture-specific initialization code, enabling dynamic hardware discovery and configuration, and supporting multi-platform deployments with a single kernel binary.[1] For instance, it allows broad compatibility across architectures like ARM, PowerPC, and RISC-V, where varying hardware layouts can be accommodated through standardized descriptions rather than compiled-in assumptions.[4] This data-driven model not only streamlines development for diverse systems but also enhances maintainability by centralizing hardware details outside the kernel codebase.[1]
Devicetree evolved from the Open Firmware (OF) mechanism originally developed for PowerPC systems, where it served as a structured way to represent hardware in firmware environments, and has since been generalized into a standalone specification.[1] Today, its modern standards are maintained by the Devicetree.org project, a community-driven initiative established around 2017 to oversee the evolution and release of the Devicetree Specification, with the current version (v0.4) published in 2023.[2][5]
History and Development
The Devicetree originated in the 1990s as part of Open Firmware, a standardized firmware interface developed under IEEE 1275 and initially created by Sun Microsystems as OpenBoot. Published in 1994, IEEE 1275 defined a Forth-based scripting environment for hardware initialization and probing on systems like SPARC and PowerPC platforms.[6] IBM and Apple adopted Open Firmware for their PowerPC-based systems, including Apple's Power Macintosh and IBM's RS/6000 servers, where it enabled dynamic hardware discovery without hard-coded platform specifics.[7] This approach laid the foundation for Devicetree by representing hardware topology in a tree-like data structure passed from firmware to the operating system.
Devicetree was adopted into the Linux kernel around 2005 during a major refactoring of PowerPC support to unify 32-bit and 64-bit architectures, making it mandatory for all PowerPC platforms to supply a flattened device tree at boot.[1] By 2012, with the release of Linux kernel 3.3, Devicetree expanded to ARM architectures, replacing board-specific files with a standardized hardware description to support the growing diversity of ARM-based embedded devices.[8] This shift reduced kernel bloat and improved portability across hardware variants.
Formalization efforts began with the ePAPR (Embedded PowerPC Architecture Platform Requirements) standard in 2008, jointly developed by Power.org and Freescale Semiconductor to define Devicetree usage for embedded PowerPC and ARM systems, including boot protocols and binding conventions.[9] These guidelines evolved into the independent Devicetree Specification, with version 0.1 released by Devicetree.org in 2017 to provide a vendor-neutral reference beyond Linux-specific implementations.[10]
Recent developments include RISC-V architecture support integrated into the Linux kernel starting in 2017 with version 4.15, leveraging Devicetree for hardware description in this open instruction set.[11] In 2021, Linux kernel 5.15 introduced enhancements for hybrid systems combining ACPI and Devicetree, such as unified firmware node handling via the fwnode API to enable ACPI on ARM servers while retaining Devicetree for peripherals.[12] Since 2020, the kernel has incorporated YAML-based schema for Devicetree bindings, improving validation and documentation of hardware properties through json-schema tools.[13] Key contributors include Linux kernel maintainers Frank Rowand, who has led Devicetree framework improvements and testing since 2015, and Pantelis Antoniou, who pioneered overlay support for dynamic reconfiguration in kernel 3.19.[14] This transition from ad-hoc to standardized bindings has been driven by community efforts to ensure consistency across architectures.[15]
Device Tree Source (DTS)
The Device Tree Source (DTS) is a human-readable, text-based format used to describe hardware configurations in a tree structure, employing a syntax reminiscent of the C programming language. It serves as the editable input for defining device trees before they are compiled into a binary representation suitable for use by bootloaders and operating systems. The format allows developers to specify nodes representing hardware components and their properties, facilitating hardware abstraction without modifying kernel source code.[16]
DTS files typically begin with a version header such as /dts-v1/;, which indicates compatibility with version 1 of the format and enables features like enhanced integer representations. The root of the tree is denoted by the node / { ... };, enclosing all top-level properties and child nodes. For modularity, DTS supports inclusion directives like /include/ "filename.dtsi", where .dtsi files contain reusable snippets for common hardware descriptions, while primary board or SoC files use the .dts extension.[16][17]
Comments in DTS can be added using C-style block comments (/* ... */) for multi-line annotations or C++-style single-line comments (// ...) for brevity. Labels, defined by appending a colon to an identifier (e.g., mylabel:), allow forward or backward references to nodes or properties via the &label syntax, aiding in complex tree definitions without duplicating paths. These elements promote maintainable and readable source files for hardware vendors and kernel developers.[16]
Nodes in DTS are declared within curly braces, containing properties as key-value pairs terminated by semicolons. Properties can hold strings (e.g., compatible = "vendor,[device](/page/Device)";), integer arrays in angle brackets (e.g., reg = <0x1000 0x100>;), or byte strings in square brackets. A simple node example might appear as:
serial@1000 {
compatible = "ns16550a";
reg = <0x1000 0x100>;
clock-frequency = <1843200>;
};
serial@1000 {
compatible = "ns16550a";
reg = <0x1000 0x100>;
clock-frequency = <1843200>;
};
This defines a serial port device with its memory region and clock rate. Such snippets build hierarchical trees, with child nodes nested inside parent braces.[16][18]
The compilation process involves feeding DTS files into the Device Tree Compiler (dtc), a tool that validates the syntax and generates a compact Device Tree Blob (DTB) binary file for runtime consumption. Since Linux kernel version 3.18, released in late 2014, DTS has supported the creation of overlays—partial tree fragments that can be applied dynamically to a base tree—enhancing flexibility for modular hardware extensions like add-on boards.[16][18][19]
Device Tree Blob (DTB)
The Device Tree Blob (DTB) serves as a flattened, binary representation of the device tree, enabling efficient exchange of hardware description data between bootloaders, firmware, and operating system kernels during system initialization.[20] This format eliminates pointers and hierarchical indirections present in the source representation, serializing the tree into a compact, linear byte stream that can be loaded into memory and parsed at runtime.[20] DTBs are platform-independent, allowing the same binary to describe hardware across different architectures, provided the underlying conventions are followed.[20]
The DTB begins with a fixed-size header that provides metadata for locating and validating the blob's components. Defined by the C structure struct fdt_header, the header consists of 32-bit big-endian integers, starting with a magic number of 0xd00dfeed to identify the format.[20] Key fields include the total size of the DTB in bytes (totalsize), offsets from the header start to the structure block (off_dt_struct), strings block (off_dt_strings), and memory reservation map (off_mem_rsvmap), as well as the DTB version (up to 17 in the current specification, with backwards compatibility to version 16) and the last compatible version (last_comp_version).[20] Additional fields cover the physical boot CPU ID (boot_cpuid_phys) and sizes of the structure and strings blocks. Following the header is the memory reservation block, comprising zero or more 64-bit big-endian pairs of address and size for regions to be reserved from the kernel's usable memory, terminated by a (0, 0) pair and aligned to 8 bytes.[20] The structure block then follows, aligned to 4 bytes, containing tokens such as FDT_BEGIN_NODE (0x00000001) for node starts, FDT_PROP (0x00000003) for properties with length and name offset, FDT_END_NODE (0x00000002), FDT_NOP (0x00000004) for padding or overlay modifications, and FDT_END (0x00000009) to mark the block's conclusion.[20] The strings block, unaligned, stores null-terminated property name strings referenced by offsets in the structure.[20]
The flattening process involves an in-order depth-first traversal of the device tree, serializing nodes and properties into this token-based stream without recursive pointers, which ensures the DTB remains self-contained and relocatable.[20] This linearization supports dynamic memory reservations for scenarios like initrd placement or bootloader-allocated regions, preventing kernel overlap.[20] They require 8-byte alignment when loaded into memory to maintain portability across big-endian and little-endian platforms, with the entire blob fitting within contiguous physical memory.[20]
Runtime modifications to the DTB, such as applying overlays for modular hardware extensions, occur by inserting FDT_NOP tokens to effectively remove or alter nodes and properties in place, preserving the original structure while enabling updates without recompilation.[20] For portability, the DTB is loaded at a platform-specific 64-bit aligned physical address (often above the 128 MiB boundary from the start of RAM on ARM systems) and passed to the kernel via registers or boot parameters, allowing parsers to validate the header and traverse the blob independently of the source format from which it was compiled.[21]
Structure and Syntax
Nodes and Properties
In the Devicetree, nodes serve as the fundamental hierarchical containers that represent hardware entities within a system, forming a tree structure that describes the logical organization of devices and components.[3] The root node, denoted as "/", acts as the top-level container for all other nodes, with each subsequent node potentially containing child nodes to reflect parent-child relationships in the hardware topology, such as a /soc node encapsulating system-on-chip peripherals.[3] This acyclic hierarchy ensures no cycles exist in the tree, maintaining a directed acyclic graph that supports references like interrupts without loops.[3]
Properties within nodes are key-value pairs that provide descriptive metadata about the represented hardware, consisting of a property name followed by a value in a structured format.[3] Property names are strings limited to 1-31 characters using a defined set (a-z, A-Z, 0-9, _, +, ,, -, .), while values can be empty, unsigned 32-bit or 64-bit integers, strings, phandles (for cross-referencing other nodes), string lists, or encoded arrays.[3] For instance, properties may use integer arrays for address specifications, such as reg = <0x3000 0x20>; to denote a base address and size, or string lists for compatibility, like compatible = "vendor,[device](/page/Device)";.[3] In the Device Tree Source (DTS) format, properties are declared sequentially within node braces as property-name = value;, preserving their order in the compiled Device Tree Blob (DTB) for efficient parsing.[16]
Several standard properties are defined to convey common characteristics across nodes, enhancing interoperability.[3] The name is implicitly derived from the node's name in the source, while device_type (a deprecated string property from IEEE 1275 for FCode model description) may appear in legacy contexts but is not recommended for new designs.[3] The status property, typically set to "okay" or "disabled", indicates whether the node represents active hardware.[3] Phandles provide unique 32-bit integer identifiers for cross-references between nodes, assigned via the phandle property or automatically in the DTB.[3]
In DTS syntax, the hierarchy is explicitly constructed using braces to nest child nodes, such as / { subnode@address { ... }; };, where the @address optionally specifies a unit address matching the first entry in the node's reg property for addressable devices.[16] Labels, prefixed as label:, can be attached to nodes or properties for reuse, enabling references via &label in phandles or paths, which facilitates modular descriptions without duplication.[16] Upon compilation to DTB, property strings are stored in a shared offset-based string table to optimize space and access efficiency, while integer values and phandles remain as binary data.[3] This structure ensures the Devicetree remains portable and machine-readable across firmware and operating systems.[3]
Compatible Strings and Binding Documents
The compatible property in a Devicetree node is an array of strings that specifies the device model and its binding, enabling software to match hardware descriptions to appropriate drivers. It is defined as a list of null-terminated strings, typically in the format "manufacturer,device-variant", ordered from most specific to most generic to support fallback matching for legacy or generic drivers. For example, a UART node might use compatible = "fsl,imx6q-uart", "fsl,imx21-uart";, where the kernel first attempts to match the specific Freescale i.MX6Q variant and falls back to the generic i.MX21 if no exact match is found.[3]
Devicetree binding documents provide standardized specifications for the properties required or optional for specific device classes, ensuring consistent hardware representation across systems. These documents define semantic rules, such as property types, values, and relationships, often using examples to illustrate usage; for instance, they may specify clock or interrupt phandles for peripherals. Bindings are crucial for validating Devicetree sources against expected hardware interfaces and facilitating driver development.[13]
The evolution of binding documents has progressed from informal text-based descriptions in early Linux kernel releases to structured YAML files employing JSON-schema vocabulary for machine-readable validation. This shift began with initial YAML proposals around 2017 and saw schema validation infrastructure added in Linux kernel 4.20 in late 2018, enabling automated checks during builds. By 2019, the use of YAML schemas became the standard for new bindings, with mandatory validation checks enforced for all submissions since 2021 to improve consistency and error detection. These bindings are hosted in the Linux kernel source tree under the Documentation/devicetree/bindings directory.[13][15][22]
Representative examples include bindings for GPIO controllers, which require the boolean gpio-controller property to indicate the node acts as a GPIO provider, along with #gpio-cells, an integer specifying the number of cells (typically 2: pin number and flags) needed to encode a GPIO specifier in consumer nodes. Similarly, interrupt controller bindings mandate the interrupt-controller property and #interrupt-cells to define the format of interrupt specifiers, such as 3 cells for type, number, and level in ARM GIC bindings, allowing consumers to reference interrupts via phandles. These standards ensure interoperable referencing of hardware resources like pins or IRQs across the tree.[23][24]
In the Linux kernel, the compatible property plays a central role in driver probing by allowing the device model core to iterate through the string array for each node and match it against registered platform drivers' of_match_table. Upon a successful match, the driver's probe function is invoked to initialize the device, with fallbacks ensuring compatibility for evolving hardware descriptions; this process starts from the root node and recurses through buses like platform or I2C.[1]
Usage in Operating Systems
Linux Kernel Integration
In the Linux kernel, the Device Tree Blob (DTB) is passed from firmware or bootloaders to the kernel during the boot process to describe the hardware configuration. On ARM platforms, the bootloader loads the DTB into memory and passes its physical address to the kernel entry point via register r2, while register r0 is set to 0 to indicate Device Tree usage rather than ATAGs.[21] On PowerPC systems, the DTB is typically embedded or provided through a device tree header in the kernel image, processed by the bootwrapper before kernel initialization.[25] This mechanism allows the kernel to receive a flattened binary representation of the hardware tree without requiring platform-specific code for each board.[1]
The kernel parses the DTB using the Open Firmware (OF) APIs located in the drivers/of/ subdirectory, which unflattens the binary into an in-memory tree structure via functions like unflatten_device_tree().[1] Drivers and subsystems then traverse this tree using of_* APIs, such as of_find_node_by_path() for locating nodes and of_property_read_u32() for extracting properties.[1] Platform devices are automatically registered by calling of_platform_populate(), which iterates over compatible nodes and instantiates struct platform_device instances bound to matching drivers based on compatible strings.[1] This integration enables modular driver probing without hardcoding hardware details in the kernel source.
Support for dynamic Device Tree updates was introduced through overlays, allowing runtime modifications to the live tree. Since kernel version 4.6 (released in 2016), overlays can be applied via the configfs interface at /sys/kernel/config/device-tree/overlays, where userspace writes a DT overlay blob to apply changes like adding or modifying nodes and properties. The of_overlay subsystem handles these updates internally, using functions like of_overlay_fdt_apply() to merge fragments into the base tree and notify registered drivers of changes.[26] Live property changes are supported through mechanisms like of_overlay, enabling limited reconfiguration without rebooting, though full device instantiation requires compatible bindings.[26]
Device Tree integration is primary on ARM architectures since kernel 3.1 (2012), where it became mandatory for new multi-platform kernels to consolidate board support.[27] PowerPC has used Device Tree since early kernel versions, originating from Open Firmware standards. RISC-V support was added in kernel 4.15 (2018), providing initial architecture code and Device Tree bindings for CPU and peripherals.[11] Experimental support combining ACPI with Device Tree on x86 platforms arrived in kernel 5.18 (2022), allowing hybrid enumeration for certain firmware scenarios.[28]
Despite these features, Device Tree has limitations in handling dynamic hardware changes. It lacks native hotplug support, as the tree describes static hardware at boot time and does not automatically detect or enumerate removable devices like PCI or USB peripherals, which rely on bus-specific discovery instead.[1] Driver probe order depends on static compatible bindings and tree traversal, potentially leading to dependencies that must be resolved manually via phandles or initcall ordering, without runtime reconfiguration for hot-added hardware.[1] Overlays mitigate some rigidity but are unsuitable for frequent or complex hotplug events, where ACPI provides more robust dynamic capabilities on supported platforms.[26]
Support in Other OSes
Support for Devicetree in operating systems other than Linux varies in maturity and implementation, often adapting the standard for specific architectures and use cases while addressing compatibility with established mechanisms like ACPI.[29]
Windows on ARM (WoA) provides partial Devicetree support since 2017, primarily through the translation of Device Tree Blob (DTB) files into ACPI tables within EFI firmware during boot. This approach enables hardware discovery on ARM-based systems, but it is limited to specific platforms such as those using Qualcomm Snapdragon processors, where the firmware handles the conversion to ensure compatibility with Windows' ACPI-centric model.[30]
Apple has historically utilized Devicetree concepts in its firmware and kernel. In early PowerPC-based Macs, Open Firmware—based on the IEEE 1275 standard—constructed a device tree by probing I/O buses to describe hardware configuration, facilitating boot and device enumeration. For modern ARM-based systems like those with M1 and later chips, the Darwin kernel in macOS employs a custom Apple Device Tree (ADT) format, which is inspired by traditional Devicetree structures for hardware probing and description, though it diverges from the standard flattened format to integrate with Apple's proprietary boot process.[7][31]
FreeBSD introduced experimental Devicetree support for ARM in 2010, with initial code committed to its repository to enable parsing of flattened device trees for hardware resource description on embedded systems. This evolved into fuller integration by FreeBSD 13.0 in 2021, particularly for platforms like the Raspberry Pi, where DTB parsing supports device enumeration and driver binding without relying on platform-specific probing.[32]
NetBSD has supported Device Trees since around 2012 for ARM and other embedded architectures, using them to describe hardware in a portable way similar to Linux, aiding in cross-platform compatibility for devices like Raspberry Pi and BeagleBoard.[33]
The Zephyr RTOS fully adopted Devicetree starting with version 1.10 in 2018, incorporating a major overhaul to use Device Tree Source (DTS) files for board configuration in embedded IoT applications. This allows Zephyr to describe hardware peripherals, pins, and interrupts in a portable manner, enabling cross-platform builds for resource-constrained devices.[34]
Overall, Devicetree support in non-Linux OSes remains less mature than in Linux, frequently hybridized with ACPI for power management and dynamic features, which introduces challenges in standardization and boot complexity on diverse hardware.[35]
Usage in Firmware and Bootloaders
Coreboot Implementation
Coreboot integrates Devicetree to describe hardware configurations on ARM and RISC-V platforms, generating a Device Tree Blob (DTB) during the firmware build process for passing to payloads such as the Linux kernel or SeaBIOS.[36] This DTB provides essential hardware details, including memory regions, peripherals, and bus structures, enabling payloads to initialize devices without proprietary firmware dependencies.[36] On x86 platforms, Coreboot primarily uses a custom devicetree format for ACPI generation, but standard Devicetree support is extended via payloads like U-Boot.[37]
Configuration in Coreboot involves payload-specific Device Tree Source (DTS) files, such as coreboot.dts for U-Boot payloads, which detail mainboard hardware like CPU, memory, and I/O controllers. These DTS files are processed by the dtc compiler during the build, incorporating Kconfig selections to produce a tailored DTB embedded in the ROM image.[38] For example, Kconfig options for board variants dynamically adjust node properties, such as GPIO mappings or clock frequencies, ensuring the DTB reflects the selected hardware setup.[38]
Coreboot's Devicetree implementation supports a wide range of devices, including Chromebooks from vendors like Google and Lenovo, as well as servers from Intel and AMD, where it facilitates hardware initialization across diverse SoCs.[39] Dynamic DTB generation from Kconfig, developed alongside ARM support, allows automated adaptation based on build-time choices, reducing manual configuration efforts. This approach promotes vendor-neutral firmware by standardizing hardware descriptions independent of proprietary blobs.[40]
Integration with libpayload enables runtime access to Devicetree data in payloads, using Flattened Device Tree (FDT) helpers to query and modify hardware information post-initialization.[41] For instance, libpayload can parse DTB nodes for resource allocation in custom payloads.[41]
RISC-V support was initially added to Coreboot around 2020, with enhancements including fixes for hardware initialization and DTB handling in version 4.20, released on May 15, 2023.[42] As of October 2025, Coreboot 25.09 includes further improvements for RISC-V, such as enhanced crossgcc build system support for precise Devicetree generation targeting specific ISA extensions.[43][44]
U-Boot and Other Bootloaders
U-Boot, a widely used bootloader for embedded systems, relies on the Device Tree to describe hardware configuration during its execution phase and passes a modified Flattened Device Tree Blob (DTB) to the operating system kernel at handoff. This process enables U-Boot to initialize peripherals and adapt to specific board layouts without recompiling the bootloader binary for each variant. The bootloader parses the DTB loaded from storage or generated internally and performs runtime adjustments to ensure compatibility with the target hardware before invoking the kernel.[45]
Runtime modification of the DTB in U-Boot is facilitated by the fdt command suite, which supports operations such as inspecting nodes (fdt print), altering properties (fdt set), and resizing the tree (fdt resize) to accommodate additions like boot arguments. These modifications occur prior to kernel handoff, allowing fixes for hardware-specific details, such as adjusting memory regions or enabling/disabling peripherals based on detected variants. For example, U-Boot can set the kernel command line in the /chosen node using fdt set /chosen bootargs "<arguments>" after resizing, ensuring the kernel receives updated parameters like root filesystem location.[46][47]
Device Tree overlays, which enable modular extensions to the base DTB, have been supported in U-Boot since version 2018.07, allowing dynamic application of patches for add-on hardware without altering the core DTB. Overlays are loaded and merged using commands like fdt apply <overlay_address>, commonly for variants differing in peripherals like sensors or displays, and are passed intact to the kernel after merging.[48][49]
In the Das U-Boot implementation, environment variables control DTB handling, with fdtfile specifying the DTB filename to load from storage (e.g., via FAT or ext4 filesystems) and fdt_addr_r defining the runtime relocation address in RAM for the parsed blob. These variables are often set during board initialization scripts and persist across boots if saved to non-volatile storage. Board-specific Device Tree Source (DTS) files reside in the U-Boot source tree under arch/<arch>/dts/, where maintainers define custom nodes and properties tailored to the hardware, such as pinmux configurations or clock settings unique to the board. For new boards, providing a DTS file in this directory is required, with U-Boot-specific overrides in companion .dtsi includes to avoid duplicating upstream Linux definitions.[50][51]
Beyond U-Boot, other bootloaders incorporate Device Tree support with varying degrees of integration. GRUB2 offers limited DTB handling for ARM EFI environments, enabling the bootloader to load a separate DTB file and pass it to the kernel via the devicetree=<path> directive in menu entries, a capability refined in version 2.04 to treat loaded DTBs similarly to ACPI tables for security and compatibility. This support, introduced around 2018, facilitates booting on UEFI-based ARM systems but lacks advanced features like overlays or runtime modifications, relying instead on static DTB selection during configuration generation.[52][53]
Tianocore EDK II, the open-source UEFI firmware implementation, utilizes Device Tree for platform initialization on ARM and RISC-V architectures, where the DTB is retrieved from the EFI System Configuration Table to enumerate hardware during the boot services phase. This approach allows EDK II to probe devices, configure memory, and prepare the environment for OS loaders without hardcoded board knowledge, particularly on RISC-V where DTB provides essential details like CPU topology and interrupt controllers before transitioning to the kernel.[54]
dtc Compiler
The Device Tree Compiler (dtc) is an open-source toolchain for processing device tree files, primarily compiling human-readable Device Tree Source (DTS) files into compact binary Device Tree Blobs (DTB) suitable for embedding in kernel images or passing to bootloaders on embedded systems. Written in C, dtc originated as part of the Linux kernel development efforts, with its source maintained in the kernel tree under scripts/dtc and an upstream repository at git.kernel.org. Initial development began in May 2005, led by Benjamin Herrenschmidt with contributions from David Gibson, to support flattened device tree formats for PowerPC and other architectures, evolving from Open Firmware concepts to enable portable hardware description across diverse platforms.[55][56]
Dtc supports multiple input and output formats to facilitate compilation, decompilation, and manipulation of device trees. The core function compiles DTS to DTB using the command-line options -I dts -O dtb, producing a binary blob that adheres to the Devicetree Specification's flattened format for efficient runtime parsing. Conversely, decompilation from DTB to DTS is achieved with -I dtb -O dts, aiding in reverse-engineering or debugging binary trees. Additional output formats include assembly (-O asm) for integration with assemblers like GNU gas, and YAML (-O yaml) primarily for validation purposes rather than direct use in booting. Key options enhance flexibility and reliability: -f forces output even if input contains errors, -s sorts nodes and properties alphabetically for deterministic comparisons, and -@ generates a symbols node containing label paths, essential for device tree overlays that reference symbols dynamically during application. Dtc performs basic semantic checks during processing, such as verifying node structures and property types, issuing warnings or errors for violations; the -q option suppresses messages, with escalating levels (-qq, -qqq) for finer control.[57][58]
In practice, dtc integrates seamlessly into build systems for operating systems and firmware. Within the Linux kernel, it is invoked automatically during the make dtbs target to compile all architecture-specific DTS files into DTBs linked into the kernel image or provided separately. Bootloaders like U-Boot also embed or build dtc to generate or modify DTBs at runtime, supporting features like overlays for modular hardware configuration. As a standalone tool, dtc can be built and used independently via make install, aligning versions with kernel releases— for instance, version 1.7.2, released in 2024, provides enhanced error checking for properties like interrupts and GPIO mappings, and is incorporated in Linux kernels from 6.1 onward. Subsequent releases have added features such as improved plugin support and additional validation checks. Despite its robustness, dtc lacks native support for YAML-based schema validation against binding documents, necessitating external tools for comprehensive compliance checks.[59][51][60]
Editing and validation tools facilitate the authoring, debugging, and verification of Devicetree source files (.dts) and binaries (.dtb), ensuring hardware descriptions are accurate and compliant with specifications.[1]
For editing, the Device Tree Compiler (dtc) supports reading Devicetree structures directly from a filesystem representation using the -I fs input option, allowing developers to work with live or exported tree data such as that exposed by the kernel. Integrated development environments enhance this process; for instance, Visual Studio Code extensions like the DeviceTree extension provide syntax highlighting, autocompletion, and basic validation for .dts files, improving productivity during manual edits.[61] Similarly, the nRF DeviceTree extension offers a visual editor for graphical configuration of peripherals and pins, particularly useful for Nordic Semiconductor hardware.[62]
Validation tools focus on schema compliance for bindings and sources. The dtschema Python package, introduced in 2019, enables comprehensive checks of Devicetree files against JSON/YAML schemas derived from kernel binding documents, using the dtbs_check target in the kernel build system to identify structural errors and property mismatches.[13][63] For YAML-based binding files, yamllint is employed to enforce style and formatting rules, with a dedicated configuration file in the Linux kernel's Documentation/devicetree/bindings directory ensuring consistency in schema authoring.
Debugging utilities allow inspection of compiled and runtime Devicetree structures. The dtc tool can decompile a .dtb file back to human-readable .dts format using -I dtb -O dts, aiding in verification of binary outputs. For hex dumps and structured views of flat Device Tree blobs, fdtdump provides a readable textual representation without full decompilation.[64] At runtime in the Linux kernel, the live Device Tree is exposed via /proc/device-tree, a symlink to /sys/firmware/devicetree/base/, where userspace tools can query node properties and hierarchies for debugging booted systems.[1][65]
Additional utilities support dynamic modifications, such as Device Tree overlays, which are fragments applied to the base tree to enable runtime hardware reconfiguration; support for applying overlays via configfs was upstreamed in Linux kernel 3.18 in 2014, with scripts like those in the kernel's scripts/ directory facilitating integration.[26][66]
Best practices for Devicetree development emphasize maintaining .dts files under version control systems like Git to track hardware description changes across iterations.[67] Automated continuous integration (CI) pipelines should incorporate schema validation using dtschema and yamllint for new bindings, ensuring early detection of inconsistencies before upstream submission or deployment.[68]
Examples
Basic Hardware Description
The Devicetree provides a structured way to describe fundamental hardware components such as processors and memory in a minimal system, enabling the operating system to initialize without hard-coded assumptions about the platform. A basic example for a single-core ARM system based on the Cortex-A53 processor illustrates this by focusing on the root node, CPU topology, and primary memory region, omitting peripherals to emphasize core bootstrapping. This approach is particularly useful for simple system-on-chip (SoC) designs where the hardware configuration is straightforward.
Consider the following Device Tree Source (DTS) for a minimal ARM system:
/dts-v1/;
{
model = "Minimal ARM Cortex-A53 System";
compatible = "simple,arm-system";
#address-cells = <2>;
#size-cells = <2>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a53";
device_type = "cpu";
reg = <0x0>;
clock-frequency = <1000000000>; /* 1 GHz */
};
};
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x40000000 0x00000000>; /* 1 GB starting at 0x0 */
};
};
/dts-v1/;
{
model = "Minimal ARM Cortex-A53 System";
compatible = "simple,arm-system";
#address-cells = <2>;
#size-cells = <2>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a53";
device_type = "cpu";
reg = <0x0>;
clock-frequency = <1000000000>; /* 1 GHz */
};
};
memory@0 {
device_type = "memory";
reg = <0x00000000 0x00000000 0x40000000 0x00000000>; /* 1 GB starting at 0x0 */
};
};
In this DTS, the root node establishes the overall addressing scheme with #address-cells and #size-cells properties, which define how child nodes interpret address and size values throughout the tree. The cpus node serves as a container for describing CPU topology, with its child cpu@0 node specifying the processor instance via the reg property (indicating CPU ID 0) and the compatible property identifying the Cortex-A53 architecture for driver matching. The clock-frequency property provides the CPU's operating speed in Hz, aiding in performance scaling. Similarly, the memory@0 node maps the physical memory layout using the reg property to denote the base address and size, allowing the kernel to allocate resources correctly without platform-specific code. These nodes collectively convey the system's core hardware layout in a portable, textual format.
When compiled into a Device Tree Blob (DTB), this DTS produces a compact binary representation suitable for boot-time loading. A snippet of the DTB structure begins with a 40-byte header, including the magic number 0xd00dfeed (big-endian), followed by the total size (e.g., 0x00000068 for this minimal case), offset to the structure block (e.g., 0x00000038), offset to the strings block (e.g., 0x00000058), and other fields like version 0x00000011 and boot CPU ID 0x00000000. The structure block then encodes the tree using tokens such as 0x00000001 (FDT_BEGIN_NODE) for the root, properties like 0x00000003 (FDT_PROP) for compatible, and terminators like 0x00000004 (FDT_END_NODE). This binary form is parsed by the bootloader or kernel to populate hardware descriptions at runtime.[20]
Such a basic Devicetree is ideal for bootstrapping a single-core SoC without peripherals, as seen in early-stage embedded development or virtual platforms like QEMU's ARM machine, where the focus is on initializing the CPU and memory before adding complexity.
Complex Device Configuration
Complex device configurations in Devicetree often involve hierarchical structures describing interconnected peripherals, such as buses with child devices that share resources like interrupts via phandles. These setups require precise node definitions to ensure proper address mapping and resource referencing across the tree. For instance, an I2C bus under the system-on-chip (SoC) node might host a sensor device, with properties linking it to an interrupt controller.[69]
A representative example of a full board description appears in a Device Tree Source (DTS) file for an SoC with an I2C controller at base address 0x7000, enabling a temperature sensor at I2C address 0x48. The structure includes:
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "vendor,soc";
ranges;
i2c@7000 {
compatible = "vendor,i2c";
reg = <0x7000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
my-sensor@48 {
compatible = "ams,as6200";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <17 IRQ_TYPE_EDGE_BOTH>;
vs-supply = <&vs_regulator>;
};
};
gpio1: gpio@6000 {
compatible = "vendor,gpio";
reg = <0x6000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
};
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "vendor,soc";
ranges;
i2c@7000 {
compatible = "vendor,i2c";
reg = <0x7000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
my-sensor@48 {
compatible = "ams,as6200";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <17 IRQ_TYPE_EDGE_BOTH>;
vs-supply = <&vs_regulator>;
};
};
gpio1: gpio@6000 {
compatible = "vendor,gpio";
reg = <0x6000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
};
};
Here, the interrupt-parent phandle references the gpio1 node, and the interrupts property specifies the GPIO pin 17 with edge-both triggering, enabling the sensor to signal events to the GPIO controller acting as an interrupt source. The #address-cells and #size-cells properties ensure correct interpretation of the reg address for the I2C child, while the ranges property in the SoC node maps child addresses to physical memory.[69][70]
Devicetree overlays provide a mechanism for runtime modifications to the base tree, allowing dynamic enabling or disabling of devices like a USB controller without recompiling the entire DTS. An overlay fragment targets an existing USB node and updates its status:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/soc/usb@30000000";
__overlay__ {
status = "okay";
maximum-speed = "high-speed";
};
};
};
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/soc/usb@30000000";
__overlay__ {
status = "okay";
maximum-speed = "high-speed";
};
};
};
This fragment applies at boot via the kernel's overlay support, resolving phandles against the live tree and activating the USB controller at address 0x30000000 with high-speed capability. Overlays are particularly useful for modular hardware additions, such as peripherals added post-manufacture.[71]
In real-world implementations, such as the Raspberry Pi 4's DTS, complex configurations integrate GPIO, UART, and HDMI bindings within the Broadcom BCM2711 SoC description. Key excerpts include:
-
GPIO controller:
gpio: gpio@7e200000 { compatible = "brcm,bcm2711-gpio", "brcm,bcm2835-gpio"; reg = <0x7e200000 0xb4>; interrupts = <1 25>; gpio-controller; #gpio-cells = <2>; gpio-ranges = <&cmu 0 0 54>, <&cmu 6 54 6>; #gpio-line-names = "ID_SDA", ..., "TXD1", "RXD1", ...; }; This defines 54 GPIO lines with pin multiplexing support via the clock management unit (CMU).[72]
-
UART:
&uart0 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; }; uart0_pins: uart0-pins { brcm,pins = <14 15>; brcm,function = <4 4>; }; The primary UART (PL011) uses GPIO 14 (TX) and 15 (RX) in alternate function 4.[72]
-
HDMI:
&hdmi0 { status = "okay"; }; hdmi0: hdmi@7e900000 { compatible = "brcm,bcm2835-hdmi"; reg = <0x7e900000 0x1000>, <0x7e902000 0x1000>; interrupts = <2 101>, <2 102>; ... }; Dual HDMI ports are enabled, with the first at 0x7e900000 handling audio and video output via interrupt lines 101 and 102.[72]
These bindings interconnect via phandles, such as GPIO pins routed to UART or HDMI for signaling.
Managing complex configurations presents challenges, particularly with #address-cells and #size-cells properties, which are not inherited from parent nodes and must be explicitly set for each bus to avoid mismatches in child reg interpretations—defaults of 2 and 1 apply only if omitted, potentially causing address translation errors in multi-level hierarchies.[70] Resolving phandles in large trees or during overlay application requires dynamic adjustment to prevent conflicts, as the kernel's resolver shifts local phandles by the maximum value in the base tree plus one, ensuring uniqueness but demanding careful validation to maintain reference integrity across thousands of nodes in expansive SoCs.[73]