设备树的介绍和使用
为什么引进设备树:
- device部分是描述硬件的。一般device部分的代码会放在内核源码中arch/arm/plat-xxx和arch/arm/machxxx下面。但是随着Linux支持的硬件越来越多,在内核源码下关于硬件描述的代码也越来越多。并且每修改一下就要编译一次内核。
- 长此以往Linux内核里面就存在了大量“垃圾代码”,而且非常多,这里说的“垃圾代码”是关于对硬件描述的代码。从长远看,这些代码对Linux内核本身并没有帮助,所以相当于Linux内核是“垃圾代码”。但是并不是说平台总线这种方法不好。
- 为了解决这个问题,设备树就被引入到了Linux上。使用设备树来剔除相对内核来说的“垃圾代码”,既用设备树来描述硬件信息,用来替代原来的device部分的代码。虽然用设备树替换了原来的device部分,但是平台
总线模型的匹配和使用基本不变。并且对硬件修改以后不必重新编译内核。直接需要将设备树文件编译成二进制文件,在通过bootloader传递给内核即可。
所以设备树就是用来描述硬件资源的文件。
基本名词解释
- DT:Device Tree //设备树
- FDT:Flattened Device Tree //开放设备树,起源于OpenFirmware (OF)
- dts: device tree source的缩写 //设备树源码
- dtsi: device tree source include的缩写 //通用的设备树源码
- dtb: device tree blob的缩写//编译设备树源码得到的文件
- dtc: device tree compiler的缩写 //设备树编译器
设备树编译格式:
编译设备树: dtc -I dts -0 dtb -o xxx.dtb xxx.dts
反编译设备树: dtc -I dtb -0 dts -o xxx.dts xxx.dtb
设备树的基本语法:
常用属性
根节点:
/dts-v1/;
/{
};
子节点:
格式:
[label:]node-name[@unit-address]{
[properties-list]
[child-node-list]
};
如:
nodel{ //子节点,名称node1
nodel_child{
//同级节点下不能出现相同名称
};
};
- 节点名称:在对节点进行命名的时候,一般要体现设备的类型,比如网口一般命名成ethernet,串口一般命名成uart,对于名称一般要遵循下面的命令格式。
格式:[标签]:<名称>[@<设备地址>]
其中,[标签]和[@<设备地址>]是可选项,<名称>是必选项。另外,这里的<设备地址>没有实际意义,只是让节点名称更人性化,更方便阅读。
举例:
uart: serial@02288000 其中, uart就是这个节点标签,也叫别名, serial@02288000就是节点名称。
reg属性
reg
属性可以来描述地址信息。比如寄存器的地址。reg属性的格式如下:
reg = <address1 lengthl address2 length2 address3 length3......>
举例:
reg = <0x02200000 0x4000>:
举例:
reg = <0x02200000 0x4000
0x02205000 0x4000>
#
address-ce11
和#size-cells
属性
#address-cel1和#size-cells用来描述子节点中的reg信息中的地址和长度信息
举例1:
node1{
#address-cells =<1>;
#size-cells = <0>:
nodel-child{
reg = <0>;
}
}
#address-cells =<1>;#size-cells = <0>;所以reg里的属性值表示的是地址信息,数据为00️⃣
举例2:address=1,size=1,所以reg里的属性值一个表示地址信息,一个表示长度信息
node1{
#address-cells = <1>;
#size-cells =<1>;
nodel-child{
reg = <0x02200000 0x4000>;
}
}
举例3:address=2,size=1,所以reg里表示的是两个地址信息
nodel{
#address-cells =<2>
#size-cells =<0>:
nodel-child{
reg = <0x00 0x01>;
}
}
model
属性的值是一个字符串,一般用model描述一些信息。比如设备的名称,名字等
举例1:
model = "wm8960-audio"
举例2:
model = "This is Linux board"
status
属性是和设备的状态有关系的,status的属性值是字符串。属性值有下面几个状态可选:
compatible
属性是非常重要的一个属性。compatible是用来和驱动进行匹配的。匹配成功以后会执行驱动中的probe函数。
举例:
compatible ="nihao","hello";
在匹配的时候会先使用第一个值nihao进行匹配,如果没有就会使用第二个值hello进行匹配。
特殊节点aliases:用来定义别名
特殊节点
aliases
用来定义别名。定义别名的目的就是为了方便引用节点。当然,除了使用aliases来命名别用,也可以在对节点命名的适合添加标签来命名别名。
举例:
aliases{
mmc0 = &sdmmc0;
mmcl = &sdmmc1;
mmc2 = &sdhci;
serial0="/simple@fe000000/serial@llc500”;
}
chosen
节点用来uboot给内核传递参数。重点是bootargs参数。chosen节点必须是根节点的子节点。❗
举例:
chosen{
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200”;
}
设备树中规定的属性有时候并不能满足我们的需求,这时候我们可以自定义属性。
举例:自定义一个管教标号的属性pinnum。
pinnum =<0 12 3 4>;
特殊属性ranges
:地址映射
ranges两种格式:
- 一种带参数
ranges= <child-bus-address parent-bus-address length>
按照规则映射- 一种不带参数
ranges;
1:1映射,内存区域,表示不需要映射- 没有ranges属性
表示设备只能被父节点访问
参数:
- child-bus-address:子地址物理空间的起始地址。由 ranges 所在节点“#address-cells”属性决定该地址所占的字长
- parent-bus-address:父地址物理空间的起始地址。由 ranges 的父节“#address-cells”属性决定该地址所占的字长
- length:映射的大小,由ranges 的父节点“#size-cells”属性决定该地址所占的字长
实例分析:如何读取数据格式
作用:将子地址空间映射到父地址的空间。
使用场景:当子地址的物理空间和父地址的物理空间不在同一个总线上时,使用ranges属性来描述子地址到父地址的映射关系。
比如一些非内存映射的设备,可以类比为CPU不能直接访问的设备(内存映射设备类比可被CPU直接访问,1:1映射),需要地址映射才可以访问:好比I2c控制器下面挂载的I2c从设备,是通过I2c的协议通讯的,是串行通信方式,而CPU和I2c控制器是并行通信方式,所以CPU不能直接访问I2c控制器,需要通过I2c协议进行访问。I2c下的设备,只能被其控制器访问。
使用方法:在子地址的节点中使用ranges属性来描述子地址到父地址的映射关系。
ranges=<0x0 0x20 0x100>
把子地址空间(0x0–(0x0+0x100))映射到父地址空间 (0x10–(0x20+0x100))
设备树示例分析:中断
- 在中断控制器中,必须有一个属性
#interrupt-cells
,表示其他节点如果使用这个中断控制器需要几个cell来表示使用哪一个中断。 - 在中断控制器中,必须有一个属性
interrupt-controller
,表示他
是中断控制器。 - 在设备树中使用中断,需要使用属性
interrupt-parent=<&XXXX>
表是中断信号链接的是哪个中断控制器。接着使用interrupts属性来表示中断引脚和触发方式。 - 注:interrupt里有几个cell,是由interrupt-parent对应的中断控制
器里面的#interrupt-cells属性决定。
中断描述编写:
RK处理器的中断示例:rk3568.dtsi公共的设备树资源信息
interrupt-controller;
#interrupt-cells = <2>;
//表示引用该节点,并使用中断控制器的话,需要在interrupt中写入两个属性来描述中断信息
例如:
interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;
描述该中断资源:是PB5引脚,中断触发方式是上升沿触发。
注意
:在设备树的编写中也可以像c语言那样没使用.h中的宏定义,也需要像c语言那样将头文件包含进来
示例分析:GPIO
- 在GPIO控制器中,必须有一个属性
##gpio-cells
,表示其他节点如果使用这个GPIO控制器需要几个cell来表示使用哪一个GPIO。 - 在GPIO控制器中,必须有一个属性
gpio-controller
,表示他
是GPIO控制器。 - 在设备树中使用GPIO,需要使用属性
data-gpios=<&gpio1 12 0>
来
指定具体的GPIO引脚。data-gpios属性可以为自定义属性。
简化:
gpiol: gpiol{
gpio-controller;
#gpio-cells =<2>;
}
//同时描述多个
data-gpios = <&gpio1 12 0>,<&gpiol 15 0>:
led灯的描述编写:
gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;
引用gpio0,两个属性值表述具体的引脚信息
注意:
设备树是用来描述硬件资源的,并不是在实际的使用中对资源做了对应的初始化定义。驱动攻城狮🦁在驱动中获取设备树定义的资源后,具体怎么使用,要看驱动的编写。
Pinctrl设置复用关系
-
Linux内核提供了pinctrl子系统,pinctrl是pincontroller的缩写,目的是为了统一各芯片原厂的pin脚管理。所以一般pinctr1子系统的驱动是由芯片原厂的BSP攻城狮🦁实现。
-
有了pinctr1子系统以后,驱动攻城狮🦁就可以通过配置设备树使用pinctrl子系统去设置管脚的复用以及管脚的电气属性。
例1:
pinctrl-names = default".
pinctrl-0 = <&pinctrl hog 1>;
解析:使用pinctrl-names
表示设备的状态。这里只有一个default
状态,default为第0个状态。pinctrl-0 =<&pinctrl_hog_1>
表示第0个状态default对应得引脚在pinctrl_hog_1节点中配置。
例2:
pinctrl-names = "default","wake up"
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
解析:使用pinctrl-names表示设备的状态。这里有default和wake up俩个状态,default为第0个状态,wake up为第1个状态。pinctrl-0 =<&pinctrl_hog_1>表示第0个状态default对应得引脚在pinctrl_hog_1节点中配置。pinctrl-1同理。
例3:
pinctrl-names = "default".
pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;
解析:使用pinctrl-names表示设备的状态。这里只有一个default状态,default为第0个状态。pinctrl-0 =<&pinctrl_hog_1>表示第0个状态default对应得引脚在pinctrl_hog_l和pinctrl_hog_2俩个节点中配置
----------------------示例------------------------
示例分析:LED灯的描述:
Orangepi-3b:使用Rk3566的处理器,查看设备树的LED描述:
//客户端:
//服务端的配置: