設(shè)備樹(Device Tree)起源于IEEE1275 OpenFirmware和Power(PC) ePAPR標準,主要適用于Power(PC)架構(gòu),用于引導(dǎo)程序(Bootloader)向操作系統(tǒng)內(nèi)核傳遞硬件配置和內(nèi)核參數(shù)。目前,設(shè)備樹已經(jīng)擴展到U-Boot/Linux的Power(PC)/ARM/RISC-V/MIPS/x86等多個架構(gòu),從而大大簡化了代碼中的驅(qū)動的配置信息。
1. 簡介
設(shè)備樹的官網(wǎng)為devicetree.org,提供相關(guān)的FAQ和最新的標準等。對應(yīng)的文檔和庫存放在github.com/devicetree-org和kernel.org/dtc。
設(shè)備樹包括如下文件類型:
- DTS(Device Tree Source):文本形式的設(shè)備樹源文件,后綴為
dts
,可以包含頭文件和DTSI文件,于配置如下信息:- 硬件信息, 包括設(shè)備的驅(qū)動兼容信息字符串、寄存器地址、中斷請求、時鐘、管腳復(fù)用、引用關(guān)系,以及CPU、Cache(高速緩存)、總線、物理內(nèi)存等;
- 內(nèi)核參數(shù),包括用于匹配BSP(板級支持包)和驅(qū)動的兼容信息字符傳,啟動參數(shù),調(diào)試串口,從核啟動方式等。
- DTSI(Device Tree Source Include):文本形式的設(shè)備樹源包含文件,后綴為
dtsi
,用于將若干個DTS文件公用的配置信息抽離出來,從而被多個DTS文件包含,從而簡化DTS文件的配置;注意,DTSI文件可以包含其他DTSI文件,形成多級嵌套; - DTB(Devic Tree Blob):設(shè)備樹源文件編譯而成的二進制文件,用于被U-Boot/Linux訪問以獲取配置信息;
- DTSO:安卓引入的擴展,用于對DTS文件中已經(jīng)存在硬件信息進行修改,需要包含
/plugin/;
標簽; - DTBO:安卓引入的擴展,從DTSO編譯而來。
2. 設(shè)備樹配置
設(shè)備樹本質(zhì)是一個樹形結(jié)構(gòu),如下所示:
/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>;
};
...
};
...
};
...
};
其中:
- 所有節(jié)點都從根節(jié)點
/
開始逐級包含,名稱和路徑都不得重復(fù); - 每個節(jié)點的路徑都是從根節(jié)點開始計算,例如
/soc/i2c@2000000/i2c3dev0@62
:為了方便使用,可以為每個節(jié)點加上1個標簽,例如tempsensor0
、clockgen
; - 每個節(jié)點都包含若干個屬性,用于配置設(shè)備驅(qū)動軟件需要的各種信息;這些屬性與軟件緊密相關(guān),沒有統(tǒng)一的標準;其中的
compatible
屬性包含若干個兼容字符,按照從高到低的優(yōu)先級依次排列,用于匹配到最合適的驅(qū)動軟件。
設(shè)備樹的其他具體配置規(guī)范可以參考github.com/devicetree-org/devicetree-specification。
3. 設(shè)備樹訪問
設(shè)備樹源文件(DTS/DTSO)通過開源工具DTC(Devic Tree compiler)轉(zhuǎn)換為為DTB文件或者包含DTB二進制數(shù)據(jù)的匯編文件,進而與U-Boot/Linux等編譯在一起,并可以通過libfdt
庫從DTB文件和二進制數(shù)據(jù)中獲取信息。
DTC工具源碼托存在kernel.org/dtc,包含DTC工具和libfdt
庫,也可以使用操作系統(tǒng)自帶的工具安裝,例如:
sudo apt install device-tree-compiler
DTC工具除了可以從DTS生成DTB,還可以從DTB文件和文件系統(tǒng)(/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 # 從文件系統(tǒng)生成DTS
DTC工具包含的libfdt
庫用于訪問通過DTB文件或者包含DTB二進制數(shù)據(jù)的匯編文件形式加載到內(nèi)存或者其他存儲介質(zhì)中的設(shè)備樹信息中,其接口定義在libfdt/libfdt.h。
4. U-Boot/Linux設(shè)備樹獲取
有源碼的情況下,設(shè)備樹文件通常可以通過搜索后綴為dts
、dtsi
和dtso
的文件來獲取。其中:
對于U-Boot,設(shè)備樹文件通常可以defconfig配置文件中
CONFIG_DEFAULT_DEVICE_TREE
對應(yīng)的設(shè)備樹名字或來尋找者根據(jù)defconfig配置文件的名字來猜測;-
對于Linux,設(shè)備樹文件的名字通常可以從內(nèi)核加載信息里面根據(jù)
Machine model
即DTS中的根節(jié)點下的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的設(shè)備樹暫時沒有辦法獲取,但是Linux的設(shè)備樹卻可以通過U-Boot來獲取:
-
獲取設(shè)備樹地址:
對于Legacy加載模式,Linux內(nèi)核和DTB文件通常使用
bootm <內(nèi)核地址> <initrd文件系統(tǒng)地址> <DTB地址>
,其中initrd文件系統(tǒng)地址
可能用-
代替;因此可以先執(zhí)行bootm
之前的命令,使得DTB文件被加載到內(nèi)存中去;-
對于FIT加載模式,Linux內(nèi)核和DTB文件被編譯在一個鏡像中,加載信息中
fdt
鏡像對應(yīng)的Data Start
地址即FIT鏡像加載后設(shè)備樹的地址,因此可以先執(zhí)行bootm
之前的命令,使得FIT鏡像被加載到內(nèi)存中去,進而將設(shè)備樹拷貝到最終的內(nèi)存位置即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 ...
-
使用如下命令設(shè)置設(shè)備樹基地址到上一步發(fā)現(xiàn)的設(shè)備樹地址并dump:
=> fdt addr 90000000; fdt print
-
如果U-Boot支持U盤、SD卡等,還可以直接將設(shè)備樹文件轉(zhuǎn)存上去,以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
其中,用于轉(zhuǎn)存的USB分區(qū)為
IDE device 0
上的分區(qū)1,因此使用usb 0:1
來寫入;對于SD卡來說,不需要執(zhí)行usb start
命令,只需要確認設(shè)備和分區(qū)即可。
此外,如果Linux文件系統(tǒng)中包含/sys/firmware/devicetree/base
或者/proc/device-tree
目錄,則設(shè)備樹還可以直接通過文件系統(tǒng)來獲取:
$ dtc -I fs -O dts -o xxx.dts /sys/firmware/devicetree/base