Device Tree Linux

Learn via video courses
Topics Covered

A Device Tree is a data structure which describes hardware components in a system so that the kernel can manage those components easily. As systems can have a lot of different hardware configurations, device tree allows the Linux kernel to communicate with hardware components without hardcoding these configurations in the kernel source code.

Introduction

Device Tree is a data structure and a language to describe hardware components. Device tree was created by the Open Firmware standards to find a way to unify the discovery of hardware devices connected to the system. This was necessary since there are a lot of hardware peripherals that can be connected to a system and it becomes difficult for the Linux kernel to fetch each hardware device.

The idea of device tree is to abstract or separate the handling of hardware discovery from the Linux kernel source code so that there is no need to hardcode hardware information in the source. The usage of device tree for hardware discovery was made compulsory for ARM-based devices in 2012.

Device Tree Syntax

Device tree source files (DTS) are text files with a tree-like structure. Each structure contains nodes which have properties stored in a key-value data format.

Device tree source files in Linux are compiled to Device Tree Blobs or Device Tree Binary (DTB) format using a Device Tree Compiler (DTC). These DTBs are then loaded along with the Linux kernel during boot by the bootloader.

Device tree syntax

The structure of a device tree source file looks like this:

A device tree source file has the following objects in it:

  • Nodes: Each device tree source file has nodes which represent actual hardware devices. These hardware devices have some characteristics which are represented by the properties contained within them.
    Child nodes need to be named uniquely but can also be differentiated with "unit names" which can be used to differentiate nodes with the same name. Unit names are made up of node names and unit addresses joined together by the "@" symbol.
    In the above example, node0 or child-node0 are examples of nodes.

  • Properties: Nodes usually contain properties which are arranged in a key-value pair format. The 'key' is made up of a string and whereas the 'value' can be a string, an array of strings, 32-bit big-endian numbers, bytes, phandles or mixtures of different types. In the above example, string-property: "this is a string", phandle: <&label> are examples of node properties.

  • Phandle: Phandle stands for pointer handle. It is a 32-bit value associated with a node in the device tree structure in Linux that is essentially a pointer from the property of a node to another node. In the above example, phandle: <&label> is an example of a phandle. This is converted to label: name@address {}; by the Device Tree Compiler.

  • Aliases: Alias nodes are indices of other nodes. The properties of aliases are valid paths within the device tree. In the above example, ethernet0: &eth0 is an example of an alias where the ethernet0 key represents a path eth0.

Data Model of Device Tree in Linux

A data model is a structure that is used to organise data. We are going to take a look into the data model of device tree for an overview of its working.

High-level View

The function of device tree in Linux or other operating systems is first and foremost to describe the hardware. The basic goal of device tree is to provide a common language to describe hardware. It also helps to abstract away the hardware configuration from the device drivers in the kernel.

When we create this abstraction, it makes hardware support data-driven. This essentially means that the device setup decisions by the kernel are made based on the data that is supplied to it rather than hardcoding the approach in the kernel source code itself. This makes it easier to support a huge variety of hardware devices by the kernel.

The device tree in Linux is used to achieve three main tasks:

  • Platform Identification
  • Runtime Configuration
  • Device Population

Platform Identification

The device tree is used by the Linux kernel to identify the machine it is working on. The kernel needs to identify the hardware setup of a system and identify the different components connected to the system. This is done during the early boot process by the kernel. This step is required because some hardware devices need the kernel to run special machine-specific procedures to operate correctly.

In most cases, the kernel runs the device setup code based on the SoC (System on Chip) or CPU type present on the device. The kernel identifies the best match for the SoC or CPU based on the compatible node in the device tree source. The compatible property node in DST is used to identify with which boards the current system is compatible. This node is a list of strings which starts with the exact machine name and might optionally mention boards the SoC is partially compatible with.

An example of compatible node is:

Here, the property in the device tree first mentions the exact machine name followed by the names of all partially compatible nodes that the SoC can work with.

If a developer mentions a board to be compatible with an SoC, they are also required to document this board in the kernel device tree documentation. This is necessary so that the kernel can identify the device from the device tree.

On ARM-based devices, the device tree helps the Linux kernel first identify the machine match based on the property node and also helps search through the list of compatible devices to determine the most compatible device. If the kernel is unable to find a matching description for a compatible device, it returns a NULL value.

