嵌入式Linux开发19——Linux设备树(万字总结)

  提示:本文参考《 Devicetree SpecificationV0.2.pdf 》、《Power_ePAPR_APPROVED_v1.12.pdf》、《IMX6UL参考手册》以及正点原子的相关教程总结的学习笔记,万字总结,水平有限,仅供参考。
  笔者偏嵌入式方向,故本文更多是关于实际开发中涉及的内容。

文章目录

    • 设备树的概念
    • DTS、 DTB 和 DTC
    • DTS语法
      • 1.dtsi头文件
      • 2.设备节点
      • 3.标准属性
        • 3.1 compatible 属性
        • 3.2 model属性
        • 3.3 status 属性
        • 3.4 #address-cells 和#size-cells 属性
        • 3.5 reg 属性
        • 3.6 ranges 属性
      • 4.根节点compatible 属性
    • 设备树在系统中的体现
      • 1. 根节点“/”各个属性
      • 2、根节点“/”各子节点
    • Linux 内核解析 DTB 文件
    • 设备树常用OF操作函数
      • 1. 查找节点的 OF 函数
        • 1.1 of_find_node_by_name 函数
        • 1.2 of_find_node_by_type 函数
        • 1.3 of_find_compatible_node 函数
        • 1.4 of_find_matching_node_and_match 函数
      • 2. 查找父/子节点的 OF 函数
        • 2.1 of_get_parent 函数
        • 2.2 of_get_next_child 函数
      • 3. 提取属性值的 OF 函数
        • 3.1 of_find_property 函数
        • 3.2 of_property_count_elems_of_size 函数
        • 3.3 of_property_read_u32_index 函数
        • 3.4 of_property_read_u8_array 函数;of_property_read_u16_array 函数;of_property_read_u32_array 函数;of_property_read_u64_array 函数
        • 3.5 of_property_read_u8 函数;of_property_read_u16 函数;of_property_read_u32 函数;of_property_read_u64 函数
        • 3.6 of_property_read_string 函数
        • 3.7 of_n_addr_cells 函数
        • 3.8 of_n_size_cells 函数
      • 4. 其他常用的 OF 函数
        • 4.1 of_device_is_compatible 函数
        • 4.2 of_get_address 函数
        • 4.3 of_translate_address 函数
        • 4.4 of_address_to_resource 函数
        • 4.5 of_iomap 函数


设备树的概念

  设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。
  设备树结构示意图:
嵌入式Linux开发19——Linux设备树(万字总结)_第1张图片
  如图所示,树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。 DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息,DTS 文件描述设备信息是有相应的语法规则要求的,稍后我们会详细的讲解 DTS 语法规则。
  随着智能手机的发展,每年新出的 ARM 架构芯片少说都在数十、数百款, Linux 内核下板级信息文件将会成指数级增长!这些板级信息文件都是.c 或.h 文件,都会被硬编码进 Linux 内核中, 导致 Linux 内核“虚胖”。因此ARM 社区就引入了 PowerPC 等架构已经采用的设备树(Flattened Device Tree),将这些描述板级硬件信息的内容都从 Linux 内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。 一个 SOC 可以作出很多不同的板子,这些不同的板子肯定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dts 描述板级信息(也就是开发板上有哪些 IIC 设备、 SPI 设备等), .dtsi 描述 SOC 级信息(也就是 SOC 有几个 CPU、主频是多少、各个外设控制器信息等)。

DTS、 DTB 和 DTC

  上一小节说了,设备树源文件扩展名为.dts,但是我们在前面移植 Linux 的时候却一直在使用.dtb 文件,那么 DTS 和 DTB 这两个文件是什么关系呢? DTS 是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb需要什么工具呢?需要用到 DTC 工具! DTC 工具源码在 Linux 内核的 scripts/dtc 目录下。

DTS语法

1.dtsi头文件

  和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息的,内容如下:

