简介

由来

platform的弊端:假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译

  • 而且会在arch/arm/plat-xxxarch/arm/mach-xxx下面留下大量的关于板级细节的代码。
  • 这些代码相对于Linux内核来说就是垃圾代码

设备树的引入是为了剔除相对于内核的垃圾代码。

  • 设备树文件来描述这些设备信息,也就是代替device.c文件,platform匹配上基本不变。
  • 使用设备树不仅可以去掉大量“垃圾代码”,并且采用文本格式方便阅读和修改。
  • 如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核。
  • 内核在对其进行解析和展开得到一个关于硬件的拓扑图。我们通过内核提供的接口获取设备树的节点属性就可以。
  • 即内核对于同一soc的不同主板,只需更换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件

设备树定义

Device Tree是一种描述硬件的数据结构,由一系列被命名的节点(node)和属性(property)组成

  • 节点本身可包含子节点
  • 所谓属性,其实就是成对出现的namevalue

  • Device Tree中,可描述的信息包括:CPU的数量和类别,内存基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况,GPIO控制器和GPIO使用情况,Clock控制器和Clock使用情况。
  • 设备树基本上就是画一棵电路板上由CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核
  • 内核可以识别这棵树,并根据它展开出Linux内核中的platform_devicei2c_clientspi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

DTS 、DTC 和 DTB

文件.dts是一种ASCII文件格式设备树描述,在Linux中,一个.dts文件对应一个ARM设备,一般放置在arch/arm/boot/dts目录下。

  • dtb文件是dts文件被编译后生成的二进制文件,由Linux内核解析
  • 有了设备树文件就可以在不改动Linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件。
  • dtc是将dts编译为dtb的工具。在Linux内核下可以单独编译设备树文件
名称.dtsdtc.dtb
类型ascii文件编译工具二进制文件

在内核源码/linux/linux-imx/arch/arm64/boot/dts/freescale/Makefile中可以看出,当选中某一个选项后,所有使用到设备树文件会被编译为.dtb。

  • 如果我们配置新的开发板,只需要新建一个此开发板对应的.dts 文件,然后将对应的.dtb 文件名添加到 dtb- $(CONFIG_ARCH_ROCKCHIP)下,这样在编译设备树的时候就会将对应的.dts 编译为二进制的.dtb 文件。

DTS 设备树语法结构

  • 一般情况下,我们不会从头编写一个完整的dts文件,SOC厂商一般会直接提供一个有着基本框架的dts文件,当需要添加自己的板子设备树文件时,基于厂商提供的dts文件修改即可。所以我们要了解dts设备树文件的语法,这样我们才清楚如何添加我们自己的设备。

dtsi头文件

device tree source include(dtsi)是更通用的设备树代码,也就是相同芯片但不同平台都可以使用的代码。

  • 由于一个 SOC 可能对应多个 ARM 设备,这些 dts 文件势必包含许多共同的部分,Linux 内核为了简化,把 SOC 公用的部分或者多个设备共同的部分提炼为.dtsi 文件,类似于 C 语言的头文件
  • .dtsi 文件也可以包含其他的.dtsi#include "imx8mm-evk.dtsi"#include关键字来引用了imx8mm-evk.dtsi文件,也可以像C语言那样来引用.h文件
  • 一般.dtsi 文件用于描述 SOC内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如UARTIIC 等等。
  • 例如fsl-imx8mm.dtsi就是描述iMX8MM这个 SOC 内部外设情况信息
#include "fsl-imx8-ca53.dtsi"
#include <dt-bindings/clock/imx8mm-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/pinctrl/pins-imx8mm.h>
#include <dt-bindings/thermal/thermal.h>
 
