LV10-08-设备树-01-设备树基础知识

本文主要是设备树基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
Linux开发板 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2
u-boot 2013.01
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
文件下载链接
------

一、设备树简介

1. 什么是设备树

先来看一张图,自己照着网上画的,比较丑哈,自己凑活看。

image-20220901181151039

设备树是一种描述硬件信息的数据结构,Linux内核运行时可以通过设备树将硬件信息直接传递给Linux内核,而不再需要在Linux内核中包含大量的冗余编码。

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(DeviceTree Source),这个DTS文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。

2. dtsdtbdtsi

与设备树相关的文件有三种:

  • dtsdevice tree source,设备树源文件。
  • dtsidevice tree source include,类似于头文件,包含一些公共的信息,可被其它设备树文件引用(就是可以被dts文件包含用)。
  • dtbdevice tree binary,是将dts设备树源文件 编译以后得到的二进制文件。

我们知道将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb需要什么工具呢?需要用到DTC工具。 DTC 工具源码在 Linux 内核的scripts/dtc 目录下 。

3. 在系统中的体现

Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree目录下根据节点名字创建不同目录,例如

1
2
3
4
5
6
7
8
[root@farsight]#cd /proc/device-tree/
[root@farsightdevice-tree]#ls
#address-cells keypad@100A0000
#size-cells lcd0-power-domain@10023C80
aliases mct@10050000
amba memory
cam-power-domain@10023C00 mfc-power-domain@10023C40
# ... ... 后边的省略了

/proc/device-tree就是根节点/下的所有属性了子节点,我们再进入chosen节点:

1
2
3
4
5
6
7
8
9
[root@farsightdevice-tree]#cd chosen/
[root@farsightchosen]#ls
bootargs name
[root@farsightchosen]#ls -lh
total 0
-r--r--r-- 1 0 0 116 Jan 1 00:10 bootargs
-r--r--r-- 1 0 0 7 Jan 1 00:10 name
[root@farsightchosen]#cat bootargs
root=/dev/nfs nfsroot=192.168.10.101:/home/hk/4nfs/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.10.102

我们再打开下边这个文件:

1
linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts

找到memory节点,信息如下:

1
2
3
chosen {
bootargs ="console=ttySAC2,115200";
};

二、基本语法

1. dtsi头文件

C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi,一个 dts文件包含其他dtsi文件的一般格式如下:

1
#include "DeviceTree_name.dtsi"

一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UARTIIC 等等。

【注意事项】.dts 设备树文件中,可以通过#include来引用.h.dtsi.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi后缀。

2. 设备节点

2.1 根节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。例如,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 节选自:linux-3.14/arch/arm/boot/dts/exynos4412-origen.dts */
/ {
model = "Insignal Origen evaluation board based on Exynos4412";
compatible = "insignal,origen4412", "samsung,exynos4412";

memory {
reg = <0x40000000 0x40000000>;
};

chosen {
bootargs ="console=ttySAC2,115200";
};

firmware@0203F000 {
compatible = "samsung,secure-firmware";
reg = <0x0203F000 0x1000>;
};
/* ... ... */
};
  • /:是根节点,每个设备树文件只有一个根节点。一般来说.dtsi.dts文件都会有一个/根节点,因为这两个/根节点的内容会合并成一个根节点。

2.2 特殊节点

2.2.1 /memory

所有设备树文件的必需节点,它定义了系统物理内存的layout,例如,

1
2
3
memory {
reg = <0x40000000 0x40000000>; /* 用来指定内存的地址、大小 */
};

2.2.2 /chosen

该节点并不是一个真实的设备,主要是用于传递内核启动时使用的参数parameter,例如,

1
2
3
chosen {
bootargs ="console=ttySAC2,115200";/* 字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样 */
};

前边我们已将查看过设备树的这个节点在系统中的体现了:

