设备树
新版本的 Linux 中, ARM 相关的驱动全部采用了设备树(也有支持老式驱动的,比较少)
什么是设备树?
设备树(Device Tree),描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。
DTS 文件的主要功能就是按照下图所示的结构来描述板子上的设备信息(如 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。), DTS 文件描述设备信息是有相应的语法规则要求的。
设备树由来
1、传统板级信息(各种板子、芯片的外设文件)都硬塞在linux内核中。
2、导致linux内核太过臃肿,linus大怒,f**k!!
3、linux社区赶紧引入设备树概念,将各种板级信息从linux内核中分离出来:
.dtsi文件:各种板子共有的设备信息(如CPU、主频、各个外设控制器信息等)。
.dts文件:分别装有各个板子独有的设备信息(如 IIC 设备、 SPI 设备等)。
全局把设备树就想象成真的树就行了,便于记忆。
DTS、 DTB 和 DTC
DTS (Device Tree Source) 是设备树源码文件;
DTB (Device Tree Binary)是DTS 编译以后得到的二进制文件;
DTC (Device Tree Commpile)是DTS语法的编译器,由c语言编写的。
arch/arm/boot/dts/Makefile中:
400 dtb-$(CONFIG_SOC_IMX6ULL) += \
401 imx6ull-14x14-ddr3-arm2.dtb \
402 imx6ull-14x14-ddr3-arm2-adc.dtb \
403 imx6ull-14x14-ddr3-arm2-cs42888.dtb \
404 imx6ull-14x14-ddr3-arm2-ecspi.dtb \
405 imx6ull-14x14-ddr3-arm2-emmc.dtb \
......
添加自己的板子设备树:
1、新建自己的.dts文件;
2、添加.dtb到上述Makefile中。
3、在linux源码根目录
make all //编译所有东西
或
make dtbs //只是编译设备树
DTS 语法
DTS 语法非常的人性化,是一种 ASCII文本文件,不管是阅读还是修改都很方便。详细语法参考《 Devicetree SpecificationV0.2.pdf 》 和《Power_ePAPR_APPROVED_v1.12.pdf》这两份文档。
.dtsi 头文件
文件中可以用“#include”来引用.h、 .dtsi 和.dts 文件。
12 #include <dt-bindings/input/input.h>
13 #include "imx6ull.dtsi"
9 #include "imx6ull-14x14-evk.dts"
设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
个人理解:设备节点就是树枝。
设备树模板:
1 / {
2 aliases {
3 can0 = &flexcan1;
4 };
5
6 cpus {
7 #address-cells = <1>;
8 #size-cells = <0>;
9
10 cpu0: cpu@0 {
11 compatible = "arm,cortex-a7";
12 device_type = "cpu";
13 reg = <0>;
14 };
15 };
16
17 intc: interrupt-controller@00a01000 {
18 compatible = "arm,cortex-a7-gic";
19 #interrupt-cells = <3>;
20 interrupt-controller;
21 reg = <0x00a01000 0x1000>,
22 <0x00a02000 0x100>;
23 };
24 }
1、“/”是根节点,每个设备树文件只有一个根节点;aliases、 cpus 和 intc 是三个子节点,cpu0 是 cpus 的子节点(感觉像套娃)。
2、为了方便访问节点,标签+节点格式:
label: node-name@unit-address
node-name 是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能;
label 是节点标签;
“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。
①、字符串或字符串列表
compatible = "arm,cortex-a7";
或
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
②、 32 位无符号整数
reg = <0>;
标准属性
节点是由一堆的属性组成:标准属性、自定义属性。
常用的标准属性:
1、 compatible 属性
属性值是字符串,用于将设备和驱动绑定起来,字符串列表用于选择设备所要使用的驱动程序。
compatible = "manufacturer,model_1","manufacturer,model_2";
manufacturer 表示厂商,
model 一般是模块对应的驱动名字。
使用这个设备节点时,看看linux内核中有哪个驱动文件是model_1,没有的话再找model_2。
2、 model 属性
属性值是字符串,描述设备模块信息,比如名字什么的。
model = "wm8960-audio";
3、 status 属性
属性值是字符串,描述设备的状态信息。
4、 #address-cells 和#size-cells 属性
属性值是无符号 32 位整型,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息,主要用于决定reg的值。
reg = <address1 length1 address2 length2 address3 length3……>
#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位);
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位);
#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值。
12 aips3: aips-bus@02200000 {
13 compatible = "fsl,aips-bus", "simple-bus";
14 #address-cells = <1>;
15 #size-cells = <1>;
16
17 dcp: dcp@02280000 {
18 compatible = "fsl,imx6sl-dcp";
19 reg = <0x02280000 0x4000>;
20 };
21 };
第 14, 15 行,设置 aips3: aips-bus@02200000 节点#address-cells = <1>, #size-cells = <1>,说明 aips3: aips-bus@02200000 节点起始地址长度所占用的字长为 1,地址长度所占用的字长也为 1。
第 19 行,子节点 dcp: dcp@02280000 的 reg 属性值为<0x02280000 0x4000>,因为父节点设置了#address-cells = <1>, #size-cells = <1>, address= 0x02280000, length= 0x4000,相当于设置了起始地址为 0x02280000,地址长度为 0x40000。
为了更好地理解,自己举个例子(32位):
reg = <address1 length1 address2 length2 ……address n length n>
#address-cells = <1>; address1 32位 有1个字;
#size-cells=<1>; length1 32位 有1个字;
5、 reg 属性
属性值一般是(address, length)对,用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
323 uart1: serial@02020000 {
324 compatible = "fsl,imx6ul-uart",
325 "fsl,imx6q-uart", "fsl,imx21-uart";
326 reg = <0x02020000 0x4000>;
327 interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
328 clocks = <&clks IMX6UL_CLK_UART1_IPG>,
329 <&clks IMX6UL_CLK_UART1_SERIAL>;
330 clock-names = "ipg", "per";
331 status = "disabled";
332 };
uart1 节点描述了 I.MX6ULL 的 UART1 相关信息;
reg 属性中 address=0x02020000, length=0x4000, I.MX6ULL 的 UART1 寄存器首地址为 0x02020000。
6、 ranges 属性
ranges属性值可以为空或者(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,一个地址映射/转换表。
child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
1 soc {
2 compatible = "simple-bus";
3 #address-cells = <1>;
4 #size-cells = <1>;
5 ranges = <0x0 0xe0000000 0x00100000>;
6
7 serial {
8 device_type = "serial";
9 compatible = "ns16550";
10 reg = <0x4600 0x100>;
11 clock-frequency = <0>;
12 interrupts = <0xA 0x8>;
13 interrupt-parent = <&ipic>;
14 };
15 };
第 5 行,节点 soc 定义的 ranges 属性,值为<0x0 0xe0000000 0x00100000>,此属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
第 10 行, serial 是串口设备节点, reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作0xe0004600=0x4600+0xe0000000。
7、 name 属性
属性值为字符串,用于记录节点名字,现已经被弃用。
8、 device_type 属性
属性值为字符串,此属性只能用于 cpu 节点或者 memory 节点, IEEE 1275 会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。
根节点 compatible 属性
Linux 内核在使用设备树前后是如何判断是否支持某款设备的呢?
1、使用设备树之前设备匹配方法(旧版)
启动Linux内核时,uboot 会向 Linux 内核传递一个叫做 machine id 的值, machine id也就是设备 ID,告诉 Linux 内核自己是个什么设备,看看 Linux 内核是否支持。
613 MACHINE_START(MX35_3DS, "Freescale MX35PDK")
614 /* Maintainer: Freescale Semiconductor, Inc */
615 .atag_offset = 0x100,
616 .map_io = mx35_map_io,
617 .init_early = imx35_init_early,
618 .init_irq = mx35_init_irq,
619 .init_time = mx35pdk_timer_init,
620 .init_machine = mx35_3ds_init,
621 .reserve = mx35_3ds_reserve,
622 .restart = mxc_restart,
623 MACHINE_END
MACHINE_START 和 MACHINE_END 就是宏定义,拆开开看就是定义了一个machine_desc结构体类型地变量__mach_desc_MX35_3DS:
1 static const struct machine_desc __mach_desc_MX35_3DS \
2 __used \
3 __attribute__((__section__(".arch.info.init"))) = {
4 .nr = MACH_TYPE_MX35_3DS,
5 .name = "Freescale MX35PDK",
6 /* Maintainer: Freescale Semiconductor, Inc */
7 .atag_offset = 0x100,
8 .map_io = mx35_map_io,
9 .init_early = imx35_init_early,
10 .init_irq = mx35_init_irq,
11 .init_time = mx35pdk_timer_init,
12 .init_machine = mx35_3ds_init,
13 .reserve = mx35_3ds_reserve,
14 .restart = mxc_restart,
15 };
变 量 存 储 在 “.arch.info.init ” 段 中。
重点关注
.nr = MACH_TYPE_MX35_3DS,
.name = “Freescale MX35PDK”,
Linux 内核检查这个检查传过来的 machine id是否存在自己的内核中。
2、使用设备树以后的设备匹配方法(新版)
新版本使用DT_MACHINE_START宏定义,这个了解一下即可。
208 static const char *imx6ul_dt_compat[] __initconst = {
209 "fsl,imx6ul",
210 "fsl,imx6ull",
211 NULL,
212 };
213
214 DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
215 .map_io = imx6ul_map_io,
216 .init_irq = imx6ul_init_irq,
217 .init_machine = imx6ul_init_machine,
218 .init_late = imx6ul_init_late,
219 .dt_compat = imx6ul_dt_compat,
220 MACHINE_END //宏定义为}
1、这是linux内核中,需要关注:
.dt_compat = imx6ul_dt_compat
imx6ul_dt_compat 表:"fsl,imx6ul"和"fsl,imx6ull"两个兼容值。
2、再看设备树的设备节点中:
compatible = “fsl,imx6ull-14x14-evk”, “fsl,imx6ull”。
3、比较linux内核和设备节点
某个设备 (板子)根节点“/”的 compatible 属性值与imx6ul_dt_compat 表中的只要有相匹配的值,那么就表示 Linux 内核支持此设备,可以正常启动 Linux 内核,否则不能正常启动linux内核并卡住。
Linux 内核通过根节点 compatible 属性找到对应的设备的函数调用过程,匹配过程:
向节点追加或修改内容
脑子中有设备树的画面,设备树需要向I2C1上添加 fxls8471设备子节点,I2C1设备节点位于.dtsi文件中。
问题:如果直接向.dtsi文件中的I2C1设备节点下创建一个子节点,这样使用相同芯片的其他板子设备树中也会使用.dtsi文件,这样就把 fxls8471设备子节点也添加到其他板子上了。
解决方法:在.dts文件中使用&label 来追加或修改.dtsi文件中的I2C1设备节点。
imx6ull-alientek-emmc.dts文件
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};
clock-frequency是新添加的属性,表示 i2c1 时钟为 100KHz。
如此不会对使用 I.MX6ULL 这颗 SOC 的其他板子造成任何影响。
创建小型模板设备树
一步一步创建一个小型设备树,加深学习。
在实际产品开发中,我们是不需要完完全全的重写一个.dts 设备树文件,一般都是使用 SOC 厂商提供好的.dts 文件,我们只需要在上面根据自己的实际情况做相应的修改即可。
以 I.MX6ULL 这个 SOC 为例,需要在设备树里面描述的内容如下:
①、 I.MX6ULL 这个 Cortex-A7 架构的 32 位 CPU。
②、 I.MX6ULL 内部 ocram,起始地址 0x00900000,大小为 128KB(0x20000)。
③、 I.MX6ULL 内部 aips1 域下的 ecspi1 外设控制器,寄存器起始地址为 0x02008000,大小为 0x4000。
④、 I.MX6ULL 内部 aips2 域下的 usbotg1 外设控制器,寄存器起始地址为 0x02184000,大小为 0x4000。
⑤、 I.MX6ULL 内部 aips3 域下的 rngb 外设控制器,寄存器起始地址为 0x02284000,大小为 0x4000。
1 / {
2 compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";
3
4 cpus {
5 #address-cells = <1>;
6 #size-cells = <0>;
7
8 //CPU0 节点
9 cpu0: cpu@0 {
10 compatible = "arm,cortex-a7";
11 device_type = "cpu";
12 reg = <0>;
13 };
14 };
15
16 //soc 节点
17 soc {
18 #address-cells = <1>;
19 #size-cells = <1>;
20 compatible = "simple-bus";
21 ranges;
22
23 //ocram 节点
24 ocram: sram@00900000 {
25 compatible = "fsl,lpm-sram";
26 reg = <0x00900000 0x20000>;
27 };
28
29 //aips1 节点
30 aips1: aips-bus@02000000 {
31 compatible = "fsl,aips-bus", "simple-bus";
32 #address-cells = <1>;
33 #size-cells = <1>;
34 reg = <0x02000000 0x100000>;
35 ranges;
36
37 //ecspi1 节点
38 ecspi1: ecspi@02008000 {
39 #address-cells = <1>;
40 #size-cells = <0>;
41 compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
42 reg = <0x02008000 0x4000>;
43 status = "disabled";
44 };
45 }
46
47 //aips2 节点
48 aips2: aips-bus@02100000 {
49 compatible = "fsl,aips-bus", "simple-bus";
50 #address-cells = <1>;
51 #size-cells = <1>;
52 reg = <0x02100000 0x100000>;
53 ranges;
54
55 //usbotg1 节点
56 usbotg1: usb@02184000 {
57 compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
58 reg = <0x02184000 0x200>;
59 status = "disabled";
60 };
61 }
62
63 //aips3 节点
64 aips3: aips-bus@02200000 {
65 compatible = "fsl,aips-bus", "simple-bus";
66 #address-cells = <1>;
67 #size-cells = <1>;
68 reg = <0x02200000 0x100000>;
69 ranges;
70
71 //rngb 节点
72 rngb: rngb@02284000 {
73 compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imxrng";
74 reg = <0x02284000 0x4000>;
75 };
76 }
77 }
78 }
1、 CPU 节点, I.MX6ULL 采用 Cortex-A7 架构,而且只有一个 CPU,因此只有一个cpu0 节点
2、创建一个叫做 soc 的父节点来管理这些 SOC 内部外设的子节点,像 ocram uart, iic 控制器。ranges 属性为空,说明子空间和父空间地址范围相同。
3、I.MX6ULL 内部分为三个域: aips1~3,这三个域分管不同的外设控制器:
ecspi1 属于 aips1 的子节点,
usbotg1 属于 aips2 的子节点,
rngb 属于 aips3 的子节点。
设备树在系统中的体现
Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的 /proc/devicetree 目录下 根据节点名字创建不同文件夹。
再一次诠释了一切皆文件。
/proc/devicetree/base 目录下是根节点“/”的所有属性和子节点。
/proc/devicetree/base/soc目录中就可以看到 soc 节点的所有属性和子节点。
如此,还能查看子子节点,如此套娃!!!
特殊节点
根节点“/”中有两个特殊的子节点: aliases 和 chosen。
aliases 子节点
18 aliases {
19 can0 = &flexcan1;
20 can1 = &flexcan2;
21 ethernet0 = &fec1;
22 ethernet1 = &fec2;
23 gpio0 = &gpio1;
24 gpio1 = &gpio2;
......
42 spi0 = &ecspi1;
43 spi1 = &ecspi2;
44 spi2 = &ecspi3;
45 spi3 = &ecspi4;
46 usbphy0 = &usbphy1;
47 usbphy1 = &usbphy2;
48 };
aliases(别名) 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。
但是但是但是,设备树里面大量的使用&label 的形式来访问节点,不太需要,哈哈哈!!!。
chosen 子节点
18 chosen {
19 stdout-path = &uart1;
20 };
chosen 并不是一个真实的设备, chosen 节点主要是为了接收uboot 向 Linux 内核传递的数据,重点是 bootargs 参数,一般.dts 文件中 chosen 节点通常为空或者内容很少。
调用过程:
启动linux内核时候,调用了 fdt_chosen 函数,该函数在 chosen 节点中加入了 bootargs 属性,并存放bootargs环境变量的值。
Linux 内核解析 DTB 文件
绑定信息文档
Linux 内核源码中有详细的.txt 文档描述了如何添加节点,这些.txt 文档叫做绑定文档,路径为:Linux 源码目录 /Documentation/devicetree/bindings。
官方提供的一些关于添加设备节点的东西。
设备树常用 OF 操作函数
查找节点的 OF 函数
OF 函数就是of_xxx()函数,获取到设备相关信息(数字类型的、字符串类型的、数组类型),定义在 include/linux/of.h 。
device_node 结构体来描述一个节点,此结构体定义在文件 include/linux/of.h 。
49 struct device_node {
50 const char *name; /* 节点名字 */
51 const char *type; /* 设备类型 */
52 phandle phandle;
53 const char *full_name; /* 节点全名 */
54 struct fwnode_handle fwnode;
55
56 struct property *properties; /* 属性 */
57 struct property *deadprops; /* removed 属性 */
58 struct device_node *parent; /* 父节点 */
59 struct device_node *child; /* 子节点 */
60 struct device_node *sibling;
61 struct kobject kobj;
62 unsigned long _flags;
63 void *data;
64 #if defined(CONFIG_SPARC)
65 const char *path_component_name;
66 unsigned int unique_id;
67 struct of_irq_controller *irq_trans;
68 #endif
69 };
1、 of_find_node_by_name 函数
通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值: 找到的节点,如果为 NULL 表示查找失败。
2、 of_find_node_by_type 函数
通过 device_type 属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from, const char *type)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串,也就是 device_type 属性值。
返回值: 找到的节点,如果为 NULL 表示查找失败。
3、 of_find_compatible_node 函数
根据 device_type 和 compatible 这两个属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示忽略掉 device_type 属性。
compatible: 要查找的节点所对应的 compatible 属性列表。
返回值: 找到的节点,如果为 NULL 表示查找失败
4、 of_find_matching_node_and_match 函数
通过 of_device_id 匹配表来查找指定的节点
struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
matches: of_device_id 匹配表,也就是在此匹配表里面查找节点。
match: 找到的匹配的 of_device_id。
返回值: 找到的节点,如果为 NULL 表示查找失败
5、 of_find_node_by_path 函数
通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
//内联函数
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值: 找到的节点,如果为 NULL 表示查找失败
查找父/子节点的 OF 函数
1、 of_get_parent 函数
用于获取指定节点的父节点(如果有父节点的话)
struct device_node *of_get_parent(const struct device_node *node)
node:要查找的父节点的节点。
返回值: 找到的父节点。
2、 of_get_next_child 函数
用迭代的查找子节点
struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
返回值: 找到的下一个子节点。
提取属性值的 OF 函数
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要, Linux 内核中使用结构体 property 表示属性,此结构体同样定义在文件 include/linux/of.h 中:
35 struct property {
36 char *name; /* 属性名字 */
37 int length; /* 属性长度 */
38 void *value; /* 属性值 */
39 struct property *next; /* 下一个属性 */
40 unsigned long _flags;
41 unsigned int unique_id;
42 struct bin_attribute attr;
43 };
1、 of_find_property 函数
用于查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值: 找到的属性。
2、 of_property_count_elems_of_size 函数
用于获取属性中元素的数量,,,比如reg 属性值数组的大小
int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size)
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素长度。
返回值: 得到的属性元素数量。
3、 of_property_read_u32_index 函数
用于从属性中获取指定标号的 u32 类型数据值(无符号 32位),某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值。
int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)
np:设备节点。
proname: 要读取的属性名字。
index:要读取的值标号。
out_value:读取到的值
返回值: 0 读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
这个调用完函数,把值就传给了out_value。
4、 of_property_read_u8_array 函数、of_property_read_u16_array 函数、of_property_read_u32_array 函数、of_property_read_u64_array 函数
读取属性中 u8、 u16、 u32 和 u64 类型的数组数据
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值,分别为 u8、 u16、 u32 和 u64。
sz: 要读取的数组元素数量。
返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
5、 of_property_read_u8 函数、of_property_read_u16 函数、of_property_read_u32 函数、of_property_read_u64 函数
用于读取这种只有一个整形值的属性,分别用于读取 u8、 u16、 u32 和 u64 类型属性值。
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value)
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value)
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value)
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值。
返回值: 0,读取成功,负值,读取失败, -EINVAL 表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW 表示属性值列表太小。
6、 of_property_read_string 函数
用于读取属性中字符串值
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值: 0,读取成功,负值,读取失败。
7、 of_n_addr_cells 函数
用于获取#address-cells 属性值
int of_n_addr_cells(struct device_node *np)
np:设备节点。
返回值: 获取到的#address-cells 属性值。
8、 of_n_size_cells 函数
用于获取#size-cells 属性值
int of_n_size_cells(struct device_node *np)
np:设备节点。
返回值: 获取到的#size-cells 属性值。
其他常用的 OF 函数
1、 of_device_is_compatible 函数
用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性。
int of_device_is_compatible(const struct device_node *device, const char *compat)
device:设备节点。
compat:要查看的字符串。
返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串; 正数,节点的 compatible属性中包含 compat 指定的字符串。
2、 of_get_address 函数
用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
dev:设备节点。
index:要读取的地址标号。
size:地址长度。
flags:参数,比如 IORESOURCE_IO、 IORESOURCE_MEM 等
返回值: 读取到的地址数据首地址,为 NULL 的话表示读取失败。
3、 of_translate_address 函数
负责将从设备树读取到的**(虚拟)地址转换为物理地址**
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
dev:设备节点。
in_addr:要转换的地址。
返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
4、 of_address_to_resource 函数
用 resource结构体描述的都是设备资源信息,比如IIC、 SPI、 GPIO 等这些外设对应的寄存器的内存空间, resource 结构体定义在文件 include/linux/ioport.h
18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 };
对于 32 位的 SOC 来说, resource_size_t 是 u32 类型的。其中
start 表示开始地址,
end 表示结束地址,
name 是这个资源的名字,
flags 是资源标志位,一般表示资源类型,可选的资源标志也定义在文件 include/linux/ioport.h 中。
of_address_to_resource 函数,是从设备树里面提取资源值,但是本质上就是将 reg 属性值转换为 resource 结构体类型(将reg中的的寄存器地址变为结构体指针,更好的访问)。
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
dev:设备节点。
index:地址资源标号(如reg)。
r:得到的 resource 类型的资源值。
返回值: 0,成功;负值,失败。
5、 of_iomap 函数
用于直接内存映射(和ioremap 函数类似),以后of_iomap 函数(常用)和ioremap 函数,两个函数用哪个都行。
of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参
数指定要完成内存映射的是哪一段。
void __iomem *of_iomap(struct device_node *np, int index)
np:设备节点。
index: reg属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
总结
设备树就和数据结构的二叉树一样,很好理解,以后经常用到在加深印象。