Linux設備樹解析

ARM 420瀏覽


1. Device Tree簡介

Linus Torvalds在2011年3月17日的ARM Linux郵件列表宣稱“this whole ARM thing is a fucking pain in the ass”,引發ARM Linux社區的地震,隨后ARM社區進行了一系列的重大修正。在過去的ARM Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥著大量的垃圾代碼,相當多數的代碼只是在描述板級細節,而這些板級細節對于內核來講,不過是垃圾,如板上的platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data。 社區必須改變這種局面,于是PowerPC等其他體系架構下已經使用的Flattened Device Tree(FDT)進入ARM社區的視野。Device Tree是一種描述硬件的數據結構,它起源于OpenFirmware(OF)。在Linux2.6中,ARM架構的板極硬件細節過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,許多硬件的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗余編碼。

Device Tree由一系列被命名的結點(node)和屬性(property)組成,而結點本身可包含子結點。所謂屬性,其實就是成對出現的name和value。在Device Tree中,可描述的信息包括(原先這些信息大多被hard code到kernel中):

  • CPU的數量和類別
  • 內存基地址和大小
  • 總線和橋
  • 外設連接
  • 中斷控制器和中斷使用情況
  • GPIO控制器和GPIO使用情況

它基本上就是畫一棵電路板上CPU、總線、設備組成的樹,Bootloader會將這棵樹傳遞給內核,然后內核可以識別這棵樹,并根據它展開出Linux內核中的platform_device、i2c_client、spi_device等設備。這些設備用到的內存、IRQ等資源,也被傳遞給了kernel,kernel會將這些資源綁定給展開的相應的設備。

2. Device Tree編譯

Device Tree文件的格式為dts,包含的頭文件格式為dtsi,dts文件是一種人可以看懂的編碼格式。但是uboot和linux不能直接識別,他們只能識別二進制文件,所以需要把dts文件編譯成dtb文件。dtb文件是一種可以被kernel和uboot識別的二進制文件。把dts編譯成dtb文件的工具是dtc。Linux源碼目錄下scripts/dtc目錄包含dtc工具的源碼。在Linux的scripts/dtc目錄下除了提供dtc工具外,也可以自己安裝dtc工具,linux下執行:sudo apt-get install device-tree-compiler安裝dtc工具。其中還提供了一個fdtdump的工具,可以反編譯dtb文件。dts和dtb文件的轉換如圖1所示。
dtc工具的使用方法是:dtc –I dts –O dtb –o xxx.dtb xxx.dts,即可生成dts文件對應的dtb文件了。
圖1 dts和dtb文件轉換

3. Device Tree頭信息

fdtdump工具使用,Linux終端執行ftddump –h,輸出以下信息:
fdtdump -h
Usage: fdtdump [options]
Options: -[dshV]
-d, –debug Dump debug information while decoding the file
-s, –scan Scan for an embedded fdt in file
-h, –help Print this help and exit
-V, –version Print version and exit
本文采用s5pv21_smc.dtb文件為例說明fdtdump工具的使用。Linux終端執行fdtdump –sd s5pv21_smc.dtb > s5pv21_smc.txt,打開s5pv21_smc.txt文件,部分輸出信息如下所示:
// magic: 0xd00dfeed
// totalsize: 0xce4 (3300)
// off_dt_struct: 0x38
// off_dt_strings: 0xc34
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0xb0
// size_dt_struct: 0xbfc
以上信息便是Device Tree文件頭信息,存儲在dtb文件的開頭部分。在Linux內核中使用struct fdt_header結構體描述。struct fdt_header結構體定義在scriptsdtclibfdtfdt.h文件中。

struct fdt_header {
    fdt32_t magic;               /* magic word FDT_MAGIC */
    fdt32_t totalsize;           /* total size of DT block */
    fdt32_t off_dt_struct;       /* offset to structure */
    fdt32_t off_dt_strings;      /* offset to strings */
    fdt32_t off_mem_rsvmap;      /* offset to memory reserve map */
    fdt32_t version;                 /* format version */
    fdt32_t last_comp_version;   /* last compatible version */

    /* version 2 fields below */
    fdt32_t boot_cpuid_phys;     /* Which physical CPU id we're booting on */
    /* version 3 fields below */
    fdt32_t size_dt_strings;     /* size of the strings block */

    /* version 17 fields below */
    fdt32_t size_dt_struct;      /* size of the structure block */
};