1
2
3
4
5
6
[root@farsightproc]#ls -lh /proc/device-tree/chosen/
total 0
-r--r--r-- 1 0 0 116 Jan 1 00:16 bootargs
-r--r--r-- 1 0 0 7 Jan 1 00:16 name
[root@farsightproc]#cat /proc/device-tree/chosen/bootargs
root=/dev/nfs nfsroot=192.168.10.101:/home/hk/4nfs/rootfs rw console=ttySAC2,115200 init=/linuxrc ip=192.168.10.102

但是我们会发现,设备树文件中的参数是很少的,只有"console=ttySAC2,115200",那么其他的是哪里来的呢?

学习uboot 的时候我们知道, uboot 在启动 Linux 内核的时候会将 bootargs 的值传递给 Linux内核, bootargs 会作为 Linux 内核的命令行参数, Linux 内核启动的时候会打印出命令行参数(也就是 uboot 传递进来的 bootargs 的值)。那么这两者有什么关系呢?

我们在uboot的查找chosen 这个字符串,会找到common/fdt_support.c文件中有这么一个函数:

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
int fdt_chosen(void *fdt, int force)
{
// ... ...
/*
* Find the "chosen" node.
*/
nodeoffset = fdt_path_offset (fdt, "/chosen");

/*
* If there is no "chosen" node in the blob, create it.
*/
if (nodeoffset < 0) {
/*
* Create a new node "/chosen" (offset 0 is root level)
*/
nodeoffset = fdt_add_subnode(fdt, 0, "chosen");
if (nodeoffset < 0) {
printf("WARNING: could not create /chosen %s.\n",
fdt_strerror(nodeoffset));
return nodeoffset;
}
}

/*
* Create /chosen properites that don't exist in the fdt.
* If the property exists, update it only if the "force" parameter
* is true.
*/
str = getenv("bootargs");
if (str != NULL) {
path = fdt_getprop(fdt, nodeoffset, "bootargs", NULL);
if ((path == NULL) || force) {
err = fdt_setprop(fdt, nodeoffset,
"bootargs", str, strlen(str)+1);
if (err < 0)
printf("WARNING: could not set bootargs %s.\n",
fdt_strerror(err));
}
}
// ... ...
return err;
}

所以实际上就是在uboot中的相关函数修改了设备树的这个节点,实际的流程是这样的(引用的正点原子驱动开发手册中的一张图):

image-20220902082931968

2.2.3 /cpus

多核CPU支持,/cpus节点下有1个或多个cpu子节点,cpu子节点中用reg属性用来标明自己是哪一个cpu,所以 /cpus 中有以下2个属性:

1
2
#address-cells   /* 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address) */
#size-cells /* 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0 */

2.2.4 aliases

aliases 的意思是“别名”,因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上label,然后通过&label来访问节点,这样也很方便。

2.3 节点语法

节点的语法格式如下:

1
2
3
4
[label:] node-name[@unit-address] {    
[properties definitions];
[child nodes];
};
  • label:可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用 &label来表示引用指定节点。
  • node-name:节点的名字。
  • unit-address:设备地址,一般填写该设备寄存器组或内存块的首地址。
  • properties definitions:属性定义。
  • child nodes:子节点。

2.4 节点属性语法

节点中各个属性的基本语法格式如下:

1
2
[label:] property-name = value;
[label:] property-name;

属性可以无值,对于有值的属性,可以有三种取值:

  • arrays of cells1个或多个32位数据, 64位数据使用232位数据表示,数据之间用空格分隔,用尖括号表示(< >)。
  • string,就是字符串, 用双引号表示(" ")。
  • bytestring1个或多个字节,通过空格分隔,用方括号表示([])。
  • ,分隔的多值。

3. 数据类型

在设备树的源码中常用的几种数据类型有以下几种:

  • 字符串
1
compatible = "arm,cortex-a7"; /* 设置 compatible 属性的值为字符串"arm,cortex-a7" */
  • 32位无符号整数
