设备树
https://blog.csdn.net/m0_74712453/article/details/145589379
介绍
每一款开发板需要修改许多c文件代码去适配,这些代码文件放在arch/arm/mxch-xxx目录总中,使得内核越来越臃肿;引入设备树,将板级信息的文件与linux内核代码分离,描述板子的细节,比如CPU 数量、外设基地址、总线设备等,放在dts这种数据结构中,用到时dts将硬件细节传输给内核
dts不是直接和内核联系,而是通过bootloader将编译后的文件传输给kernel

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 | /dts-v1/; // 表示版本 |
节点的结构和约定如下。
1 | [label:] node-name[@unit-address] { |
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 | "okay" |
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 | #address-cells |
(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 | struct device_node { |
2. 查找节点
(1)通过节点名字查找节点
1 | extern struct device_node *of_find_node_by_name(struct device_node *from, |
- from:开始查找的节点,NULL 表示根节点
- name:要查找的节点名称
返回值为找到的节点,NULL 为查找失败。
(2)通过节点类型查找节点
1 | extern struct device_node *of_find_node_by_type(struct device_node *from, |
type 参数指定要查看节点对应的 type 字符串,也就是 device_type 属性值。
(3)通过 device_type 和 compatible 查找节点
1 | extern struct device_node *of_find_compatible_node(struct device_node *from, |
(4)通过 of_device_id 匹配表来查找节点
1 | extern struct device_node *of_find_matching_node_and_match( |
(5)通过路径来查找节点
1 | static inline struct device_node *of_find_node_by_path(const char *path) |
这里的 path 必须要是绝对路径。
3. 获取父子节点
(1)获取父节点
1 | extern struct device_node *of_get_parent(const struct device_node *node); |
(2)迭代查找子节点
1 | extern struct device_node *of_get_next_child(const struct device_node *node, |
prev 参数是前一个子节点,如果为 NULL 表示从第一个子节点开始。
4. 提取属性值
在节点描述类型 device_node 中,有这样一项用来描述属性值:
1 | struct property *properties; |
property 结构体类型定义如下:
1 | struct property { |
(1)查找指定节点的属性
1 | extern struct property *of_find_property(const struct device_node *np, |
参数 name 指属性名字,lenp 指属性值的字节数。
(2)获取属性中元素的数量
1 | extern int of_property_count_elems_of_size(const struct device_node *np, |
参数 propname 是需要统计元素数量的属性名字,参数 elem_size 是元素的长度。
返回值是获取到的属性元素数量。
eg. reg 属性的值通常是一个数组,使用此函数可以获取的数组的大小。
(3)从属性中获取指定索引的 u32 类型数据值
1 | extern int of_property_read_u32_index(const struct device_node *np, |
参数 out_value 用来返回获取到的值。
返回值用来表示是否获取成功。
(4)从属性中获取数组值
1 | extern int of_property_read_u8_array(const struct device_node *np, |
eg. reg 属性的值通常是一个数组,使用这个函数可以一次读取出一个数组,也就是 reg 属性的全部值。
(5)从属性中获取布尔值/整形值
1 | /** |
(6)从属性中获取字符串
1 | extern int of_property_read_string(struct device_node *np, |
(7)获取#address-cells 和#size-cells 属性值
1 | extern int of_n_addr_cells(struct device_node *np); |
6. 地址相关操作
地址相关操作的函数定义在include/linux/of_address.h文件中。
1 | static inline const __be32 *of_get_address(struct device_node *dev, int index, |
参数 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 | /* |
其中:
- start:资源起始地址
- end:资源结束地址
- name:资源名称
- flags:资源类型
- 链表节点,用于嵌套
资源类型同样定义在文件include/linux/ioport.h中,如下:
1 | /* |
常用的资源是 IORESOURCE_MEM、IORESOURCE_REG、IORESOURCE_IRQ 三个。
OF 函数中,可以将 reg 属性给出的地址转换为 resource 资源:
1 | extern int of_address_to_resource(struct device_node *dev, int index, |
(4)虚拟地址映射
之前使用 ioremap 函数来完成物理地址到虚拟地址的映射,采用设备树以后,可以直接通过 of_iomap 来完成物理地址到虚拟地址的映射。
1 | void __iomem *of_iomap(struct device_node *node, int index); |
参数 index 是 reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话,index 就设置为 0。
7. 其它常用 OF 函数
(1)检查设备兼容性
1 | extern int of_device_is_compatible(const struct device_node *device, |
第二个参数用来指定要查看的字符串,该函数会检查指定的字符串是否在节点的 compatible 属性中。
