設備樹筆記html
參考資料:http://www.wowotech.net/linux_kenrel/why-dt.htmlnode
1、背景linux
設想一下:bootloader將Linux內核複製到內存中,而後跳到內核的入口點開始執行。此時內核就像運行在處理器上的一個裸機程序。須要配置處理器,設置虛擬內存,向控制檯打印一些信息。可是這些事情如何完成?全部的這些操做都要經過寫寄存器來實現,但Linux內核如何知道這些寄存器的地址?如何知道當前有多少個CPU核可使用?有多少內存能夠訪問?最直接的辦法就是在內核代碼裏爲指定平臺寫好這些代碼,由內核配置參數決定哪些平臺代碼將被啓用。但每一塊ARM芯片都有本身的寄存器地址和不一樣的配置方式以及外設,這致使內核充斥大量垃圾代碼,因此人們但願內核能以某種方式識別硬件並加載驅動,因而設備樹出現了,他用於指明系統所使用的設備及相應的配置信息。數組
隨着愈來愈多的芯片廠商加入ARM陣營,各個ARM 供應商的SOC 家族的CPU愈來愈多,不一樣廠商的周邊硬件設備又各不相同,加之芯片供應商開發人員爲了更快的開發效率,使得不少SOC 特定的代碼都是經過複製現有代碼並稍做修改就提交到ARM Linux。這致使數據結構
一、愈來愈多的ARM 平臺相關的代碼被加入到Linux內核, #ifdef充斥在各個源代碼中,讓ARM mach-和plat-目錄下的代碼有些不忍直視。架構
二、系統充斥大量重複代碼。app
所以,內核社區成立了一個「ARM 子架構」的團隊,該團隊主要負責協調各個ARM廠商的代碼(not ARM core part),檢查各個子架構維護者提交的代碼,並創建一個ARM 平臺合併樹來維護這些代碼。針對不一樣的SOC共用的IP block(知識產權塊,例如I2C controller),將其驅動代碼從各個arch/arm/mach-xxx中獨立出來,變成通用的模塊移動到kernel/drivers目錄。而如clock control、interrupt control等並非ARM特殊部分,將其驅動放在linux/kernel目錄下,屬於core-Linux-kernel frameworks。此外,對於ARM平臺,須要保存一些和framework交互的代碼,這些代碼叫作ARM SoC core architecture code。總結以下:函數
一、ARM的核心代碼仍然保存在arch/arm目錄下,ARM SoC core architecture code(與系統內核交互的代碼)也保存在arch/arm目錄下;工具
二、ARM SOC的周邊外設模塊(如I2C控制器)的驅動保存在drivers目錄下,通用的設備(如中斷控制器)驅動直接集成到系統內核中;佈局
三、ARM SOC的專有代碼在arch/arm/mach-xxx目錄下;
四、ARM SOC board specific的代碼被移除,由設備樹機制來負責傳遞硬件拓撲和硬件資源信息。
本質上,設備樹改變了原來用hardcode方式將硬件配置信息嵌入到內核代碼的方法,改用bootloader傳遞一個DB的形式。對於基於ARM CPU的嵌入式系統,咱們習慣於針對每個平臺進行內核的編譯。可是咱們指望ARM可以像X86那樣用一個kernel image來支持多個平臺。在這種狀況下,若是咱們認爲內核是一個黑盒,那麼其輸入參數應該包括:識別平臺的信息、runtime的配置參數、設備的拓撲結構以及特性。在linux kernel中,設備樹就是爲了把上述的三個參數信息經過bootloader傳遞給kernel,以便kernel能夠有較大的靈活性。
爲一個外設寫一個設備樹entry(http://blog.csdn.net/klaus_wei/article/details/42915545):
一、爲"compatible"賦一個字符串"magicstring",自動生成工具的生成格式通常是:名字+版本。
二、在數據手冊裏查看總線上設備的地址分配信息, 寫一條 "reg=" 語句。
三、"interrupt-parent=<&gic>"
四、中斷號 "interrupt="
五、最後加上一些設備的自定義參數
Porting操做系統到硬件平臺: 一、本身撰寫一個bootloader並傳遞適當的參數給kernel。除了傳統的 command line以及tag list之類的,最重要的是申請一個machine type,當拿到屬於本身項目的machine type ID的時候,當時心情雀躍,彷佛本身已是開源社區的一份子了(其實當時是有意願,或者說有目標是想將你們的代碼併入到linux kernel main line的)。 二、在內核的arch/arm目錄下創建mach-xxx目錄,這個目錄下,放入該SOC的相關代碼,例如中斷 controller的代碼,時間相關的代 碼,內存映射,睡眠相關的代碼等等。此外,最重要的是創建一個board specific文件,定義一個machine的宏: MACHINE_START(project name, "xxx公司的xxx硬件平臺") 在xxx_init函數中,通常會加入不少的platform device。所以,伴隨這個board specific文件中是大量的靜態table,描述了各類硬件設備信息。 三、調通了system level的driver(timer,中斷處理,clock等)以及串口terminal以後,linux kernel基本是能夠起來了,後續各類driver不斷的添加,直到系統軟件支持全部的硬件。 |
2、設備樹
2.1 概念
若是要使用Device Tree,首先用戶要了解本身的硬件配置和系統運行參數,並把這些信息組織成Device Tree source file。經過DTC(Device Tree Compiler),編譯爲Device Tree binary file(有一個更好聽的名字,DTB,device tree blob)。系統啓動時被加載到內存並將起始地址傳給OS內核。
另外,設備樹中不用描述全部硬件信息,對於能夠動態探測到的設備沒必要在其中描述,如USB設備、PCI 設備,但usb host controller是沒法動態識別的,PCI bridge若是不能被探測,則須要在device tree中描述。對於同一系列的SOC家族,一般將公共的硬件描述保存在一個單獨的dtsi文件中,方便你們include共用,省去代碼的重複。
2.2 設備樹源文件
2.2.1 語法
device tree的基本單元是node。這些node被組織成樹狀結構,除了root node,每一個node都只有一個parent。一個device tree文件中只能有一個root node。每一個node中包含了若干的property/value來描述該node的一些特性。在linux kernel中,擴展名是dts的文件就是描述硬件信息的device tree source file,在dts文件中,一個node被定義成:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
}
說明:
[]表示可選項;
label方便在dts文件中引用;
每一個node用節點名字(node name)標識,節點名字的格式是node-name@unit-address;@unit-address的格式和設備掛在哪一個bus上相關,如cpu,其unit-address就是從0開始編址,如以太網控制器,其unit-address就是寄存器地址,若是該node沒有reg屬性(尋址需求屬性),那麼該節點名字中必須不能包括@和unit-address。root node的node name是肯定的,必須是「/」。
屬性(property)值標識了設備的特性,它的值(value)是多種多樣的:
一、多是空,也就是沒有值的定義。
二、多是一個u3二、u64的數值(值得一提的是cell這個術語,在Device Tree表示32bit的信息單位)。例如#address-cells = <1> 。固然,多是一個數組。例如<0x00000000 0x00000000 0x00000000 0x20000000>
三、多是一個字符串。例如device_type = "memory" ,固然也多是一個string list。例如"PowerPC,970"
child node的格式和node是徹底同樣的。
2.2.2 節點和屬性
根節點/ |
節點 |
屬性 |
屬性說明 |
節點說明 |
|
#address-cells |
#是數量的意思,#address-cells屬性用來描述sub node中的reg屬性的地址域特性,也就是說須要用多少個u32的cell來描述該地址域。 |
若是節點中包含了有尋址需求reg的子節點,則須要定義這兩個屬性, |
|
#size-cells |
||||
Compatible 該屬性的值是string list,定義了一系列的modle(每一個string是一個model)。這些字符串列表被操做系統用來選擇用哪個driver來驅動該設備。 |
model屬性指明瞭該設備屬於哪一個設備生產商的哪個model。通常model賦值「生產商,模型(系列)」 。對於root node,compatible屬性用來匹配machine type,對於普通的HW block的節點,如中斷控制器,屬性被用來匹配適合的driver。 |
|
||
interrupt-parent |
用於標識能產生中斷的設備鏈接到哪一個中斷控制器 |
|
||
chosen { } |
bootargs |
傳遞命令行參數 |
描述由系統firmware指定的runtime parameter。若是存在chosen這個node,其parent node必須是根節點。 |
|
initrd-start |
傳遞initrd的開始地址 |
|||
aliases { } |
|
|
定義了一些別名,方便引用節點時省寫完整路徑 |
|
memory { } |
device_type |
對於memory node,其device_type必須爲memory。 |
是全部設備樹文件的必備節點,它定義了系統物理內存的佈局 |
|
reg屬性定義了訪問該device node的地址信息, |
該屬性的值被解析成任意長度的(address,size)數組, address和size在其父節點中定義(#address-cells和#size-cells)。對於device node,reg描述了memory-mapped IO register的offset和length。對於memory node,定義了該memory的起始地址和長度。 |
|||
interrupt-controller @4a000000{} |
#interrupt-cells |
用多少個u32(即cells)來標識一個interrupt source |
中斷控制器節點,其中包含屬性值。4a000000表示中斷控制器寄存器的起始地址 |
|
Serial @50000000{} |
interrupts |
對於一個能產生中斷的設備,必須定義interrupts這個屬性。也能夠定義interrupt-parent這個屬性,若是不定義,則繼承其parent node的interrupt-parent屬性。 |
|
|
status |
|
|||
|
|
|
|
|
Cpus{} |
|
對於cpus node,#address-cells 是1,而#size-cells是0。 |
對於根節點,必須有一個cpus的child node來描述系統中的CPU信息。 |
2.3 設備樹二進制文件
設備樹二進制文件的組織格式以下:
說明:
1 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)若干子節點定義。(被FDT_BEGIN_NODE和FDT_END_NODE包圍)
(5)若干個FDT_NOP(可選)
(6)FDT_END_NODE
(7)FDT_END
五、device tree strings bloc的格式描述
device tree strings bloc定義了各個node中使用的屬性的字符串表。因爲不少屬性會出如今多個node中,所以,全部的屬性字符串組成了一個string block。這樣能夠壓縮DTB的size。
2.4 設備樹數據流
一、在ARM的彙編啓動代碼中,定義了兩個變量_machine_rach_type(保存了機器類型ID)、_atags_pointer(保存了設備樹/標籤列表指針);
二、上電後,引導加載程序被加載到內存(ARM沒有BIOS這類固件程序),在將控制權交給內核時,將設備樹指針傳給內核;
三、將DTB轉換爲樹狀結構,節點用一個結構體標識;
四、掃描DTB,獲取chosen節點的bootargs、initrd屬性的值,並保存在全局變量中,其中保存了一些系統參數;
五、內核根據機器類型ID掃描機器描述符列表(機器描述符在編譯時被保存在一個特殊段,並使用一個數據結構標識),肯定機器描述符。
3、代碼分析
請參考http://www.wowotech.net/linux_kenrel/dt-code-analysis.html