设备树(Device Tree)起源于IEEE1275 OpenFirmware和Power(PC) ePAPR标准,主要适用于Power(PC)架构,用于引导程序(Bootloader)向操作系统内核传递硬件配置和内核参数。目前,设备树已经扩展到U-Boot/Linux的Power(PC)/ARM/RISC-V/MIPS/x86等多个架构,从而大大简化了代码中的驱动的配置信息。
1. 简介
设备树的官网为devicetree.org,提供相关的FAQ和最新的标准等。对应的文档和库存放在github.com/devicetree-org和kernel.org/dtc。
设备树包括如下文件类型:
- DTS(Device Tree Source):文本形式的设备树源文件,后缀为
dts
,可以包含头文件和DTSI文件,于配置如下信息:- 硬件信息, 包括设备的驱动兼容信息字符串、寄存器地址、中断请求、时钟、管脚复用、引用关系,以及CPU、Cache(高速缓存)、总线、物理内存等;
- 内核参数,包括用于匹配BSP(板级支持包)和驱动的兼容信息字符传,启动参数,调试串口,从核启动方式等。
- DTSI(Device Tree Source Include):文本形式的设备树源包含文件,后缀为
dtsi
,用于将若干个DTS文件公用的配置信息抽离出来,从而被多个DTS文件包含,从而简化DTS文件的配置;注意,DTSI文件可以包含其他DTSI文件,形成多级嵌套; - DTB(Devic Tree Blob):设备树源文件编译而成的二进制文件,用于被U-Boot/Linux访问以获取配置信息;
- DTSO:安卓引入的扩展,用于对DTS文件中已经存在硬件信息进行修改,需要包含
/plugin/;
标签; - DTBO:安卓引入的扩展,从DTSO编译而来。
2. 设备树配置
设备树本质是一个树形结构,如下所示:
/dts-v1/;
#include "xxx.dtsi"
/ {
model = "board name";
compatible = "vendor,BSP name";
aliases {
ts0 = &tempsensor0;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a72";
enable-method = "psci";
reg = <0x0>;
clocks = <&clockgen 1 0>;
d-cache-size = <0x8000>;
...
};
...
};
memory@0 {
device_type = "memory";
reg = < 0x00 0x80000000 0x00 0x40000000 0x00 0x00 0x00 0x00 >;
};
chosen {
stdout-path = "/soc/serial@48020000";
bootargs = "console=ttyS0,115200n8 root=xxx rw rootfstype=ext4 ...";
};
...
soc {
#address-cells = <2>;
#size-cells = <2>;
...
i2c0: i2c@2000000 {
compatible = "fsl,vf610-i2c";
#address-cells = <1>;
#size-cells = <0>;
reg = <0x0 0x2000000 0x0 0x10000>;
interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
clock-names = "i2c";
clocks = <&clockgen 4 15>;
scl-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>;
tempsensor0: i2c3dev0@62 {
compatible = "nxp,sa56004";
reg = <0x4c>;
vcc-supply = <&sb_3v3>;
};
...
};
...
};
...
};
其中:
- 所有节点都从根节点
/
开始逐级包含,名称和路径都不得重复; - 每个节点的路径都是从根节点开始计算,例如
/soc/i2c@2000000/i2c3dev0@62
:为了方便使用,可以为每个节点加上1个标签,例如tempsensor0
、clockgen
; - 每个节点都包含若干个属性,用于配置设备驱动软件需要的各种信息;这些属性与软件紧密相关,没有统一的标准;其中的
compatible
属性包含若干个兼容字符,按照从高到低的优先级依次排列,用于匹配到最合适的驱动软件。
设备树的其他具体配置规范可以参考github.com/devicetree-org/devicetree-specification。
3. 设备树访问
设备树源文件(DTS/DTSO)通过开源工具DTC(Devic Tree compiler)转换为为DTB文件或者包含DTB二进制数据的汇编文件,进而与U-Boot/Linux等编译在一起,并可以通过libfdt
库从DTB文件和二进制数据中获取信息。
DTC工具源码托存在kernel.org/dtc,包含DTC工具和libfdt
库,也可以使用操作系统自带的工具安装,例如:
sudo apt install device-tree-compiler
DTC工具除了可以从DTS生成DTB,还可以从DTB文件和文件系统(/sys/firmware/devicetree/base
或者/proc/device-tree
)生成DTS文件,例如:
$ dtc -h # 查看帮助信息
$ dtc -I dts -O dtb -o xxx.dtb xxx.dts # 从DTS生成DTB
$ dtc -I dtb -O dts -o xxx.dts xxx.dtb # 从DTB生成DTS
$ dtc -I fs -O dts -o xxx.dts /sys/firmware/devicetree/base # 从文件系统生成DTS
DTC工具包含的libfdt
库用于访问通过DTB文件或者包含DTB二进制数据的汇编文件形式加载到内存或者其他存储介质中的设备树信息中,其接口定义在libfdt/libfdt.h。
4. U-Boot/Linux设备树获取
有源码的情况下,设备树文件通常可以通过搜索后缀为dts
、dtsi
和dtso
的文件来获取。其中:
-
对于U-Boot,设备树文件通常可以defconfig配置文件中
CONFIG_DEFAULT_DEVICE_TREE
对应的设备树名字或来寻找者根据defconfig配置文件的名字来猜测; -
对于Linux,设备树文件的名字通常可以从内核加载信息里面根据
Machine model
即DTS中的根节点下的model
属性来定位DTS文件:[ 0.000000] Booting Linux on physical CPU 0x0 ... [ 0.000000] CPU: ARMv7 Processor [412fc0f2] revision 2 (ARMv7), cr=30c5387d [ 0.000000] CPU: div instructions available: patching division code [ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache [ 0.000000] OF: fdt:Machine model: TL570x-EVM
没有源码的情况下,U-Boot的设备树暂时没有办法获取,但是Linux的设备树却可以通过U-Boot来获取:
-
获取设备树地址:
-
对于Legacy加载模式,Linux内核和DTB文件通常使用
bootm <内核地址> <initrd文件系统地址> <DTB地址>
,其中initrd文件系统地址
可能用-
代替;因此可以先执行bootm
之前的命令,使得DTB文件被加载到内存中去; -
对于FIT加载模式,Linux内核和DTB文件被编译在一个镜像中,加载信息中
fdt
镜像对应的Data Start
地址即FIT镜像加载后设备树的地址,因此可以先执行bootm
之前的命令,使得FIT镜像被加载到内存中去,进而将设备树拷贝到最终的内存位置即Booting using the fdt blob at 0xXXXXXXXX
包含的地址:## Loading kernel from FIT Image at a0000000 ... Using 'ls1046afrwy' configuration Trying 'kernel' kernel subimage ... ## Loading ramdisk from FIT Image at a0000000 ... Using 'ls1046afrwy' configuration Trying 'initrd' ramdisk subimage ... ## Loading fdt from FIT Image at a0000000 ... Using 'ls1046afrwy' configuration Trying 'ls1046afrwy-dtb' fdt subimage Description: ls1046afrwy-dtb Type: Flat Device Tree Compression: uncompressed Data Start: 0xa19d41b8 Data Size: 31569 Bytes = 30.8 KiB Architecture: AArch64 Load Address: 0x90000000 Hash algo: crc32 Hash value: d30014cb Verifying Hash Integrity ... crc32+ OK Loading fdt from 0xa19d41b8 to 0x90000000 Booting using the fdt blob at 0x90000000 ... Starting kernel ...
-
-
使用如下命令设置设备树基地址到上一步发现的设备树地址并dump:
=> fdt addr 90000000; fdt print
-
如果U-Boot支持U盘、SD卡等,还可以直接将设备树文件转存上去,以USB为例(SD卡类似):
=> usb start starting USB... USB0: Register 2000140 NbrPorts 2 Starting the controller USB XHCI 1.00 scanning bus 0 for devices... 2 USB Device(s) found scanning usb for storage devices... 1 Storage Device(s) found => usb dev IDE device 0: Vendor: SanDisk Rev: 0 Prod: Extreme Pro Type: Removable Hard Disk Capacity: 122112.0 MB = 119.2 GB (250085376 x 512) => usb part Partition Map for USB device 0 -- Partition Type: DOS Part Start Sector Num Sectors UUID Type 1 2048 195309568 000930ef-01 0b Boot 2 195313662 54769666 000930ef-02 05 Extd 5 195313664 54769664 000930ef-05 83 => fatwrite usb 0:1 ${fdtaddr} am5708_uboot.dtb 16F67 writing am5708_uboot.dtb 94055 bytes written
其中,用于转存的USB分区为
IDE device 0
上的分区1,因此使用usb 0:1
来写入;对于SD卡来说,不需要执行usb start
命令,只需要确认设备和分区即可。
此外,如果Linux文件系统中包含/sys/firmware/devicetree/base
或者/proc/device-tree
目录,则设备树还可以直接通过文件系统来获取:
$ dtc -I fs -O dts -o xxx.dts /sys/firmware/devicetree/base
网友评论