1、前言html
一些背景知識(例如:爲什麼要引入Device Tree,這個機制是用來解決什麼問題的)請參考引入Device Tree的緣由,本文主要是介紹Device Tree的基礎概念。node
簡單的說,若是要使用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。linux
本文主要描述下面兩個主題:數組
一、Device Tree source file語法介紹app
二、Device Tree binaryfile格式介紹dom
2、Device Tree的結構佈局
在描述Device Tree的結構以前,咱們先問一個基礎問題:是否Device Tree要描述系統中的全部硬件信息?答案是否認的。基本上,那些能夠動態探測到的設備是不須要描述的,例如USB device。不過對於SOC上的usb host controller,它是沒法動態識別的,須要在device tree中描述。一樣的道理,在computer system中,PCI device能夠被動態探測到,不須要在device tree中描述,可是PCI bridge若是不能被探測,那麼就須要描述之。ui
爲了瞭解Device Tree的結構,咱們首先給出一個Device Tree的示例:spa
/ 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)是多種多樣的:
一、多是空,也就是沒有值的定義。例如上圖中的64-bit ,這個屬性沒有賦值。
二、多是一個u3二、u64的數值(值得一提的是cell這個術語,在Device Tree表示32bit的信息單位)。例如#address-cells = <1> 。固然,多是一個數組。例如<0x00000000 0x00000000 0x00000000 0x20000000>
四、多是一個字符串。例如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 的數據格式。假設蝸窩科技製做了一個S3C2416的開發板,咱們把該development board命名爲snail,那麼須要撰寫一個s3c2416-snail.dts的文件。若是把全部的開發板的硬件信息(SOC以及外設)都描述在一個文件中是不合理的,所以有可能其餘公司也使用S3C2416搭建本身的開發板並命令pig、cow什麼的,若是你們都用本身的dts文件描述硬件,那麼其中大部分是重複的,所以咱們把和S3C2416相關的硬件描述保存成一個單獨的dts文件能夠供使用S3C2416的target board來引用並將文件的擴展名變成dtsi(i表示include)。同理,三星公司的S3C24xx系列是一個SOC family,這些SOCs(24十、241六、2450等)也有相同的內容,所以一樣的道理,咱們能夠將公共部分抽取出來,變成s3c24xx.dtsi,方便你們include。一樣的道理,各家ARM vendor也會共用一些硬件定義信息,這個文件就是skeleton.dtsi。咱們自下而上(相似C++中的從基類到頂層的派生類)逐個進行分析。
一、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>;
};
二、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的根節點,device一、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 的子節點,用來描述有喚醒功能的中斷源。
三、s3c2416.dtsi。位於linux-3.14\arch\arm\boot\dts目錄下,具體該文件的內容以下(有些內容省略了,領會精神便可,不須要描述每個硬件定義的細節):
#include "s3c24xx.dtsi"
#include "s3c2416-pinctrl.dtsi"/ {
model = "Samsung S3C2416 SoC";
compatible = "samsung,s3c2416"; ---------------Acpus { ----------------------------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>;
};
四、s3c2416-pinctrl.dtsi
這個文件定義了pinctrl@56000000 這個節點的若干child node,主要用來描述GPIO的bank信息。
五、s3c2416-snail.dts
這個文件應該定義一些SOC以外的peripherals的定義。
4、Device Tree binary格式
一、DTB總體結構
通過Device Tree Compiler編譯,Device Tree source file變成了Device Tree Blob(又稱做flattened device tree)的格式。Device Tree Blob的數據組織以下圖所示:
二、DTB header。
對於DTB header,其各個成員解釋以下:
header field name | description |
magic | 用來識別DTB的。經過這個magic,kernel能夠肯定bootloader傳遞的參數block是一個DTB仍是tag list。 |
totalsize | DTB的total size |
off_dt_struct | device tree structure block的offset |
off_dt_strings | device tree strings block的offset |
off_mem_rsvmap | offset to memory reserve map。有些系統,咱們也許會保留一些memory有特殊用途(例如DTB或者initrd image),或者在有些DSP+ARM的SOC platform上,有寫memory被保留用於ARM和DSP進行信息交互。這些保留內存不會進入內存管理系統。 |
version | 該DTB的版本。 |
last_comp_version | 兼容版本信息 |
boot_cpuid_phys | 咱們在哪個CPU(用ID標識)上booting |
dt_strings_size | device tree strings block的size。和off_dt_strings一塊兒肯定了strings block在內存中的位置 |
dt_struct_size | device tree structure block的size。和和off_dt_struct一塊兒肯定了device tree structure block在內存中的位置 |
三、 memory reserve map的格式描述
這個區域包括了若干的reserve memory描述符。每一個reserve memory描述符是由address和size組成。其中address和size都是用U64來描述。
四、device tree structure block的格式描述
device tree structure block區域是由若干的分片組成,每一個分片開始位置都是保存了token,以此來描述該分片的屬性和內容。共計有5種token:
(1)FDT_BEGIN_NODE (0x00000001)。該token描述了一個node的開始位置,緊挨着該token的就是node name(包括unit address)
(2)FDT_END_NODE (0x00000002)。該token描述了一個node的結束位置。
(3)FDT_PROP (0x00000003)。該token描述了一個property的開始位置,該token以後是兩個u32的數據,分別是length和name offset。length表示該property value data的size。name offset表示該屬性字符串在device tree strings block的偏移值。length和name offset以後就是長度爲length具體的屬性值數據。
(4)FDT_NOP (0x00000004)。
(5)FDT_END (0x00000009)。該token標識了一個DTB的結束位置。
一個可能的DTB的結構以下:
(1)若干個FDT_NOP(可選)
(2)FDT_BEGIN_NODE
node name
paddings
(3)若干屬性定義。
(4)若干子節點定義。
(5)若干個FDT_NOP(可選)
(6)FDT_END
五、device tree strings bloc的格式描述
device tree strings bloc定義了各個node中使用的屬性的字符串表。因爲不少屬性會出如今多個node中,所以,全部的屬性字符串組成了一個string block。這樣能夠壓縮DTB的size。
原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net。