https://blog.csdn.net/m0_74712453/article/details/145589379

介绍

每一款开发板需要修改许多c文件代码去适配,这些代码文件放在arch/arm/mxch-xxx目录总中,使得内核越来越臃肿;引入设备树,将板级信息的文件与linux内核代码分离,描述板子的细节,比如CPU 数量、外设基地址、总线设备等,放在dts这种数据结构中,用到时dts将硬件细节传输给内核

dts不是直接和内核联系,而是通过bootloader将编译后的文件传输给kernel

image-20251020004804745

DTS、DTSI、DTB

DTS:设备树描述文件为.dts格式,一般一个dts对应一个arm设备,放置在内核的arch/arm/boot/dts/目录中

DTSI: 头文件,引入不同板子的commin部分,比如描述imx6null芯片

DTB:用dtc工具将dts,dtsi源码编译成.dtb文件,可以被内核解析的二进制文件

语法

设备树是由一个个节点组成的,每个节点相当于树上的一片叶子,通过指定从根节点到所有子节点到所需节点的完整路径,可以唯一地标识设备树中的节点:/node-name-1/node-name-2/node-name-N

1
2
3
4
5
6
/dts-v1/;                // 表示版本
[memory reservations] // 格式为: /memreserve/ <address> <length>;
/ {
[property definitions]
[child nodes]
};

节点的结构和约定如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};

/ {
uart0: uart@fe001000 {
};
};
&uart0 {
status = "ok";
};
&{/uart@fe001000} {};

unit-address表示设备地址或者寄存器首地址(具体节点具体分析,不一定是绝对地址),必须与节点 reg 属性中指定的首地址匹配。

label是标号,作用是方便引用node

属性

每个节点都有用来描述节点信息的属性,属性由名称和值两部分组成

1、compatible

compatible 属性值由 string list 组成,定义了设备的兼容性,推荐格式为manufacturer,model,manufacturer 描述了生产商,model 描述了型号。

1
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

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

2、model

model 属性值是一个 string,指明了设备的厂商和型号,推荐格式为manufacturer,model

1
model = "Freescale i.MX6 ULL 14x14 EVK Board";

3、phandle

phandle 属性值是一个 u32,为设备树中唯一的节点指定一个数字标识符,用于其它节点指明关系。

4、status

status 属性值是一个 string,表示设备的运行状态,可用值如下:

1
2
3
4
"okay"
"disabled"
"faile"
"fail-sss"

5、**#address-cells 和 #size-cells**

#address-cells 和#size-cells 属性值是一个 u32,可以用在任何拥有子节点的设备中,并描述子设备节点应该如何寻址

#address-cells属性定义子节点 reg 属性地址字段所占用的字长,也就是占用 u32 单元格的数量。

#size-cells属性定义子节点 reg 属性值长度所占用的 u32 单元格的数量。

6、reg

定义设备的寄存器地址范围(物理地址),内核通过该属性映射寄存器到虚拟地址,供驱动访问。

reg = <基地址高32位 基地址低32位 大小高32位 大小低32位>;(64 位系统,32 位系统简化为 <基地址 大小>)。

节点

1)根节点

树是由树根开始的,在设备树中称之为根节点,路径为/,根节点不需要节点名称,所有子节点都是挂在根节点上的,可以看到最简单的根节点如下:

1
2
3
4
#address-cells
#size-cells
model
capatible

(2)aliases

aliases 节点用来定义一个或多个别名属性,为了内核方便访问节点。

按照约定,该节点应该在根节点上。

(3)chosen

chosen 节点是为了uboot 向 Linux 内核传递数据,重点是 bootargs 参数,一般.dts 文件中 chosen 节点通常为空或者内容很少。

(4)cpus

cpus 节点所有的设备树都需要 cpus 节点,用来描述系统的 CPU 信息。

系统运行

Linux 内核启动的时候会解析设备树 dtb 文件,所以启动以后可以在根文件系统中看到设备树的节点信息,在/proc/device-tree目录中:

这里 device-tree 目录是一个软链接,实际指向/sys/firmware/devicetree/base目录。

在 device-tree 目录中,首先可以看到设备树根节点下的所有一级子节点。

(1)属性是以文件的方式给出,可以直接查看。

(2)节点以目录的方式给出

OF函数

Linux 内核提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀of_,所以也称为 OF 函数,声明在文件include/linux/of.h文件中。

1. 内核对于设备树节点的描述

Linux 内核使用 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
struct device_node {
const char *name;
const char *type;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;

struct property *properties;
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;
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
};

2. 查找节点

(1)通过节点名字查找节点

1
2
extern struct device_node *of_find_node_by_name(struct device_node *from,
const char *name);
  • from:开始查找的节点,NULL 表示根节点
  • name:要查找的节点名称

返回值为找到的节点,NULL 为查找失败。

(2)通过节点类型查找节点

1
2
extern struct device_node *of_find_node_by_type(struct device_node *from,
const char *type);

type 参数指定要查看节点对应的 type 字符串,也就是 device_type 属性值。

