Device Tree


設備樹筆記

參考資料:http://www.wowotech.net/linux_kenrel/why-dt.html

 

一、背景

  設想一下:bootloader將Linux內核復制到內存中,然后跳到內核的入口點開始執行。此時內核就像運行在處理器上的一個裸機程序。需要配置處理器,設置虛擬內存,向控制台打印一些信息。但是這些事情如何完成?所有的這些操作都要通過寫寄存器來實現,但Linux內核如何知道這些寄存器的地址?如何知道當前有多少個CPU核可以使用?有多少內存可以訪問?最直接的辦法就是在內核代碼里為指定平台寫好這些代碼,由內核配置參數決定哪些平台代碼將被啟用。但每一塊ARM芯片都有自己的寄存器地址和不同的配置方式以及外設,這導致內核充斥大量垃圾代碼,所以人們希望內核能以某種方式識別硬件並加載驅動,於是設備樹出現了,他用於指明系統所使用的設備及相應的配置信息。

  隨着越來越多的芯片廠商加入ARM陣營,各個ARM 供應商的SOC 家族的CPU越來越多,不同廠商的周邊硬件設備又各不相同,加之芯片供應商開發人員為了更快的開發效率,使得很多SOC 特定的代碼都是通過復制現有代碼並稍作修改就提交到ARM Linux。這導致

  1、越來越多的ARM 平台相關的代碼被加入到Linux內核, #ifdef充斥在各個源代碼中,讓ARM mach-和plat-目錄下的代碼有些不忍直視。

  2、系統充斥大量重復代碼。

  因此,內核社區成立了一個“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。總結如下:

  1、ARM的核心代碼仍然保存在arch/arm目錄下,ARM SoC core architecture code(與系統內核交互的代碼)也保存在arch/arm目錄下;

  2、ARM SOC的周邊外設模塊(如I2C控制器)的驅動保存在drivers目錄下,通用的設備(如中斷控制器)驅動直接集成到系統內核中;

  3、ARM SOC的專有代碼在arch/arm/mach-xxx目錄下;

  4、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):

 

1、為"compatible"賦一個字符串"magicstring",自動生成工具的生成格式一般是:名字+版本。

 

2、在數據手冊里查看總線上設備的地址分配信息, 寫一條 "reg=" 語句。

 

3、"interrupt-parent=<&gic>"

 

4、中斷號 "interrupt="

 

5、最后加上一些設備的自定義參數

 

 

 

Porting操作系統到硬件平台:

1、自己撰寫一個bootloader並傳遞適當的參數給kernel。除了傳統的 command line以及tag list之類的,最重要的是申請一個machine type,當拿到屬於自己項目的machine type ID的時候,當時心情雀躍,似乎自己已經是開源社區的一份子了(其實當時是有意願,或者說有目標是想將大家的代碼並入到linux kernel main line的)。

2、在內核的arch/arm目錄下建立mach-xxx目錄,這個目錄下,放入該SOC的相關代碼,例如中斷 controller的代碼,時間相關的代 碼,內存映射,睡眠相關的代碼等等。此外,最重要的是建立一個board specific文件,定義一個machine的宏:

MACHINE_START(project name, "xxx公司的xxx硬件平台")
    .phys_io    = 0x40000000,
    .boot_params    = 0xa0000100,  
    .io_pg_offst    = (io_p2v(0x40000000) >> 18) & 0xfffc,
    .map_io        = xxx_map_io,
    .init_irq    = xxx_init_irq,
    .timer        = &xxx_timer,
    .init_machine    = xxx_init,
MACHINE_END

在xxx_init函數中,一般會加入很多的platform device。因此,伴隨這個board specific文件中是大量的靜態table,描述了各種硬件設備信息。

3、調通了system level的driver(timer,中斷處理,clock等)以及串口terminal之后,linux kernel基本是可以起來了,后續各種driver不斷的添加,直到系統軟件支持所有的硬件。

 

二、設備樹

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)是多種多樣的:

1、可能是空,也就是沒有值的定義。

2、可能是一個u32、u64的數值(值得一提的是cell這個術語,在Device Tree表示32bit的信息單位)。例如#address-cells = <1> 。當然,可能是一個數組。例如<0x00000000 0x00000000 0x00000000 0x20000000>

3、可能是一個字符串。例如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在內存中的位置

3、 memory reserve map的格式描述

這個區域包括了若干的reserve memory描述符。每個reserve memory描述符是由address和size組成。其中address和size都是用U64來描述。

 

4、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

 

5、device tree strings bloc的格式描述

device tree strings bloc定義了各個node中使用的屬性的字符串表。由於很多屬性會出現在多個node中,因此,所有的屬性字符串組成了一個string block。這樣可以壓縮DTB的size。

2.4 設備樹數據流

    1、在ARM的匯編啟動代碼中,定義了兩個變量_machine_rach_type(保存了機器類型ID)、_atags_pointer(保存了設備樹/標簽列表指針);

    2、上電后,引導加載程序被加載到內存(ARM沒有BIOS這類固件程序),在將控制權交給內核時,將設備樹指針傳給內核;

    3、將DTB轉換為樹狀結構,節點用一個結構體標識;

    4、掃描DTB,獲取chosen節點的bootargs、initrd屬性的值,並保存在全局變量中,其中保存了一些系統參數;

    5、內核根據機器類型ID掃描機器描述符列表(機器描述符在編譯時被保存在一個特殊段,並使用一個數據結構標識),確定機器描述符。

 

三、代碼分析

請參考http://www.wowotech.net/linux_kenrel/dt-code-analysis.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM