参考:
基本数据格式
设备树是一个简单的包含节点和属性的树结构。属性通过键值对形式描述,一个节点可以包含多个属性或子节点,一个简单的 .dts 格式设备树如下所示。
/dts-v1/;
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
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
- node1 有两个子节点:child-node1 和 child-node2
- 分散在整个树中的一些属性
属性是一个简单的键值对,它的值可以是空的或者是任意大小的字节流。而这样的数据结构中并不需要包含数据类型,下面是可以在dts文件中表达的一些基本数据:
文本字符串(包含’\0’结束符)用双引号表示:
string-property = "a string";
Cells(32位无符号整数)用尖括号表示:
cell-property = <0xbeef 123 0xabcd1234>;
二进制数据用方括号表示:
binary-property = [0x01 0x23 0x45 0x67];
类型不同的数据的组合也是可以的,但需要用逗号隔开:
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
逗号同样用于创建字符串列表:
string-list = "red fish", "blue fish";
基本概念
为了理解 device tree 到底是怎么用的,我们将以一个简单的机器为例,一步步地建立起描述它的 device tree。
示范机器
假设有这样一台计算机(基于ARM Versatile),由“Acme”制造并命名为"Coyote’s Revenge":
- 一个 32 位的 ARM 处理器;
- 串口、SPI 总线控制器、I2C 总线控制器、中断控制器以及一些外部总线桥通过内存映射连接到到处理器的总线上;
- 256MB 的 SDRAM,基地址为 0;
- 两个串口,基地址分别为 0x101F1000 和 0x101F2000;
- GPIO 控制器,基地址为 0x101F3000;
- SPI 控制器,基地址为 0x10170000,并且连接了 MMC 插槽(SS管脚连接到GPIO #1);
- 外部总线桥,连接了以下设备:
① 连接到外部总线的 SMC SMC91111 以太网设备,基地址为 0x10100000;
② I2C 控制器,基地址为 0x10160000,并连接了 Maxim DS1338 实时时钟(设备从地址为1101000,即0x58);
③ 64MB 的 NOR flash,基地址为 0x30000000;
初始结构
首先搭建该机器 device tree 的基本框架,即一个有效设备树的最小架构。在这一步,要唯一地标识这台计算机。
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
};
compatible 属性指定了系统的名字,它的值是一个"<manufacturer>,<model>"
格式的字符串。为一个特定设备指定 compatible 值是很必要的,因为包含制造商名字可以避免命名空间冲突。操作系统会根据 compatible 值来决定如何运行这台计算机,所以在该属性中填入正确的数据很重要。
理论上,compatible 是操作系统唯一标识一台机器的所有数据,如果所有机器细节都硬编码,那么 OS 可以在顶层 compatible 属性中专门查找"acme,coyotes-revenge"。
CPUs
接下来就要描述各个 CPU 了。先添加一个“cpus”容器节点,再将每个 CPU 作为子节点添加进去。在这个例子中,是基于 ARM 的双核 Cortex A9 处理器。
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
};
类似于顶层 compatible 属性,每个 cpu 节点中的 compatible 属性也是一个 "<manufacturer>,<model>"
格式的字符串,指定了 CPU 的确切型号。
后面会有更多的属性被添加到 cpu 节点,因此我们首先需要先了解一些基本概念。
节点命名
花些时间谈谈命名习惯是值得的。每个节点都必须有一个<name>[@<unit-address>]
格式的名称。
<name>
是一个最大长度为 31 个字符的 ASCII 字符串,一般来说,节点的命名是根据它所表示的设备而定的。比如说,一个表示 3com 以太网适配器的节点应该命名为 ethernet,而不是 3com509。
如果该节点所表示的设备有相关设备地址的话,则需要包含 <unit-address>
信息。<unit-address>
通常是用来访问设备的首地址,并在节点的 reg 属性中会被列出。后面我们还会详细介绍 reg 属性。
同级节点必须唯一地命名,但只要地址不相同,多个节点采用相同的命名是没问题的(例如 serial@101f1000 和 serial@101f2000)。
关于节点命名的全部细节请参考 ePAPR 规范 2.2.1 节。
设备
系统中的每个设备都用设备树上的节点来表示,所以接下来就是要在设备树中为各个设备添加设备节点。现在我们添加的新节点先置为空,后面我们谈到寻址和中断的时候再进行处理。
/dts-v1/;
/ {
compatible = "acme,coyotes-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
};
cpu@1 {
compatible = "arm,cortex-a9";
};
};
serial@101F0000 {
compatible = "arm,pl011";
};
serial@101F2000 {
compatible = "arm,pl011";
};
gpio@101F3000 {
compatible = "arm,pl061";
};
interrupt-controller@10140000 {
compatible = "arm,pl190";
};
spi@10115000 {
compatible = "arm,pl022";
};
external-bus {
ethernet@0,0 {
compatible = "smc,smc91c111";
};
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
rtc@58 {
compatible = "maxim,ds1338";
};
};
flash@2,0 {
compatible = "samsung,k8f1315ebm", "cfi-flash";
};
};
};
在上面这个树中,已经为系统中的每个设备添加了节点,并且设备树的层次结构反映了设备是如何连接到系统的。例如,挂接在外部总线上的设备是外部总线节点的子节点,I2C 设备是 I2C 总线控制器节点的子节点。总的来说,设备树的层次结构是从 CPU 的角度来看的系统视图。
现在这个设备树仍然是无效的,因为它缺少了描述设备之间到底如何连接的信息。后面我们会给它添加这些数据。
关于这个设备树有以下一些注意事项:
- 每个设备节点都有一个 compatible 属性;
- flash 节点的 compatible 属性有两个字符串;(下一节会说明为什么)
- 之前提到的,节点命名应该反映设备的类型,而不是特定型号;(请参考ePAPR规范2.2.2节的通用节点命名,应优先使用这些命名)
理解 compatible 属性
设备树中的每个节点都需要有 compatible 属性,操作系统会通过这个 compatible 的值来决定用该设备应该绑定哪个设备驱动程序。
compatible 的值是一个字符串列表,列表中的第一个字符串用“<manufacturer>,<model>
" 格式准确描述相应设备,后面的字符串表示兼容的其他设备。
例如,Freescale MPC8349 SoC有一个串口设备实现了National Semiconductor ns16550寄存器接口。因此MPC8349串口设备的compatible属性为:compatible = “fsl,mpc8349-uart”, “ns16550”。在这里,fsl,mpc8349-uart指定了确切的设备,ns16550表明它与National Semiconductor 16550 UART是寄存器级兼容的。
注:由于历史原因,ns16550没有制造商前缀,所有新的compatible值都应使用制造商的前缀。这种做法使得现有的设备驱动程序可以绑定到一个新设备上,同时仍能唯一准确的识别硬件。
警告:不要使用通配符compatible值,如"fsl,mpc83xx-uart"等类似表达,芯片厂商总会改变并打破你的通配符假设,到时候再想修改就为时已晚了。相反,你应当选择一个特定的芯片实现,并与所有后续芯片保持兼容。
How Addressing Works
可编址的设备使用下面属性来将地址信息编码进设备树:
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. To see how this all works, lets add the addressing properties to the sample device tree, starting with the CPUs.
CPU addressing
Memory Mapped Devices
Non Memory Mapped Devices
Ranges (Address Translation)
4 How Interrupts Work
5 Device Specific Data
6 Special Nodes
6.1 aliases Node
6.2 chosen Node
7 Advanced Topics
7.1 Advanced Sample Machine
7.2 PCI Host Bridge
7.2.1 PCI Bus numbering
7.2.2 PCI Address Translation
7.3 Advanced Interrupt Mapping
8 Notes
(未完待续)