6. Linux设备树前置基础

一. 知识点:

我们在uboot启动的时候,会下载zImage内核,.dtb文件,这里面的 .dtb文件就是设备树文件
1. 什么是设备树: 将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等;在这里插入图片描述

如上图所示:树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。 DTS 文件的主要功能就是按照上图的结构来描述板子上的设备信息DTS 文件描述设备信息是有相应的语法规则要求的

内核会在启动时解析设备树文件,从而根据设备树中的设备结点建立相应的 device_node来管理设备;而我们的驱动中可以是使用这些device_node结点来获取我们定义在设备树中的信息,一般为外设的各种硬件信息等等;另一方面,如果设备树中某些子节点没有被内核自动创建device_node设备结点,我们也可以通过一些内核提供的of_函数来获取到设备树中的结点信息;


2. 设备树的目的:
如果将所有的板级信息都存储在.c文件中,并将它们都编译进入内核,这样内核过于庞大,因为每一个板子都需要一个文件;
设备树文件通过结点的方式,支持结点的扩展,文件的引用,同时将设备树文件和内核分离开来,轻量化内核文件的大小;


3. DTS、DTB、DTC都是啥
.dts文件是设备树文件的原码文件;.dtb文件是使用编译工具DTC编译.dts文件后生成的二进制文件;
其中DTC 工具源码在 Linux 内核的 scripts/dtc 目录下


4. 如何编译.dts设备树文件?(3种方法都可以)
1)使用 make all:在内核源码根目录下面使用make all会编译整个内核以及.dts文件,生成zImage内核镜像文件、ko 驱动模块和.dtb设备树文件
2)使用make dtbs:在内核源码根目录下使用make dtbs只编译设备树;
3)使用make xxxxxx.dtb:在内核源码根目录下使用make xxxxxx.dtb编译指定名字xxxxxx的设备树.dts文件(注意:命令中使用的是》dtb不是.dts)


5. 为什么使用上面的make命令能够编译设备树文件?
arch/arm/boot/dts/Makefile目录下存放着设备树文件的makefile,里面指定了使用某种芯片对应需要编译的设备树文件;
如果你使用这个型号的芯片,那么这个芯片厂商做的linux内核makefile中一定会编译这个芯片对应的dts文件,例如下面代码中的
CONFIG_SOC_IMX6ULL这个宏就是芯片的名字;

dtb-$(CONFIG_SOC_IMX6ULL) += \           // 这个就是芯片型号;
	imx6ull-14x14-ddr3-arm2.dtb \
	imx6ull-14x14-ddr3-arm2-adc.dtb \
	imx6ull-14x14-ddr3-arm2-cs42888.dtb \
	imx6ull-14x14-ddr3-arm2-ecspi.dtb \
	imx6ull-14x14-ddr3-arm2-emmc.dtb \
	imx6ull-14x14-ddr3-arm2-epdc.dtb \
	imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
	imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
	imx6ull-14x14-ddr3-arm2-lcdif.dtb \
	imx6ull-14x14-ddr3-arm2-ldo.dtb \
	imx6ull-14x14-ddr3-arm2-qspi.dtb \
	imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
	imx6ull-14x14-ddr3-arm2-tsc.dtb \
	imx6ull-14x14-ddr3-arm2-uart2.dtb \
	imx6ull-14x14-ddr3-arm2-usb.dtb \
	imx6ull-14x14-ddr3-arm2-wm8958.dtb \
	imx6ull-14x14-evk.dtb \
	imx6ull-14x14-evk-btwifi.dtb \
	imx6ull-14x14-evk-emmc.dtb \
	imx6ull-14x14-evk-gpmi-weim.dtb \
	imx6ull-14x14-evk-usb-certi.dtb \
	imx6ull-alientek-emmc.dtb \
	imx6ull-alientek-nand.dtb \
	imx6ull-9x9-evk.dtb \
	imx6ull-9x9-evk-btwifi.dtb \
	imx6ull-9x9-evk-ldo.dtb

6. 简单的.dts文件语法与使用规则
我们以一个简单的设备树文件为例:

下图shows an example representation of a simple devicetree that is nearly complete enough to boot a simple operating system, with the platform type, CPU,memory and a single UART described. Device nodes are shown with properties and values shown beside the inside each node.
下图就是一个具有代表性的简单例子,基本可以满足一个简单操作系统的使用了;
在这里插入图片描述在这里插入图片描述
它的主要结构就是像树一样,由根节点,一层一层写;

1)头文件与文件之间的联系
设备树也有头文件,扩展名为.dtsi,可以使用 C语言中的#inlcude语法,因为会使用gcc -E对其进行预处理展开;
可以将一款SOC的所有设备/平台的公用信息提取出来,做一个通用的.dtsi文件,在其设备树文件中引用(比如芯片内部的cpus,各种芯片中的控制器信息);在这里插入图片描述
2)节点的命名(更多采用2.2这样命名):
2.1:node-name@unit-address:例如:“cpu@0”、“interrupt-controller@00a01000”;
其中后面的unit-address是这个节点设备的起始物理地址;
2.2:label: node-name@unit-address:例如:cpu0:cpu@0,i2c2:i2c@021a4000{xxxx};

3)结点内部的属性类型:每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式:
3.1)字符串:例如:compatible = “arm,cortex-a7”;
3.2)32 位无符号整数:例如:reg = <0 0x123456 100>这就是一组值,每一个都是u32类型的;
3.3)字符串列表:compatible = “fsl,imx6ull-gpmi-nand”, “fsl, imx6ul-gpmi-nand”;上述代码设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

