Linux 设备树

43.1 什么是设备树?设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(DeviceTree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等.在 3.x 版本(具体哪个版本笔者也无从考证)以前的 Linux 内核中 ARM 架构并没有采用设备树。在没有设备树的时候 Linux 是如何描述 ARM 架构中的板级信息呢
摘要由CSDN通过智能技术生成

43.1 什么是设备树?

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device
Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如
CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等.3.x 版本(具体哪个版本笔者也无从考证)以前的 Linux 内核中 ARM 架构并没有采用设备
树。在没有设备树的时候 Linux 是如何描述 ARM 架构中的板级信息呢?在 Linux 内核源码中
大量的 arch/arm/mach-xxx 和 arch/arm/plat-xxx 文件夹,这些文件夹里面的文件就是对应平台下
的板级信息。比如在 arch/arm/mach-smdk2440.c 中有如下内容(有缩减):
示例代码 43.1.1 mach-smdk2440.c 文件代码段
90 static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
   
91
        92 .lcdcon5 = S3C2410_LCDCON5_FRM565 |
        93 S3C2410_LCDCON5_INVVLINE |
        94 S3C2410_LCDCON5_INVVFRAME |
        95 S3C2410_LCDCON5_PWREN |
        96 S3C2410_LCDCON5_HWSWP,
......
113 };
115 static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
   
        116 .displays = &smdk2440_lcd_cfg,
        117 .num_displays = 1,
        118 .default_display = 0,
......
133 };
134
135 static struct platform_device *smdk2440_devices[] __initdata = {
   
        136 &s3c_device_ohci,
        137 &s3c_device_lcd,
        138 &s3c_device_wdt,
        139 &s3c_device_i2c0,
        140 &s3c_device_iis,
141 };
上述代码中的结构体变量 smdk2440_fb_info 就是描述 SMDK2440 这个开发板上的 LCD 信
息的,结构体指针数组 smdk2440_devices 描述的 SMDK2440 这个开发板上的所有平台相关信
息。这个仅仅是使用 2440 这个芯片的 SMDK2440 开发板下的 LCD 信息, SMDK2440 开发板
还有很多的其他外设硬件和平台硬件信息。
    当 Linux 之父 linus 看到 ARM 社区向 Linux 内核添加了大量“无用”、冗余
的板级信息文件,不禁的发出了一句“ This whole ARM thing is a f*cking pain in the ass”。从此以
后 ARM 社区就引入了 PowerPC 等架构已经采用的设备树(Flattened Device Tree),将这些描述
板级硬件信息的内容都从 Linux 内中分离开来,用一个专属的文件格式来描述,这个专属的文
件就叫做设备树,文件扩展名为.dts。 一个 SOC 可以作出很多不同的板子,这些不同的板子肯
定是有共同的信息, 将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引
用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dts 描述
板级信息(也就是开发板上有哪些 IIC 设备、 SPI 设备等).dtsi 描述 SOC 级信息(也就是 SOC 有
几个 CPU、主频是多少、各个外设控制器信息等)。
这个就是设备树的由来,简而言之就是, Linux 内核中 ARM 架构下有太多的冗余的垃圾板
级信息文件,导致 linus 震怒,然后 ARM 社区引入了设备树。

43.2 DTS、 DTB 和 DTC

上一小节说了,设备树源文件扩展名为.dts,但是我们在前面移植 Linux 的时候却一直在使
用.dtb 文件,那么 DTS 和 DTB 这两个文件是什么关系呢? DTS 是设备树源码文件, DTB 是将
DTS 编译以后得到的二进制文件。将.c 文件编译为.o 需要用到 gcc 编译器,那么将.dts 编译为.dtb
需要什么工具呢?需要用到 DTC 工具! DTC 工具源码在 Linux 内核的 scripts/dtc 目录下,
scripts/dtc/Makefile 文件内容如下:
示例代码 43.2.1 scripts/dtc/Makefile 文件代码段
1 hostprogs-y := dtc
2 always := $(hostprogs-y)
3 4
dtc-objs:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
5 srcpos.o checks.o util.o
6 dtc-objs += dtc-lexer.lex.o dtc-parser.tab.o
......
可以看出, DTC 工具依赖于 dtc.c、 flattree.c、 fstree.c 等文件,最终编译并链接出 DTC 这
个主机文件。如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命
令:
make all
或者:
make dtbs
“ make all”命令是编译 Linux 源码中的所有东西,包括 zImage, .ko 驱动模块以及设备
树,如果只是编译设备树的话建议使用“ make dtbs”命令。
    基于 ARM 架构的 SOC 有很多种,一种 SOC 又可以制作出很多款板子,每个板子都有一