This step is crucial since multiple boards from different vendors might have the same SoC. In that case, a machine can support most of the boards since they share the same SoC but there might be exceptions and some boards might require specific setup code to function properly.

This compatibility list helps the kernel support a large number of common boards and devices. Since the order of devices in the compatible node goes from most compatible to least compatible, generic device boards can also claim partial compatibility with a machine in case they share the same SoC but slightly different hardware configurations.

Runtime Configuration

Usually, the only method to communicate data from a device firmware to the kernel is through device tree. So the device tree is also passed during runtime using kernel parameters or with the initrd.

The initrd is a compressed image that the bootloader loads after loading the kernel. The initrd contains the device drivers and a small root filesystem that the kernel can use to mount and bootstrap the rest of the system.

The configuration data is present in the chosen node in the device tree which looks like this:

Here, the bootargs contains the kernel boot parameters. The initrd-start and initrd-end denote the addresses where the initrd starts and ends on the disk.

During early boot, the setup code calls the Linux kernel function of_scan_flat_dt() multiple times to parse the DTS which retrieves the data required to boot the system.

Device Population

Once the system board has been identified, the kernel can be initialized normally. Sometime during this process, a kernel function unflatten_device_tree() is called which converts the device tree data into a more efficient runtime representation. Machine-specific setup codes are also executed during this process.

A hook .init_machine() is called during this process which populates the Linux device tree model with information about the system.

Compiling a Device Tree Blob

Device Tree Source files in Linux (DTS) are text files which are compiled to Device Tree Blob (DTB) format using the Device Tree Compiler (DTC). The kernel does not read Device Tree Source files but it reads the Device Tree Binary format files. These DTBs are in binary and are not human-readable.

To install the DTC tool on Ubuntu, run the command:

DTB compilation using DTC is lossless. No data is lost or changed during the compilation process.

To compile a DTS to DTB, we need to run the command:

We can also decompile a DTB to DTS losslessly using DTC. To do that, we need to run the command:

Compiling a Device Tree Blob

Real-world DTS Example

We are going to take a look at the DTS for the Juno SOC for GIC-400.

Breaking this down:

  • The first nodeinterrupt-controller@2c010000 is an interrupt controller. The 0x2C010000 refers to the bus address on the device bus.

  • The compatible property is read from the device tree by the Linux kernel to determine the device type and also to determine the driver to bind with the connected device.

  • The reg property defines the range of addresses on which the device will respond to. The property has various fields. They contain the region's base address and its size.

  • To determine the values derived from the reg property, we need #address-cells and #size-cells. The #address-cells defines how many cell values are present in the base address of the region and the #size-cells defines the length of a region according to cell values.

  • Every interrupt control node must have an empty interrupt-controller.

  • The interrupts property defines the SoC interrupts. Each cell has a specific meaning.

    • The first cell <0x1> denotes the interrupt type.
    • The second cell holds various flags. These flags define the SoC's trigger types and level flags. The second cell also defines the CPU interrupt mask.
  • The ranges property is used to translate a range of addresses from the parent node to the child node. This is needed since the addresses mentioned in a node are local to that node and we need a way to map these local bus addresses to their parent nodes.

  • Here, the v2m@0 is a child node of the interrupt controller. The ranges property specifies that the v2m child node has the address of 0x0 on the local interrupt controller bus. The ranges property also takes in the #address-cells and #size-cells to determine a tuple consisting of the local bus address, parent's address and length.

Conclusion

  • Device tree in Linux is a data structure which describes hardware components without having to hardcode the hardware configurations in the Linux kernel source code.

  • Device trees are saved as text files called Device Tree Source (DTS) files. These files are compiled into binary blobs called Device Tree Blobs using a tool called Device Tree Compiler (DTC).

  • Device Tree Source files in Linux are divided into top-level components called nodes which contain properties in the form of key-value pairs. Properties can be strings, arrays of strings, numbers, bytes or a mixture of different types.

  • Device trees perform three main tasks: platform identification, runtime configuration and device population.

  • Device trees have made it possible for the Linux kernel to support a large variety of hardware devices having some common structures without requiring hardcoding of these values inside the kernel's source code.