1
2
reg = <0>;               /* 设置 reg 属性的值为 0 */
reg = <0 0x123456 100>; /* reg 的值也可以设置为一组值 */
  • 字符串列表
1
compatible = "insignal,origen4412", "samsung,exynos4412";

三、标准属性

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

1. compatible

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

1
compatible = "manufacturer,model";
  • manufacturer :表示厂商。
  • model :一般是模块对应的驱动名字。

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

根节点下的compatible属性,用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台 。l例如,

1
compatible = "insignal,origen4412", "samsung,exynos4412";

通过根节点的 compatible属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用是origen4412这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是exynos4412这颗 SOCLinux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动Linux内核。

2. model

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

1
model = "Insignal Origen evaluation board based on Exynos4412";

根节点下的model可以用于区分不同的板子,比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子。

3. status

status 属性看名字就知道是和设备状态有关的,status 属性值也是字符串,字符串是设备的状态信息,可选的状态如下表:

描述
"okay"表明设备是可操作的。
"disabled" 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。 至于 disabled 的具体含义还要看设备的绑定文档。
"fail"表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
"fail-sss" 含义和"fail"相同,后面的 sss 部分是检测到的错误内容。

4. 子节点地址信息

4.1 #address-cells

该属性的值为无符号32位整数,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。该属性的值决定了子节点 reg 属性中地址信息所占用的字长(32 位),意思就是在子节点的reg属性中, 使用多少个u32整数来描述地址(address) 。一般使用格式为:

1
#address-cells = <数字>;

4.2 #size-cells

该属性的值也为无符号32位整数,可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。该属性的值决定了子节点 reg 属性中长度信息所占的字长(32 位),意思就是在子节点的reg属性中, 使用多少个u32整数来描述大小(size) 。一般使用格式为:

1
#size-cells = <数字>;

4.3 reg

reg属性是用来描述子结点的地址信息的,一般格式如下:

1
reg = <address1 length1 [address2 length2] [address3 length3] [... ...]>;

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

4.4 使用实例

4.4.1 实例1

1
2
3
4
5
6
7
8
9
10
spi4 {
compatible = "spi-gpio";
#address-cells = <1>;
#size-cells = <0>;

gpio_spi: gpio_spi@0 {
compatible = "fairchild,74hc595";
reg = <0>;
};
};
  • #address-cells = <1>;

这一句表示spi4 的子节点reg属性中起始地址所占用的字长为1

  • #size-cells = <0>;

这一句表示spi4 的子节点reg属性中地址长度所占用的字长为 0

  • reg = <0>;

子节点 gpio_spi: gpio_spi@0reg 属性值为 <0>,因为父节点设置了#addresscells = <1>#size-cells = <0>,因此 addres=0,没有 length 的值,相当于设置了起始地址,而没有设置地址长度。

4.4.2 实例2

1
2
3
4
5
6
7
8
9
10
aips3: aips-bus@02200000 {
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;

dcp: dcp@02280000 {
compatible = "fsl,imx6sl-dcp";
reg = <0x02280000 0x4000>;
};
};
  • #address-cells = <1>;

这一句表示aips3 的子节点reg属性中起始地址所占用的字长为1

  • #size-cells = <1>;

这一句表示aips3 的子节点reg属性中地址长度所占用的字长为 1

  • reg = <0x02280000 0x4000>;

子节点dcp: dcp@02280000reg属性值为<0x02280000 0x4000>,因为父节点设置了#address-cells = <1>#size-cells = <1>address= 0x02280000length= 0x4000,相当于设置了起始地址为0x02280000,地址长度为0x40000

5. ranges

ranges属性值可以为空或者按照(child-bus-address,parent-bus-addresslength)格式编写的数字矩阵, ranges 是一个地址映射/转换表, ranges 属性每个项目由子地址、父地址和地址空间长度这三部分组成:

  • child-bus-address

子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。

  • parent-bus-address

父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长。

  • length

子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。

【注意事项】如果 ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换。

6. name

name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name属性,一些老的设备树文件可能会使用此属性。

7. device_type

device_type 属性值为字符串, IEEE 1275 会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu 节点或者 memory 节点。

四、常用属性

1. phandle

数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点,例如:

1
2
3
4
5
6
7
pic@10000000 {    
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; /* 使用phandle值为1来引用上述节点 */
};

2. reg

reg属性:表示内存区域region,一般格式如下:

1
reg = <address1 length1 [address2 length2] [address3 length3]>;

#address-cellsreg属性中, 使用多少个u32整数来描述地址(address),一般格式如下:

1
#address-cells = <数字>;

#size-cellsreg属性中, 使用多少个u32整数来描述大小(size),一般格式如下:

1
#size-cells = <数字>;

3. compatible

驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,一般格式如下:

1
compatible = "字符串1","字符串2",...;

4. 中断相关属性

4.1 中断控制器节点

  • interrupt-controller

一个无值空属性,用来声明这个node接收中断信号,表示该节点是一个中断控制器。

  • #interrupt-cells

这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符。用来描述子节点中interrupts属性使用了父节点中的interrupts属性的具体的哪个值。

1
2
interrupts = <中断域 中断号 触发方式>   /* 父节点的 #interrupt-cells 属性的值是3 */
interrupts = <中断号 触发方式> /* 父节点的 #interrupt-cells 属性是2 */

4.2 中断源设备节点

  • interrupt-parent

标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,一般格式如下:

1
interrupt-parent = <引用某中断控制器节点>
  • interrupts

一个中断标识符列表,表示每一个中断输出信号,一般格式如下:

1
2
interrupts = <中断域 中断号 触发方式>   /* 父节点的 #interrupt-cells 属性的值是3  */
interrupts = <中断号 触发方式> /* 父节点的 #interrupt-cells 属性是 2 */

这里的中断号指的是组内中断号,触发方式对应数字定义在linux内核源码的这个文件中:

1
include/dt-bindings/interrupt-controller/irq.h

宏定义如下所示:

1
2
3
4
5
6
#define IRQ_TYPE_NONE           0
#define IRQ_TYPE_EDGE_RISING 1 /* low-to-high,上升沿触发 */
#define IRQ_TYPE_EDGE_FALLING 2 /* high-to-low,下降沿触发 */
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) /* 3, 上升沿下降沿均触发 */
#define IRQ_TYPE_LEVEL_HIGH 4 /* high level,高电平触发 */
#define IRQ_TYPE_LEVEL_LOW 8 /* low level,低电平触发 */

4.3 使用实例

我们可以打开linux内核源码中的这个文件:

1
arch/arm/boot/dts/exynos4.dtsi

该文件中对exynos4412的中断控制器(GIC)节点描述如下:

1
2
3
4
5
6
gic: interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>; /* 这里意味着,后边使用gic的子节点的interrupt属性需要写中断域 中断号 触发方式*/
interrupt-controller;
reg = <0x10490000 0x1000>, <0x10480000 0x100>;
};

我们可以再找一个引用了中断控制器节点的子节点,我们打开这个文件:

1
arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

该文件中有什么一个子节点,这里边就包含了一些中断号:

1
2
3
4
5
6
7
8
9
10
gpx0: gpx0 {
gpio-controller;
#gpio-cells = <2>;

interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 16 0>, <0 17 0>, <0 18 0>, <0 19 0>,
<0 20 0>, <0 21 0>, <0 22 0>, <0 23 0>;
#interrupt-cells = <2>; /* 这里意味着,后边使用GPX引脚作为中断的话,子节点的interrupt属性只需要写中断号和触发方式即可*/
};

5. gpio相关属性

5.1 GPIO控制器

  • gpio-controller