(3)通过 device_type 和 compatible 查找节点

1
2
extern struct device_node *of_find_compatible_node(struct device_node *from,
const char *type, const char *compat);

(4)通过 of_device_id 匹配表来查找节点

1
2
3
4
extern 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);

(5)通过路径来查找节点

1
2
3
4
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}

这里的 path 必须要是绝对路径。

3. 获取父子节点

(1)获取父节点

1
extern struct device_node *of_get_parent(const struct device_node *node);

(2)迭代查找子节点

1
2
extern struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);

prev 参数是前一个子节点,如果为 NULL 表示从第一个子节点开始。

4. 提取属性值

在节点描述类型 device_node 中,有这样一项用来描述属性值:

1
struct property *properties;

property 结构体类型定义如下:

1
2
3
4
5
6
7
8
9
struct property {
char *name;
int length;
void *value;
struct property *next;
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};

(1)查找指定节点的属性

1
2
3
extern struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);

参数 name 指属性名字,lenp 指属性值的字节数。

(2)获取属性中元素的数量

1
2
extern int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);

参数 propname 是需要统计元素数量的属性名字,参数 elem_size 是元素的长度。

返回值是获取到的属性元素数量。

eg. reg 属性的值通常是一个数组,使用此函数可以获取的数组的大小。

(3)从属性中获取指定索引的 u32 类型数据值

1
2
3
extern int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index, u32 *out_value);

参数 out_value 用来返回获取到的值。

返回值用来表示是否获取成功。

(4)从属性中获取数组值

1
2
3
4
5
6
7
8
9
10
11
12
extern int of_property_read_u8_array(const struct device_node *np,
const char *propname, u8 *out_values, size_t sz);
extern int of_property_read_u16_array(const struct device_node *np,
const char *propname, u16 *out_values, size_t sz);
extern int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz);
extern int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values,
size_t sz);

eg. reg 属性的值通常是一个数组,使用这个函数可以一次读取出一个数组,也就是 reg 属性的全部值。

(5)从属性中获取布尔值/整形值

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
43
/**
* of_property_read_bool - Findfrom a property
* @np: device node from which the property value is to be read.
* @propname: name of the property to be searched.
*
* Search for a property in a device node.
* Returns true if the property exist false otherwise.
*/
static inline bool of_property_read_bool(const struct device_node *np,
const char *propname)
{
struct property *prop = of_find_property(np, propname, NULL);

return prop ? true : false;
}

static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
{
return of_property_read_u8_array(np, propname, out_value, 1);
}

static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
{
return of_property_read_u16_array(np, propname, out_value, 1);
}

static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}

static inline int of_property_read_s32(const struct device_node *np,
const char *propname,
s32 *out_value)
{
return of_property_read_u32(np, propname, (u32*) out_value);
}

(6)从属性中获取字符串

1
2
3
extern int of_property_read_string(struct device_node *np,
const char *propname,
const char **out_string);

(7)获取#address-cells 和#size-cells 属性值

1
2
extern int of_n_addr_cells(struct device_node *np);
extern int of_n_size_cells(struct device_node *np);

6. 地址相关操作

地址相关操作的函数定义在include/linux/of_address.h文件中。

1
2
static inline const __be32 *of_get_address(struct device_node *dev, int index,
u64 *size, unsigned int *flags);

参数 index 是要读取的地址标号,size 是地址长度,flag 是是参数,比如 IORESOURCE_IO、IORESOURCE_MEM 等。

返回值是读取到的数据首地址,为 NULL 则表示读取失败。

(2)将从设备树读取到的地址转换为物理地址

1
extern u64 of_translate_address(struct device_node *np, const __be32 *addr);

返回值为转换得到的地址,如果为 OF_BAD_ADDR 的话表示转换失败。

(3)将地址转换为 resources 资源

GPIO、IIC、SPI 这些外设都有对应的寄存器,这些寄存器就是一段内存空间,Linux 内核使用 resource 结构体来描述一段内存空间,定义在文件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;
};

其中:

  • start:资源起始地址
  • end:资源结束地址
  • name:资源名称
  • flags:资源类型
  • 链表节点,用于嵌套

资源类型同样定义在文件include/linux/ioport.h中,如下:

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
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */

#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000

#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000

#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */

#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */

#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */

常用的资源是 IORESOURCE_MEM、IORESOURCE_REG、IORESOURCE_IRQ 三个。

OF 函数中,可以将 reg 属性给出的地址转换为 resource 资源:

1
2
extern int of_address_to_resource(struct device_node *dev, int index,
struct resource *r);

(4)虚拟地址映射

之前使用 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后,可以直接通过 of_iomap 来完成物理地址到虚拟地址的映射。

1
void __iomem *of_iomap(struct device_node *node, int index);

参数 index 是 reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话,index 就设置为 0。

7. 其它常用 OF 函数

(1)检查设备兼容性

1
2
extern int of_device_is_compatible(const struct device_node *device,
const char *);

第二个参数用来指定要查看的字符串,该函数会检查指定的字符串是否在节点的 compatible 属性中。