10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
16
17 / {
18 aliases {
19 can0 = &flexcan1;
......
48 };
49
50 cpus {
51 #address-cells = <1>;
52 #size-cells = <0>;
53
54 cpu0: cpu@0 {
55 compatible = "arm,cortex-a7";
56 device_type = "cpu";
......
89 };
90 };
91
92 intc: interrupt-controller@00a01000 {
93 compatible = "arm,cortex-a7-gic";
94 #interrupt-cells = <3>;
95 interrupt-controller;
96 reg = <0x00a01000 0x1000>,
97 <0x00a02000 0x100>;
98 };
99
100 clocks {
101 #address-cells = <1>;
102 #size-cells = <0>;
103
104 ckil: clock@0 {
105 compatible = "fixed-clock";
106 reg = <0>;
107 #clock-cells = <0>;
108 clock-frequency = <32768>;
109 clock-output-names = "ckil";
110 };
......
135 };
136
137 soc {
138 #address-cells = <1>;
139 #size-cells = <1>;
140 compatible = "simple-bus";
141 interrupt-parent = <&gpc>;
142 ranges;
143
144 busfreq {
145 compatible = "fsl,imx_busfreq";
......
162 };
197
198 gpmi: gpmi-nand@01806000{
199 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpminand";
200 #address-cells = <1>;
201 #size-cells = <1>;
202 reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
......
216 };
......
1177 };
1178 };

  第 54~89 行就是 cpu0 这个设备节点信息,这个节点信息描述了I.MX6ULL 这颗 SOC 所使用的 CPU 信息,比如架构是 cortex-A7,频率支持 996MHz、 792MHz、528MHz、396MHz 和 198MHz 等等。在 imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息,I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚,比如 ecspi1~4、 uart1~8、 usbphy1~2、 i2c1~4等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

2.设备节点

  设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从imx6ull.dtsi 文件中缩减出来的设备树文件内容:

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 行,“/”是根节点,每个设备树文件只有一个根节点。第 2、 6 和 17 行, aliases、 cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:

node-name@unit-address

  其中“node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是 UART1 外设。“unit-address”一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话“unit-address”可以不要,比如“cpu@0”、“interrupt-controller@00a01000”。
  还有另外一种形式:

label: node-name@unit-address

  引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “intc:interrupt-controller@00a01000”,节点 label 是 intc,而节点名字就很长了,为“ interruptcontroller@00a01000”。很明显通过&intc 来访问“interrupt-controller@00a01000”这个节点要方便很多!
  每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:
  ①、字符串

compatible = "arm,cortex-a7";

  上述代码设置 compatible 属性的值为字符串“arm,cortex-a7”。
  ②、 32 位无符号整数

reg = <0>;

  上述代码设置 reg 属性的值为 0,
  ③、字符串列表
  属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:

compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

  上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

3.标准属性

  节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux 下的很多外设驱动都会使用这些标准属性。

3.1 compatible 属性

  compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序, compatible 属性的值格式如下所示:

"manufacturer,model"

  其中 manufacturer 表示厂商, model 一般是模块对应的驱动名字。
  一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。

3.2 model属性

  model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的,比如:

model = "wm8960-audio";
3.3 status 属性

  status 属性看名字就知道是和设备状态有关的, status 属性值也是字符串,字符串是设备的状态信息,可选的状态如表所示:
嵌入式Linux开发19——Linux设备树(万字总结)_第2张图片

3.4 #address-cells 和#size-cells 属性

  这两个属性的值都是无符号 32 位整形, #address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。 #address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg 属性的格式一为:

reg = <address1 length1 address2 length2 address3 length3……>

  每个“address length”组合表示一个地址范围,其中 address 是起始地址, length 是地址长度, #address-cells 表明 address 这个数据所占用的字长, #size-cells 表明 length 这个数据所占用的字长。

3.5 reg 属性

  reg 属性前面已经提到过了, reg 属性的值一般是(address, length)对。 reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。

3.6 ranges 属性

  ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:
  child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
  parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。
  length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
  如果 ranges 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 I.MX6ULL 来说,子地址空间和父地址空间完全相同,因此会在 imx6ull.dtsi中找到大量的值为空的 ranges 属性。

4.根节点compatible 属性

  每个节点都有 compatible 属性,根节点“/”也不例外, imx6ull-emmc.dts 文件中根节点的 compatible 属性内容如下所示:

/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";..
}

  可以看出, compatible 有两个值:“fsl,imx6ull-14x14-evk”和“fsl,imx6ull”。前面我们说了,设备节点的 compatible 属性值是为了匹配 Linux 内核中的驱动程序,那么根节点中的 compatible属性是为了做什么工作的? 通过根节点的 compatible 属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的 SOC,比如这里使用的是“imx6ull”这颗 SOC。 Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。