无值空属性,用来说明该节点描述的是一个gpio控制器。

  • #gpio-cells

用来表示要用几个cell描述一个GPIO引脚。

5.2 GPIO使用者节点

一般语法格式如下:

1
xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
  • 工作模式:

1,低电平有效GPIO_ACTIVE_HIGH

0,高电平有效GPIO_ACTIVE_LOW。

五、常用接口

设备树描述了设备的详细信息,这些信息包括数字类型的、字符串类型的、数组类型的,我们在编写驱动的时候需要获取到这些信息。

比如设备树使用reg属性描述了某个外设的寄存器地址为0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg 属性的0X020054820X400这两个值,然后初始化外设。

Linux 内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀of_,所以在很多资料里面也被叫做OF函数。这些OF函数原型都定义在linux内核源码的该文件中:

1
include/linux/of.h

1. 三个结构体

1.1 struct device_node

这个结构体定义在include/linux/of.h 文件中,结构体成员如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct device_node {
const char *name; /* 节点名字 */
const char *type; /* 设备类型 */
phandle phandle;
const char *full_name; /* 节点全名 */

struct property *properties; /* 属性 */
struct property *deadprops; /* removed properties, removed 属性 */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling;
struct device_node *next; /* next device of same type */
struct device_node *allnext; /* next in list of all nodes */
struct proc_dir_entry *pde; /* this node's proc directory */
struct kref kref;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};

1.2 struct property

节点的属性信息里面保存了驱动所需要的内容, Linux 内核中使用结构体 property 表示属性:

1
2
3
4
5
6
7
8
struct property{
char *name; /* 属性名字 */
int length; /* 属性长度 */
void *value; /* 属性值 */
struct property *next; /* 下一个属性 */
unsigned long _flags;
unsigned int unique_id;
};

1.3 struct resource

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

1
2
3
4
5
6
7
8
9
10
11
/*
* Resources are tree-like, allowing
* nesting etc..
*/
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_tu32 类型的。

  • start :表示开始地址。
  • end :表示结束地址。
  • name :是这个资源的名字。
  • flags: 是资源标志位,一般表示资源类型,可选的资源标志定义在文件include/linux/ioport.h中。一般来说常见的资源标志就是IORESOURCE_MEMIORESOURCE_REGIORESOURCE_IRQ等。

2. 查找节点的函数

2.1 of_find_node_by_name()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_node_by_name -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);

【函数说明】该函数通过节点名字查找指定的节点。

【函数参数】

  • fromstruct device_node *类型,开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
  • namechar *类型,要查找的节点名字。

【返回值】struct device_node *类型,成功返回找到的节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

2.2 of_find_node_by_type ()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_node_by_type -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);

【函数说明】该函数通过device_type属性查找指定的节点 。

【函数参数】

  • fromstruct device_node *类型,开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
  • namechar *类型,要查找的节点对应的type字符串,也就是device_type属性值。

【返回值】struct device_node *类型,成功返回找到的节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

2.3 of_find_compatible_node ()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_compatible_node -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct device_node *of_find_compatible_node(struct device_node *from,
const char *type,
const char *compatible);

【函数说明】该函数根据device_typecompatible这两个属性查找指定的节点。

【函数参数】

  • fromstruct device_node *类型,开始查找的节点,如果为NULL表示从根节点开始查找整个设备树 。
  • namechar *类型,要查找的节点对应的type字符串,也就是device_type属性值,可以为 NULL,表示忽略掉 device_type 属性 。
  • compatiblechar *类型,要查找的节点所对应的compatible属性列表。

【返回值】struct device_node *类型,成功返回找到的节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

2.4 of_find_matching_node_and_match()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_matching_node_and_match -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
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);

【函数说明】该函数通过of_device_id匹配表来查找指定的节点。

【函数参数】

  • fromstruct device_node *类型,开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
  • matchesstruct of_device_id *类型,of_device_id 匹配表,也就是在此匹配表里面查找节点。
  • matchstruct of_device_id *类型,找到的匹配的of_device_id

