学习章节:第4章 Linux设备树
前言:
前面章节中我们多次提到“设备树”这个概念,因为时机未到,所以当时并没有详细的讲解什么是“设备树”,本章我们就来详细的谈一谈设备树。掌握设备树是 Linux 驱动开发人员必备的技能!因为在新版本的 Linux 中, ARM 相关的驱动全部采用了设备树(也有支持老式驱动的,比较少),最新出的 CPU 其驱动开发也基本都是基于设备树的,比如 ST 新出的 STM32MP157、NXP的 I.MX8系列等。我们所使用的Linux版本为 4.1.15,其支持设备树,所以正点原子I.MX6UALPHA 开发板的所有 Linux 驱动都是基于设备树的。本章我们就来了解一下设备树的起源、重点学习一下设备树语法。
什么是设备树:
在我们学习stm32的时候,我们经常会用到I2c,SPI等协议来外接一些设备,我们需要在编译器里添加对应的I2c.c和I2c.h,或者直接使用st官方的cubemx软件启用相应的协议,便会自动生成代码,虽然很是方便,但不方便管理,在Linux系统中,设备树(Device Tree)是一种描述硬件信息和设备布局的数据结构,用于在启动时动态配置硬件设备。设备树以一种与硬件无关的方式描述硬件设备和设备之间的连接关系,从而简化了内核的开发和移植工作。描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备。书中用一张图形象的描绘了设备树
在图中,树的主干就是系统总线, IIC 控制器、 GPIO 控制器、 SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02
这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。 DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息。
DTS,DTB,DTC:
DTS(Device Tree Source):DTS是设备树的源文件,使用一种类似于C语言的语法来描述硬件设备的信息、属性和连接关系。DTS文件通常以.dts为后缀,可以使用文本编辑器来编辑。DTS文件描述了硬件设备的树状结构,包括各种设备节点、设备属性和设备之间的连接关系。
DTB(Device Tree Blob):DTB是设备树的编译结果,是DTS文件经过编译后生成的二进制文件。DTB文件通常以.dtb为后缀,是Linux内核在启动时加载并解析的设备树文件。DTB文件包含了硬件设备的信息、属性和连接关系,以供内核在运行时进行设备的配置和管理。
DTC(Device Tree Compiler):DTC是设备树的编译器,用于将DTS文件编译成DTB文件。DTC工具可以在Linux系统中使用,通过命令行将DTS文件编译成DTB文件,然后将DTB文件加载到内核中。DTC还可以用于验证和检查设备树的语法正确性,以确保设备树文件能够正确地描述硬件设备和设备之间的连接关系。
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、 IIC 等等。以下是 imx6ull.dtsi 描述 I.MX6ULL 这颗 SOC 内部外设情况信息
的,内容如下:
#include <dt-bindings/clock/imx6ul-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "imx6ull-pinfunc.h"
#include "imx6ull-pinfunc-snvs.h"
#include "skeleton.dtsi"
/ {
/* 定义设备的别名 */
aliases {
can0 = &flexcan1;
...
};
/* 定义 CPU 节点 */
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
...
};
};
/* 定义中断控制器节点 */
intc: interrupt-controller@00a01000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x00a01000 0x1000>,
<0x00a02000 0x100>;
};
/* 定义时钟节点 */
clocks {
#address-cells = <1>;
#size-cells = <0>;
ckil: clock@0 {
compatible = "fixed-clock";
reg = <0>;
#clock-cells = <0>;
clock-frequency = <32768>;
clock-output-names = "ckil";
};
...
};
/* 定义 SoC 节点 */
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
interrupt-parent = <&gpc>;
ranges;
busfreq {
compatible = "fsl,imx_busfreq";
...
};
...
};
/* 定义 GPMI NAND 节点 */
gpmi: gpmi-nand@01806000 {
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpminand";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x01806000 0x2000>, <0x01808000 0x4000>;
...
};
};
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。
设备树模板:
学习设备树最重要的就是要能写出设备树文件,书中提供了一个小型设备树模板供学习,我学着写了一个简单的设备树模板
/dts-v1/;
/plugin/;
/ {
compatible = "myboard";
/* 定义设备节点 */
mydevice {
compatible = "mydevice";
status = "okay";
/* 定义属性 */
myproperty = <123>;
/* 定义子节点 */
subnode {
compatible = "subdevice";
status = "okay";
};
};
/* 定义中断控制器节点 */
interrupt-controller@0 {
compatible = "my-intc";
#interrupt-cells = <2>;
interrupt-controller;
};
/* 定义时钟节点 */
clocks {
#address-cells = <1>;
#size-cells = <0>;
myclock {
compatible = "myclock";
reg = <0>;
};
};
/* 定义GPIO节点 */
gpio {
compatible = "mygpio";
#gpio-cells = <2>;
mygpio {
gpio-controller;
#gpio-cells = <2>;
gpio-line-names = "mygpio";
};
};
};
这段设备树代码创建了一个名为"myboard"的设备树根节点,并定义了以下内容:
-
设备节点 "mydevice":定义了一个名为"mydevice"的设备节点,其compatible属性为"mydevice",状态为"okay"。该设备节点包含了一个名为"myproperty"的属性,其值为123,并包含一个子节点"subnode",其compatible属性为"subdevice",状态为"okay"。
-
中断控制器节点:定义了一个名为"interrupt-controller@0"的中断控制器节点,其compatible属性为"my-intc",指定了中断单元的格式为2个单元。
-
时钟节点 "clocks":定义了一个名为"myclock"的时钟节点,其compatible属性为"myclock",并指定了寄存器地址为0。
-
GPIO节点 "gpio":定义了一个GPIO节点,其compatible属性为"mygpio",并指定了GPIO单元的格式为2个单元。在该节点下定义了一个名为"mygpio"的GPIO控制器,包含了GPIO线的名称为"mygpio"。
这个小型设备树没有实际的意义,做这个的目的是为了掌握设备树的语法。在实际产品开发中,我们是不需要完完全全的重写一个.dts 设备树文件,一般都是使用 SOC 厂商提供好的.dts 文件,我们只需要在上面根据自己的实际情况做相应的修改即可。以我手上的mp135为例,我们来修改一个pwm的设备树代码
修改设备树:
先进入板子的串口终端,用之前用到的Mobaxterm进入,输入
命令,终端没有反应,说明这个板子的系统没有支持pwm驱动,下面就通过设备树改驱动,想要改驱动可不是件容易事,我们一步一步来,首先下载板子的出场源码,然后跟我们下载板子的SDK那一期一样,从win拷贝到Ubuntu虚拟机上,创建文件夹,然后解压,之后我们便会得到
与sdk那一期不同的是,我们此次还需要对源码进行打补丁和选择应用架构等操作,具体的可以参考大佬的这一篇帖子,【STM32MP135F-DK】3-舵机驱动与pwm脉冲宽度调制功能测试 - stm32/stm8 - 电子工程世界-论坛 (eeworld.com.cn),然后我们进入文件找到linux-6.1.28源码目录,开始找寻我们的设备树文件,打开arch/arm/boot/dts所在的文件夹,这个文件夹是存放所有板子的设备树文件的文件夹。找到stm32mp131.dtsi文件,这个文件描述了系统的一些内部资源,找到timers(因为pwm由定时器产生),这些timers全部是关着的,参考书中讲到的status属性:
由此可以断定这个设备树是需要我们修改的。于是我们在stm32mp135f-dk.dts这个文件中找ctrl+f搜索timers3,
把所有的disable改为okey,这样我们就完成了一次开发板设备树的修改,之后我们需要编译设备树文件,然后把编译好的设备树文件拷贝到板子上,这个时候我们再ls一下,终端就会显示我们开启的time3的通道4口pwm已开启,至此此次修改设备树的操作就完成了。