目录
一、什么是设备树
二、DTS、DTB和DTC的关系
三、DTS基本语法
四、创建小型的设备树模板
五、设备树在系统中的体现
六、特殊节点
1、aliases节点
2、chosen节点
七、特殊的属性
1、compatible属性(兼容性属性)
2、 model属性
3、status属性
4、 #address-cells和 #size-cells属性
5、 reg属性
6、 ranges属性
7、 device_type属性
八、Linux内核的OF操作函数
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫DTS(Device Tree Source),这个DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等,如图所示:
1、DTS相当于.c,就是设备树源码文件
2、DTC工具相当于gcc编译器,将.dts编译成.dtb,DTC工具源码在 Linux内核的 scripts/dtc目录下,
3、DTB是将DTS编译以后得到的二进制文件
通过 make dtbs 编译所有的dts文件
也可以编译指定的dtbs,比如:make imx6ull-alientek-emmc.dtb
在编译dtb文件的时候,可以通过 \arch\arm\boot\dts\Makefile 指定要编译的dts设备树源码文件
设备树也有头文件,扩展名为.dtsi。可以将一款SOC他的其他所有设备/平台的共有的信息提出来,作为一个通用的.dtsi文件
1、DTS也以 / 开始,/ 是整个设备树的跟节点
2,从根节点开始描述设备信息
3,在根节点外有一些&cpu0这样的语句是“追加“
4,节点命名
label:node-name@unit-address
label:是节点的标签,后面才是名字,可以没有,比如
intc: interrupt-controller@00a01000 完整的名字是interrupt-controller@00a01000
在后面追加的时候直接使用&intc
node-name:节点名字
unit-address:一般都是外设寄存器的起始地址,有时候是I2C的设备地址,或者其他含义,具体节点具体分析
/dts-v1
#include .h
#include .dtsi
/ {
/* skeleton.dtsi文件*/
#address-cells = <1>;
#size-cells = <1>;
chosen {
stdout-path = &uart1;
};
aliases {
};
cpus {
};
intc: interrupt-controller@00a01000 {
};
clocks {
};
soc {
};
memory {
};
reserved-memory{
};
...
}
Linux 内核在启动的时候会解析DTB 文件,然后在/proc/device-tree 目录下生成相应的设备树节点文件
Linux 内核是如何解析DTB 文件的,流程如图:
主要目的就是将uboot里面bootargs环境变量值,传递给Linux内核作为命令行参数,command line。uboot里面bootargs值为:
bootargs=console=ttymxc0,115200 root=/dev/nfs rw
nfsroot=192.168.199.158:/home/denghengli/linux/nfs/rootfs ip=192.168.199.20:192.168.199.158:192.168.199.1:255.255.255.0::eth0:off
//说明:192.168.199.158为ubuntu主机ip,192.168.199.20为开发板ip
linux内核cmdline值为:
Kernel command line: console=ttymxc0,115200 root=/dev/nfs rw
nfsroot=192.168.199.158:/home/denghengli/linux/nfs/rootfs ip=192.168.199.20:192.168.199.158:192.168.199.1:255.255.255.0::eth0:off
那么uboot是如何向kernel传递bootargs的呢?
经过查看发现chosen节点中包含bootargs属性,属性值和uboot的bootargs一致。uboot接触过dtb,最终通过bootz 80800000 – 83000000 来启动内核。经过分析判断uboot拥有bootargs环境变量和dtb,因此最有可能“作案”。最终发现在uboot的fdt_chosen函数中会查找chosen节点,并且在里面添加bootargs属性,属性值为bootargs变量值。
(1)值是字符串。ompatible属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序, compatible属性的值格式如下所示:"manufacturer,model",其中 manufacturer表示厂商, model一般是模块对应的驱动名字。比如imx6ull-alientek-emmc.dts中 sound节点是 I.MX6U-ALPHA开发板的音频设备节点:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
其中 fsl表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和 imx-audio-wm8960”表示驱动模块名字。
sound这个设备首先使用第一个兼容值在 Linux内核里面查找,看看能不能找到与之匹配的驱动文件,
如果没有找到的话就使用第二个兼容值查。
(2)一般驱动程序文件都会有一个 OF匹配表,此 OF匹配表保存着一些 compatible值,如果设备节点的 compatible属性值和 OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。
static const struct of_device_id imx_wm8960_dt_ids[] = {
{ .compatible = "fsl,imx-audio-wm8960", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);
static struct platform_driver imx_wm8960_driver = {
.driver = {
.name = "imx-wm8960",
.pm = &snd_soc_pm_ops,
.of_match_table = imx_wm8960_dt_ids,
},
.probe = imx_wm8960_probe,
.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);
(3)根节点/下面的compatible。内核启动的时候会检查是否支持此平台,或机器。
不使用设备树的时候通过machine id来判断内核是否支持此机器:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
MACHINE_START(MX35_3DS, "Freescale MX35PDK")
/* Maintainer: Freescale Semiconductor, Inc */
.atag_offset = 0x100,
.map_io = mx35_map_io,
.init_early = imx35_init_early,
.init_irq = mx35_init_irq,
.init_time = mx35pdk_timer_init,
.init_machine = mx35_3ds_init,
.reserve = mx35_3ds_reserve,
.restart = mxc_restart,
MACHINE_END
展开以后:
static const struct machine_desc __mach_desc_MX35_3DS __used
__attribute__((__section__(".arch.info.init"))) = {
.nr = MACH_TYPE_MX35_3DS, //机器ID
.name = "Freescale MX35PDK",
.atag_offset = 0x100,
.map_io = mx35_map_io,
.init_early = imx35_init_early,
.init_irq = mx35_init_irq,
.init_time = mx35pdk_timer_init,
.init_machine = mx35_3ds_init,
.reserve = mx35_3ds_reserve,
.restart = mxc_restart,
};
使用设备树的时候不使用机器ID,而是使用根节点/下的compatible:
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
MACHINE_END
展开以后:
static const struct machine_desc __mach_desc_IMX6UL __used
__attribute__((__section__(".arch.info.init"))) = {
.nr = ~0,
.name = "Freescale i.MX6 Ultralite (Device Tree)",
.map_io = imx6ul_map_io,
.init_irq = imx6ul_init_irq,
.init_machine = imx6ul_init_machine,
.init_late = imx6ul_init_late,
.dt_compat = imx6ul_dt_compat,
};
model属性值也是一个字符串,一般 model属性描述设备模块信息,比如名字什么的,比如:
model = "wm8960-audio";
status属性看名字就知道是和设备状态有关的, status属性值也是字符串,字符串是设备的状态信息,可选的状态如表
在有子节点的设备中,用于描述子节点的地址信息。 #address-cells属性值决定了子节点 reg属性中地址信息所占用的字长 (32位 ),#size-cells属性值决定了子节点 reg属性中长度信息所占的字长 (32位 )。 #address-cells和 #size-cells表明了子节点应该如何编写 reg属性值,一般 reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度, reg属性的格式一为:
reg =
一般用于描述设备地址空间资源信息,一般都是某个外 设的寄存器地址范围信息
reg = <0x4600 0x100>;
ranges属性值可以为空或者按照 (child-bus-address,parent-bus-address,length)格式编写的数字矩阵, ranges是一个地址映射 /转换表, ranges属性每个项目由子地址、父地址和地址空间长度这三部分组成:
child-bus-address:子总线地址空间的物理地址,由父节点的 #address-cells确定此物理地址所占用的字长。
parent-bus-address :父总线地址空间的物理地址,同样由父节点的 #address-cells确定此物理地址所占用的字长。
length: 子地址空间的长度,由父节点的 #size-cells确定此地址 长度所占用的字长。
如果 ranges属性值为空值,说明子地址空间和父地 址空间完全相同,不需要进行地址转换,
device_type属性值为字符串, IEEE 1275会用到此属性,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。此属性只能用于 cpu节点或者 memory节点。imx6ull.dtsi的 cpu0节点用到了此属性
1、在驱动中想要获取设备树中的节点或属性信息,需要使用OF函数,这些 OF函数原型都定义在 include/linux/of.h文件中。
2,驱动要想获取到设备树节点内容,首先要找到节点。