DTS简介
在Linux内核早期的时候,每个嵌入式系统的板载信息(总线,设备的寄存器地址等)都是Hardcode在arch/<cpu>/match-xxx/board-xxx.c之类的文件中,即必须存在这样的C文件,里面定义了板子上的硬件的地址等信息,这样做会造成很多很多的大量相似的没多少用的重复代码;为了解决这个问题,而引入了DTS(device tree source)
DTS即Device Tree Source 设备树源码, Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)
本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用Bootloader传递一个DB的形式。
每个嵌入式系统的描述硬件信息的DTS文件存放在arch/<cpu>/boot/dts/目录下,比如mediatek 的mt7622-rfb1.dts位于linux-5.4.6/arch/arm64/boot/dts/mediatek/ 目录下
#include <dt-bindings/input/input.h>
#include <dt-bindings/gpio/gpio.h>
#include "mt7622.dtsi"
#include "mt6380.dtsi"
/ {
model = "MediaTek MT7622 RFB1 board";
compatible = "mediatek,mt7622-rfb1", "mediatek,mt7622";
aliases {
serial0 = &uart0;
};
chosen {
stdout-path = "serial0:115200n8";
bootargs = "earlycon=uart8250,mmio32,0x11002000 swiotlb=512";
};
cpus {
cpu@0 {
proc-supply = <&mt6380_vcpu_reg>;
sram-supply = <&mt6380_vm_reg>;
};
cpu@1 {
proc-supply = <&mt6380_vcpu_reg>;
sram-supply = <&mt6380_vm_reg>;
};
};
DTS文件编译加载流程
如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file;通过DTC(Device Tree Compiler),可以将的Device Tree Source file变成适合机器处理的Device Tree Binary File(DTB,Device Tree Blob);在系统启动的时候,Bootloader可以将保存在FLASH中的DTB copy到内存,并把DTB的起始地址传递给kernel
dts文件编译加载流程如下图所示:
Device Tree可以描述的信息包括CPU的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况等等
DeviceTree 是一种描述了电路板上CPU、总线、设备组成的树形数据结构,Bootloader会将这些信息传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_Device、I2c_Client、Spi_Device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备
DeviceTree 不需要描述所有的硬件信息,那些可以动态探测到的设备是不需要描述的,例如USB device,不过对于SOC上的Usb Hostcontroller,它是无法动态识别的,需要在Device Tree中描述;同样的道理,在Computer System中,PCI Device可以被动态探测到,不需要在Device Tree中描述,但是PCI Bridge如果不能被探测,所以需要描述
DeviceTree 相关文件描述:
dts:DT源文件称为dts文件,是ASCII格式文本文件,一般一个dts文件对应一个Machine,ARM架构下dts文件存放于arch/arm/boot/dts/目录下
dtsi:多个Machine/SoC公用的dt文件,i代表includedtc;
dtsi和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi,即#include"skeleton.dtsi“或者 /include/ "skeleton.dtsi"
dtb:DeviceTree Blob,由dtc编译dts文件生成的二进制目标文件
dt.img:多个dtb文件打包形成dt.img,以适配多个Machine,dts/dtb的结构是标准化的,dt.img有头信息和多个dtb组成,因为没有统一的标准,不同的厂商头信息可能是不同的
DTS文件构成
Device Tree的基本单元是node,这些node被组织成树状结构,除了root node,每个node都只有一个parent;一个device tree文件中只能有一个root node
- root node的node name是确定的,必须是“/”
- 每个node中包含了若干的property/value来描述该node的一些特性。
- 每个node用节点名字(nodename)标识,节点名字的格式是node-name@unit-address。
如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address;unit-address的具体格式是和设备挂在那个bus上相关,例如对于cpu,其unit-address就是从0开始编址,以此加一;而具体的设备,例如以太网控制器,其unit-address就是寄存器地址
正常情况下所有的dts文件以及dtsi文件都含有一个根节点”/”,Device Tree Compiler会对DTS的node进行合并,最终生成的DTB中只有一个 root node.
DTS 常见Node和常见属性
Chosen Node
chosen {
stdout-path = "serial0:115200n8";
bootargs = "earlycon=uart8250,mmio32,0x11002000 swiotlb=512";
};
chosen node 主要用来描述由系统指定的runtime parameter,它并没有描述任何硬件设备节点信息。原先通过tag list传递的一些linux kernel运行的参数,可以通过chosen节点来传递。如command line可以通过bootargs这个property来传递。如果存在chosen node,它的parent节点必须为“/”根节点。
Aliases Node
aliases {
serial0 = &uart0;
};
uart0: serial@11002000 {
compatible = "mediatek,mt7622-uart",
"mediatek,mt6577-uart";
reg = <0 0x11002000 0 0x400>;
interrupts = <GIC_SPI 91 IRQ_TYPE_LEVEL_LOW>;
clocks = <&topckgen CLK_TOP_UART_SEL>,
<&pericfg CLK_PERI_UART0_PD>;
clock-names = "baud", "bus";
status = "disabled";
};
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
status = "okay";
};
aliases node用来定义别名,类似C++中引用。上面是一个在.dtsi中的典型应用,当使用uart0时,也即使用serial@11002000,使得引用节点变得简单方便。例:当.dts include 该.dtsi时,将uart0的status属性赋值为okay,则表明该主板上的serial@11002000处于enable状态;反之,status赋值为disabled,则表明该主板上的serial@11002000处于disable状态。
Memory Node
memory {
reg = <0 0x40000000 0 0x20000000>;
};
对于memory node,device_type必须为memory,由之前的描述可以知道该memory node是以0x00000000为起始地址,以0x80000000为结束地址的1GB的空间。
其中的reg 属性:
reg的组织形式为reg = <address1 length1 [address2 length2][address3 length3] ... >,其中的每一组address length表明了设备使用的一个地址范围。address为1个或多个32位的整型(即cell),而length则为cell的列表或者为空(若#size-cells = 0)。address和length字段是可变长的,父结点的#address-cells和#size-cells分别决定了子结点的reg属性的address和length字段的长度。
Compatible 属性
/ {
model = "MediaTek MT7622 RFB1 board";
compatible = "mediatek,mt7622-rfb1", "mediatek,mt7622";
aliases {
serial0 = &uart0;
};
compatible属性为string list,用来将设备匹配对应的driver驱动,优先级为从左向右。即compatible实现了原先内核版本3.x之前,platform_device中.name的功能
上述.dts文件中,root结点"/"的compatible 属性mediatek,mt7622-rfb1", "mediatek,mt7622;定义了系统的名称,它的组织形式为:<manufacturer>,<model>,Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine
Interrupts 属性
scpsys: scpsys@10006000 {
compatible = "mediatek,mt7622-scpsys",
"syscon";
#power-domain-cells = <1>;
reg = <0 0x10006000 0 0x1000>;
interrupts = <GIC_SPI 165 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 166 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 167 IRQ_TYPE_LEVEL_LOW>,
<GIC_SPI 168 IRQ_TYPE_LEVEL_LOW>;
infracfg = <&infracfg>;
clocks = <&topckgen CLK_TOP_HIF_SEL>;
clock-names = "hif_sel";
};
<>中第一个u32表示中断类型,第二个是中断号,第三个是中断触发条件
若子节点使用到中断(中断号、触发方法等等),则需用interrupt属性来指定,该属性的数值长度受中断控制器中#inrerrupt-controller值③控制,即interrupt属性<>中数值的个数为#inrerrupt-controller的值;本例中#inrerrupt-controller=<2>,因而④中interrupts的值为<0x3d 0>形式,具体每个数值的含义由驱动实现决定。
Ranges属性
power-domains = <&scpsys MT7622_POWER_DOMAIN_HIF0>;
bus-range = <0x00 0xff>;
ranges = <0x82000000 0 0x20000000 0x0 0x20000000 0 0x10000000>;
status = "disabled";
pcie0: pcie@0,0 {
reg = <0x0000 0 0 0 0>;
#address-cells = <3>;
#size-cells = <2>;
#interrupt-cells = <1>;
ranges;
status = "disabled";
ranges属性为地址转换表,这在pcie中使用较为常见,它表明了该设备在到parent节点中所对用的地址映射关系。ranges格式长度受当前节点#address-cell、parent节点#address-cells、当前节点#size-cell所控制。顺序为ranges=<前节点#address-cell, parent节点#address-cells , 当前节点#size-cell。
注:对于相同名称的节点,dtc会根据定义的先后顺序进行合并,其相同属性,取后定义的那个。
网友评论