1. 程式人生 > >分析核心原始碼,裝置樹

分析核心原始碼,裝置樹

U-Boot需要將裝置樹在記憶體中的儲存地址傳給核心。該樹主要由三大部分組成:頭(Header)、結構塊(Structure block)、字串塊(Strings block)。

裝置樹在記憶體中的儲存佈局圖如下


1.1 頭(header


1.2 結構塊(struct block

 扁平裝置樹結構塊是線性化的樹形結構,和字串塊一起組成了裝置樹的主體,以節點形式儲存目標板的裝置資訊

  在結構塊中,節點起始標誌為32位常值巨集OF_DT_BEGIN_NODE,節點結束標誌為巨集OF_DT_END_NODE

  子節點定義在節點結束標誌前。一個節點的基本結構如下所示:

 (1). 節點起始標誌OF_DT_BEGIN_NODE(即0x0000_0001);


 (2). 節點路徑或者節點單元名(version < 3以及節點路徑表示,version > 16時以節點單元名錶示);
 (3). 填充位元組保證四位元組對齊;
 (4). 節點屬性。

  每個屬性以常值巨集OF_DT_PROP 開始,後面依次為屬性值的位元組長度、屬性名在字串塊中的偏移值,屬性值及位元組對齊填充段;

 (5). 如果存在子節點,則定義子節點

 (6). 節點結束標誌OF_DT_END_NODE(即0x0000_0002)。

歸納起來,一個節點可以概括為以OF_DT_BEGIN_NODE開始,節點路徑、屬性列表、子節點列表以及OF_DT_END_NODE結束的序列,每一個子節點自身也是類似的結構。

1.3 字串塊(Strings block)

為了節省空間,對於那些屬性名,尤其是很多屬性名是重複冗餘出現的,提取出來單獨存放到字串塊。這個塊中包含了很多有結束標誌的屬性名字串。 在裝置樹的結構塊中儲存了這些字串的偏移地址,因為可以很容易的查詢到屬性名字串。字串塊的引入節省嵌入式系統較為緊張的儲存空間。

1.4 裝置樹原始碼 DTS 表示

       裝置樹原始碼檔案(.dts)以可讀可編輯的文字形式描述系統硬體配置裝置樹,支援 C/C++方式的註釋,該結構有一個唯一的根節點“/”,每個節點都有自己的名字並可以包含多個子節點。裝置樹的資料格式遵循了 Open Firmware IEEE standard 1275。這個裝置樹中有很多節點,每個節點都指定了節點單元名稱。每一個屬性後面都給出相應的值。以雙引號引出的內容為 ASCII 字串,以尖括號給出的是 32 位的16進位制值。這個樹結構是啟動 Linux 核心所需節點和屬性簡化後的集合,包括了根節點的基本模式資訊、CPU 和實體記憶體佈局,它還包括通過/chosen 節點傳遞給核心的命令列引數資訊。

1.5 machine_desc結構

      核心提供了一個重要的結構體struct machine_desc ,這個結構體在核心移植中起到相當重要的作用,核心通過machine_desc結構體來控制系統體系架構相關部分的初始化。machine_desc結構體通過MACHINE_START巨集來初始化,在程式碼中, 通過在mian->setup_arch中呼叫setup_machine_fdt來獲取。



1.6 裝置節點結構體


1.7 屬性結構體


三、裝置樹初始化及解析

分析Linux核心的原始碼,可以看到其對扁平裝置樹的解析流程如下

(1)首先在核心入口處將從u-boot傳遞過來的映象基地址。 (2)通過呼叫early_init_dt_scan()函式來獲取核心前期初始化所需的bootargs,cmd_line等系統引導引數。 (3)根據bootargs,cmd_line等系統引導引數進入start_kernel()函式,進行核心的第二階段初始化。 (4)呼叫unflatten_device_tree()函式來解析dtb檔案,構建一個由device_node結構連線而成的單項鍊表,並使用全域性變數of_allnodes指標來儲存這個連結串列的頭指標。 (5)核心呼叫OF提供的API函式獲取of_allnodes連結串列資訊來初始化核心其他子系統、裝置等 原始碼如下 在init目錄下的main.c中的start_kernel函式中
進入這個setup_arch 其餘程式碼先省略

檢視setup_machine_fdt函式

這裡呼叫了early_init_dt_scan_nodes函式


來看看這個函式


看看這個fdt_next_node函式


上面還有一個很重要的地方


這裡,這裡對這個initial_boot_parems進行賦值,這裡這個指標指向了裝置樹的虛擬起始地址


在這裡進行偏移,詳細資訊看這裡的程式碼

chosen屬性

chosen 節點並不代表一個真實的裝置,只是作為一個為韌體和作業系統之間傳遞資料的地方,比如引導引數。chosen 節點裡的資料也不代表硬體。通常,chosen 節點在.dts 原始檔中為空,並在啟動時填充。在我們的示例系統中,韌體可以往 chosen 節點新增以下資訊: chosen {     bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; //節點屬性
    linux,initrd-start = <0x85500000>; //節點屬性
    linux,initrd-end = <0x855a3212>; //節點屬性
};
1.2 根節點"/"        裝置樹有且僅有一個根節點,即“/”,根節點下包含很多子節點,例入下圖,根節點為"/",根節點的子節點為"chosen",根節點的屬性包含"compatible","#address-cells","#size-cells","interrupt-parent"等。屬性model指明瞭目標板平臺或模組的名稱,屬性compatible值指明和目標板為同一系列的相容的開發板名稱。對於大多數32位平臺,屬性#address-cells和#size-cells的值一般為1。#address-cells = <1>; 1表示地址32位,2表示地址64位。#size-cells = <1>;1表示rangs的每部分佔一個cell,依此類推   {
    compatible = "sprd,spx15";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = <&gic>;


    chosen {
        bootargs = "loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw";
        linux,initrd-start = <0x85500000>;
        linux,initrd-end = <0x855a3212>;
    };
} 所以本函式就是讀取根節點的"#address-cells","#size-cells"屬性
1.3 memory節點
      memory節點用於描述目標板上實體記憶體範圍,一般稱作/memory節點,可以有一個或多個。當有多個節點時,需要後跟單元地址予以區分;只有一個單元地址時,可以不寫單元地址,預設為0。此節點包含板上實體記憶體的屬性,一般要指定device_type(固定為"memory")和reg屬性。其中reg的屬性值以<起始地址 空間大小>的形式給出,如下示例中目標板記憶體起始地址為0x80000000,大小為0x20000000位元組。 
memory {
    device_type = "memory";
    reg = <0x80000000 0x20000000>;
};

在看看解析裝置樹這一塊