共计 5591 个字符,预计需要花费 14 分钟才能阅读完成。
本文丸趣 TV 小编为大家详细介绍“linux dts 的作用是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“linux dts 的作用是什么”文章能帮助大家解决疑惑,下面跟着丸趣 TV 小编的思路慢慢深入,一起来学习新知识吧。
在 linux 中,dts 是设备树源文件,用于描述设备信息的;设备树技术将设备的硬件资源信息就写在 dts 文件中。设备树源文件 dts 被编译成 dtb 二进制,在 bootloader 运行时传递给操作系统,操作系统对其进行解析展开,从而产生一个硬件设备的拓扑图,有了这个拓扑图,在编成过程可以直接通过系统提供的接口获取到设备树的节点和属性信息。
1、什么是设备树?
设备树 (dt:device tree) 是 linux 内核采用的参数表示和传递技术,在系统引导启动阶段进行设备初始化的时候,将设备树中描述的硬件信息传递给操作系统;
dts(device tree source):设备树源文件,描述设备信息的;
设备树源文件 dts 被编译成 dtb 二进制,在 bootloader 运行时传递给操作系统,操作系统对其进行解析展开,从而产生一个硬件设备的拓扑图,有了这个拓扑图,在编成过程可以直接通过系统提供的接口获取到设备树的节点和属性信息
dtc(device tree compiler):设备树编译 / 反编译 / 调试工具;
dtb(device tree binary):二进制设备树镜像;
dtsi(device tree source include): 功能类似设备树文件的头文件,可以被 dts 文件通过 include 引用,dtsi 文件一般是描述共性部分;
2、设备树解决什么问题
在设备驱动源码中,分为驱动代码和设备代码,驱动代码是操作硬件的方法,设备代码是硬件资源、数据,当驱动代码和设备代码匹配时就会调用驱动的 probe 函数,probe 函数会利用设备代码的资源去初始化设备;
设备树之前,设备代码都是直接写在内核源码中的,以 platform_device 结构体的形式存在,驱动代码和设备代码也是在 platform 总线上匹配,当需要修改设备资源时,就需要修改内核源码;
设备树技术将设备的硬件资源信息就写在 dts 文件中,需要修改就修改 dts 文件,不必在修改内核源码;
不采用设备树技术:内核源码中会充斥大量设备硬件描述信息,导致内核源码不停增多,但是增多的硬件描述信息代码和内核功能并不相关;
采用设备树技术之后:设备的硬件描述信息都在 dts 文件中,修改方便,但是内核要增加解析 dts 文件格式的代码;
3、设备树怎么工作
驱动开发者根据硬件编写 / 修改 dts 文件,使得将来驱动代码能匹配到合适的设备硬件信息;
编译内核时,kernel 会先编译出 dtc,然后再用 dtc 将 dts 文件编译成 dtb;
uboot 启动 kernel 时,将内核镜像和 dtb 都重定位到内存,并告诉内核 dtb 的所在内存地址;
内核启动初期调用内部函数解析 dtb,得到硬件信息后再组装成硬件函数,最后去和驱动代码进行匹配;
4、设备树源码 dts 文件格式讲解
4.1、dts 文件在内核源码中的存放位置
arm 架构:arch/arm/boot/dts 目录中
4.2、dts 文件格式简介
注释用 /* */,注意 #开头的不是注释
分号是段落块之间的分隔符,{}和 [] 和 是段落块的封装符号,和 C 语言语言类
/dts-v1/ 节点,表示 dts 的版本号,目前都是 v1
/{}是根节点 root node,理论上只应该有一个根节点,有说法 dtc 会合并所有 root node 为同一个
dts 是树状的多节点组织,基本单元是 node,除 root 外其他 node 都有 parent,还可以有 child
4.3、节点格式
4.3.1、格式定义
[label:] node-name [@ unit-address]{[property]
[child nodes]
[child nodes]
......
};
4.3.2、格式解读
[]:表示该项可以省略,: 表示不可省略;
[label:]:label 是标签名,为了方便访问节点,后面可以直接通过 label 来访问该节点。
node-name:节点名称。根节点的名称必须是 /
[@unit-address]:unit-address 是设备地址,如 cpu node 就是 0、1 这种,reg node 就是 0x12010000 这种;
4.3.3、示例代码
cpus {
/* 下面三项是 cpus 节点的属性 */
#address-cells = 1
#size-cells = 0
enable-method = hisilicon,hi3516dv300
/* 下面是子节点 */
cpu@0 {
device_type = cpu
compatible = arm,cortex-a7
clock-frequency = HI3516DV300_FIXED_1000M
reg = 0
};
cpus 是 cpu 的父节点,从形式来能直观的看出来,cpu 节点是被 cpus 节点的大括号括起来的;
cpus 节点省略了标签名和设备地址,只有节点名称;
5、节点属性分析
5.1、GPIO 属性格式
/{
gpx1:gpx1{
controller;
#gpio-cells= 2
key@11400c24{
compatible= fs4412,key
reg= 0x11400c24 0x4
intn-key= gpx1 2 2
}
gpio-controller:说明该节点描述的是一个 gpio 控制器;
#gpio-cells:描述 gpio 使用节点的属性一个 cell 的内容;
5.2、compatible 属性格式
uart0: uart@120a0000 {
compatible = arm,pl011 , arm,primecell
reg = 0x120a0000 0x1000
interrupts = 0 6 4
clocks = clock HI3516DV300_UART0_CLK
clock-names = apb_pclk
status = disabled
/* 在驱动中对应的结构体 */
//struct device_driver- of_match_table- compatible
struct of_device_id {char name[32];
char type[32];
char compatible[128];
const void *data;
};
(1)compatible 属性是用于设备节点和设备驱动匹配用的,在内核描述驱动的 structdevice_driver 结构体中,compatible 变量中就会保存用于匹配的字符串,当设备节点和驱动的
compatible 相同时就匹配成功;
(2)compatible 后面可以有多个字符串,优先匹配靠前的字符串,靠前的字符串匹配不上才会匹配后面的字符串;
5.3、model 属性格式
/ {
model = Tyr DEMO Board
compatible = hisilicon,hi3516dv300
memory {
device_type = memory
reg = 0x82000000 0x20000000
};};
(1)model 是描述模块信息的,一般只有根节点才有,标明设备树文件对应的开发板的名称;
(2)在内核的启动打印中可以看到 model 的值:“OF: fdt:Machine model: Tyr DEMO Board”;
5.4、status 属性格式
uart0 {status = okay};
状态值含义 okey 表示设备是可操作的 disabled 表示当前不可操作,但是后续是可以更改为可操作性的 fail、failed 表示有严重错误,几乎不可能再可操作了
(1)status 描述设备信息状态,在设备树文件中可以根据需求设置模块的状态,功能就是开启 / 关闭某个模块;
(2)在 dtsi 文件中,默认都是关闭模块的,在开发板对应的 dts 文件中自己去打开需要的模块;
5.5、reg 属性格式
clock: clock@12010000 {
compatible = hisilicon,hi3516dv300-clock
#address-cells = 1 /* 表示 reg 里面的数据 address 占用一个字长 */
#size-cells = 1 /* 表示 reg 里面的数据 size 占用一个字长,注意字长不是字节 */
#clock-cells = 1
#reset-cells = 2
reg = 0x12010000 0x1000 /* 起始地址是 0x12010000,长度是 0x1000*/
};
reg 属性:配置某个硬件模块对应的地址范围信息;
#address-cells 属性:表示 reg 里面的数据 address 占用的字长,注意字长不是字节;
#size-cells:表示 reg 里面的数据 size 占用的字长,注意字长不是字节;
reg = address1 length2 address2 length3 …:address 一般用来表示起始地址,length 一般表示持续长度;
5.6、中断属性格式
gic: interrupt-controller@10300000 {
compatible = arm,cortex-a7-gic
#interrupt-cells = 3 /* 表示 interrupts 用三个 cell 来描述中断 */
#address-cells = 0
interrupt-controller; /* 标明 gic 节点是中断控制器 */
/* gic dist base, gic cpu base , no virtual support */
reg = 0x10301000 0x1000 , 0x10302000 0x100
};
ipcm: ipcm@045E0000 {
compatible = hisilicon,ipcm-interrupt
interrupt-parent = gic /* 父节点是 gic 节点 */
interrupts = 0 10 4 /* 中断域 中断 触发方式 */
reg = 0x10300000 0x4000
status = okay
};
(1)interrupt-controller:无值属性,表示这是个中断控制器 node
(2)#interrupt-cells:这是中断控制器节点的属性,用来标识这个控制器需要几个 cell 做中断描述符
(3)interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有这个属性,会自动依附父节点
(4)interrupts:一个中断标识符列表,表示每一个中断输出信号
6、特殊节点
6.1、chosen 子节点
6.1.1、chosen 子节点功能介绍
chosen {stdout-path = serial0:115200n8};
(1)chosen 子节点不对应真实的设备,是用来描述内核启动参数的,对应于 uboot 启动内核时传递的 bootargs 参数;
(2)上面是摘抄的内核 dts 文件中的 chosen 子节点,里面只设置了 stdout-path 属性,也就是把输出设置成串口 0,波特率是 115200;
(3)dts 文件中设置的属性会被覆盖点,具体就是 uboot 在启动内核时,会将 bootargs 启动参数转换成 chosen 子节点的属性,替换掉 dts 文件中设置的属性;
6.1.2、chosen 子节点在内核中的体现
~ # ls /proc/device-tree/chosen/
bootargs name
~ #
~ # cat /proc/device-tree/chosen/bootargs
mem=1408M console=ttyS0,115200 root=/dev/mmcblk0p7 rootfstype=squashfs rootwait
~ #
~ # cat /proc/device-tree/chosen/name
chosen
~ #
6.2、aliases 子节点
aliases {
serial0 = uart0;
gpio0 = gpio_chip0;
gpio1 = gpio_chip1;
gpio2 = gpio_chip2;
······
};
aliases 就是别名的意思,aliases 节点主要功能就是给节点定义别名,为了方便访问节点。不过我们在节点命名的时候可以加上 label 标签,直接通过 label 引用标签来访问也很方便,aliases 节点内部其实也是通过引用标签名来定义别名;
7、节点相关操作
7.1、节点引用和内容替换
gpio_chip1: gpio_chip@120d1000 {
compatible = arm,pl061 , arm,primecell
reg = 0x120d1000 0x1000
interrupts = 0 17 4
clocks = clock HI3516DV300_SYSAPB_CLK
clock-names = apb_pclk
#gpio-cells = 2
status = disabled
/* 引用 gpio_chip1 节点 */
gpio_chip1 {status = okay /* 替换 status 属性内容 */};
对于已经定义好的节点,我们通过引用节点的方式,重新定义某些属性,效果上看就是替换掉某些属性的值;
7.2、合并节点内容
/{
node{
key1=value1;node{
key2=value2;// 合并的结果
node{
key1=value1;key2=value2;}
有时候我们需要增加硬件描述的信息,这时候就可以在后面创新定义该节点,最后解析的时候会把同名节点不同的部分进行合并。
读到这里,这篇“linux dts 的作用是什么”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注丸趣 TV 行业资讯频道。