【返回值】struct device_node *类型,成功返回找到的节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

2.5 of_find_node_by_path()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_node_by_path -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
inline struct device_node *of_find_node_by_path(const char *path);

【函数说明】该函数通过路径来查找指定的节点。

【函数参数】

  • pathchar *类型,带有全路径的节点名,可以使用节点的别名,比如/backlight就是backlight这个节点的全路径。

【返回值】struct device_node *类型,成功返回找到的节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

3. 查找父子节点的函数

3.1 of_get_parent()

我们使用以下命令查询一下函数所在头文件:

1
grep of_get_parent -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct device_node *of_get_parent(const struct device_node *node);

【函数说明】该函数获取指定节点的父节点(如果有父节点的话) 。

【函数参数】

  • nodestruct device_node *类型,要查找的父节点的节点。

【返回值】struct device_node *类型,成功返回找到的父节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

3.2 of_get_next_child()

我们使用以下命令查询一下函数所在头文件:

1
grep of_get_next_child -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);

【函数说明】该函数用迭代的方式查找子节点。

【函数参数】

  • nodestruct device_node *类型,父节点。
  • prevstruct device_node *类型,前一个子节点,也就是从哪一个子节点开始迭代地查找下一个子节点。可以设置为
    NULL,表示从第一个子节点开始。

【返回值】struct device_node *类型,成功返回找到的下一个子节点,如果为NULL表示查找失败 。

【使用格式】none

【注意事项】none

4. 提取属性的函数

4.1 of_find_property()

我们使用以下命令查询一下函数所在头文件:

1
grep of_find_property -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

【函数说明】该函数提取指定属性的值 。

【函数参数】

  • npstruct device_node *类型,设备节点指针。
  • namechar *类型,属性名字。
  • lenpint *类型,属性值的字节数。

【返回值】struct property *类型,成功返回属性值的首地址,失败返回NULL

【使用格式】none

【注意事项】none

4.2 of_property_count_elems_of_size()

我们使用以下命令查询一下函数所在头文件:

1
grep of_property_count_elems_of_size -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_property_count_elems_of_size(const struct device_node *np,
const char *propname,
int elem_size);

【函数说明】该函数用于获取属性中元素的数量,比如reg属性值是一个数组,那么使用此函数可以获取到这个数组的大小。

【函数参数】

  • nodestruct device_node *类型,设备节点。
  • propnamechar *类型,需要统计元素数量的属性名字。
  • elem_sizeint类型,元素长度。

【返回值】int类型,得到的属性元素数量。

【使用格式】none

【注意事项】none

4.3 of_property_read_u32_index()

我们使用以下命令查询一下函数所在头文件:

1
grep of_property_read_u32_index -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index,
u32 *out_value);

【函数说明】该函数用于从属性中获取指定标号的u32类型数据值(无符号32位),比如某个属性有多个u32类型的值,那么就可以使用此函数来获取指定标号的数据值。

【函数参数】

  • nodestruct device_node *类型,设备节点。
  • propnamechar *类型,要读取的属性名字。
  • indexu32类型,要读取的值标号。
  • out_valueu32类型,读取到的值。

【返回值】int类型,得到的属性元素数量。

【使用格式】none

【注意事项】none

4.4 获取属性数组数据

有四个函数分别用于获取u8u16u32u64 类型的数组数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
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);

【函数说明】4 个函数分别是读取属性中u8u16u32u64类型的数组数据,比如大多数的reg属性都是数组数据,可以使用这 4 个函数一次读取出reg属性中的所有数据。

【函数参数】

  • npstruct device_node *类型,设备节点。
  • propnamechar *类型,要读取的属性名字。
  • out_values:表示读取到的数组值 ,分别为u8u16u32u64
  • szsize_t类型,要读取的数组元素数量。

