目录
- 1. 设备树简介
- 1.1 编译-引用
- 1.2 设备树文件结构
- 1.3 设备树节点介绍
- 1.3.1 特殊节点chosen
- 1.4 节点内容追加
- 2. 设备树常用OF操作函数
- 2.1 节点寻找类
- 2.2 属性提取类
- 2.3 其它常用类
- 4. 设备树下LED实验
- 4.1 实验简介
- 4.2 添加LED设备节点
- 4.3 获取设备节点并提取属性
- 4.3.1 获取设备节点
- 4.3.2 提取设备节点属性
- 4.4 节点属性使用-总结
1. 设备树简介
设备树可以理解为一种硬件数据结构,只不过这种数据结构中描述了板子和外设的信息,例如CPU 数量、内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等;
设备树将描述板级硬件信息的内容从 Linux 内核中分离开来,用一个专属的文件格式来描述,文件扩展名为.dts,设备树的主要功能是按照特定的结构来描述板子上的设备信息,它的出现是为了解决在没有设备树时,Linux 通过大量的arch/arm/mach-xxx 和 arch/arm/plat-xxx 文件夹来描述对应平台的板机信息,导致 Linux 内核虚胖的问题。
1.1 编译-引用
设备树的编译流程,对于C语言我们使用GCC
就能把mx.c -> mx.o
,同理对于设备树而言我们使用的是DTC
的编译工具把mx.dts -> mx.dtb
,作为理解就行;
语法简介,对于dtc而言有自己的一套语法,但是比较简单,对于.dts文件同样支持头文件的扩展,对于C语言而言我们添加头文件是<mx.h>后缀是h
文件,对于设备树我们添加的是<mx.dtsi>后缀是dtsi文件
;但是我们也可以来引用.h、.dtsi 和.dts 文件,只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。DTS 语法非常的人性化,是一种 ASCII文本文件,不管是阅读还是修改都很方便;
1.2 设备树文件结构
对于一个设备树文件而言,其结构也是层级结构,其中根目录为/,如下:不过我们这里称为节点,也就是有父节点,父节点下面有子节点,子节点下面有子子节点等等;
1.3 设备树节点介绍
我们已经知道对于设备树而言是由一个个节点组成,你把这个节点放在几级目录,那么这个节点就是几级节点,因此下面对一个节点进行一个简要的说明:要注意的就是#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性 都是和地址有关的内容
,整体结构就是这样,其它的节点中的属性就不再一一介绍;
1.3.1 特殊节点chosen
uboot 中的 fdt_chosen 函数在设备树的 chosen 节点中加入了 bootargs属性,并且还设置了 bootargs 属性值,在设备树中就是chose的属性:图中框起来的部分就是函数do_bootm_linux函数的执行流程,也就是说do_bootm_linux函数会通过一系列复杂的调用,最终通过fdt_chosen 函数在chosen 节点中加入了 bootargs 属性,而我们通过 bootz 命令启动 Linux 内核的时候会运行do_bootm_linux 函数;
Linux 内核在启动的时候会解析 DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件,接下来我们简单分析一下 Linux 内核是如何解析 DTB 文件的,流程如图 所示:
1.4 节点内容追加
产品开发过程中可能面临着频繁的需求更改,比如第一版硬件上有一个 IIC 接口的六轴芯片 MPU6050,第二版硬件又要把这个 MPU6050 更换为 MPU9250 等。一旦硬件修改了,我们就要同步的修改设备树文件,毕竟设备树是描述板子硬件信息的文件。因此这里例如向iic中追加两个设备节点:对于imx6ull.dtsi文件中iic的节点中的内容很少,如下:
现在进行追加,追加符是&+标签/节点名字;如下在我们的设备书文件imx6ull-14x14-evk.dts中对iic进行追加,我们这个文件已经引入了imx6ull.dtsi头文件
:
2. 设备树常用OF操作函数
对于设备树而言已经介绍了其结构,节点,属性,但是我们如何把节点中的属性给提取出来呢?例如我们在reg属性中有:reg=<0x454145512 0x04 0x4597911651 0x04>
,我们如何把reg中的属性给提取出来呢?我们可以使用OF操作函数,具体而言有下列比较重要的部分:具体的用法用多了就熟练了;
2.1 节点寻找类
1. 寻找节点类的常用如下:(待补充)
static inline struct device_node *of_find_node_by_path(const char *path)
:struct device_node *of_get_parent(const struct device_node *node)
:struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev)
:
2.2 属性提取类
2. 提取属性的的常用如下:(待补充)
static inline int of_property_read_string(struct device_node *np,const char *propname,const char **out_string)
:static inline int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_values, size_t sz)
:int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)
int of_n_addr_cells(struct device_node *np)
int of_n_size_cells(struct device_node *np)
2.3 其它常用类
3. 其它的常用如下:(待补充)
int of_device_is_compatible(const struct device_node *device,const char *compat)
:onst __be32 *of_get_address(struct device_node *dev,int index,u64 *size,unsigned int *flags)
:主要是reg或assigned-addresses属性;u64 of_translate_address(struct device_node *dev,const __be32 *in_addr)
:int of_address_to_resource(struct device_node *dev,int index,struct resource *r)
:void __iomem *of_iomap(struct device_node *np,int index)
:限定reg属性
4. 设备树下LED实验
4.1 实验简介
我们回顾一下我们几次点灯实验:
第一次点亮LED实验
。我们通过裸机进行操控寄存器进行LED灯的点亮:在裸机下进行LED点灯本质上和单片机开发差不多,是在寄存器层面进行操控,相当于把IMX6ULL当成了一个性能更强的单片机;第二次点亮LED实验
。linux内核下不用设备树对LED进行点亮,而且我们使用的手动对设备号进行分配,并在Linux系统上手动对驱动节点进行挂载;到这里我们以及慢慢的离开了底层的裸机开发,我们用上了操作系统,也就是给IMX6ULL装上了系统,因此我们在这里引入了内核态和用户态的概念,我们在Linux系统上对LED操控本质归根到底还是要操控寄存器,但是我们在系统上不能直接操控寄存器,而是通过寄存器映射的方式把实际的物理地址映射成虚拟的地址,然后我们对虚拟的寄存器进行操控,以实现在系统层面对LED进行驱动点亮;第三次点亮LED实验
。我们在第二次点亮实验中已经在系统层面对LED进行了驱动实验,但是缺点还是有的,就是需要我们手动进行设备号的分配以及需要我我们手动对驱动节点的创建,也就是要用到mknod命令,这显然是对用户是不友好的,因此我们在第三次实验中重点是完善了LED的驱动框架,也就是对于LED的驱动,完成了自动对设备号的分配以及自动对设备节点的创建,其流程是常用的驱动框架,不过缺点也有,这种开发驱动的模式是linux系统没有引入设备树之前常用的开发模式,但是对于引入设备树后,这种驱动开发模式就逐渐被抛弃了;第四次点亮LED实验
。第四次LED点亮实验是在第三次的基础上引入了设备树,也就是本次的设备树下LED驱动点亮实验:因此这次驱动实验目的就是如何在引入设备树下的情况对LED驱动进行开发:从这几次的点灯实验中我们也可以看到程序也是越来越复杂,但是这种复杂性是必要的,对于大型复杂的程序,这反而是一种更加高效的方式;
4.2 添加LED设备节点
由于本次点灯实验是在第三次的基础上引入了设备树,因此这节内容主要是记录设备树的引入相关的细节,对于框架搭建和第三次驱动的实验参考我的下面两个连接:
1.简单驱动框架搭建:连接
2.完善的驱动框架(未引入设备树):连接
前面对设备树进行了结构,节点,属性进行简要分析,同时也介绍了一些常用的OF函数,我们要想在驱动中引入设备树,第一件事就是想dts设备树文件中添加我们的LED设备,添加如下:
添加完毕后.进行make dtbs编译,编译完毕后挂载到板子上,然后在/proc/device-tree/目录中查看是否有“alphaled”这个节点;
4.3 获取设备节点并提取属性
4.3.1 获取设备节点
当我们成功的在设备树中添加了设备节点,我们就可以在驱动文件中对我们添加的节点进行获取,获取的方式就是利用我们上面所说的OF常用的函数,如下:
4.3.2 提取设备节点属性
当获取设备节点后就是对节点中的属性进行提取;
4.4 节点属性使用-总结
我们可以这样认为,我们本质上就是操控寄存器进行LED的控制,只不过我们要操控寄存器就要获取寄存器的地址,当我们不引入设备树时我们直接可以进行地址映射然后进行操控虚拟寄存器,但是当我们引入设备树后,我们就无法直接获取寄存器的地址,因为寄存器的地址被我们写在了设备树中,因此我们想进行虚拟地址的映射我们就必须先获取设备树中LED节点的寄存器节点信息,如果我直接宏定义的话,设备树的引入就没有意义了,当然对于点灯的实验而言是多此一举,但是对以后的学习是必要的;