fdtdump工具的輸出信息即是以上結構中每一個成員的值,struct fdt_header結構體包含了Device Tree的私有信息。示例: fdt_header.magic是fdt的魔數,固定值為0xd00dfeed,fdt_header.totalsize是fdt文件的大小。使用二進制工具打開s5pv21_smc.dtb驗證。s5pv21_smc.dtb二進制文件頭信息如圖2所示。從圖2中可以得到Device Tree的文件是以大端模式儲存。并且,頭部信息和fdtdump的輸出信息一致。
圖2 頭信息

Device Tree中的節點信息舉示例圖3所示。
圖3 設備樹全景試圖
上述.dts文件并沒有什么真實的用途,但它基本表征了一個Device Tree源文件的結構。1個root結點”/”;root結點下面含一系列子結點,本例中為”[email protected]”和”[email protected]”;結點”[email protected]”下又含有一系列子結點,本例中為”[email protected]”;各結點都有一系列屬性。這些屬性可能為空,如” an-empty-property”;可能為字符串,如”a-string-property”;可能為字符串數組,如”a-string-list-property”;可能為Cells(由u32整數組成),如”second-child-property”,可能為二進制數,如”a-byte-data-property”。Device Tree源文件的結構分為header、fill_area、dt_struct及dt_string四個區域。header為頭信息,fill_area為填充區域,填充數字0,dt_struct存儲節點數值及名稱相關信息,dt_string存儲屬性名。示例:a-string-property就存儲在dt_string區,”A string”及node1就存儲在dt_struct區域。
我們可以給一個設備節點添加lable,之后可以通過&lable的形式訪問這個lable,這種引用是通過phandle(pointer handle)進行的。示例,圖3中的node1就是一個lable,[email protected]的子節點[email protected]通過&node1引用[email protected]節點。像是這種phandle的節點,在經過DTC工具編譯之后,&node1會變成一個特殊的整型數字n,假設n值為1,那么在[email protected]節點下自動生成兩個屬性,屬性如下:
linux,phandle = <0x00000001>;
phandle = <0x00000001>;
[email protected]的子節點[email protected]中的a-reference-to-something = <&node1>會變成a-reference-to-something = < 0x00000001>。此處0x00000001就是一個phandle得值,每一個phandle都有一個獨一無二的整型值,在后續kernel中通過這個特殊的數字間接找到引用的節點。通過查看fdtdump輸出信息以及dtb二進制文件信息,得到struct fdt_header和文件結構之間的關系信息如所示。
圖4 struct fdt_header和文件結構之間的關系

4. Device Tree文件結構

通過以上分析,可以得到Device Tree文件結構如圖5所示。dtb的頭部首先存放的是fdt_header的結構體信息,接著是填充區域,填充大小為off_dt_struct – sizeof(struct fdt_header),填充的值為0。接著就是struct fdt_property結構體的相關信息。最后是dt_string部分。
圖5 Device Tree文件結構
Device Tree源文件的結構分為header、fill_area、dt_struct及dt_string四個區域。fill_area區域填充數值0。節點(node)信息使用struct fdt_node_header結構體描述。屬性信息使用struct fdt_property結構體描述。各個結構體信息如下:

struct fdt_node_header {
    fdt32_t tag;
    char name[0];
};

struct fdt_property {
    fdt32_t tag;
    fdt32_t len;
    fdt32_t nameoff;
    char data[0];
};

struct fdt_node_header描述節點信息,tag是標識node的起始結束等信息的標志位,name指向node名稱的首地址。tag的取值如下:

#define FDT_BEGIN_NODE  0x1     /* Start node: full name */
#define FDT_END_NODE    0x2     /* End node */
#define FDT_PROP        0x3     /* Property: name off, size, content */
#define FDT_NOP         0x4     /* nop */
#define FDT_END         0x9

FDT_BEGIN_NODE和FDT_END_NODE標識node節點的起始和結束,FDT_PROP標識node節點下面的屬性起始符,FDT_END標識Device Tree的結束標識符。因此,對于每個node節點的tag標識符一般為FDT_BEGIN_NODE,對于每個node節點下面的屬性的tag標識符一般是FDT_PROP。
描述屬性采用struct fdt_property描述,tag標識是屬性,取值為FDT_PROP;len為屬性值的長度(包括‘七星彩走势图2元网官网