设备树是节点和属性的简单树型结构。属性是键值对,节点可以包含属性和子节点。如下是一个简单的.dts格式的树:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | /dts-v1/; / { node1 { a-string-property = "A string"; a-string-list-property = "first string", "second string"; // hex is implied in byte arrays. no '0x' prefix is required a-byte-data-property = [01 23 34 56]; child-node1 { first-child-property; second-child-property = <1>; a-string-property = "Hello, world"; }; child-node2 { }; }; node2 { an-empty-property; a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */ child-node1 { }; }; }; |
一个简单的根节点"/"
一对子节点"node1"和"node2"
子节点还有子节点,以及一些属性。
属性是简单的键值对,值可以为空,也可以包含任意字节流。数据类型没有编码进树结构。在设备树源文件中有一些基本的数据表示:
字符串用双引号表示 string-property = " a string"
32位无符号整数用尖括号分开 cell-property = <0xbeef 123 0xabcd1234>
二进制数据用方括号 binary-property = [0x01 0x23 0x45 0x67]
不同类型的数据可以结合在一起,用逗号分开 mixed-property = "a string, [0x1 0x23], <0x12345678>"
逗号也可以创建字符串列表 string-list = "red fish", "blue fish"
Linux 设备树的基本约定。
每个节点应该是<name>[@<unit-address>]的格式。
<name>最多31个字符,表示设备的类型。unit-address是设备的地址。
表示设备的每一个节点,要求有一个compatible属性。
<manufacturer>,<model>
接下来的字符串表示兼容的设备。如
compatible = "fsl,mpc8349-uart", "ns16550"
表示兼容"ns16550"
下列3个属性用来编码地址信息:
reg
#address-cells
#size-cells
reg = <addr1 len1 addr2 len2 ... >
addr1有#address-cells个,同理addr2也有同样多个。
len1有#size-cells个,同理len2也有同样多个。#size-cells可以为0.
例子1:
1 2 3 4 5 6 7 8 9 10 | / { #address-cells = <1>; #size-cells = <1>; ... serial@101f0000 { compatible = "arm,pl011"; reg = <0x101f0000 0x1000 >; }; |
例子2:
1 2 3 4 5 6 7 8 | external-bus { #address-cells = <2>; #size-cells = <1>; ethernet@0,0 { compatible = "smc,smc91c111"; reg = <0 0 0x1000>; }; |
有些总线有不同的地址框架,因此灵活使用地址模型。注意地址并不仅仅是地址,也可能是id或者其它标识设备的信息。
如果设备节点有reg属性,则设备名中的unit-address必须为reg的第一个地址域。
如上述:serial@101f0000 ethernet@0,0
Ranges (Address Translation)
如果设备直接使用CPU地址,则不需要ranges,如果设备没有使用CPU的地址域,为了获得地址映射关系,使用ranges属性表述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | / { 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>; }; |
如上,0 0是子设备的地址,0x1010000是CPU的起始地址,0x10000是地址的长度。
有4个属性用来描述中断连接:
interrupt-controller
节点中的一个空的该属性,表明这个节点的设备是一个中断控制器。
#interrupt-cells
表示interrupts属性中标识中断id的个数。
interrupt-parent
这个属性标识设备的终端控制器是哪一个。子节点没有这个属性,可继承。
interrupts
设备节点的该属性,标识该设备产生的中断号。
需要一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | /dts-v1/; / { compatible = "acme,coyotes-revenge"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a9"; reg = <0>; }; cpu@1 { compatible = "arm,cortex-a9"; reg = <1>; }; }; 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>; }; }; }; |
这个machine有一个中断控制器。
标签intc,用来表示设备用的哪个中断控制器,在interrupt-parent属性中。
每个设备有一个interrupt属性,表示不同的中断输入线。
#interrupt-cells是2,第一个表示中断id,第二个表示flags,如active high等。
aliases node
property = &label,例子:
1 2 3 4 | aliases { ethernet0 = ð0; serial0 = &serial0; }; |
chosen node
chosen node不是真的设备,用来在固件和操作系统中传递参数,例如boot arguments。通常chosen node在dts文件中为空,在启动时增加:
1 2 3 | chosen { bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; }; |
2022/126 补充
node可以有label。而且可以有一个或多个label,格式如下:
label : node-name {
}
使用 @lablel的方式,引用节点。可以作为phandle引用,也可以直接作为节点名:
节点名称是全局的,不能重复。可以在根节点之外,写节点属性,device tree compiler会合并成一个。
在编译出的dtb中,不存在label。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | / { gsw: gsw@0 { compatible = "mediatek,mt753x"; mediatek,ethsys = <ðsys>; #address-cells = <1>; #size-cells = <0>; }; } @gsw { mediatek,mdio = <&mdio>; mediatek,portmap = "llllw"; mediatek,mdio_master_pinmux = <0>; reset-gpios = <&pio 39 0>; interrupt-parent = <&pio>; interrupts = <38 IRQ_TYPE_LEVEL_HIGH>; status = "okay"; port5: port@5 { compatible = "mediatek,mt753x-port"; reg = <5>; phy-mode = "sgmii"; fixed-link { speed = <2500>; full-duplex; }; }; port6: port@6 { compatible = "mediatek,mt753x-port"; mediatek,ssc-on; reg = <6>; phy-mode = "sgmii"; fixed-link { speed = <2500>; full-duplex; }; }; }; &hnat { mtketh-wan = "eth1"; mtketh-lan = "eth0"; mtketh-max-gmac = <2>; status = "okay"; }; |
如上,gsw@0有label gsw。且分成了两个地方。编译后,合并成一个。
mediatek,mdio = <&mdio>;
mdio是一个label。
dts文件中可以包含头文件,然后使用宏。例如:
1 2 | #include <dt-bindings/gpio/gpio.h> interrupts = <GIC_SPI 205 IRQ_TYPE_LEVEL_HIGH>, |
IRQ_TYPE_LEVEL_HIGH就是一个宏。
参考
https://elinux.org/Device_Tree_Usage
https://elinux.org/Device_Tree_Reference
https://www.nxp.com/docs/en/application-note/AN5125.pdf