【返回值】int类型。读取成功返回0,读取失败返回一个负值。 -EINVAL表示属性不存在, -ENODATA 表示没有要读取的数据, -EOVERFLOW表示属性值列表太小。

【使用格式】none

【注意事项】none

4.5 获取属性数值

有四个函数分别用于获取u8u16u32u64 类型的数值数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
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);

【函数说明】4 个函数分别是读取属性中u8u16u32u64类型的数值数据。

【函数参数】

  • npstruct device_node *类型,设备节点。
  • propnamechar *类型,要读取的属性名字。
  • out_values:表示读取到的数值 ,分别为u8u16u32u64

【返回值】int类型。读取成功返回0,读取失败返回一个负值。

【使用格式】none

【注意事项】none

4.6 获取属性字符串值

4.6.1 of_property_read_string()

我们使用以下命令查询一下函数所在头文件:

1
grep of_property_read_string -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string);

【函数说明】该函数用于读取属性中字符串值。

【函数参数】

  • npstruct device_node *类型,设备节点指针。
  • propnamechar *类型,要读取的属性名字。
  • out_stringchar **类型,读取到的字符串值。

【返回值】int类型,成功返回0,失败返回负数,负数的绝对值是错误码 。

【使用格式】这里写一个简单实例,主要是说明第三个参数的定义使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 设备树节点 */
fs4412-hello-dt{
compatible = "fs4412,hello-dt";
my-name = "my-hello-dt";
};

/* 需要包含的头文件 */
#include <linux/of.h>
/* 至少应该有的语句 */
const char *str = NULL;
if(of_property_read_string(pnode, "my-name", &str) < 0)
printk("of_property_read_string failed!\n");
else
printk("name=%s\n", str);

【注意事项】none

4.7 of_n_addr_cells()

我们使用以下命令查询一下函数所在头文件:

1
grep of_n_addr_cells -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_n_addr_cells(struct device_node *np);

【函数说明】该函数用于获取#address-cells属性值。

【函数参数】

  • npstruct device_node *类型,设备节点指针。

【返回值】int类型,获取到的#address-cells属性值。

【使用格式】none

【注意事项】none

4.8 of_n_size_cells()

我们使用以下命令查询一下函数所在头文件:

1
grep of_n_size_cells -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_n_size_cells(struct device_node *np);

【函数说明】该函数用于获取#size-cells 属性值。

【函数参数】

  • npstruct device_node *类型,设备节点指针。

【返回值】int类型,获取到的#size-cells 属性值。

【使用格式】none

【注意事项】none

5. 其他常用of函数

5.1 of_device_is_compatible()

我们使用以下命令查询一下函数所在头文件:

1
grep of_device_is_compatible -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_device_is_compatible(const struct device_node *device,
const char *compat);

【函数说明】该函数用于查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性。

【函数参数】

  • devicestruct device_node *类型,设备节点指针。
  • compatchar *类型,要查看的字符串。

【返回值】int类型,返回0表示节点的compatible属性中不包含 compat 指定的字符串; 返回一个正数表示节点的compatible
属性中包含 compat 指定的字符串。

【使用格式】none

【注意事项】none

5.2 of_get_address()

我们使用以下命令查询一下函数所在头文件:

1
grep of_get_address -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
const __be32 *of_get_address(struct device_node *dev,
int index,
u64 *size,
unsigned int *flags);

【函数说明】该函数用于获取地址相关属性,主要是reg或者assigned-addresses属性值。

【函数参数】

  • devstruct device_node *类型,设备节点指针。
  • indexint类型,要读取的地址标号。
  • sizeu64 *类型,地址长度 。
  • flagsunsigned int *类型,参数,比如IORESOURCE_IOIORESOURCE_MEM等。

【返回值】__be32 *类型,成功返回读取到的地址数据首地址,读取失败返回NULL

【使用格式】none

【注意事项】none

