1 前言
如果要使用Device Tree,首先用戶要了解自己的硬件配置和系統運行參數,並把這些信息組織成Device Tree source file。通過DTC(Device Tree Compiler),可以將這些適合人類閱讀的Device Tree source file變成適合機器處理的Device Tree binary file(有一個更好聽的名字,DTB,device tree blob)。在系統啟動的時候,boot program(例如:firmware、bootloader)可以將保存在flash中的DTB copy到內存(當然也可以通過其他方式,例如可以通過bootloader的交互式命令加載DTB,或者firmware可以探測到device的信息,組織成DTB保存在內存中),並把DTB的起始地址傳遞給client program(例如OS kernel,bootloader或者其他特殊功能的程序)。對於計算機系統(computer system),一般是firmware->bootloader->OS,對於嵌入式系統,一般是bootloader->OS。
1.1 dts
硬件的相應信息都會寫在.dts為后綴的文件中,每一款硬件可以單獨寫一份xxxx.dts,一般在Linux源碼中存在大量的dts文件,對於arm架構可以在arch/arm/boot/dts找到相應的dts,一個dts文件對應一個ARM的machie。
1.2 dtsi (dts include)
對於一些相同的dts配置可以抽象到dtsi文件中,然后類似於C語言的方式可以include到dts文件中,對於同一個節點的設置情況,dts中的配置會覆蓋dtsi中的配置。
1.3 dtc
dtc是編譯dts的工具,可以在Ubuntu系統上通過指令apt-get install device-tree-compiler安裝dtc工具,不過在內核源碼scripts/dtc路徑下已經包含了dtc工具。
1.4 dtb
dtb(Device Tree Blob),dts經過dtc編譯之后會得到dtb文件,dtb通過Bootloader引導程序加載到內核。所以Bootloader需要支持設備樹才行;Kernel也需要加入設備樹的支持。
1.5 u-boot
Uboot設備從v1.1.3開始支持設備樹,其對ARM的支持則是和ARM內核支持設備樹同期完成。
為了使能設備樹,需要在編譯Uboot的時候在config文件中加入:
#define CONfiG_OF_LIBFDT·
在Uboot中,可以從NAND、SD或者TFTP等任意介質中將.dtb讀入內存,假設.dtb放入的內存地址為0x71000000,之后可在Uboot中運行fdt addr命令設置.dtb的地址,如:
·UBoot> fdt addr 0x71000000·
fdt的其他命令就變得可以使用,如fdt resize、fdt print等。
對於ARM來講,可以通過bootz kernel_addr initrd_address dtb_address的命令來啟動內核,即dtb_address作為bootz或者bootm的最后一個參數,第一個參數為內核映像的地址,第二個參數為initrd的地址,若不存在initrd,可以用“-”符號代替。
2 Device Tree的結構
在描述Device Tree的結構之前,我們先問一個基礎問題:是否Device Tree要描述系統中的所有硬件信息?答案是否定的。基本上,那些可以動態探測到的設備是不需要描述的,例如USB device。不過對於SOC上的usb host controller,它是無法動態識別的,需要在device tree中描述。同樣的道理,在computer system中,PCI device可以被動態探測到,不需要在device tree中描述,但是PCI bridge如果不能被探測,那么就需要描述之。
為了了解Device Tree的結構,我們首先給出一個Device Tree的示例:
/ o device-tree |- name = "device-tree" |- model = "MyBoardName" |- compatible = "MyBoardFamilyName" |- #address-cells = <2> |- #size-cells = <2> |- linux,phandle = <0> | o cpus | | - name = "cpus" | | - linux,phandle = <1> | | - #address-cells = <1> | | - #size-cells = <0> | | | o PowerPC,970@0 | |- name = "PowerPC,970" | |- device_type = "cpu" | |- reg = <0> | |- clock-frequency = <0x5f5e1000> | |- 64-bit | |- linux,phandle = <2> | o memory@0 | |- name = "memory" | |- device_type = "memory" | |- reg = <0x00000000 0x00000000 0x00000000 0x20000000> | |- linux,phandle = <3> | o chosen |- name = "chosen" |- bootargs = "root=/dev/sda2" |- linux,phandle = <4>
從上圖中可以看出,device tree的基本單元是node。這些node被組織成樹狀結構,除了root node,每個node都只有一個parent。一個device tree文件中只能有一個root node。每個node中包含了若干的property/value來描述該node的一些特性。每個node用節點名字(node name)標識,節點名字的格式是node-name@unit-address。如果該node沒有reg屬性(后面會描述這個property),那么該節點名字中必須不能包括@和unit-address。unit-address的具體格式是和設備掛在那個bus上相關。例如對於cpu,其unit-address就是從0開始編址,以此加一。而具體的設備,例如以太網控制器,其unit-address就是寄存器地址。root node的node name是確定的,必須是“/”。
在一個樹狀結構的device tree中,如何引用一個node呢?要想唯一指定一個node必須使用full path,例如/node-name-1/node-name-2/node-name-N。在上面的例子中,cpu node我們可以通過/cpus/PowerPC,970@0訪問。
屬性(property)值標識了設備的特性,它的值(value)是多種多樣的:
(1)可能是空,也就是沒有值的定義。例如上圖中的64-bit ,這個屬性沒有賦值。
(2)可能是一個u32、u64的數值(值得一提的是cell這個術語,在Device Tree表示32bit的信息單位)。例如#address-cells = <1> 。當然,可能是一個數組。例如<0x00000000 0x00000000 0x00000000 0x20000000>
(3)可能是一個字符串。例如device_type = "memory" ,當然也可能是一個string list。例如"PowerPC,970"
3 Device Tree source file語法介紹
了解了基本的device tree的結構后,我們總要把這些結構體現在device tree source code上來。在linux kernel中,擴展名是dts的文件就是描述硬件信息的device tree source file,在dts文件中,一個node被定義成:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
}
“[]”表示option,因此可以定義一個只有node name的空節點。label方便在dts文件中引用,具體后面會描述。child node的格式和node是完全一樣的,因此,一個dts文件中就是若干嵌套組成的node,property以及child note、child note property描述。
考慮到空泛的談比較枯燥,我們用實例來講解Device Tree Source file 的數據格式。如果大家都用自己的dts文件描述硬件,那么其中大部分是重復的,因此我們把和S3C2416相關的硬件描述保存成一個單獨的dts文件可以供使用S3C2416的target board來引用並將文件的擴展名變成dtsi(i表示include)。同理,三星公司的S3C24xx系列是一個SOC family,這些SOCs(2410、2416、2450等)也有相同的內容,因此同樣的道理,我們可以將公共部分抽取出來,變成s3c24xx.dtsi,方便大家include。同樣的道理,各家ARM vendor也會共用一些硬件定義信息,這個文件就是skeleton.dtsi。我們自下而上(類似C++中的從基類到頂層的派生類)逐個進行分析。
3.1 skeleton.dtsi。位於linux-3.14\arch\arm\boot\dts目錄下,具體該文件的內容如下:
/ { #address-cells = <1>; #size-cells = <1>; chosen { }; aliases { }; memory { device_type = "memory"; reg = <0 0>; }; };
device tree顧名思義是一個樹狀的結構,既然是樹,必然有根。“/”是根節點的node name。大括號之間的內容是該節點的具體的定義,其內容包括各種屬性的定義以及child node的定義。chosen、aliases和memory都是sub node,sub node的結構和root node是完全一樣的,因此,sub node也有自己的屬性和它自己的sub node,最終形成了一個樹狀的device tree。屬性的定義采用property = value的形式。例如#address-cells和#size-cells就是property,而<1>就是value。value有三種情況:
(1)屬性值是text string或者string list,用雙引號表示。例如device_type = "memory"
(2)屬性值是32bit unsigned integers,用尖括號表示。例如#size-cells = <1>
(3)屬性值是binary data,用方括號表示。例如binary-property = [0x01 0x23 0x45 0x67]
如果一個device node中包含了有尋址需求(要定義reg property)的sub node(后文也許會用child node,和sub node是一樣的意思),那么就必須要定義這兩個屬性。“#”是number的意思,#address-cells這個屬性是用來描述sub node中的reg屬性的地址域特性的,也就是說需要用多少個u32的cell來描述該地址域。同理可以推斷#size-cells的含義,下面對reg的描述中會給出更詳細的信息。
chosen node主要用來描述由系統firmware指定的runtime parameter。如果存在chosen這個node,其parent node必須是名字是“/”的根節點。原來通過tag list傳遞的一些linux kernel的運行時參數可以通過Device Tree傳遞。例如command line可以通過bootargs這個property這個屬性傳遞;initrd的開始地址也可以通過linux,initrd-start這個property這個屬性傳遞。在本例中,chosen節點是空的,在實際中,建議增加一個bootargs的屬性,例如:
"root=/dev/nfs nfsroot=1.1.1.1:/nfsboot ip=1.1.1.2:1.1.1.1:1.1.1.1:255.255.255.0::usbd0:off console=ttyS0,115200 mem=64M@0x30000000"
通過該command line可以控制內核從usbnet啟動,當然,具體項目要相應修改command line以便適應不同的需求。我們知道,device tree用於HW platform識別,runtime parameter傳遞以及硬件設備描述。chosen節點並沒有描述任何硬件設備節點的信息,它只是傳遞了runtime parameter。
aliases節點定義了一些別名。為何要定義這個node呢?因為Device tree是樹狀結構,當要引用一個node的時候要指明相對於root node的full path,例如/node-name-1/node-name-2/node-name-N。如果多次引用,每次都要寫這么復雜的字符串多少是有些麻煩,因此可以在aliases 節點定義一些設備節點full path的縮寫。skeleton.dtsi中沒有定義aliases,下面的section中會進一步用具體的例子描述之。
memory device node是所有設備樹文件的必備節點,它定義了系統物理內存的layout。device_type屬性定義了該node的設備類型,例如cpu、serial等。對於memory node,其device_type必須等於memory。reg屬性定義了訪問該device node的地址信息,該屬性的值被解析成任意長度的(address,size)數組,具體用多長的數據來表示address和size是在其parent node中定義(#address-cells和#size-cells)。對於device node,reg描述了memory-mapped IO register的offset和length。對於memory node,定義了該memory的起始地址和長度。
本例中的物理內存的布局並沒有通過memory node傳遞,其實我們可以使用command line傳遞,我們command line中的參數“mem=64M@0x30000000”已經給出了具體的信息。我們用另外一個例子來加深對本節描述的各個屬性以及memory node的理解。假設我們的系統是64bit的,physical memory分成兩段,定義如下:
RAM: starting address 0x0, length 0x80000000 (2GB)
RAM: starting address 0x100000000, length 0x100000000 (4GB)
對於這樣的系統,我們可以將root node中的#address-cells和#size-cells這兩個屬性值設定為2,可以用下面兩種方法來描述物理內存:
方法1: memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000 0x000000001 0x00000000 0x00000001 0x00000000>; }; 方法2: memory@0 { device_type = "memory"; reg = <0x000000000 0x00000000 0x00000000 0x80000000>; }; memory@100000000 { device_type = "memory"; reg = <0x000000001 0x00000000 0x00000001 0x00000000>; };
3.2 s3c24xx.dtsi。位於linux-3.14\arch\arm\boot\dts目錄下,具體該文件的內容如下(有些內容省略了,領會精神即可,不需要描述每一個硬件定義的細節):
#include "skeleton.dtsi" / { compatible = "samsung,s3c24xx"; -------------------(A) interrupt-parent = <&intc>; ----------------------(B) aliases { pinctrl0 = &pinctrl_0; ------------------------(C) }; intc:interrupt-controller@4a000000 { ------------------(D) compatible = "samsung,s3c2410-irq"; reg = <0x4a000000 0x100>; interrupt-controller; #interrupt-cells = <4>; }; serial@50000000 { ----------------------(E) compatible = "samsung,s3c2410-uart"; reg = <0x50000000 0x4000>; interrupts = <1 0 4 28>, <1 1 4 28>; status = "disabled"; }; pinctrl_0: pinctrl@56000000 {------------------(F) reg = <0x56000000 0x1000>; wakeup-interrupt-controller { compatible = "samsung,s3c2410-wakeup-eint"; interrupts = <0 0 0 3>, <0 0 1 3>, <0 0 2 3>, <0 0 3 3>, <0 0 4 4>, <0 0 5 4>; }; }; …… };
這個文件描述了三星公司的S3C24xx系列SOC family共同的硬件block信息。首先提出的問題就是:為何定義了兩個根節點?按理說Device Tree只能有一個根節點,所有其他的節點都是派生於根節點的。我的猜測是這樣的:Device Tree Compiler會對DTS的node進行合並,最終生成的DTB只有一個root node。OK,我們下面開始逐一分析:
(A)在描述compatible屬性之前要先描述model屬性。model屬性指明了該設備屬於哪個設備生產商的哪一個model。一般而言,我們會給model賦值“manufacturer,model”。例如model = "samsung,s3c24xx"。samsung是生產商,s3c24xx是model類型,指明了具體的是哪一個系列的SOC。OK,現在我們回到compatible屬性,該屬性的值是string list,定義了一系列的modle(每個string是一個model)。這些字符串列表被操作系統用來選擇用哪一個driver來驅動該設備。假設定義該屬性:compatible = “aaaaaa”, “bbbbb"。那么操作操作系統可能首先使用aaaaaa來匹配適合的driver,如果沒有匹配到,那么使用字符串bbbbb來繼續尋找適合的driver,對於本例,compatible = "samsung,s3c24xx",這里只定義了一個modle而不是一個list。對於root node,compatible屬性是用來匹配machine type的(在device tree代碼分析文章中會給出更細致的描述)。對於普通的HW block的節點,例如interrupt-controller,compatible屬性是用來匹配適合的driver的。
(B)具體各個HW block的interrupt source是如何物理的連接到interruptcontroller的呢?在dts文件中是用interrupt-parent這個屬性來標識的。且慢,這里定義interrupt-parent屬性的是root node,難道root node會產生中斷到interrupt controller嗎?當然不會,只不過如果一個能夠產生中斷的device node沒有定義interrupt-parent的話,其interrupt-parent屬性就是跟隨parent node。因此,與其在所有的下游設備中定義interrupt-parent,不如統一在root node中定義了。
intc是一個lable,標識了一個device node(在本例中是標識了interrupt-controller@4a000000 這個device node)。實際上,interrupt-parent屬性值應該是是一個u32的整數值(這個整數值在Device Tree的范圍內唯一識別了一個device node,也就是phandle),不過,在dts文件中中,可以使用類似c語言的Labels and References機制。定義一個lable,唯一標識一個node或者property,后續可以使用&來引用這個lable。DTC會將lable轉換成u32的整數值放入到DTB中,用戶層面就不再關心具體轉換的整數值了。
關於interrupt,我們值得進一步描述。在Device Tree中,有一個概念叫做interrupt tree,也就是說interrupt也是一個樹狀結構。我們以下圖為例(該圖來自Power_ePAPR_APPROVED_v1.1):
系統中有一個interrrupt tree的根節點,device1、device2以及PCI host bridge的interrupt line都是連接到root interrupt controller的。PCI host bridge設備中有一些下游的設備,也會產生中斷,但是他們的中斷都是連接到PCI host bridge上的interrupt controller(術語叫做interrupt nexus),然后報告到root interrupt controller的。每個能產生中斷的設備都可以產生一個或者多個interrupt,每個interrupt source(另外一個術語叫做interrupt specifier,描述了interrupt source的信息)都是限定在其所屬的interrupt domain中。
在了解了上述的概念后,我們可以回頭再看看interrupt-parent這個屬性。其實這個屬性是建立interrupt tree的關鍵屬性。它指明了設備樹中的各個device node如何路由interrupt event。另外,需要提醒的是interrupt controller也是可以級聯的,上圖中沒有表示出來。那么在這種情況下如何定義interrupt tree的root呢?那個沒有定義interrupt-parent的interrupt controller就是root。
(C)pinctrl0是一個縮寫,他是/pinctrl@56000000的別名。這里同樣也是使用了Labels and References機制。
(D)intc(node name是interrupt-controller@4a000000 ,我這里直接使用lable)是描述interrupt controller的device node。根據S3C24xx的datasheet,我們知道interrupt controller的寄存器地址從0x4a000000開始,長度為0x100(實際2451的interrupt的寄存器地址空間沒有那么長,0x4a000074是最后一個寄存器),也就是reg屬性定義的內容。interrupt-controller屬性為空,只是用來標識該node是一個interrupt controller而不是interrupt nexus(interrupt nexus需要在不同的interrupt domains之間進行翻譯,需要定義interrupt-map的屬性,本文不涉及這部分的內容)。#interrupt-cells 和#address-cells概念是類似的,也就是說,用多少個u32來標識一個interrupt source。我們可以看到,在具體HW block的interrupt定義中都是用了4個u32來表示,例如串口的中斷是這樣定義的:
interrupts = <1 0 4 28>, <1 1 4 28>;
(E) 從reg屬性可以serial controller寄存器地址從0x50000000 開始,長度為0x4000。對於一個能產生中斷的設備,必須定義interrupts這個屬性。也可以定義interrupt-parent這個屬性,如果不定義,則繼承其parent node的interrupt-parent屬性。 對於interrupt屬性值,各個interrupt controller定義是不一樣的,有的用3個u32表示,有的用4個。具體上面的各個數字的解釋權歸相關的interrupt controller所有。對於中斷屬性的具體值的描述我們會在device tree的第三份文檔-代碼分析中描述。
(F)這個node是描述GPIO控制的。這個節點定義了一個wakeup-interrupt-controller 的子節點,用來描述有喚醒功能的中斷源。
3.3 s3c2416.dtsi。位於linux-3.14\arch\arm\boot\dts目錄下,具體該文件的內容如下(有些內容省略了,領會精神即可,不需要描述每一個硬件定義的細節):
#include "s3c24xx.dtsi" #include "s3c2416-pinctrl.dtsi" / { model = "Samsung S3C2416 SoC"; compatible = "samsung,s3c2416"; ---------------A cpus { ----------------------------B #address-cells = <1>; #size-cells = <0>; cpu { compatible = "arm,arm926ejs"; }; }; interrupt-controller@4a000000 { -----------------C compatible = "samsung,s3c2416-irq"; }; …… };
(A)在s3c24xx.dtsi文件中已經定義了compatible這個屬性,在s3c2416.dtsi中重復定義了這個屬性,一個node不可能有相同名字的屬性,具體如何處理就交給DTC了。經過反編譯,可以看出,DTC是丟棄掉了前一個定義。因此,到目前為止,compatible = samsung,s3c2416。在s3c24xx.dtsi文件中定義了compatible的屬性值被覆蓋了。
(B)對於根節點,必須有一個cpus的child node來描述系統中的CPU信息。對於CPU的編址我們用一個u32整數就可以描述了,因此,對於cpus node,#address-cells 是1,而#size-cells是0。其實CPU的node可以定義很多屬性,例如TLB,cache、頻率信息什么的,不過對於ARM,這里只是定義了compatible屬性就OK了,arm926ejs包括了所有的processor相關的信息。
(C)s3c24xx.dtsi文件和s3c2416.dtsi中都有interrupt-controller@4a000000這個node,DTC會對這兩個node進行合並,最終編譯的結果如下:
interrupt-controller@4a000000 { compatible = "samsung,s3c2416-irq"; reg = <0x4a000000 0x100>; interrupt-controller; #interrupt-cells = <0x4>; linux,phandle = <0x1>; phandle = <0x1>; };
3.4 s3c2416-pinctrl.dtsi
這個文件定義了pinctrl@56000000 這個節點的若干child node,主要用來描述GPIO的bank信息。
3.5 s3c2416-snail.dts
這個文件應該定義一些SOC之外的peripherals的定義。
4 語法格式總結
4.1 設備樹框架
設備樹用樹狀結構描述設備信息,它有以下幾種特性
(1)每個設備樹文件都有一個根節點,每個設備都是一個節點。
(2)節點間可以嵌套,形成父子關系,這樣就可以方便的描述設備間的關系。
(3)每個設備的屬性都用一組key-value對(鍵值對)來描述。
(4)每個屬性的描述用;
結束
所以,一個設備樹的基本框架可以寫成下面這個樣子,一般來說,/表示板子,它的子節點node1表示SoC上的某個控制器,控制器中的子節點node2表示掛接在這個控制器上的設備(們)。
/{ //根節點 node1{ //node1是節點名,是/的子節點 key=value; //node1的屬性 ... node2{ //node2是node1的子節點 key=value; //node2的屬性 ... } } //node1的描述到此為止 node3{ key=value; ... } }
4.2節點名
理論個節點名只要是長度不超過31個字符的ASCII字符串即可,此外
Linux內核還約定設備名應寫成形如<name>[@<unit_address>]
的形式,其中name就是設備名,最長可以是31個字符長度。unit_address一般是設備地址,用來唯一標識一個節點,下面就是典型節點名的寫法
serial@13800000 { status = "okay"; };
上面的節點名是serial,節點路徑是/
serial@13800000,這點要注意,因為根據節點名查找節點的API的參數是不能有"@xxx"這部分的。
Linux中的設備樹還包括幾個特殊的節點,比如chosen,chosen節點不描述一個真實設備,而是用於firmware傳遞一些數據給OS,比如bootloader傳遞內核啟動參數給內核
chosen { bootargs ="console=ttySAC2,115200"; };
4.3 引用
當我們找一個節點的時候,我們必須書寫完整的節點路徑,這樣當一個節點嵌套比較深的時候就不是很方便,所以,設備樹允許我們用下面的形式為節點標注引用(起別名),借以省去冗長的路徑。這樣就可以實現類似函數調用的效果。編譯設備樹的時候,相同的節點的不同屬性信息都會被合並,相同節點的相同的屬性會被重寫,使用引用可以避免移植者四處找節點,直接在板級.dts增改即可。
ldo1_reg: LDO1 { regulator-name = "VDD_ALIVE"; regulator-min-microvolt = <1100000>; regulator-max-microvolt = <1100000>; regulator-always-on; regulator-boot-on; op_mode = <1>; /* Normal Mode */ };
4.4 address
(幾乎)所有的設備都需要與CPU的IO口相連,所以其IO端口信息就需要在設備節點節點中說明。常用的屬性有
- #address-cells,用來描述子節點"reg"屬性的地址表中用來描述首地址的cell的數量,
- #size-cells,用來描述子節點"reg"屬性的地址表中用來描述地址長度的cell的數量。
有了這兩個屬性,子節點中的"reg"就可以描述一塊連續的地址區域。
4.5 interrupts
一個計算機系統中大量設備都是通過中斷請求CPU服務的,所以設備節點中就需要在指定中斷號。常用的屬性有
- interrupt-controller 一個空屬性用來聲明這個node接收中斷信號,即這個node是一個中斷控制器。
- #interrupt-cells,是中斷控制器節點的屬性,用來標識這個控制器需要幾個單位做中斷描述符,用來描述子節點中"interrupts"屬性使用了父節點中的interrupts屬性的具體的哪個值。一般,如果父節點的該屬性的值是3,則子節點的interrupts一個cell的三個32bits整數值分別為:<中斷域 中斷 觸發方式>,如果父節點的該屬性是2,則是<中斷 觸發方式>
- interrupt-parent,標識此設備節點屬於哪一個中斷控制器,如果沒有設置這個屬性,會自動依附父節點的
- interrupts,一個中斷標識符列表,表示每一個中斷輸出信號
設備樹中中斷的部分涉及的部分比較多,interrupt-controller表示這個節點是一個中斷控制器,需要注意的是,一個SoC中可能有不止一個中斷控制器,這就會涉及到設備樹中斷組織的很多概念
4.6 status
device tree中的status標識了設備的狀態,使用status可以去禁止設備或者啟用設備,看下設備樹規范中的status可選值
okay :表示設備正在運行
disabled :表示該設備目前尚未運行,但將來可能會運行
fail :表示設備無法運行。 在設備中檢測到嚴重錯誤,確實如此沒有修理就不可能投入運營
fail-sss :表示設備無法運行。 在設備中檢測到嚴重錯誤,它是沒有修理就不可能投入運營。 值的sss部分特定於設備並指示檢測到的錯誤情況。
參考博文:
http://www.wowotech.net/linux_kenrel/dt_basic_concept.html
https://blog.csdn.net/u012489236/article/details/97137007
https://www.cnblogs.com/xiaojiang1025/p/6131381.html
https://blog.csdn.net/baidu_38661691/article/details/97013406