个对应的 DTS 文件,那么如何确定编译哪一个 DTS 文件呢?我们就以 I.MX6ULL 这款芯片对
应的板子为例来看一下,打开 arch/arm/boot/dts/Makefile,有如下内容:
示例代码 43.2.2 arch/arm/boot/dts/Makefile 文件代码段
381 dtb-$(CONFIG_SOC_IMX6UL) += \
382 imx6ul-14x14-ddr3-arm2.dtb \
383 imx6ul-14x14-ddr3-arm2-emmc.dtb \
......
400 dtb-$(CONFIG_SOC_IMX6ULL) += \
401 imx6ull-14x14-ddr3-arm2.dtb \
...
422 imx6ull-alientek-emmc.dtb \
423 imx6ull-alientek-nand.dtb \
424 imx6ull-9x9-evk.dtb \
425 imx6ull-9x9-evk-btwifi.dtb \
...
可以看出,当选中 I.MX6ULL 这个 SOC 以后(CONFIG_SOC_IMX6ULL=y),所有使用到
I.MX6ULL 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb。如果我们使用 I.MX6ULL 新做
了一个板子,只需要新建一个此板子对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb-
$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb
文件。
示例代码 43.2.2 中第 422423 行就是我们在给正点原子的 I.MX6U-ALPHA 开发板移植
Linux 系统的时候添加的设备树。关于.dtb 文件怎么使用这里就不多说了,前面讲解 Uboot 移
植、 Linux 内核移植的时候已经无数次的提到如何使用.dtb 文件了(uboot 中使用 bootz 或 bootm
命令向 Linux 内核传递二进制设备树文件(.dtb))。

## 43.3 DTS 语法
虽然我们基本上不会从头到尾重写一个.dts 文件,大多时候是直接在 SOC 厂商提供的.dts
文件上进行修改。但是 DTS 文件语法我们还是需要详细的学习一遍,因为我们肯定需要修改.dts
文件。大家不要看到要学习新的语法就觉得会很复杂, DTS 语法非常的人性化,是一种 ASCII
文本文件,不管是阅读还是修改都很方便。
本节我们就以 imx6ull-alientek-emmc.dts 这个文件为例来讲解一下 DTS 语法。关于设备树
详 细 的 语 法 规 则 请 参 考 《 Devicetree SpecificationV0.2.pdf 》 和
《 Power_ePAPR_APPROVED_v1.12.pdf》这两份文档,此两份文档已经放到了开发板光盘中,
路 径 为 : 4 、 参 考 资 料 ->Devicetree SpecificationV0.2.pdf 、 4 、 参 考 资 料 ->
Power_ePAPR_APPROVED_v1.12.pdf

43.3.1 .dtsi 头文件
和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。在 imx6ull-alientekemmc.dts 中有如下所示内容:
示例代码 43.3.1.1 imx6ull-alientek-emmc.dts 文件代码段
12 #include <dt-bindings/input/input.h>
13 #include "imx6ull.dtsi"12 行,使用“ #include”来引用“ input.h”这个.h 头文件。
第 13 行,使用“ #include”来引用“ imx6ull.dtsi”这个.dtsi 头文件。
看到这里,大家可能会疑惑,不是说设备树的扩展名是.dtsi 吗?为什么也可以直接引用 C
语言中的.h 头文件呢?这里并没有错, .dts 文件引用 C 语言中的.h 文件,甚至也可以引用.dts 文
件,打开 imx6ull-14x14-evk-gpmi-weim.dts 这个文件,此文件中有如下内容:
示例代码 43.3.1.2 imx6ull-14x14-evk-gpmi-weim.dts 文件代码段
9 #include "imx6ull-14x14-evk.dts"
可以看出,示例代码 43.3.1.2 中直接引用了.dts 文件,因此在.dts 设备树文件中,可以通过
“ #include”来引用.h、 .dtsi 和.dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后
缀。
一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范
围,比如 UART、 IIC 等等。比如 imx6ull.dtsi 就是描述 I.MX6ULL 这颗 SOC 内部外设情况信息
的,内容如下:
示例代码 43.3.1.3 imx6ull.dtsi 文件代码段
10 #include <dt-bindings/clock/imx6ul-clock.h>
11 #include <dt-bindings/gpio/gpio.h>
12 #include <dt-bindings/interrupt-controller/arm-gic.h>
13 #include "imx6ull-pinfunc.h"
14 #include "imx6ull-pinfunc-snvs.h"
15 #include "skeleton.dtsi"
......
50 cpus {
   
    51 #address-cells = <1>;
    52 #size-cells = <0>;
    53
    54 cpu0: cpu@0 {
   
            55 compatible = "arm,cortex-a7";
            56 device_type = "cpu";
            ......
    89 };
90 };
示例代码 43.3.1.3 中第 54~89 行就是 cpu0 这个设备节点信息,这个节点信息描述了
I.MX6ULL 这颗 SOC 所使用的 CPU 信息,比如架构是 cortex-A7,频率支持 996MHz、792MHz、
528MHz、396MHz 和 198MHz 等等。在 imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息,
I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚,比如 ecspi1~4、 uart1~8、 usbphy1~2、 i2c1~4
等等,关于这些设备节点信息的具体内容我们稍后在详细的讲解。