5.3 of_translate_address()

我们使用以下命令查询一下函数所在头文件:

1
grep of_translate_address -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
u64 of_translate_address(struct device_node *dev
const __be32 *in_addr);

【函数说明】该函数用于将从设备树读取到的地址转换为物理地址。

【函数参数】

  • devstruct device_node *类型,设备节点指针。
  • in_addr__be32 *类型,要转换的地址。

【返回值】u64类型,得到的物理地址,如果为OF_BAD_ADDR的话表示转换失败。

【使用格式】none

【注意事项】none

5.4 of_property_read_bool()

我们使用以下命令查询一下函数所在头文件:

1
grep of_property_read_bool -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_property_read_bool(const struct device_node *np,
const char *propname);

【函数说明】该函数用于判断指定的属性是否存在。

【函数参数】

  • devstruct device_node *类型,设备节点指针。
  • propnamechar *类型,要判断是否存在的的属性名称。

【返回值】int类型,若属性存在则返回true,不存在则返回false

【使用格式】none

【注意事项】none

5.5 of_address_to_resource()

我们使用以下命令查询一下函数所在头文件:

1
grep of_address_to_resource -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
int of_address_to_resource(struct device_node *dev,
int index,
struct resource *r);

【函数说明】前边已经介绍过struct resource结构体了,函数看名字像是从设备树里面提取资源值,但是本质上就是将 reg 属性值,然后将其转换为 resource 结构体类型。

【函数参数】

  • devstruct device_node *类型,设备节点指针。
  • indexint类型,地址资源标号。
  • rstruct resource *类型(前边有介绍),得到的 resource 类型的资源值。

【返回值】int类型,成功返回0,失败返回一个负数。

【使用格式】none

【注意事项】none

5.6 of_iomap()

我们使用以下命令查询一下函数所在头文件:

1
grep of_iomap -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of.h>

/* 函数声明 */
void __iomem *of_iomap(struct device_node *np,
int index);

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

【函数参数】

  • npstruct device_node *类型,设备节点指针。
  • indexint类型,reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0

【返回值】void __iomem *类型,成功映射的话返回经过内存映射后的虚拟内存首地址,内存映射失败返回NULL

【使用格式】none

【注意事项】none

5.7 of_get_named_gpio()

我们使用以下命令查询一下函数所在头文件:

1
grep of_get_named_gpio -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 需包含的头文件 */
#include <linux/of_gpio.h>

/* 函数声明 */
/**
* of_get_named_gpio() - Get a GPIO number to use with GPIO API
* @np: device node to get GPIO from
* @propname: Name of property containing gpio specifier(s)
* @index: index of the GPIO
*
* Returns GPIO number to use with Linux generic GPIO API, or one of the errno
* value on the error condition.
*/
static inline int of_get_named_gpio(struct device_node *np,
const char *propname, int index)
{
return of_get_named_gpio_flags(np, propname, index, NULL);
}

【函数说明】该函数从设备树中提取gpio口。

【函数参数】

  • npstruct device_node *类型,设备节点指针。
  • propnamechar *类型,包含要获取 GPIO 信息的属性名。
  • indexint类型, GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0

【返回值】int类型,返回一个正数表示获取到了GPIO编号,获取失败返回一个负数。

【使用格式】none

【注意事项】none

5.8 irq_of_parse_and_map()

我们使用以下命令查询一下函数所在头文件:

1
grep irq_of_parse_and_map -r -n ~/5linux/linux-3.14/include

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/of_irq.h>

/* 函数声明 */
unsigned int irq_of_parse_and_map(struct device_node *node,
int index);

【函数说明】该函数获得设备树中的中断号并进行映射。

【函数参数】

  • nodestruct device_node *类型,设备节点指针。
  • indexint类型, GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0

【返回值】int类型,成功返回中断号,失败返回一个负数,绝对值表示错误码。

【使用格式】none

【注意事项】none