设备树在系统中的体现

  Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/devicetree 目录下根据节点名字创建不同文件夹:
嵌入式Linux开发19——Linux设备树(万字总结)_第3张图片
  以上就是就是目录/proc/device-tree 目录下的内容, /proc/device-tree 目录下是根节点“/”的所有属性和子节点,我们依次来看一下这些属性和子节点。

1. 根节点“/”各个属性

  在上图中,根节点属性属性表现为一个个的文件(图中细字体文件),比如图 43.5.1 中的“#address-cells”、“#size-cells”、“compatible”、“model”和“name”这 5 个文件,它们在设备树中就是根节点的 5个属性。既然是文件那么肯定可以查看其内容,输入cat 命令来查看 model和 compatible 这两个文件的内容:
在这里插入图片描述

2、根节点“/”各子节点

  各个文件夹(图中粗字体文件夹)就是根节点“/”的各个子节点,比如“aliases”、“ backlight”、“ chosen”和“ clocks”等等。

Linux 内核解析 DTB 文件

  Linux 内核在启动的时候会解析 DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件。接下来我们简单分析一下 Linux 内核是如何解析 DTB 文件的,流程图如下所示:
嵌入式Linux开发19——Linux设备树(万字总结)_第4张图片

设备树常用OF操作函数

  设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。比如设备树使用 reg 属性描述了某个外设的寄存器地址为 0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg 属性的0X02005482 和 0X400 这两个值,然后初始化外设。 Linux 内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀“of_”,所以在很多资料里面也被叫做 OF 函数。这些 OF 函数原型都定义在 include/linux/of.h 文件中。

1. 查找节点的 OF 函数

  设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。 Linux 内核使用 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 };

  与查找节点有关的 OF 函数有 5 个,我们依次来看一下。

1.1 of_find_node_by_name 函数

  of_find_node_by_name 函数通过节点名字查找指定的节点,函数原型如下:

struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);

  函数参数和返回值含义如下:
  from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
  name:要查找的节点名字。
  返回值: 找到的节点,如果为 NULL 表示查找失败。

1.2 of_find_node_by_type 函数

  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 表示查找失败。

1.3 of_find_compatible_node 函数

  of_find_compatible_node 函数根据 device_type 和 compatible 这两个属性查找指定的节点,函数原型如下:

struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compatible)

1.4 of_find_matching_node_and_match 函数

  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 表示查找失败。
  1.5 of_find_node_by_path 函数
  of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:

inline struct device_node *of_find_node_by_path(const char *path)

  函数参数和返回值含义如下:
  path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight这个节点的全路径。
  返回值: 找到的节点,如果为 NULL 表示查找失败。

2. 查找父/子节点的 OF 函数

2.1 of_get_parent 函数

  of_get_parent 函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:

struct device_node *of_get_parent(const struct device_node *node)

  函数参数和返回值含义如下:
  node:要查找的父节点的节点。
  返回值: 找到的父节点。

2.2 of_get_next_child 函数

  of_get_next_child 函数用迭代的方式查找子节点,函数原型如下:

struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev)

  函数参数和返回值含义如下:
  node:父节点。
  prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
  返回值: 找到的下一个子节点。

3. 提取属性值的 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 };

  Linux 内核也提供了提取属性值的 OF 函数,我们依次来看一下。

3.1 of_find_property 函数

  of_find_property 函数用于查找指定的属性,函数原型如下:

property *of_find_property(const struct device_node *np,
                            const char   *name,
                                     int   *lenp)

  函数参数和返回值含义如下:
  np:设备节点。
  name: 属性名字。
  lenp:属性值的字节数
  返回值: 找到的属性。

3.2 of_property_count_elems_of_size 函数

  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.3 of_property_read_u32_index 函数

  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 表示属性值列表太小。

3.4 of_property_read_u8_array 函数;of_property_read_u16_array 函数;of_property_read_u32_array 函数;of_property_read_u64_array 函数

  这 4 个函数分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。这四个函数的原型如下:

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 表示属性值列表太小。

3.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 表示属性值列表太小。

3.6 of_property_read_string 函数

  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,读取成功,负值,读取失败。

3.7 of_n_addr_cells 函数

  of_n_addr_cells 函数用于获取#address-cells 属性值,函数原型如下:

int of_n_addr_cells(struct device_node *np)

  函数参数和返回值含义如下:
  np:设备节点。
  返回值: 获取到的#address-cells 属性值。

3.8 of_n_size_cells 函数

  of_size_cells 函数用于获取#size-cells 属性值,函数原型如下:

int of_n_size_cells(struct device_node *np)

  函数参数和返回值含义如下:
  np:设备节点。
  返回值: 获取到的#size-cells 属性值。

4. 其他常用的 OF 函数

4.1 of_device_is_compatible 函数

  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 指定的字符串。

4.2 of_get_address 函数

  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 的话表示读取失败。

4.3 of_translate_address 函数

  of_translate_address 函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:

u64 of_translate_address(struct device_node *dev,
                         const __be32 *in_addr)

  函数参数和返回值含义如下:
  dev:设备节点。
  in_addr:要转换的地址。
  返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。

4.4 of_address_to_resource 函数

  IIC、 SPI、 GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间, Linux内核使用 resource 结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息, resource 结构体定义在文件 include/linux/ioport.h 中,定义如下:

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

  对于 32 位的 SOC 来说, resource_size_t 是 u32 类型的。其中 start 表示开始地址, end 表示结束地址, name 是这个资源的名字, flags 是资源标志位,一般表示资源类型,可选的资源标志定义在文件 include/linux/ioport.h 中,如下所示:

1 #define IORESOURCE_BITS 0x000000ff
2 #define IORESOURCE_TYPE_BITS 0x00001f00
3 #define IORESOURCE_IO 0x00000100
4 #define IORESOURCE_MEM 0x00000200
5 #define IORESOURCE_REG 0x00000300
6 #define IORESOURCE_IRQ 0x00000400
7 #define IORESOURCE_DMA 0x00000800
8 #define IORESOURCE_BUS 0x00001000
9 #define IORESOURCE_PREFETCH 0x00002000
10 #define IORESOURCE_READONLY 0x00004000
11 #define IORESOURCE_CACHEABLE 0x00008000
12 #define IORESOURCE_RANGELENGTH 0x00010000
13 #define IORESOURCE_SHADOWABLE 0x00020000
14 #define IORESOURCE_SIZEALIGN 0x00040000
15 #define IORESOURCE_STARTALIGN 0x00080000
16 #define IORESOURCE_MEM_64 0x00100000
17 #define IORESOURCE_WINDOW 0x00200000
18 #define IORESOURCE_MUXED 0x00400000
19 #define IORESOURCE_EXCLUSIVE 0x08000000
20 #define IORESOURCE_DISABLED 0x10000000
21 #define IORESOURCE_UNSET 0x20000000
22 #define IORESOURCE_AUTO 0x40000000
23 #define IORESOURCE_BUSY 0x80000000

  大 家 一 般 最 常 见 的 资 源 标 志 就 是 IORESOURCE_MEM 、 IORESOURCE_REG 和IORESOURCE_IRQ 等。接下来我们回到 of_address_to_resource 函数,此函数看名字像是从设备树里面提取资源值,但是本质上就是将 reg 属性值,然后将其转换为 resource 结体类型,函数原型如下所示:

int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r)

  函数参数和返回值含义如下:
  dev:设备节点。
  index:地址资源标号。
  r:得到的 resource 类型的资源值。
  返回值: 0,成功;负值,失败。

4.5 of_iomap 函数

  of_iomap 函数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址,不需要使用 ioremap 函数了。当然了,你也可以使用 ioremap 函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用 of_iomap 函数了。 of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是哪一段, of_iomap 函数原型如下:

void __iomem *of_iomap(struct device_node *np,
                       int index)

  函数参数和返回值含义如下:
  np:设备节点。
  index: reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
  返回值: 经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
  关于设备树常用的 OF 函数就先讲解到这里, Linux 内核中关于设备树的 OF 函数不仅仅只有前面讲的这几个,还有很多 OF 函数我们并没有讲解,这些没有讲解的 OF 函数要结合具体的驱动,比如获取中断号的 OF 函数、获取 GPIO 的 OF 函数等等,这些 OF 函数我们在后面的驱动实验中再详细的讲解。

你可能感兴趣的:(Linux驱动开发,嵌入式,linux,arm,I.MX6ULL,设备树)