43.3.2 设备节点
设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设
备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。以下是从
imx6ull.dtsi 文件中缩减出来的设备树文件内容:
示例代码 43.3.2.1 设备树模板
1 / {
   
        2 aliases {
   
            3 can0 = &flexcan1;
        4 };
        5 6
        cpus {
   
            7 #address-cells = <1>;
            8 #size-cells = <0>;
            9
            10 cpu0: cpu@0 {
   
                11 compatible = "arm,cortex-a7";
                12 device_type = "cpu";
                13 reg = <0>;
            14 };
        15 };
        16
        17 intc: interrupt-controller@00a01000 {
   
            18 compatible = "arm,cortex-a7-gic";
            19 #interrupt-cells = <3>;
            20 interrupt-controller;
            21 reg = <0x00a01000 0x1000>,
            22 <0x00a02000 0x100>;
        23 };
24 }1 行,“ /”是根节点,每个设备树文件只有一个根节点。细心的同学应该会发现,imx6ull.dtsi
和 imx6ull-alientek-emmc.dts 这两个文件都有一个“ /”根节点,这样不会出错吗?不会的,因为
这两个“ /”根节点的内容会合并成一个根节点。
第 2617 行, aliases、 cpus 和 intc 是三个子节点,在设备树中节点命名格式如下:
node-name@unit-address
其中“ node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的
功能,比如“ uart1”就表示这个节点是 UART1 外设。“ unit-address”一般表示设备的地址或寄
存器首地址,如果某个节点没有地址或者寄存器的话“ unit-address”可以不要,比如“ cpu@0”、
“ interrupt-controller@00a01000”。
但是我们在示例代码 43.3.2.1 中我们看到的节点命名却如下所示:
cpu0:cpu@0
上述命令并不是“ node-name@unit-address”这样的格式,而是用“:”隔开成了两部分,“ :”
前面的是节点标签(label),“:”后面的才是节点名字,格式如下所示:
label: node-name@unit-address
引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,比如通过
&cpu0 就可以访问“ cpu@0”这个节点,而不需要输入完整的节点名字。再比如节点 “ intc:
interrupt-controller@00a01000”,节点 label 是 intc,而节点名字就很长了,为“interruptcontroller@00a01000”。很明显通过&intc 来访问“ interrupt-controller@00a01000”这个节点要方
便很多!
第 10 行, cpu0 也是一个节点,只是 cpu0 是 cpus 的子节点。
每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任
意的字节流。设备树源码中常用的几种数据形式如下所示:
①、字符串
compatible = "arm,cortex-a7";
上述代码设置 compatible 属性的值为字符串“ arm,cortex-a7”。
②、 32 位无符号整数
reg = <0>;
上述代码设置 reg 属性的值为 0, reg 的值也可以设置为一组值,比如:
reg = <0 0x123456 100>;
③、字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“ ,”隔开,如下所示:
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
上述代码设置属性 compatible 的值为“ fsl,imx6ull-gpmi-nand”和“ fsl, imx6ul-gpmi-nand”。

43.3.3 标准属性
节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以
自定义属性。除了用户自定义属性,有很多属性是标准属性, Linux 下的很多外设驱动都会使用
这些标准属性,本节我们就来学习一下几个常用的标准属性。
1、 compatible 属性
compatible 属性也叫做“兼容性”属性,这是非常重要的一个属性! compatible 属性的值是
一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要
使用的驱动程序, compatible 属性的值格式如下所示:
"manufacturer,model"
其中 manufacturer 表示厂商, model 一般是模块对应的驱动名字。比如 imx6ull-alientekemmc.dts 中 sound 节点是 I.MX6U-ALPHA 开发板的音频设备节点, I.MX6U-ALPHA 开发板上
的音频芯片采用的欧胜(WOLFSON)出品的 WM8960, sound 节点的 compatible 属性值如下:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
属性值有两个,分别为“ fsl,imx6ul-evk-wm8960”和“ fsl,imx-audio-wm8960”,其中“ fsl”
表示厂商是飞思卡尔,“ imx6ul-evk-wm8960”和“ imx-audio-wm8960”表示驱动模块名字。 sound
这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,
如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个 Linux 内核也没有找到
对应的驱动。
一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设
备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个
驱动。比如在文件 imx-wm8960.c 中有如下内容:
示例代码 43.3.3.1 imx-wm8960.c 文件代码段
632 static const struct of_device_id imx_wm8960_dt_ids[] = {
   
    633 {
    .compatible = "fsl,imx-audio-wm8960", },
    634 {
    /* sentinel */ }
635 }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值