/ {
	compatible = "fsl,imx8mm";
	interrupt-parent = <&gpc>;
	#address-cells = <2>;
	#size-cells = <2>;
 
	aliases {
		ethernet0 = &fec1;
		i2c0 = &i2c1;
		i2c1 = &i2c2;
		i2c2 = &i2c3;
		i2c3 = &i2c4;
		serial0 = &uart1;
		serial1 = &uart2;
		serial2 = &uart3;
		gpio0 = &gpio1;
		gpio1 = &gpio2;
		gpio2 = &gpio3;
		gpio3 = &gpio4;
		gpio4 = &gpio5;
		spi0 = &flexspi0;
		usb0 = &usbotg1;
		usb1 = &usbotg2;
	};
 
	cpus {
		idle-states {
			entry-method = "psci";
 
			CPU_SLEEP: cpu-sleep {
				compatible = "arm,idle-state";
				arm,psci-suspend-param = <0x0010033>;
				local-timer-stop;
				entry-latency-us = <1000>;
				exit-latency-us = <700>;
				min-residency-us = <2700>;
				wakeup-latency-us = <1500>;
			};
		};
	};
 
	memory@40000000 {
		device_type = "memory";
		reg = <0x0 0x40000000 0 0x80000000>;
	};
 
	reserved-memory {
		#address-cells = <2>;
		#size-cells = <2>;
		ranges;
 
		/* global autoconfigured region for contiguous allocations */
		linux,cma {
			compatible = "shared-dma-pool";
			reusable;
			size = <0 0x28000000>;
			alloc-ranges = <0 0x40000000 0 0x80000000>;
			linux,cma-default;
		};
	};
 
	gic: interrupt-controller@38800000 {
		compatible = "arm,gic-v3";
		reg = <0x0 0x38800000 0 0x10000>, /* GIC Dist */
		      <0x0 0x38880000 0 0xC0000>; /* GICR (RD_base + SGI_base) */
		#interrupt-cells = <3>;
		interrupt-controller;
		interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-parent = <&gic>;
	};
 
	timer {
		compatible = "arm,armv8-timer";
		interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Physical Secure */
			     <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Physical Non-Secure */
			     <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>, /* Virtual */
			     <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_LOW)>; /* Hypervisor */
		clock-frequency = <8000000>;
		interrupt-parent = <&gic>;
	};
 
	clocks {
		#address-cells = <1>;
		#size-cells = <0>;
 
		osc_32k: clock@0 {
			compatible = "fixed-clock";
			reg = <0>;
			#clock-cells = <0>;
			clock-frequency = <32768>;
			clock-output-names = "osc_32k";
		};
 
		osc_24m: clock@1 {
			compatible = "fixed-clock";
			reg = <1>;
			#clock-cells = <0>;
			clock-frequency = <24000000>;
			clock-output-names = "osc_24m";
		};
 
		clk_ext1: clock@2 {
			compatible = "fixed-clock";
			reg = <3>;
			#clock-cells = <0>;
			clock-frequency = <133000000>;
			clock-output-names = "clk_ext1";
		};
 
		clk_ext2: clock@3 {
			compatible = "fixed-clock";
			reg = <4>;
			#clock-cells = <0>;
			clock-frequency = <133000000>;
			clock-output-names = "clk_ext2";
		};
 
		clk_ext3: clock@4 {
			compatible = "fixed-clock";
			reg = <5>;
			#clock-cells = <0>;
			clock-frequency = <133000000>;
			clock-output-names = "clk_ext3";
		};
 
		clk_ext4: clock@5 {
			compatible = "fixed-clock";
			reg = <6>;
			#clock-cells = <0>;
			clock-frequency= <133000000>;
			clock-output-names = "clk_ext4";
		};
	};
 
	mipi_pd: gpc_power_domain@0 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <0>;
		domain-name = "MIPI_PD";
	};
 
	pcie0_pd: gpc_power_domain@1 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <1>;
		domain-name = "PCIE0_PD";
	};
 
	usb_otg1_pd: gpc_power_domain@2 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <2>;
		domain-name = "USB_OTG1_PD";
	};
 
	usb_otg2_pd: gpc_power_domain@3 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <3>;
		domain-name = "USB_OTG2_PD";
	};
 
	gpu_2d_pd: gpc_power_domain@4 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <4>;
		domain-name = "GPU_2D_PD";
	};
 
	gpu_mix_pd: gpc_power_domain@5 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <5>;
		domain-name = "GPU_MIX_PD";
	};
 
	vpu_mix_pd: gpc_power_domain@6 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <6>;
		domain-name = "VPU_MIX_PD";
	};
 
	disp_mix_pd: gpc_power_domain@7 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <7>;
		domain-name = "DISP_MIX_PD";
	};
 
	vpu_g1_pd: gpc_power_domain@8 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <8>;
		domain-name = "VPU_G1_PD";
	};
 
	vpu_g2_pd: gpc_power_domain@9 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <9>;
		domain-name = "VPU_G2_PD";
	};
 
	vpu_h1_pd: gpc_power_domain@10 {
		compatible = "fsl,imx8mm-pm-domain";
		#power-domain-cells = <0>;
		domain-id = <10>;
		domain-name = "VPU_H1_PD";
	};
 
	gpio1: gpio@30200000 {
		compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
		reg = <0x0 0x30200000 0x0 0x10000>;
		interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
 
	gpio2: gpio@30210000 {
		compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
		reg = <0x0 0x30210000 0x0 0x10000>;
		interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
 
	gpio3: gpio@30220000 {
		compatible = "fsl,imx8mm-gpio", "fsl,imx35-gpio";
		reg = <0x0 0x30220000 0x0 0x10000>;
		interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
 
                ..................

设备节点信息

设备树从根节点开始,每个设备都是一个节点。

  • 根节点就相当于树根。节点和节点之间可以互相嵌套,形成父子关系。
  • 设备的属性用key-value对(键值对)来描述,每个属性用分号结束。
设备树结构模板
/ {
    node1 {
        a-string-property = "A string";
        a-string-list-property = "first string", "second string";
        a-byte-data-property = [0x01 0x23 0x34 0x56];
        child-node1 {
            first-child-property;
            second-child-property = <1>;
            a-string-property = "Hello, world";
        };
        child-node2 {
        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3 4>;
        child-node1 {
        };
    };
};
  • 一个单独的根节点:/
  • 两个子节点:node1node2
  • 两个 node1 的子节点:“child-node1”和“child-node2
  • 一些分散在树里的属性,属性是最简单的键-值对,它的值可以为或者包含一个任意的字节流
设备树源文件中仍有几个基本的数据表示形式
/ {
    *节点示例*
    node1 {
        *文本字符串*
        a-string-property = "A string";
        
        *cells*
        cell-property = <0xbeef 123 0xabcd1234>;
        
        *二进制数据*
        binary-property = [0x01 0x23 0x45 0x67];
        
        *混合数据*
        mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
        
        *字符串列表*
        string-list = "red fish", "blue fish";
    };
};
  • 文本字符串 (a-string-property):表示一个简单的字符串。
  • Cells (cell-property):表示一系列的32位无符号整数。
  • 二进制数据 (binary-property):表示一组二进制数据。
  • 混合数据 (mixed-property):包含字符串、二进制数据和整数的组合。
  • 字符串列表 (string-list):表示一个由逗号分隔的字符串列表。
设备节点及lable命名
  • 格式:<名称>[@<设备地址>]
  • <设备地址>就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,主要用来区分用。
  • 同一级的节点只要地址不一样,名字是可以不唯一的。
  • 设备地址是一个可选选项
  • 节点路径必须使用完整路径,可以使用别名来简化书写:别名:<名称>[@<设备地址>],后续直接通过别名访问。
  • 一般是通过引用向节点中添加内容
&uart8 {
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart8>;
        status = "okay";
};

注意事项:编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写

  • 使用引用可以避免四处找节点。如dtsdtsi里面都有根节点,但最终会合并成一个根节点

标准属性

  • address-cellssize-cells属性
  • 不同的平台,不同的总线,地址位长度可能不同,规范规定一个 32 位的长度为一个 cell
  • #address-cells属性用来表示总线地址需要几个 cell 表示,该属性本身是u32 类型的。
  • #size-cells属性用来表示子总线地址空间的长度需要几个cell 表示,属性本身的类型也是 u32
  • 这两个属性不可以继承,未定义这两个属性的时候,不会继承更高一级父节点的设置
  • 内核默认认为#address-cells2#size-cells1
spi4 {  // 主节点 spi4
    compatible = "spi-gpio";  // 兼容性字符串,指示这是一个 SPI GPIO 设备
    #address-cells = <1>;     // 地址由一个32位单元表示
    #size-cells = <0>;        // 由于是 SPI 设备,不需要指定大小
 
    gpio_spi: gpio_spi@0 {  // 子节点 gpio_spi
        compatible = "fairchild,74hc595";  // 兼容性字符串,指示这是一个特定的 SPI 设备
        reg = <0>;  // SPI 设备的地址,这里用一个单元表示
    };
};
 
aips3: aips-bus@02200000 {  // 主节点 aips-bus
    compatible = "fsl,aips-bus", "simple-bus";  // 兼容性字符串,指示这是一个 AIPS 总线设备
    #address-cells = <1>;  // 地址由一个32位单元表示
    #size-cells = <1>;     // 大小也由一个32位单元表示
 
    dcp: dcp@02280000 {  // 子节点 dcp
        compatible = "fsl,imx6sl-dcp";  // 兼容性字符串,指示这是一个特定的 DCP 设备
        reg = <0x02280000 0x4000>;  // 地址为 0x02280000,大小为 0x4000
    };
};

reg属性

reg属性用来表示节点地址资源的,比如常见的就是寄存器的起始地址及大小。要想表示一块连续地址,必须包含起始地址和空间大小两个参数,如果有多块地址,那么就需要多组这样的值表示。

  • 对于reg属性,每个元素是一个二元组,包含起始地址和大小。
  • 地址和大小用几个 u32 表示呢?由父节点的address-cellssize-cells属性决定。
uart1: serial@02020000 {  // 主节点 uart1
    compatible = "fsl,imx6ul-uart",  // 兼容性字符串,指示这是一个 Freescale i.MX6 UltraLite UART 设备
                 "fsl,imx6q-uart",   // 兼容性字符串,指示这是一个 Freescale i.MX6 Quad UART 设备
                 "fsl,imx21-uart";   // 兼容性字符串,指示这是一个 Freescale i.MX21 UART 设备
    reg = <0x02020000 0x4000>;       // 地址为 0x02020000,大小为 0x4000
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;  // 中断信息,使用 GIC_SPI 中断控制器,中断号为 26,触发方式为高电平
    clocks = <&clks IMX6UL_CLK_UART1_IPG>,  // IPG 时钟,引用名为 clks 的时钟节点
             <&clks IMX6UL_CLK_UART1_SERIAL>;  // 串行时钟,引用名为 clks 的时钟节点
    clock-names = "ipg", "per";      // 时钟名称,分别为 IPG 时钟和 PER 时钟
    status = "disabled";             // 设备状态为禁用
};

compatible属性

compatible 属性是操作系统用来决定设备和驱动绑定的关键因素。

  • 设备树中的每个表示一个设备的节点都需要一个 compatible 属性
  • compatible 属性也叫做兼容性属性,属性的值是一个字符串列表,用于表示是何种设备,可以在代码中进行匹配。
compatible = "manufacturer,model"
  • sound 节点的 compatible 属性值如下:
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
  • 属性值有两个,分别为fsl,imx6ul-evk-wm8960fsl,imx-audio-wm8960,其中fsl表示厂商是 飞思卡尔,imx6ul-evk-wm8960imx-audio-wm8960表示设备驱动的名字。
  • sound 这个设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查找,直到找到或者查找完整个Linux 内核也没有找到对应的驱动。

status属性

status 属性用来表示节点的状态,其实就是硬件的状态,用字符串表示。

  • okay表示硬件正常工作
  • disable表示当前硬件不可用
  • fail表示因为出错不可用
  • fail-sss表示某种原因出错不可用,sss 表示具体出错的原因。
  • 实际中,基本只用okaydisabl