While running an FPGA with an ARM CPU on Linux, it became necessary to modify what is called a device tree. Therefore, I would like to write about what I investigated (understood by myself) so that I could roughly understand the contents of the device tree source (.dts) included in the Kernel source. I hope that people who look at the .dts file in the Kernel source and think "what's wrong" will feel like they understand it and feel like they can make additional corrections.
The device tree can be called a data structure (file format) that describes hardware parts that can be accessed from the OS (CPU) from the perspective of software (device driver). This format was developed by a group called Open Firmware (a group separate from the Linux development group).
This format is also used in Linux, and the Kernel code and the information for each board (that is, the device tree) are separated and operated. This makes it possible to prepare a device tree for a board so that it can operate on different boards while keeping the Kernel binaries common.
The Kernel source has a device tree source (.dts) file for the major supported boards, and the device tree binaries are on the Makefile system as well as build the Kernel binaries (eg make zImage
). It can be generated (eg make dt bs
). By the way, at the time of writing this article (May 2020), the Linux kernel source tree of the original Kernel.org registered more than 1200 .dts files only for ARM 32-bit CPUs.
The Kernel code (each device driver) searches the device tree for the information that it needs at the time of initialization, and completes device registration and initialization according to that information.
Therefore, there may be situations where the Kernel will not start unless the information required by the Kernel code is described in the device tree, so in principle, the Kernel binary and device tree should be created from the same version of the Kernel source. Kernel.
Written in human-readable text format is called a device tree source and has a .dts extension. The file converted to binary format is called a device tree blob and has a .dtb extension. The Kernel reads the device tree blob (.dtb) in binary format. The device tree compiler (dtc command) is used to convert the device tree source to the device tree blob. The device tree compiler has the option of doing an inverse conversion .dtb-> .dts as well as .dts-> .dtb, which you can use if you want to see the contents of binary .dtb.
To understand the device tree in Linux (although the introduction has been lengthened) --Device tree format (how to write) --What information needs to be written in the device tree (what information the Linux device driver reads from the device tree)
I think it is good to understand it by dividing it into two parts.
Here, we will focus on the device tree writing (format)
.
What information needs to be written in the device tree
depends on what information the Linux device driver needs. At the end of this article, I'll give you a brief introduction.
** Roughly speaking about dts files **
--A list of hardware (device nodes) that you want the CPU to access as a tree-like database. --There are only two types of components, node and property. --node can have various attribute information properties and child nodes --property is represented by a combination of the attribute name name and its value value, and is used to describe hardware information such as address information and interrupt numbers.
is. For more details, please see the link below. https://elinux.org/Device_Tree_Usage
3.2 Node
node format
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
}
--device is represented as node --node has property and node (child node) --By having a node inside a node, a tree structure of nodes will be created.
--node name Define the name in the format @ unit-address, and define the node contents in the following curly braces {}
--The unit-address part describes the address specified by reg property in hexadecimal. Even if there are multiple devices with the same node name, they can be distinguished by unit-addres. No unit-address is needed for nodes that don't have a reg property. Linux dtc can compile without unit-address (although Warning appears)
--The node name is a simple ASCII string with a maximum length of 31 characters.
--It is recommended to give a name (ethernet, serial, gpio, etc.) that represents the function of the device.
--The top (root) node has no node name, only "/". / {...};
--The node tree structure (parent node --relationship between child nodes) is described so as to indicate the connection status of the device as seen from the CPU (for example, Bus bridge is the parent and SlaveDevice connected to Bus bridge is defined as its child node. To do)
--If you add a label (label :), you can specify the phandle of that node by writing & label
. If it is compiled with the "-@" option, it can be referenced from another file (overlay file). (See also the description of phandle property below)
3.3 Property --property is a combination of property-name = value --There may be no value
[label:] property-name = value;
[label:] property-name ;
--Text String: Enclose in double quotes compatible = " arm, cortex-a9 ";
--Array / Cell-list: A set of 32-bit unsigned integers separated by angle brackets <>
reg = <0xffd04000 0x1000>;
--Binary Data: Separated by square brackets [] mac-address = [12 34 56 ab cd ef];
--Multiple data: Can be concatenated using commas reg = <0xff900000 0x100000>, <0xffb80000 0x10000>;
mixed-property ="a string", [0x01 0x23 0x45 0x67], <0x12345678>;
compatible
--The most important Property used to connect with the device driver.
--This property is required for devices that require device drivers
--The device driver initialization routine with compatible-string that matches the character string specified by this value is called and device registration is performed.
--A list of Text Strings in the format "manufacturer, model".
--Example: compatible =" altr, socfpga-stmmac "," snps, dwmac-3.70a "," snps, dwmac ";
status --Whether or not to enable the device. --Value is "okay" or "disabled"
phandle --Set id number indicating node as Cell-list --Usually omitted (in human-written .dts) --For nodes where phandle is omitted, the dtc compiler will automatically generate a unique id, add phandle and generate .dtb. --You can specify the phandle of node with label “XXX” in the format'<& XXX>'. Often used in the description of interrupt property.
reg --Address information for the device. Specified by a pair of (address-cells, size-cells). --Specify the base address with address-cells and the memory size (byte size) with size-cells. --The number of address-cells and size-cells is determined by the values of # address-cells and # size-cells on the parent node.
#address-cells, #size-cells --Defined by node with child node. Defines how child node is addressed. This determines how to specify the address in the ranges of the hierarchy and the reg property of the child hierarchy.
ranges --Define with node representing Bus, and specify the address translation method between yourself and child node. -<Child address-cells value, parent address-cells value, child size-cells value> --If it does not have value, it means that the parent node and the child node are in the same address space.
#address-cells,Example of how to use renges
/{
....
sopc0: sopc@0 {
ranges;
/*
sopc@0 (Parent) and bridge@Relationship of 0xc0000000 (child)
sopc@0 and bridge@0xc0000000 has the same memory space (no address translation).
*/
#address-cells = <1>;
#size-cells = <1>;
....
hps_0_bridges: bridge@0xc0000000 {
reg = <0xc0000000 0x20000000>,
<0xff200000 0x00200000>;
/*
bridge@0xc0000000 is a memory space from 0xc0000000 to size 0x20000000,
Has two memory areas in the memory space from 0xff200000 to 0x00200000
*/
#address-cells = <2>;
#size-cells = <1>;
/*
bridge@The child node of 0xc0000000 is address-cells are two 32-bit numbers, size-The cell is represented by one 32-bit numerical value.
*/
ranges = < 0x00000000 0x00000000 0xc0000000 0x00010000 >,
< 0x00000001 0x00020000 0xff220000 0x00000008 >,
< 0x00000001 0x00010040 0xff210040 0x00000020 >;
/*
The memory area from 0xc0000000 to size 0x00010000 is the address of the child node.<0x00000000 0x00000000>Assigned to size 0x00010000 from
The memory area from 0xff220000 to size 0x00000008 is the address of the child node.<0x00000001 0x00020000>Assigned to size 0x00000008 from
The memory area from 0xff210040 to size 0x00000020 is the address of the child node.<0x00000001 0x00010040>Assign to size 0x00000020 from
(Other areas are not allocated to child nodes).
*/
....
led_pio: gpio@0x100010040 {
compatible = "altr,pio-16.0", "altr,pio-1.0";
reg = <0x00000001 0x00010040 0x00000020>;
/*
gpio@0x10001004 is<0x00000001 0x00010040>From size 0x00000020
Has a memory area, but this is the parent(Upper) size from 0xff210040 of node
Maps to 0x00000020 memory area(0xff210040 from CPU-Accessable in a range of 60).
*/
....
}; //end gpio@0x100010040 (led_pio)
....
}; //end bridge@0xc0000000 (hps_0_bridges)
};
};
--The first line is / dts-v1 /; --Comments can use C style (/ * ... * /) and C ++ style (// ...). --If a definition for the same node name appears in the same hierarchy, it will be regarded as the same node and will be an additional definition for that node. Property can be redefined (later definition will be used) --When compiling with Kernel Makefile, it is processed by gcc preprocessor first. -#include ..., #define ... can be used
In this regard, it's best to read the Kernel Source documentation. As an example, let's take a look at the description of node with the following interrupt.
intc: intc@fffed000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
....
};
soc {
interrupt-parent = <&intc>;
....
i2c0: i2c@ffc04000 {
....
interrupts = <0 158 0x4>;
....
};
};
In the above code, the interrupts property of i2c @ ffc04000 consists of 3 cells, but how to find out what to write in these 3 numbers. Search the files under the Kernel Source Documentation / devicetree / bindings directory for the interrupt-controller compatible string "arm, cortex-a9-gic" (eg find Documentation / devicetree / bindings -name" *. txt "| xargs grep" arm, cortex-a9-gic "
, git grep" arm, cortex-a9-gic "--Documentation / devicetree / bindings
if you brought the Kernel Source with git). This will hit Documentation / devicetree / bindings / interrupt-controller / arm, gic.txt. Looking at the contents of this file, # interrupt-cells is set to 3, and the contents of each are
--First interrupt type: 0 is Shared Peripheral Interrupt (SPI), 1 is Private Peripheral Interrupt (PPI) --Second interrupt number: [0-987] for SPI, [0-5] for PPI --The third flag has the following meaning: - 1 = low-to-high edge triggered - 2 = high-to-low edge triggered (invalid for SPIs) - 4 = active high level-sensitive - 8 = active low level-sensitive (invalid for SPIs).
It is stated to be. <0 158 4> in the above code is --Shared Peripheral Interrupt interrupt, --SPI 158th interrupt number, --active high level interrupt Means. By the way, if you check the SoC specifications described by this .dts, the Shared Peripheral Interrupt interrupt number starts from 32 of the GIC interrupt controller, so subtract 32 from the GIC interrupt number of the SoC specifications. The value will be written second (i2c @ ffc04000 is the 190th interrupt in the SoC specifications). Probably, in SoC using ARM GIC, the interrupt number obtained by subtracting 32 is described in .dts in common.
Also, if you want to know more about how to write .dts in general, you may want to read Documentation / devicetree / booting-without-of.txt under the Documentation folder of Linux Source.
As a required node
- root node
- /cpus node
- /cpus/* nodes
- /memory node(s)
- /chosen node
- /soc
That's it.
https://elinux.org/Device_Tree_Usage https://elinux.org/Device_Tree_presentations_papers_articles https://devicetree-specification.readthedocs.io/ https://git.kernel.org/?p=/linux/kernel/git/torvalds/linux.git;a=blob_plain;f=Documentation/devicetree https://www.nds-osk.co.jp/forum/freedownload/08/casestudy2/DeviceTree2.pdf
Recommended Posts