Device trees (Embedded Linux)
Based on Chapter Ch13 Part7, Mastering Embedded System from Scratch
The device tree is a hierarchical data structure that describes the hardware configuration of a system in a platform-independent manner. Here are some key points about the Linux device tree:
·????????The device tree is represented as a tree of nodes, where each node corresponds to a device in the system. Nodes are connected through parent-child relationships, with the root node representing the entire system.
·????????Each node in the device tree has a name, which identifies the device it represents. The names are usually chosen to reflect the device's function or location in the system.
·????????Nodes in the device tree can have properties, which are key-value pairs that describe various characteristics of the device. For example, a node representing a network interface might have properties such as "phy-handle" (to specify the physical interface) and "mac-address" (to specify the device's MAC address).
·????????The device tree is stored in a binary format that is typically compiled from a human-readable source file (usually with a .dts or .dtsi extension) using the Device Tree Compiler (DTC) tool.
·????????The device tree is loaded into memory by the bootloader and passed to the kernel as a pointer to a data structure that describes the location and size of the tree in memory.
·????????The Linux kernel provides a set of APIs (such as of_find_node_by_name and of_get_property) that can be used to access the device tree from kernel code.
Here is an example device tree that describes a system with a single UART device:
/dts-v1/;
/ {
??? compatible = "simple-bus";
??? #address-cells = <1>;
??? #size-cells = <1>;
???
????uart0: serial@80000000 {
??????? compatible = "ns16550a";
??????? reg = <0x80000000 0x1000>;
??????? interrupts = <42>;
??????? clock-frequency = <1843200>;
??? };
};
In this device tree, the root node has a compatible property that identifies it as a "simple-bus" device. The #address -cells and #size -cells properties indicate that the device tree uses single-cell addressing.
The uart0 node represents the UART device, with a name of serial@80000000. The compatible property indicates that the device is compatible with the NS16550A UART controller. The reg property specifies the base address and size of the device's memory-mapped registers, and the interrupts property specifies the interrupt number for the device. Finally, the clock-frequency property specifies the frequency of the device's clock signal.
Overall, the device tree is a powerful mechanism for describing the hardware configuration of a system in a platform-independent manner, and is a key component of the Linux kernel's device driver infrastructure.
booting with a Device Tree
When booting a Linux kernel on an ARM-based system using a bootloader that has no specific support for the Device Tree, the Device Tree can be appended to the kernel image itself to provide the necessary hardware configuration information to the kernel.
To append the Device Tree to the kernel image, the CONFIG_ARM_APPENDED_DTB option can be used. This option tells the kernel to look for the Device Tree right after the kernel image. Here are the steps to append the Device Tree to the kernel image:
1.?????Create a Device Tree source file (usually with a .dts extension) that describes the hardware configuration of the system.
2.?????Compile the Device Tree source file using the Device Tree Compiler (DTC) tool to generate a Device Tree Blob (DTB) file.
3.?????Combine the kernel image and the DTB file into a single file using the cat command. For example:
4.? cat arch/arm/boot/zImage arch/arm/boot/dts/myboard.dtb > my-zImage
5.?????Create a bootable image from the combined file using a tool such as mkimage. For example:
6.? mkimage -A arm -O linux -T kernel -C none -a 0x80008000 -e 0x80008000 -n "My Linux Kernel" -d my-zImage my-uImage
In this example, the mkimage tool creates a bootable image (my-uImage) from the combined kernel image and DTB file (my-zImage). The -A, -O, and -T options specify the architecture,?
operating system, and image type, respectively. The -C option specifies that no compression should be used. The -a and -e options specify the load and entry points for the kernel image, respectively. The -n option specifies a descriptive name for the kernel, and the -d option specifies the input file.
Overall, appending the Device Tree to the kernel image is a straightforward way to provide hardware configuration information to the kernel when using a bootloader that has no specific support for the Device Tree.
The Device Tree is a hierarchical data structure that describes the hardware configuration of a system. Here are some key points about the basic structure of the Device Tree:
·????????The Device Tree is represented as a tree of nodes, where each node corresponds to a device or subsystem in the system. Nodes are connected through parent-child relationships, with the root node representing the entire system.
·????????The Device Tree starts at a root node, which is named "/". The root node contains child nodes, which in turn may contain additional child nodes.
·????????Each node in the Device Tree has a name, which identifies the device or subsystem it represents. The names are usually chosen to reflect the device's function or location in the system.
·????????Nodes in the Device Tree can have properties, which are key-value pairs that describe various characteristics of the device or subsystem. For example, a node representing a network interface might have properties such as "phy-handle" (to specify the physical interface) and "mac-address" (to specify the device's MAC address).
·????????The Device Tree specification requires that each Device Tree file must contain a version node. This is typically included at the beginning of the file and looks like: /dts-v1/;
·????????Comments in the Device Tree file are C style and are enclosed in "/" and "/".
Here is an example Device Tree node that represents a UART device:
uart0: serial@80000000 {
??? compatible = "ns16550a";
??? reg = <0x80000000 0x1000>;
??? interrupts = <42>;
??? clock-frequency = <1843200>;
};
In this example, the node is named uart0 and represents a UART device located at address 0x80000000. The compatible property indicates that the device is compatible with the NS16550A UART controller. The reg property specifies the base address and size of the device's memory-mapped registers, and the interrupts property specifies the interrupt number for the device. Finally, the clock-frequency property specifies the frequency of the device's clock signal.
Overall, the Device Tree is a powerful mechanism for describing the hardware configuration of a system in a platform-independent manner, and is a key component of the Linux kernel's device driver infrastructure.
Exploring the DT on the target
To explore the contents of the Device Tree on the target system, one can use the /sys/firmware/devicetree/base directory, which provides a file system-like view of the Device Tree. Each node in the Device Tree is represented by a file or subdirectory in this directory.
For example, to list the nodes at the root level of the Device Tree, one can use the ls command:
$ ls /sys/firmware/devicetree/base/
#address -cells???????? #interrupt -cells?????? model
#size -cells??????????? interrupt-parent?????? name
compatible????????? ???interrupts???????????? ranges
In this example, the root node of the Device Tree has several child nodes, each represented as a file or subdirectory in the /sys/firmware/devicetree/base directory. The compatible property, for example, can be read from the compatible file:
$ cat /sys/firmware/devicetree/base/compatible
vendor,board-name
To further explore the contents of the Device Tree, one can use the dtc tool to "unpack" the Device Tree into a human-readable format. On a system where dtc is available, one can run the following command:
$ dtc -I fs /sys/firmware/devicetree/base
This will output the entire Device Tree in a human-readable format, showing the hierarchy of nodes and their properties.
Overall, the /sys/firmware/devicetree/base directory and the dtc tool provide useful tools for exploring the contents of the Device Tree on the target system.
Let us try to write a device tree from scratch to Sample Machine
?One 32bit ARM CPU
?processor local bus attached to memory mapped serial port, spi bus controller, i2c controller, interrupt controller, and external bus bridge
?256MB of SDRAM based at 0
?2 Serial ports based at 0x101F1000 and 0x101F2000
?GPIO controller based at 0x101F3000
?SPI controller based at 0x10170000 with following devices
?MMC slot with SS pin attached to GPIO #1
?External bus bridge with following devices
?SMC SMC91111 Ethernet device attached to external bus based at 0x10100000
?i2c controller based at 0x10160000 with following devices
?Maxim DS1338 real time clock. Responds to slave address 1101000 (0x58)
?64MB of NOR flash based at 0x30000000
领英推荐
Device Tree Syntax
Here are some key aspects of the Device Tree Source (DTS) syntax:
·????????Aliases: Aliases are shorthand names that can be used to refer to devices or nodes in the Device Tree. Aliases are defined using the aliases node, which contains a list of alias names and their corresponding target nodes.
aliases {
??? eth0 = &mac0;
??? eth1 = &mac1;
};
In this example, the eth0 and eth1 aliases are defined, which refer to the mac0 and mac1 nodes, respectively.
·????????Phandles: A phandle is a unique identifier that is used to reference a node in the Device Tree. Phandles are typically used to reference parent nodes or to specify the target of a property.
node1 {
??? phandle = <0x1234>;
??? ...
};
node2 {
??? parent = <&node1>;
??? ...
};
In this example, the node1 node has a phandle of 0x1234, which is used as the parent of the node2 node.
Device tree Nodes example
·????????Properties: Properties are key-value pairs that describe the characteristics of a node or device in the Device Tree. Properties are specified using the property = <value> syntax.
node {
??? compatible = "vendor,device";
??? reg = <0x1000 0x100>;
??? interrupts = <42 2
??? ...
};;>
In this example, the node node has several properties, including the compatible property, which specifies the vendor and device, the reg property, which specifies the address range of the device's registers, and the interrupts property, which specifies the interrupt number and type.
·????????Node names: Node names are used to identify nodes in the Device Tree hierarchy. Node names must be unique within the Device Tree, and are typically chosen to reflect the function or location of the node.
node {
??? ...
};
subnode {
??? ...
};
In this example, the node node has a child node named subnode, which is identified by its node name.
Overall, the DTS syntax provides a flexible and expressive way to describe the hardware configuration of a system using a hierarchical data structure with properties, phandles, aliases, and node names.
?DTS Addressing
In the Device Tree Source (DTS) syntax, there are several mechanisms for addressing devices and memory regions:
·????????Ranges: The ranges property is used to specify an address translation between two address spaces. This is typically used to translate device addresses from a bus-specific address space to a system memory address space.
node {
??? compatible = "vendor,device";
??? reg = <0x1000 0x100>;
??? ranges = <0x0 0x10000000 0x8000>;
??? ...
};
In this example, the ranges property specifies that the device's address range (0x1000-0x10ff) is translated to the system memory address range (0x8000-0x80ff).
·????????Non-memory mapped devices: For devices that are not memory-mapped, the reg property is used to specify the device's address and size.
node {
??? compatible = "vendor,device";
??? reg = <0x1000 0x100>;
??? ...
};
In this example, the reg property specifies that the device is located at address 0x1000 with a size of 0x100 bytes.
·????????Memory-mapped devices: For devices that are memory-mapped, the reg property is used to specify the device's address and size, and the ranges property is used to specify the address translation between the device's address space and the system memory address space.
node {
??? compatible = "vendor,device";
??? reg = <0x1000 0x100>;
??? ranges = <0x0 0x10000000 0x8000>;
??? ...
};
In this example, the reg property specifies that the device is located at address 0x1000 with a size of 0x100 bytes, and the ranges property specifies that the device's address range is translated to the system memory address range (0x8000-0x80ff).
·????????CPU addressing: The ranges property can also be used to specify the CPU's address space, which is typically used for mapping system memory to the CPU's address space.
chosen {
??? bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
};
memory {
??? device_type = "memory";
??? reg = <0x0 0x40000000>;
};
soc {
??? ranges = <0x0 0x0 0x80000000 0x40000000>;
??? ...
};
In this example, the ranges property in the soc node specifies that the CPU's address space (0x0-0x7fffffff) is translated to the system memory address space (0x0-0x3fffffff). The memory node specifies the size and location of the system memory.
Overall, the DTS addressing mechanisms provide a flexible way to specify the mapping between devices, address spaces, and memory regions in a system.
DTS Interrupts?
In the Device Tree Source (DTS) syntax, interrupts are described using several properties:
·????????interrupts: The interrupts property is used to specify the interrupt number(s) and type(s) for a device.
node {
??? compatible = "vendor,device";
??? interrupts = <42 2>;
??? ...
};
In this example, the interrupts property specifies that the device uses interrupt number 42 with type 2 (which could correspond to a rising edge, falling edge, level, etc.).
·????????interrupt-parent: The interrupt-parent property is used to specify the node that serves as the parent interrupt controller for a device.
node {
??? compatible = "vendor,device";
??? interrupts = <42 2>;
??? interrupt-parent = <&intc>;
??? ...
};
intc {
??? compatible = "vendor,intc";
??? ...
};
In this example, the interrupt-parent property specifies that the intc node serves as the parent interrupt controller for the node device.
·????????#interrupt -cells: The #interrupt -cells property is used to specify the number of cells that are used to encode each interrupt specifier in the interrupts property. Each cell typically encodes a portion of the interrupt information (e.g., the interrupt number, type, polarity, etc.).
node {
??? compatible = "vendor,device";
??? interrupts = <42 2>;
??? #interrupt-cells = <2>;
??? ...
};
In this example, the #interrupt -cells property specifies that each interrupt specifier in the interrupts property contains two cells (which could correspond to the interrupt number and type, for example).
·????????interrupt-controller: The interrupt-controller property is used to specify that a node serves as an interrupt controller for its child nodes.
intc {
??? compatible = "vendor,intc";
??? interrupt-controller;
??? #interrupt-cells = <2>;
??? ...
};
In this example, the interrupt-controller property specifies that the intc node serves as an interrupt controller for its child nodes.
describe the interrupt configuration of a system, including the interrupt numbers, types, controllers, and encoding.
Device Tree inclusion
In the Device Tree Source (DTS) syntax, it is common to include one DTS file within another, typically to describe a board or a module that is used in multiple systems. This is done using the include statement, which takes a path to the included DTS file as an argument.
For example, suppose we have a DTS file for a board that uses a specific CPU and set of devices:
/dts-v1/;
/ {
??? model = "MyBoard";
??? compatible = "vendor,board";
??? cpu {
??????? compatible = "arm,cortex-a9";
??????? ...
??? }
??? ethernet {
??????? compatible = "vendor,ethernet";
??????? ...
??? }
??? ...
};
We can include this DTS file within another DTS file that describes a system that uses the board:
/dts-v1/;
/ {
??? model = "MySystem";
??? compatible = "vendor,system";
??? chosen {
??????? bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
??? };
??? memory {
??????? device_type = "memory";
??????? reg = <0x0 0x40000000>;
??? };
??? myboard {
??????? compatible = "vendor,board";
??????? model = "MyBoard";
??????? status = "okay";
??? };
};
In this example, the myboard node includes the DTS file for the board using the include statement:
myboard {
??? compatible = "vendor,board";
??? model = "MyBoard";
??? status = "okay";
??? include "myboard.dtsi";
};
The include statement causes the contents of the myboard.dtsi file to be included within the myboard node.
By using DTS inclusion, we can create a modular and reusable description of a system that can be adapted to different hardware configurations and use cases.
R&D TV Product Support Engineer at ELaraby Group||Master Student at Ain Shams university
1 年Thank you for sharing