通常由.dts文件以文本方式对系统设备树进行描述,经过Device Tree Compiler(dtc)将dts文件转换成二进制文件binary device tree blob(dtb),.dtb文件可由Linux内核解析,有了device tree就可以在不改动Linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件,即可满足。
在系统启动的时候,bootloader将dtb传递给内核,内核解析改文件得到一个个的device_node,linux driver初始化的时候回去获取device_node定义好的资源。
设备树是一个由节点和属性组成的简单树状结构。属性是键值对,节点可包含属性和子节点。如下是一个简单的设备树的例子:
/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 {
};
};
};
很明显,上面的设备树并没有实际意义,因为它没有描述任何设备,但是它展示了节点和属性的结构,如下:
属性是简单的键值对,它可以为空,也可以是任意的字节流。
基本类型 | |
---|---|
字符串 | 文本字符串(以空字符结尾)用双引号表示: string-property = “a string”; |
字符串列表 | 逗号分隔 string-list = “red fish”, “blue fish”; |
cell | 用尖括号括起来 |
二进制数据 | 二进制数据用中括号‘[]’限定 用中括号括起来的字节序列(byte string)[xx xx xx ...],使用16进制表示1个/多个byte,需要注意的是,byte string中,一个byte必须用2位16进制数来表示,例如[00 11 22],其中00不能简写为0,但是byte之间的空格可以省略,也就是[00 11 22]和[001122]是一样的; |
自由组合 | 多种不同类型的数据之间用逗号分隔 mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>; |
我们注意到节点和子节点之间的命名有所不同,它们都遵循了下面的命名格式:
node-name@unit-address
node-name 是必选项,name 是一个 ASCII 字符串,用于描述节点对应 的设备类型,如:“uart1”,可以清晰的描述出设备的功能。
unit-address 则是可选项。如果一个节点描述的设备有地址,则应该给出@unit-address。
这个地址并不是驱动中操作的某一个寄存器地址,而是作为区分用的。多个相同类型设备节点的 name 可以一样,只要 unit-address 不同即可,如 cpu@0、cpu@1 以及 serial@101f0000 与 serial@101f2000 这样的同名节点。
当我们找一个节点的时候,必须书写完整的节点路径,如果节点的路径很长,在引用的时候就非常不方便,所以,设备数允许给设备取一个别名(lable)帮助我们更加方便地索引节点。
label: node-name@unit-address
如:“cpu0:cpu@0”,和上面的命名格式差不多,前面多了一个 label,称作节点标签。label 的作用就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过“&cpu0”就可以访问“cpu@0”这 个节点,而不用输入完整的节点名字。在例如节点“intc: interrupt-controller@00a01000”,通过&intc 来访 问比节点名字方便很多。
设备书规范预定义了一些标准的属性
每个节点都要有一个compatible属性。
compatible 属性的值是一个字符串列表,compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible 属性的值格式如下所示:
compatible = "manufacturer,model"
manufacturer 表示厂商,model 一般是模块对应的驱动名字。
例如:
sound {undefined
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
. . . . . .
}
上述实例中compatible属性有两个值,分别是“fsl,imx6ul-evk-wm8960”和“fsl,imx-audio-wm8960”,其中“fsl” 表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字,这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查。
那么怎么找?去哪里找?
一般驱动程序文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
比如在文件imx-wm8960.c中有如下内容
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
static struct platform_driver imx_wm8960_driver = {
.driver = {
.name = "imx-wm8960",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
imx_wm8960_dt_ids 就是 imx-wm8960.c 这个驱动文件的匹配表,此匹配表只有一个匹配值“fsl,imx-audio-wm8960”。如果在设备树中有哪个节点的 compatible 属性值与此相等,那么这个节点就会使用此驱动文件
status 属性是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,默认情况下不设置该属性是使能状态(okay)。
dtsi 文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个 status 属性,设置为“disabled”.
Value | Description |
"okay" | 设备正常运行 |
"disabled" | 设备不可操作,但是后面可能恢复工作 |
"fail" | 发生了严重错误,需修复 |
"fail-sss" | 发生了严重错误,需要修复;sss表示错误信息 |
可寻址的设备可以使用两种方式来声明设备地址,第一种是结点名@地址,第二种是在结点内部使用reg属性指定。
每一个可寻址的设备都有一个”reg”属性,其是以reg =
格式组成的一系列元组,每一个元组代表了设备使用的地址空间,包含起始地址和大小。还有另外一个问题,地址和大小用几个u32表示呢?这个就由父节点的"#address-cells","#size-cells"属性确定。
cpus {
#address-cells = <1>; //地址个数为1
#size-cells = <0>; //length个数为0,即只有地址,没有长度
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a8";
reg = <0>;
};
};
amba {
#address-cells = <1>; // 地址个数为1
#size-cells = <1>; // 长度个数为1
compatible = "arm,amba-bus";
ranges;
pdma0: dma@e0900000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0xe0900000 0x1000>;//起始地址0xe0900000,长度0x1000
interrupt-parent = <&vic0>;
interrupts = <19>;
clocks = <&clocks CLK_PDMA0>;
clock-names = "apb_pclk";
#dma-cells = <1>;
#dma-channels = <8>;
#dma-requests = <32>;
};
};
我们之前讨论了如何给设备分配地址,但是这个地址仅仅是相对于当前的设备节点而言的。我们还没有介绍如何将这个地址转换成CPU能够使用的地址。
根节点描述了从CPU侧看的地址分配情况,根节点的子节点已经使用了CPU的地址域,因此无需做任何显性的映射。例如我们为串口设备”serial@101f0000”直接分配CPU地址0x101f0000。
注意那些不是根节点的子节点没有使用CPU的地址域。为了得到映射地址,设备树必须指明如何实现一个地址域到另一个地址域的转换。ranges属性就是为这个目的设置的。
总结:
1、ranges属性值的格式 <local地址, parent地址, size>, 表示将local地址向parent地址的转换。
比如对于#address-cells和#size-cells都为1的话,以<0x0 0x10 0x20>为例,表示将local的从0x0~(0x0 + 0x20)的地址空间映射到parent的0x10~(0x10 + 0x20)
其中,local地址的个数取决于当前含有ranges属性的节点的#address-cells属性的值,size取决于当前含有ranges属性的节点的#size-cells属性的值。
而parent地址的个数取决于当前含有ranges属性的节点的parent节点的#address-cells的值。
2、对于含有ranges属性的节点的子节点来说,其reg都是基于local地址的
3、ranges属性值为空的话,表示1:1映射,说明子地址空间和父地址空间完全相同,不需要进行地址转换。
4、对于没有ranges属性的节点,代表不是memory map区域
'dma-ranges'属性的结构和定义与'ranges'属性完全相同,唯一不同的是地址是dma使用的地址,'ranges'中的地址是cpu使用的地址。
五、设备树调试
当烧录到开发板后,可以通过
系统启动后进入到/sys/firmware/devicetree/base目录可以看到当前已注册设备的设备树信息,通过相关命令可以查看当前设备的结点信息、状态等。
也可以通过/proc/device-tree/目录查看已经注册的设备树信息,这两个目录的内容是一样的
ref:
Device Tree(一):背景介绍
Device Tree(二):基本概念
设备树中ranges属性分析(1) - 摩斯电码 - 博客园
5. 设备树的规范 - DTS格式 - 简书
linux设备树笔记--dts基本概念及语法
一文带你搞懂设备树 - 云+社区 - 腾讯云
Linux device tree(设备树)_往事随风的博客-CSDN博客
2.1设备树的规范(dts和dtb)——DTS格式_一个嵌入式软件工程师的博客-CSDN博客_设备树规范
【linux】设备树dts文件中节点的属性compatible #address-cells 和#size-cells reg属性_那可真是太开心了的博客-CSDN博客_dts reg属性