4)结点内部标准属性
①compatible:兼容性(如:compatible = “fsl,imx6ul-evk-wm8960”,“fsl,imx-audio-wm8960”;);根节点下的compatible属性:内核通过根节点下的此属性,判断当前内核能够在适配这个设备树文件;
②model:描述模块信息(如:model = “wm8960-audio”;);
③status:设备状态(如:status = “okay”;status = “disabled”;status = “fail”;status = “fail-sss”;)
④#address-cells和#size-cells:描述子节点的地址信息,#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位), #size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位);
⑤reg 属性,前面已经提到过了, reg 属性的值一般是< address length>对。 reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息(如:reg = <0x02020000 0x4000>;);
注意:address-cells 和 size-cells都是表示的子节点的reg寄存器属性,不是当前结点;
⑥ranges
⑦name
⑧device_type,此属性只用于cpu和memory结点;

5)特殊结点:aliase与chosen(关于bootargs与chosen的关系)
①aliases子节点:单词 aliases 的意思是“别名”,因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点
不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label 的形式来访问节点。

aliases {
	can0 = &flexcan1;
	can1 = &flexcan2;
	ethernet0 = &fec1;
	ethernet1 = &fec2;
	gpio0 = &gpio1;
	gpio1 = &gpio2;
	spi0 = &ecspi1;
	spi1 = &ecspi2;
	spi2 = &ecspi3;
	spi3 = &ecspi4;
	usbphy0 = &usbphy1;
	usbphy1 = &usbphy2;
};

②chosen子节点:chosen 并不是一个真实的设备, chosen 节点主要是为了 uboot 向 Linux 内核传递数据,重点是 bootargs 参数。一般**.dts 文件**中 chosen 节点通常为空或者内容很少(一般都在.dtsi文件中定义), imx6ull-alientekemmc.dts 中 chosen 节点内容如下

chosen {
	stdout-path = &uart1;	//仅设置属性std-out,表示标准输出使用uart1
};

我们进入板子的文件系统中查看,发现:/proc/device-tree/chosen 目录里面,会发现有个 bootargs 这个属性
这并不是我们这个imx6ull-alientekemmc.dts文件中定义的呀~ 为了验证不是其他包含的头文件中定义的,我特意查找了所有包含的头文件,没有一个头文件的chosen节点中包含这个bootargs的属性;

我们知道,bootargs属性是uboot中设置的环境变量;我们cat来看一下:在这里插入图片描述
发现与我们在uboot时,设置的环境变量内容是完全相同的;

推理:既然并非我们在.dtsi和.dts文件中手动添加的chosen结点下的bootargs属性,那么它一定是在系统启动过程中,或是uboot或是启动zImage的过程中,从uboot的环境变量中得到的这个变量内容;
在这里插入图片描述
上图展示了,uboot中是如何完成调用的;
源头是bootz命令,从而一步一步调用,从而完成向chosen结点中添加bootargs属性的任务;


7. 内核对设备树文件的解析
Linux 内核在启动的时候会解析 DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件。具体的函数调用过程就不深入了;
补充一点,关于兼容性的问题,内核是通过检查设备树根节点下的compatible属性下是否与内核中的兼容性相匹配来判断的;


8. 设备树在文件系统中的体现
在 /proc/device_tree目录下,存放着设备结点文件;其中,属性以文件的形式表示,可以cat;节点以文件夹的形式表示,可以cd进入;

二 设备树of_函数

作用:在驱动中来获得设备树中的信息;
举例:比如设备树使用 reg 属性描述了某个外设的寄存器地址为 0X02005482,长度为 0X400,我们在编写驱动的时候需要获取到 reg属性的0X02005482 和 0X400 这两个值,然后初始化外设。这时我们就可以使用内核提供的of_函数来获取信息;
头文件include/linux/of.h

**思路:**想要获取设备信息,首先要找到这个设备结点,结点的结构体是device_node,由内核管理;
之后才能根据结点找到这个结点的属性;

所以1. 使用函数找到结点 2.使用函数找到结点下的属性信息

当然,有很多类型的of函数,这里我们只举例;我在Linux设备树常用的OF函数总结中详细列举了各种OF函数;
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 表示查找失败。

2. 找到结点下的信息:
of_find_property 函数用于查找指定的属性,函数原型如下:

property *of_find_property(const struct device_node *np,const char *name,int *lenp)
函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数(可以给定NULL)
返回值: 找到的属性。

注:对于如何通过device_node结构体指针+属性名字找到对应的属性,我查看了源码;由于函数的返回值是property类型的指针,我以为会找到类似分配内存的代码,毕竟返回的指针要指向存储的属性信息;但是并不是这样,内核中的device_node结构体初始化的时候,就已经完成了属性的初始化,也就是说,在解析设备树的时候,如果对应结点创建了device_node结构体,那么其对应的属性结构体property也就初始化完成了; 在内核函数of_of_find_property中,仅仅是访问传入参数中的device_node结构体中的属性信息而已;

有很多其他OF函数,在Linux设备树常用的OF函数总结详细列出了各种of函数

三.实验

实验内容一:
自己在imx6ull-emmc-alientek.dts设备树文件中添加几个结点,尝试在根节点、其他子节点下添加自己的结点;
编译新的设备树文件,并在板子上观察文件系统下/proc/device-tree下是否出现了自己创建的结点;

实验内容二:

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值