Arm Community
Arm Community
  • Site
  • User
  • Site
  • Search
  • User
Open Source Software and Platforms
Open Source Software and Platforms
Wiki Device Tree
  • Help
  • Jump...
  • Cancel
  • About this wiki
  • Supported platforms
  • Obtaining support
  • +Arm Reference Platforms deliverables
  • +A-class platforms
  • +M-class platforms
  • +R-class platforms
  • +FPGA prototyping boards
  • -Open source software
    • -Linux/Android
      • ------- Power management -------
      • cpufreq (DVFS)
      • cpuidle (hotplug)
      • +Energy Aware Scheduling (EAS)
      • System Suspend to RAM
      • ------- Configuration -------
      • -Device Tree
        • Modifying CPU nodes in Device Tree
      • Modify Linux kernel config flags in the Arm Platforms deliverables
      • ----- Virtualization -----
      • Spawn a Linux virtual machine on Arm using QEMU (KVM)
      • ------- User-space -------
      • Android
      • Debian
      • Fedora Server
      • OpenEmbedded
      • Build a Buildroot user-space
      • Build an ILP32 user-space
      • BusyBox
    • +Trusted Firmware-A
    • Trusted Firmware-M
    • +EDK II UEFI
    • OP-TEE
    • +U-Boot
    • Robotics
    • Mbed OS
    • +SCP

Device Tree

Introduction

During the Linux boot process, a "Device Tree Blob" (DTB) file is loaded into memory by U-Boot / UEFI, and a pointer to it is passed to the kernel. This DTB file describes the system's hardware layout to the Linux kernel, allowing for platform-specific code to be moved out of the kernel sources and replaced with generic code that can parse the DTB and configure the system as required. From DeviceTree.org:

[..] Device Tree is a data structure for describing hardware. Rather than hard coding every detail of a device into an operating system, many aspects of the hardware can be described in the data structure that is passed to the operating system at boot time.

For a quick introduction to Device Tree and how it relates to the kernel, see Thomas Petazzoni's "Device Tree for Dummies" slide deck. Another useful resource is the "A Symphony of Flavours: Using the device tree to describe embedded hardware" paper by Grant Likely and Josh Boyer.

We have another FAQ here which outlines how to use Device Tree to alter the number of CPUs available to Linux on Juno.

Compiling a Device Tree Blob

Device Tree Source (DTS) files are simple text files that can be compiled into a binary Device Tree Blob (DTB) format using the Device Tree Compiler (DTC) tool. The DTC tool is available in the Linux kernel sources under /scripts/dtc, and is also available for installation through some distribution package managers such as APT on Ubuntu:

$ sudo apt-get update
$ sudo apt-get install device-tree-compiler

Decompiling a DTB using DTC is lossless, i.e. one can decompile a DTB into a DTS and then compile that DTS back into a DTB without losing any information. To decompile:

$ dtc -I dtb -O dts juno.dtb > juno.dts

And to recompile:

$ dtc -I dts -O dtb juno.dts > juno.dtb

Device Tree Source (DTS) syntax

Note: Full documentation for general Device Tree syntax can be found at DeviceTree.org, and Linux binding documentation can be found the Linux kernel sources here

Device Tree Source files are a tree structure consisting of nodes with associated properties and child nodes. The general syntax is as follows:

/ {
    node1 {
        a-string-property = "a string";
        a-string-list-property = "first string", "second string";
        a-byte-data-property = [0x01 0x02 0x03];
        child-node1 {
            first-child-property;
            second-child-property
            a-string-property = "child string";
        };
        child-node2 {
        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3>;
        child-node1 {
        };
    };
};

Where:

  • / denotes the root node of the tree
  • node1 and node2 are child nodes of the root node
  • node1 has two child nodes: child-node1 and child-node2

Each node has a set of properties associated with it:

  • Null-terminated text strings are wrapped with double quotes:
    • a-string-property = "a string";
  • Cells (32-bit unsigned integers) are space-delimited and wrapped with angle brackets:
    • a-cell-property = <1 2 3>;
  • Binary data is space-delimited and wrapped with square brackets:
    • a-binary-property = [0x01 0x02 0x03];
  • Mixed representation data can be concatenated using commas:
    • a-mixed-property = "a string", [0x01 0x02 0x03], <1 2 3>;
  • Commas are also used to create string lists:
    • a-string-list = "first string", "second string";

Real-world DTS example

Here is an example DTS entry for the Juno, specifically for the GIC-400:

interrupt-controller@2c010000 {
    compatible = "arm,gic-400";
    reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;
    #address-cells = <0x2>;
    #interrupt-cells = <0x3>;
    #size-cells = <0x2>;
    interrupt-controller;
    interrupts = <0x1 0x9 0x3f04>;
    ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
    linux,phandle = <0x1>;
    phandle = <0x1>;
    v2m@0 {
        compatible = "arm,gic-v2m-frame";
        msi-controller;
        reg = <0x0 0x0 0x0 0x1000>;
    };
};

Let's break down this entry to get an idea of how a device tree node can be used to describe real hardware:

interrupt-controller@2c010000 {

The interrupt controller is at address 0x2C010000 on the parent node "bus" (more on that later). In this case, the parent node is the root node, so this means address 0x2C010000 from the point of view of the CPU.

compatible = "arm,gic-400";

This is used by the Linux kernel to decide which device driver to bind to the peripheral. In this case, the interrupt controller is compatible with the ARM GIC-400 driver.

reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;

The reg property defines a range of addresses that the peripheral will respond to. The property consists of a number of tuples, each holding the base address of a region and the size of that region. To parse the property we need to understand three things:

  1. Each cell value is a 32-bit unsigned integer
  2. The parent node's #address-cells value defines how many cell values constitute a region's base address
  3. The parent node's #size-cells value defines how many cell values constitute a region's length

The interrupt-controller node is a child of the root node, which on the Juno has #address-cells = #size-cells = 2. Moving sequentially through the reg property and combining the first 2 cells for a base address, the next 2 cells for a length, the next 2 cells for a base address, and so on, gives the following regions that the interrupt controller will respond to:

  • 64-bit address 0x0000_0000_2C01_0000 with 64-bit length 0x0000_0000_0000_1000
  • 64-bit address 0x0000_0000_2C02_F000 with 64-bit length 0x0000_0000_0000_2000
  • 64-bit address 0x0000_0000_2C04_F000 with 64-bit length 0x0000_0000_0000_2000
  • 64-bit address 0x0000_0000_2C06_F000 with 64-bit length 0x0000_0000_0000_2000
interrupt-controller;

All interrupt controller nodes must include an empty interrupt-controller property.

interrupts = <0x1 0x9 0x3f04>;

The GIC's interrupt device tree binding format can be found here in the Linux kernel docs :

  • The first cell denotes the interrupt type (0 for SPIs, 1 for PPIs)
  • The second cell contains a number of flags, encoded as follows:
    • Bits [3:0] define the trigger type and level flags, where 4 corresponds to Active High Level-Sensitive
    • Bits [15:8] define the CPU interrupt mask, where each bit indicates that the interrupt is wired to that particular CPU (bit 8 = CPU0, bit 9 = CPU1, ..., bit 15 = CPU7)

So here the GIC itself has PPI #9 as an Active High Level-Sensitive interrupt targeting CPUs [0..5], i.e. all CPUs on the Juno which has 6 CPUs in total. This PPI (interrupt ID 25) corresponds to the VGIC maintenance interrupt on the Juno and is required for CPUs that support the virtualization extensions.

ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
    v2m@0 {
        compatible = "arm,gic-v2m-frame";
        msi-controller;
        reg = <0x0 0x0 0x0 0x1000>;
    };

All addresses in a node are specified as bus addresses that are local to that node. The range property provides a mechanism for mapping these bus addresses to the parent node's address space.

In the Juno's interrupt controller node is a v2m child node at address 0x0, local to the interrupt controller "bus". This node is used for the GICv2m extensions, and needs to be mapped from the interrupt controller "bus" onto the CPU's point of view of memory.

Similarly to the reg property, ranges uses #address-cells and #size-cells to parse a tuple of (Local Address, Parent Address, Length), with:

  • The width of Local Address being determined by the local node's #address-cells value
  • The width of Parent Address being determined by the parent node's #address-cells value
  • The width of Length being determined by the local node's #size-cells value

On the Juno these are all 2, so the ranges property is interpreted as "map 64-bit local address 0x0 to 64-bit parent address 0x2C1C0000 with 64-bit length 0x40000". Because the v2m node is at local address 0x0, it will be mapped to the parent node's address 0x2C1C0000, and the parent node is the root node, which will be the CPU's point of view of memory.

  • Kernel Developers
  • Linux Developers
  • Linux
  • Share
  • History
  • More
  • Cancel
Related
Recommended