- 了解device tree
1.Introduction
Most modern general-purpose computers, like a desktop or laptop, will consist of several peripherals connected to a main processor through a bus such as PCI, USB, etc. An operating system, such as Windows or Linux, running on the computer can discover or learn about the connected peripherals through enumeration. Enumeration is a process through which the OS can enquire and receive information, such as the type of the device, the manufacturer, or the device configuration, about all the devices connected to a given bus. Once the OS gathers information about a device, it can load the appropriate driver for the device. However, this is not the case when it comes to most embedded systems.
In embedded systems, many peripherals are connected to the main processor with busses like I2C, SPI, and UART, which do not support enumeration. Therefore, when an embedded system uses an operating system, such as Linux, it is necessary to convey the hardware configuration of the system, i.e. all the information about the connected peripherals, to the OS in a standard form. The most common way to do this in Linux is to use a device tree.
A device tree is a tree structure used to describe the physical hardware in a system. Each node in the tree describes the characteristics of the device being represented. The purpose of the device tree is to describe device information in a system that cannot necessarily be dynamically detected or discovered by a client program. For example, a PCI host may be able to probe and detect attached devices; and so a device tree node
describing PCI devices may not be required. However, a device node is required to describe the PCI host bridge in the system, if that cannot be detected by probing
Devices which use buses that support enumeration do not need to be included in the device tree. For example, devices that are connected to a USB Host port do not need to be included in the device tree, since the USB bus protocol supports enumeration. Similarly, all devices that are connected to an I2C bus must be included in the device tree, since the I2C bus protocol does not support enumeration.
1.1.Device Tree Structure and Properties
Linux device tree begins from a root node (i.e. the Tree Root) and will consist of a level of child nodes and one or more levels of children nodes. Each child node represents a hardware component of the micro-processor.
A hardware device tree is a tree data structure with nodes and properties that describes the physical devices in a system, that is Direct Memory Access, Universal Serial Bus Interface, Frame Manager, or Security Monitor. The ePAPR standard describes the logical structure of the hardware device tree and specifies a base set of required nodes and properties. This set is minimal, but complete enough to boot a simple operating system.
- DTS (Device Tree Syntax) is the textual representation of a hardware device tree consumed by a DTC.
- DTC (Device Tree Compiler) is an open source tool used to create DTB files from DTS files.
- DTB (Device Tree Blob) is a compact binary representation of the hardware device tree consumed by the Uboot and operating system.
2.Overview
A device tree is a tree data structure with nodes that describe the devices in a system. Each node has property/value pairs that describe the characteristics of the device being represented. Each node has exactly one parent except for the root node, which has no parent.
poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTAyODYyMQ==,size_16,color_FFFFFF,t_70)
2.1.Device Tree Structure and Conventions
2.1.1 Node Names
Each node in the device tree is named according to the following convention:
node-name@unit-address
- “node-name” specifies the name of the node.It shall start with a lower or uppercase character and should describe the general class of device.
- “unit-address” component of the name is specific to the bus type on which the node sits.
Note:
- “unit-address” must match the first address specified in the reg property of the node. If the node has no reg property, the @ and unit-address must be omitted and the node-name alone differentiates the node from other nodes at the same level in the tree.
- The root node does not have a node-name or unit-address. It is identified by a forward slash (/).
2.1.2.Path Names
A node in the device tree can be uniquely identified by specifying the full path from the root node.The convention for specifying a device path is:
/node-name-1/node-name-2/node-name-N
Note: A unit address may be omitted if the full path to the node is unambiguous.
2.1.3 Properties
Each node in the device tree has properties that describe the characteristics of the node. Properties consist of a name and a value.
1>.“compatible”:
It specifies the name of the system. It contains a string in the form ",. It is important to specify the exact device, and to include the manufacturer name to avoid namespace collisions. Since the operating system will use the compatible value to make decisions about how to run on the machine, it is very important to put correct data into this property.
该属性作用1:
The operating system will use the compatible value to make decisions about how to run on the machine,
/ {
compatible = "acme,coyotes-revenge";
};
该属性作用2:
Every node in the tree that represents a device is required to have the compatible property. compatible is the key an operating system uses to decide which device driver to bind to a device.
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
“flash device compatible” is a list of strings.
- First string specifies the exact device that the node represents in the form “,”.
- Second strings represent other devices that the device is compatible with.
For example:
compatible = “fsl,mpc8349-uart”, “ns16550”.
the Freescale MPC8349 System on Chip (SoC) has a serial device which implements the National Semiconductor ns16550 register interface.
- fsl,mpc8349-uart specifies the exact device.
- ns16550 states that it is register-level compatible with a National Semiconductor 16550 UART.
2>.“model”:
The model property value is a that specifies the manufacturer’s model number of the device.
3>."status”:
The status property indicates the operational status of a device.
4>.device_type
It should be included only on cpu and memory nodes for compatibility with IEEE 1275–derived device trees.
How Addressing Works
Devices that are addressable use the following properties to encode address information into the device tree:
- reg
- #address-cells
- #size-cells
Each addressable device gets a reg which is a list of tuples in the form reg = <address1 length1 [address2 length2] [address3 length3] … >. Each tuple represents an address range used by the device. Each address value is a list of one or more 32 bit integers called cells. Similarly, the length value can either be a list of cells, or empty.
Since both the address and length fields are variable of variable size, the #address-cells and #size-cells properties in the parent node are used to state how many cells are in each field. Or in other words, interpreting a reg property correctly requires the parent node’s #address-cells and #size-cells values.
5>.#address-cells and #size-cells
The #address-cells and #size-cells properties may be used in any device node that has children in the device tree hierarchy and describes how child device nodes should be addressed.
- #address-cells defines the number of cells used to encode the address field in a child node’s reg property.
- #size-cells defines the number of cells used to encode the size field in a child node’s reg property.
32bit machines:
#address-cells = <1>;
#size-cells = <1>;
64 bit machines:
#address-cells = <2>;
#size-cells = <2>;
For example1:CPU addressing
The CPU nodes represent the simplest case when talking about addressing. Each CPU is assigned a single unique ID, and there is no size associated with CPU ids.
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
cpu@1 {
compatible = "arm,cortex-a9";
reg = <1>;
};
};
In the cpus node, #address-cells is set to 1, and #size-cells is set to 0. This means that child reg values are a single uint32 that represent the address with no size field. In this case, the two cpus are assigned addresses 0 and 1. #size-cells is 0 for cpu nodes because each cpu is only assigned a single address.
Note:You’ll also notice that the reg value matches the value in the node name. By convention, if a node has a reg property, then the node name must include the unit-address, which is the first address value in the reg property.
For example2:Memory Mapped Devices
A memory mapped device is assigned a range of addresses that it will respond to.
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
};
...
};
For example3:Non Memory Mapped Devices
Other devices are not memory mapped on the processor bus. They can have address ranges, but they are not directly accessible by the CPU. Instead the parent device’s driver would perform indirect access on behalf of the CPU.
To take the example of i2c devices, each device is assigned an address, but there is no length or range associated with it. This looks much the same as CPU address assignments.
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
6>.“reg”
The reg property describes the address of the device’s resources within the address space defined by its parent bus. Most commonly this means the offsets and lengths of memory-mapped IO register blocks, but may have a different meaning on some bus types. Addresses in the address space defined by root node are cpu real addresses.
Suppose a device within a system-on-a-chip had two blocks of registers—a 32-byte block at offset 0x3000 in the SOC and a 256-byte block at offset 0xFE00. The reg property would be encoded as follows (assuming #address-cells and #size-cells values of 1):
reg = <0x3000 0x20 0xFE00 0x100>;
6>.“ranges”
The root node always describes the CPU’s view of the address space. Child nodes of the root are already using the CPU’s address domain, and so do not need any explicit mapping. For example, the serial@101f0000 device is directly assigned the address 0x101f0000.
Nodes that are not direct children of the root do not use the CPU’s address domain. In order to get a memory mapped address the device tree must specify how to translate addresses from one domain to another. The ranges property is used for this purpose.
The ranges property provides a means of defining a mapping or translation between the address space of the bus (the child address space) and the address space of the bus node’s parent (the parent address space).
The format of the value of the ranges property is an arbitrary number of triplets of (child-bus-address, parent-bus-address, length)
- The child-bus-address is a physical address within the child bus’ address space. The number of cells to represent the address is bus dependent and can be determined from the #address-cells of this node (the node in which the ranges property appears).
- The parent-bus-address is a physical address within the parent bus’ address space.The number of cells to represent the parent address is bus dependent and can be determined from the #address-cells property of the node that defines the parent’s address space.
- The length specifies the size of the range in the child’s address space. The number of cells to represent the size can be determined from the #size-cells of this node (the node in which the ranges property appears).
Example:
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
...
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
Note:
- If the property is defined with an value, it specifies that the parent and child address space is identical, and no address translation is required.
- If the property is not present in a bus node, it is assumed that no mapping exists between children of the node and the parent address space.
How Interrupts Work
Interrupt signals can originate from and terminate on any device in a machine. Unlike device addressing which is naturally expressed in the device tree, interrupt signals are expressed as links between nodes independent of the tree. Four properties are used to describe interrupt connections:
- interrupt-controller - An empty property declaring a node as a device that receives interrupt signals
- #interrupt-cells - This is a property of the interrupt controller node. It states how many cells are in an interrupt specifier for this interrupt controller (Similar to #address-cells and #size-cells).
- interrupt-parent - A property of a device node containing a phandle to the interrupt controller that it is attached to. If this property is missing from a device, its interrupt parent is assumed to be its device tree parent.
- interrupts - A property of a device node containing a list of interrupt specifiers, one for each interrupt output signal on the device.
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
...
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < 3 0 >;
};
intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};
spi@10115000 {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < 4 0 >;
};
external-bus {
#address-cells = <2>
#size-cells = <1>;
ranges = <0 0 0x10100000 0x10000 // Chipselect 1, Ethernet
1 0 0x10160000 0x10000 // Chipselect 2, i2c controller
2 0 0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>;
interrupts = < 5 2 >;
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
#address-cells = <1>;
#size-cells = <0>;
reg = <1 0 0x1000>;
interrupts = < 6 2 >;
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = <2 0 0x4000000>;
};
};
};
- #interrupt-cells is 2, so each interrupt specifier has 2 cells.
- The first cell to encode the interrupt line number
- The second cell to encode flags such as active high vs. active low, or edge vs. level sensitive.
Special Nodes
- aliases Node
- chosen Node
7>.Aliases Node
The aliases node can be used to assign a short alias to a full device path. For example:
aliases {
ethernet0 = ð0;
serial0 = &serial0;
};
The operating system is welcome to use the aliases when assigning an identifier to a device.
8>.chosen Node
The chosen node doesn’t represent a real device, but serves as a place for passing data between firmware and the operating system, like boot arguments. Data in the chosen node does not represent the hardware. Typically the chosen node is left empty in .dts source files and populated at boot time.
Firmware might add the following to the chosen node:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
};
参考资料:
https://elinux.org/Device_Tree_Usage
https://elinux.org/Device_Tree_Reference
https://elinux.org/Device_Tree_Usage#Ranges_.28Address_Translation.29