一文搞定 Linux 設備樹
設備樹是一種描述硬件的數據結構,它起源于OpenFirmware(OF)。
在Linux 2.6中, ARM架構的板極硬件細節過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx中,采用設備樹后,許多硬件的細節可以直接通過它傳遞給Linux,而不再需要在內核中進行大量的冗余編碼。
1. linux設備樹中DTS、 DTC和DTB的關系
- (1) DTS:.dts文件是設備樹的源文件。由于一個SoC可能對應多個設備,這些.dst文件可能包含很多共同的部分,共同的部分一般被提煉為一個 .dtsi 文件,這個文件相當于C語言的頭文件。
- (2) DTC:DTC是將.dts編譯為.dtb的工具,相當于gcc。
- (3) DTB:.dtb文件是 .dts 被 DTC 編譯后的二進制格式的設備樹文件,它可以被linux內核解析。
2. DTS語法
2.1 .dtsi 頭文件
和 C 語言一樣,設備樹也支持頭文件,設備樹的頭文件擴展名為 .dtsi;同時也可以像C 語言一樣包含 .h頭文件;例如:(代碼來源 linux-4.15/arch/arm/boot/dts/s3c2416.dtsi)
- #include <dt-bindings/clock/s3c2443.h>
- #include "s3c24xx.dtsi"
注:.dtsi 文件一般用于描述 SOC 的內部外設信息,比如 CPU 架構、主頻、外設寄存器地址范圍,比如 UART、 IIC 等等。
2.2 設備節點
在設備樹中節點命名格式如下:
- node-name@unit-address
node-name:是設備節點的名稱,為ASCII字符串,節點名字應該能夠清晰的描述出節點的功能,比如“uart1”就表示這個節點是UART1外設;unit-address:一般表示設備的地址或寄存器首地址,如果某個節點沒有地址或者寄存器的話 “unit-address” 可以不要;注:根節點沒有node-name 或者 unit-address,它被定義為 /。
設備節點的例子如下圖:
在上圖中:cpu 和 ethernet依靠不同的unit-address 分辨不同的CPU;可見,node-name相同的情況下,可以通過不同的unit-address定義不同的設備節點。
2.2.1 設備節點的標準屬性
2.2.1.1 compatible 屬性
compatible 屬性也叫做 “兼容性” 屬性,這是非常重要的一個屬性!compatible 屬性的值是一個字符串列表, compatible 屬性用于將設備和驅動綁定起來。字符串列表用于選擇設備所要使用的驅動程序。compatible 屬性值的推薦格式:
- "manufacturer,model"
- ① manufacturer : 表示廠商;
- ② model : 一般是模塊對應的驅動名字。
例如:
- compatible = "fsl,mpc8641", "ns16550";
上面的compatible有兩個屬性,分別是 "fsl,mpc8641" 和 "ns16550";其中 "fsl,mpc8641" 的廠商是 fsl;設備首先會使用第一個屬性值在 Linux 內核里面查找,看看能不能找到與之匹配的驅動文件;
如果沒找到,就使用第二個屬性值查找,以此類推,直到查到到對應的驅動程序 或者 查找完整個 Linux 內核也沒有對應的驅動程序為止。
注:一般驅動程序文件都會有一個 OF 匹配表,此 OF 匹配表保存著一些 compatible 值,如果設備節點的 compatible 屬性值和 OF 匹配表中的任何一個值相等,那么就表示設備可以使用這個驅動。
2.2.1.2 model 屬性
model 屬性值也是一個字符串,一般 model 屬性描述設備模塊信息,比如名字什么的,例如:
- model = "Samsung S3C2416 SoC";
2.2.1.3 phandle 屬性
phandle屬性為devicetree中唯一的節點指定一個數字標識符,節點中的phandle屬性,它的取值必須是唯一的(不要跟其他的phandle值一樣),例如:
- pic@10000000 {
- phandle = <1>;
- interrupt-controller;
- };
- another-device-node {
- interrupt-parent = <1>; // 使用phandle值為1來引用上述節點
- };
注:DTS中的大多數設備樹將不包含顯式的phandle屬性,當DTS被編譯成二進制DTB格式時,DTC工具會自動插入phandle屬性。
2.2.1.4 status 屬性
status 屬性看名字就知道是和設備狀態有關的, status 屬性值也是字符串,字符串是設備的狀態信息,可選的狀態如下表所示:
status值 | 描述 |
---|---|
“okay” | 表明設備是可操作的。 |
“disabled” | 表明設備當前是不可操作的,但是在未來可以變為可操作的,比如熱插拔設備插入以后。至于 disabled 的具體含義還要看設備的綁定文檔。 |
“fail” | 表明設備不可操作,設備檢測到了一系列的錯誤,而且設備也不大可能變得可操作。 |
“fail-sss” | 含義和“fail”相同,后面的 sss 部分是檢測到的錯誤內容 |
2.2.1.5 #address-cells 和 #size-cells
#address-cells 和 #size-cells的值都是無符號 32 位整型,可以用在任何擁有子節點的設備中,用于描述子節點的地址信息。#address-cells 屬性值決定了子節點 reg 屬性中地址信息所占用的字長(32 位), #size-cells 屬性值決定了子節點 reg 屬性中長度信息所占的字長(32 位)。#address-cells 和 #size-cells 表明了子節點應該如何編寫 reg 屬性值,一般 reg 屬性都是和地址有關的內容,和地址相關的信息有兩種:起始地址和地址長度,reg 屬性的格式一為:
- reg = <address1 length1 address2 length2 address3 length3……>
例如一個64位的處理器:
- soc {
- #address-cells = <2>;
- #size-cells = <1>;
- serial {
- compatible = "xxx";
- reg = <0x4600 0x5000 0x100>; /*地址信息是:0x00004600 00005000,長度信息是:0x100*/
- };
- };
2.2.1.6 reg 屬性
reg 屬性的值一般是 (address, length) 對,reg 屬性一般用于描述設備地址空間資源信息,一般都是某個外設的寄存器地址范圍信息。
例如:一個設備有兩個寄存器塊,一個的地址是0x3000,占據32字節;另一個的地址是0xFE00,占據256字節,表示如下:
- reg = <0x3000 0x20 0xFE00 0x100>;
注:上述對應#address-cells = <1>; #size-cells = <1>;。
2.2.1.7 ranges 屬性
ranges屬性值可以為空或者按照 (child-bus-address,parent-bus-address,length) 格式編寫的數字矩陣, ranges 是一個地址映射/轉換表, ranges 屬性每個項目由子地址、父地址和地址空間長度這三部分組成:
- child-bus-address:子總線地址空間的物理地址,由父節點的 #address-cells 確定此物理地址所占用的字長。
- parent-bus-address:父總線地址空間的物理地址,同樣由父節點的 #address-cells 確定此物理地址所占用的字長。
- length:子地址空間的長度,由父節點的 #size-cells 確定此地址長度所占用的字長。
- soc {
- compatible = "simple-bus";
- #address-cells = <1>;
- #size-cells = <1>;
- ranges = <0x0 0xe0000000 0x00100000>;
- serial {
- device_type = "serial";
- compatible = "ns16550";
- reg = <0x4600 0x100>;
- clock-frequency = <0>;
- interrupts = <0xA 0x8>;
- interrupt-parent = <&ipic>;
- };
- };
節點 soc 定義的 ranges 屬性,值為 <0x0 0xe0000000 0x00100000>,此屬性值指定了一個 1024KB(0x00100000) 的地址范圍,子地址空間的物理起始地址為 0x0,父地址空間的物理起始地址為 0xe0000000。
serial 是串口設備節點,
reg 屬性定義了 serial 設備寄存器的起始地址為 0x4600,寄存器長度為 0x100。
經過地址轉換, serial 設備可以從 0xe0004600 開始進行讀寫操作,0xe0004600=0x4600+0xe0000000。
2.2.1.8 name 屬性
name 屬性值為字符串, name 屬性用于記錄節點名字, name 屬性已經被棄用,不推薦使用name 屬性,一些老的設備樹文件可能會使用此屬性。
2.2.1.9 device_type 屬性
device_type 屬性值為字符串, IEEE 1275 會用到此屬性,用于描述設備的 FCode,但是設備樹沒有 FCode,所以此屬性也被拋棄了。此屬性只能用于 cpu 節點或者 memory 節點。
- memory@30000000 {
- device_type = "memory";
- reg = <0x30000000 0x4000000>;
- };
2.2.2 根節點
每個設備樹文件只有一個根節點,其他所有的設備節點都是它的子節點,它的路徑是 /。根節點有以下屬性:
屬性 | 屬性值類型 | 描述 |
---|---|---|
#address-cells | < u32 > | 在它的子節點的reg屬性中, 使用多少個u32整數來描述地址(address) |
model | < string > | 用于標識系統板卡(例如smdk2440開發板),推薦的格式是“manufacturer,model-number” |
compatible | < stringlist > | 定義一系列的字符串, 用來指定內核中哪個machinedesc可以支持本設備 |
例如:compatible = "samsung,smdk2440","samsung,s3c24xx" ,內核會優先尋找支持smdk2440的machinedesc結構體,如果找不到才會繼續尋找支持s3c24xx的machine_desc結構體(優先選擇第一項,然后才是第二項,第三項……)
2.2.3 特殊節點
2.2.3.1 /aliases 子節點
aliases 節點的主要功能就是定義別名,定義別名的目的就是為了方便訪問節點。
例如:定義 flexcan1 和 flexcan2 的別名是 can0 和 can1。
- aliases {
- can0 = &flexcan1;
- can1 = &flexcan2;
- };
2.2.3.2 /memory 子節點
所有設備樹都需要一個memory設備節點,它描述了系統的物理內存布局。如果系統有多個內存塊,可以創建多個memory節點,或者可以在單個memory節點的reg屬性中指定這些地址范圍和內存空間大小。
例如:一個64位的系統有兩塊內存空間:RAM1:起始地址是0x0,地址空間是 0x80000000;RAM2:起始地址是0x10000000,地址空間也是0x80000000;同時根節點下的 #address-cells = <2>和#size-cells = <2>,這個memory節點描述為:
- memory@0 {
- device_type = "memory";
- reg = <0x00000000 0x00000000 0x00000000 0x80000000
- 0x00000000 0x10000000 0x00000000 0x80000000>;
- };
或者:
- memory@0 {
- device_type = "memory";
- reg = <0x00000000 0x00000000 0x00000000 0x80000000>;
- };
- memory@10000000 {
- device_type = "memory";
- reg = <0x00000000 0x10000000 0x00000000 0x80000000>;
- };
2.2.3.3 /chosen 子節點
chosen 并不是一個真實的設備, chosen 節點主要是為了 uboot 向 Linux 內核傳遞數據,重點是 bootargs 參數。例如:
- chosen {
- bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
- };
2.2.3.4 /cpus 和 /cpus/cpu* 子節點
cpus節點下有1個或多個cpu子節點, cpu子節點中用reg屬性用來標明自己是哪一個cpu,所以 /cpus 中有以下2個屬性:
- #address-cells // 在它的子節點的reg屬性中, 使用多少個u32整數來描述地址(address)
- #size-cells // 在它的子節點的reg屬性中, 使用多少個u32整數來描述大小(size)
- // 必須設置為0
例如:
- cpus {
- #address-cells = <1>;
- #size-cells = <0>;
- cpu@0 {
- device_type = "cpu";
- reg = <0>;
- cache-unified;
- cache-size = <0x8000>; // L1, 32KB
- cache-block-size = <32>;
- timebase-frequency = <82500000>; // 82.5 MHz
- next-level-cache = <&L2_0>; // phandle to L2
- L2_0:l2-cache {
- compatible = "cache";
- cache-unified;
- cache-size = <0x40000>; // 256 KB
- cache-sets = <1024>;
- cache-block-size = <32>;
- cache-level = <2>;
- next-level-cache = <&L3>; // phandle to L3
- L3:l3-cache {
- compatible = "cache";
- cache-unified;
- cache-size = <0x40000>; // 256 KB
- cache-sets = <0x400>; // 1024
- cache-block-size = <32>;
- cache-level = <3>;
- };
- };
- };
- cpu@1 {
- device_type = "cpu";
- reg = <1>;
- cache-unified;
- cache-block-size = <32>;
- cache-size = <0x8000>; // L1, 32KB
- timebase-frequency = <82500000>; // 82.5 MHzclock-frequency = <825000000>; // 825 MHz
- cache-level = <2>;
- next-level-cache = <&L2_1>; // phandle to L2
- L2_1:l2-cache {
- compatible = "cache";
- cache-unified;
- cache-size = <0x40000>; // 256 KB
- cache-sets = <0x400>; // 1024
- cache-line-size = <32>; // 32 bytes
- next-level-cache = <&L3>; // phandle to L3
- };
- };
- };
2.2.3.5 引用其他節點
2.2.3.5.1 phandle
節點中的phandle屬性, 它的取值必須是唯一的(不要跟其他的phandle值一樣)
- pic@10000000 {
- phandle = <1>;
- interrupt-controller;
- };
- another-device-node {
- interrupt-parent = <1>; // 使用phandle值為1來引用上述節點
- };
2.2.3.5.2 label
- PIC: pic@10000000 {
- interrupt-controller;
- };
- another-device-node {
- interrupt-parent = <&PIC>; // 使用label來引用上述節點,
- // 使用lable時實際上也是使用phandle來引用,
- // 在編譯dts文件為dtb文件時, 編譯器dtc會在dtb中插入phandle屬性
- };
2.2.4 DTB格式
.dtb文件是 .dts 被 DTC 編譯后的二進制格式的設備樹文件,它的文件布局如下:
從上圖可以看出,DTB文件主要包含四部分內容:struct ftdheader、memory reservation block、structure block、strings block;
① struct ftdheader:用來表明各個分部的偏移地址,整個文件的大小,版本號等;
② memory reservation block:在設備樹中使用/memreserve/ 定義的保留內存信息;
③ structure block:保存節點的信息,節點的結構;
④ strings block:保存屬性的名字,單獨作為字符串保存;
struct ftd_header結構體的定義如下:
- struct fdt_header {
- uint32_t magic; /*它的值為0xd00dfeed,以大端模式保存*/
- uint32_t totalsize; /*整個DTB文件的大小*/
- uint32_t off_dt_struct; /*structure block的偏移地址*/
- uint32_t off_dt_strings; /*strings block的偏移地址*/
- uint32_t off_mem_rsvmap; /*memory reservation block的偏移地址*/
- uint32_t version; /*設備樹版本信息*/
- uint32_t last_comp_version; /*向后兼容的最低設備樹版本信息*/
- uint32_t boot_cpuid_phys; /*CPU ID*/
- uint32_t size_dt_strings; /*strings block的大小*/
- uint32_t size_dt_struct; /*structure block的大小*/
- };
fdtreserveentry結構體如下:
- struct fdt_reserve_entry {
- uint64_t address; /*64bit 的地址*/
- uint64_t size; /*保留的內存空間的大小*/
- };
該結構體用于表示memreserve的起始地址和內存空間的大小,它緊跟在struct ftdheader結構體后面。
例如:/memreserve/ 0x33000000 0x10000,fdtreserve_entry 結構體的成員 address = 0x33000000,size = 0x10000。
structure block是用于描述設備樹節點的結構,保存著節點的信息、節點的結構,它有5種標記類型:
① FDTBEGINNODE (0x00000001):表示節點的開始,它的后面緊跟的是節點的名字;
② FDTENDNODE (0x00000002):表示節點的結束;
③ FDTPROP (0x00000003) :表示開始描述節點里面的一個屬性,在FDTPROP后面緊跟一個結構體如下所示:
- struct {
- uint32_t len; /*表示屬性值的長度*/
- uint32_t nameoff; /*屬性的名字在string block的偏移*/
- }
注:上面的這個結構體后緊跟著是屬性值,屬性的名字保存在字符串塊(Strings block)中。
④ FDT_END (0x00000009):表示structure block的結束。
單個節點在structure block的存儲格式如下圖如所示:(注:子節點的存儲格式也是一樣)
總結:
(1) DTB文件可以分為四個部分:struct ftdheader、memory reservation block、structure block、strings block;
(2) 最開始的為struct ftdheader,包含其它三個部分的偏移地址;
(3) memory reservation block記錄保留內存信息;
(4) structure block保存節點的信息,節點的結構;
(5) strings block保存屬性的名字,將屬性名字單獨作為字符串保存。
2.2.5 DTB文件分析
編譯生成dtb文件的源設備樹jz2440.dts文件如下:
- // SPDX-License-Identifier: GPL-2.0
- /*
- * SAMSUNG SMDK2440 board device tree source
- *
- * Copyright (c) 2018 weidongshan@qq.com
- * dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
- */
- #define S3C2410_GPA(_nr) ((0<<16) + (_nr))
- #define S3C2410_GPB(_nr) ((1<<16) + (_nr))
- #define S3C2410_GPC(_nr) ((2<<16) + (_nr))
- #define S3C2410_GPD(_nr) ((3<<16) + (_nr))
- #define S3C2410_GPE(_nr) ((4<<16) + (_nr))
- #define S3C2410_GPF(_nr) ((5<<16) + (_nr))
- #define S3C2410_GPG(_nr) ((6<<16) + (_nr))
- #define S3C2410_GPH(_nr) ((7<<16) + (_nr))
- #define S3C2410_GPJ(_nr) ((8<<16) + (_nr))
- #define S3C2410_GPK(_nr) ((9<<16) + (_nr))
- #define S3C2410_GPL(_nr) ((10<<16) + (_nr))
- #define S3C2410_GPM(_nr) ((11<<16) + (_nr))
- /dts-v1/;
- / {
- model = "SMDK2440";
- compatible = "samsung,smdk2440";
- #address-cells = <1>;
- #size-cells = <1>;
- memory@30000000 {
- device_type = "memory";
- reg = <0x30000000 0x4000000>;
- };
- chosen {
- bootargs = "console=ttySAC0,115200 rw root=/dev/mtdblock4 rootfstype=yaffs2";
- };
- led {
- compatible = "jz2440_led";
- reg = <S3C2410_GPF(5) 1>;
- };
- };
jz2440.dtb 文件的內容如下:
接下來我們對應上圖的編號逐一分析,其中編號①~⑩表示的是fdtheader 結構體的成員信息:
① 對應 magic,表示設備樹魔數,固定為0xd00dfeed;
② 對應 totalsize,表示整個設備設dtb文件的大小,從上圖可知0x000001B9正好是dtb文件的大小441B;
③ 對應 offdtstruct,表示structure block的偏移地址,為 0x00000038;
④ 對應offdtstrings,表示 strings block的偏移地址,為 0x00000174;
⑤ 對應 offmemrsvmap;,表示memory reservation block的偏移地址,為 0x00000028;
⑥ 對應 version ,設備樹版本的版本號為0x11;
⑦ 對應 lastcompversion,向下兼容版本號0x10;
⑧ 對應 bootcpuidphys,在多核處理器中用于啟動的主cpu的物理id,為0x0;
⑨ 對應 sizedtstrings,strings block的大小為 0x45;
⑩ 對應 sizedtstruct,structure block的大小為 0x0000013C;
⑪~⑫ 對應結構體 fdtreserve_entry ,它所在的地址為0x28,jz2440.dts 設備樹文件沒有設置 /memreserve/,所以address = 0x0,size = 0x0;
⑬ 所處的地址是0x38,它處在structure block中,0x00000001表示的是設備節點的開始;
⑭ 接著緊跟的是設備節點的名字,這里是根節點,所以為0x00000000;
⑮ 0x00000003 表示的是開始描述設備節點的一個屬性;
⑯ 表示這個屬性值的長度為0x09;
⑰ 表示這個屬性的名字在strings block的偏移量是0,找到strings block的地址0x0174的地方,可知這個屬性的名字是model;
⑱ 這個model屬性的值是"SMDK2440",加上字符串的結束符NULL,剛好9個字節。
2.2.6 DTB文件結構圖
(1) dtb 文件的結構圖如下:
Linux設備樹語法規范
(2) 設備節點的結構圖如下: