linux設備樹dts移植詳解

摘 要:設備樹的引入減少了內核爲支持新硬件而需要的改變,提高代碼重用,加速了Linux
支持包的開發,使得單個內核鏡像能支持多個系統。作爲U-Boot 和Linux 內核之間的動態
接口,本文闡述了設備樹的數據存儲格式以及源碼描述語法,進而分析了U-Boot 對扁平設
備樹的支持設置,Linux 內核對設備樹的解析流程。
關鍵詞:扁平設備樹; DTS; PowerPC; Linux
IBM、Sun 等廠家的服務器最初都採用了Firmware(一種嵌入到硬件設備中的程序,用
於提供軟件和硬件之間的接口),用於初始化系統配置,提供操作系統軟件和硬件之間的接
口,啓動和運行系統。後來爲了標準化和兼容性,IBM、Sun 等聯合推出了固件接口IEEE 1275
標準,讓他們的服務器如IBM PowerPC pSeries,Apple PowerPC,Sun SPARC 等均採用Open
Firmware,在運行時構建系統硬件的設備樹信息傳遞給內核,進行系統的啓動運行[1]。這樣
做的好處有,減少內核對系統硬件的嚴重依賴,利於加速支持包的開發,降低硬件帶來的變
化需求和成本,降低對內核設計和編譯的要求。
隨着 Linux/ppc64 內核的發展,內核代碼從原來的arch/ppc32 和arch/ppc64 逐漸遷移到
統一的arch/powerpc 目錄,並在內核代碼引入Open Firmware API 以使用標準固件接口[2]。
Linux 內核在運行時,需要知道硬件的一些相關信息。對於使用ARCH=powerpc 參數編譯的
內核鏡像,這個信息需要基於Open Firmware 規範,以設備樹的形式存在[3]。這樣內核在啓
動時讀取掃描Open Firmware 提供的設備樹,從而獲得平臺的硬件設備信息,搜索匹配的設
備驅動程序並將該驅動程序綁定到設備。
在嵌入式 PowerPC 中,一般使用U-Boot 之類的系統引導代碼,而不採用Open Firmware。
早期的U-Boot 使用include/asm-ppc/u-boot.h 中的靜態數據結構struct bd_t 將板子基本信息傳
遞給內核,其餘的由內核處理。這樣的接口不夠靈活,硬件發生變化就需要重新定製編譯燒
寫引導代碼和內核,而且也不再適應於現在的內核。爲了適應內核的發展及嵌入式PowerPC
平臺的千變萬化,吸收標準Open Firmware 的優點,U-Boot 引入了扁平設備樹FDT 這樣的
動態接口,使用一個單獨的FDT blob(二進制大對象,是一個可以存儲二進制文件的容器)
存儲傳遞給內核的參數[3]。一些確定信息,例如cache 大小、中斷路由等直接由設備樹提供,
而其他的信息,例如eTSEC 的MAC 地址、頻率、PCI 總線數目等由U-Boot 在運行時修改。
U-Boot 使用扁平設備樹取代了bd_t,而且也不再保證對bd_t 的後向兼容。
2 設備樹概念
簡單的說,設備樹是一種描述硬件配置的樹形數據結構,有且僅有一個根節點[4]。它包
含了有關CPU、物理內存、總線、串口、PHY 以及其他外圍設備信息等。該樹繼承了Open
Firmware IEEE 1275 設備樹的定義。操作系統能夠在啓動時對此結構進行語法分析,以此配
置內核,加載相應的驅動。
3 設備樹存儲格式
U-Boot 需要將設備樹在內存中的存儲地址傳給內核。該樹主要由三大部分組成:頭
(Header)、結構塊(Structure block)、字符串塊(Strings block)。設備樹在內存中的存
儲佈局圖1 如下:
圖1 設備樹存儲格式圖
Fig1 The layout of a DT block
3.1 頭(header)
頭主要描述設備樹的基本信息,如設備樹魔數標誌、設備樹塊大小、結構塊的偏移地址
等,其具體結構boot_param_header 如下。這個結構中的值都是以大端模式表示,並且偏移
地址是相對於設備樹頭的起始地址計算的。
3.2 結構塊(structure block)
扁平設備樹結構塊是線性化的樹形結構,和字符串塊一起組成了設備樹的主體,以節點
形式保存目標板的設備信息。在結構塊中,節點起始標誌爲常值宏OF_DT_BEGIN_NODE,
節點結束標誌爲宏OF_DT_END_NODE;子節點定義在節點結束標誌前。一個節點可以概
括爲以OF_DT_BEGIN_NODE 開始,包括節點路徑、屬性列表、子節點列表,最後以
OF_DT_END_NODE 結束的序列,每一個子節點自身也是類似的結構。
3.3 字符串塊(Strings block)
爲了節省空間,將一些屬性名,尤其是那些重複冗餘出現的屬性名,提取出來單獨存放
到字符串塊。這個塊中包含了很多有結束標誌的屬性名字符串。在設備樹的結構塊中存儲了
這些字符串的偏移地址,這樣可以很容易地查找到屬性名字符串。字符串塊的引入節省了嵌
入式系統較爲緊張的存儲空間。
4 設備樹源碼DTS 表示
設備樹源碼文件(.dts)以可讀可編輯的文本形式描述系統硬件配置設備樹,支持C/C++
方式的註釋,該結構有一個唯一的根節點“/”,每個節點都有自己的名字並可以包含多個
子節點。設備樹的數據格式遵循了Open Firmware IEEE standard 1275。本文只簡述設備樹的
數據佈局及語法,Linux 板級支持包開發者應該詳細參考IEEE 1275 標準[5]及其他文獻[2] [4]。
爲了說明,首先給出基於PowerPC MPC8349E 處理器的最小系統的設備樹源碼示例。
可以看到,這個設備樹中有很多節點,每個節點都指定了節點單元名稱。每一個屬性後面都
給出相應的值。以雙引號引出的內容爲ASCII 字符串,以尖括號給出的是32 位的16 進制
值。這個樹結構是啓動Linux 內核所需節點和屬性簡化後的集合,包括了根節點的基本模式
信息、CPU 和物理內存佈局,它還包括通過/chosen 節點傳遞給內核的命令行參數信息。
/ {
model = "MPC8349EMITX";
compatible = "MPC8349EMITX", "MPC834xMITX", "MPC83xxMITX";
#address-cells = <1>; /* 32bit address */
#size-cells = <1>; /* 4GB size */
cpus {
#address-cells = <1>;
#size-cells = <0>;
PowerPC,8349@0 {
device_type = "cpu";
reg = <0>;
d-cache-line-size = <20>; /* 32 Bytes */
i-cache-line-size = <20>;
d-cache-size = <8000>; /* L1 dcache, 32K */
i-cache-size = <8000>;
timebase-frequency = <0>; /* from bootloader */
bus-frequency = <0>;
clock-frequency = <0>;
};
};
memory {
device_type = "memory";
reg = <00000000 10000000>; /* 256MB */
};
chosen {
name = "chosen";
bootargs = "root=/dev/ram rw console=ttyS0,115200";
linux,stdout-path = "/soc8349@e0000000/serial@4500";
};
};
4.1 根節點
設備樹的起始點稱之爲根節點"/"。屬性model 指明瞭目標板平臺或模塊的名稱,屬性
compatible 值指明和目標板爲同一系列的兼容的開發板名稱。對於大多數32 位平臺,屬性
#address-cells 和#size-cells 的值一般爲1。
4.2 CPU 節點
/cpus 節點是根節點的子節點,對於系統中的每一個CPU,都有相應的節點。/cpus 節點
沒有必須指明的屬性,但指明#address-cells = <1>和 #size-cells = <0>是個好習慣,這同時指
明瞭每個CPU 節點的reg 屬性格式,方便爲物理CPU 編號。
此節點應包含板上每個CPU 的屬性。CPU 名稱一般寫作PowerPC,<name>,例如
Freescale 會使用PowerPC,8349 來描述本文的MPC8349E 處理器。CPU 節點的單元名應該是
cpu@0 的格式,此節點一般要指定device_type(固定爲"cpu"),一級數據/指令緩存的表項
大小,一級數據/指令緩存的大小,核心、總線時鐘頻率等。在上面的示例中通過系統引導
代碼動態填寫時鐘頻率相關項。
4.3 系統內存節點
此節點用於描述目標板上物理內存範圍,一般稱作/memory 節點,可以有一個或多個。
當有多個節點時,需要後跟單元地址予以區分;只有一個單元地址時,可以不寫單元地址,
默認爲0。
此節點包含板上物理內存的屬性,一般要指定device_type(固定爲"memory")和reg
屬性。其中reg 的屬性值以<起始地址空間大小>的形式給出,如上示例中目標板內存起始
地址爲0,大小爲256M 字節。
4.4 /chosen 節點
這個節點有一點特殊。通常,這裏由Open Firmware 存放可變的環境信息,例如參數,
默認輸入輸出設備。
這個節點中一般指定bootargs 及linux,stdout-path 屬性值。bootargs 屬性設置爲傳遞給內
核命令行的參數字符串。linux,stdout-path 常常爲標準終端設備的節點路徑名,內核會以此作
爲默認終端。
U-Boot 在1.3.0 版本後添加了對扁平設備樹FDT 的支持,U-Boot 加載Linux 內核、
Ramdisk 文件系統(如果使用的話)和設備樹二進制鏡像到物理內存之後,在啓動執行Linux
內核之前,它會修改設備樹二進制文件。它會填充必要的信息到設備樹中,例如MAC 地址、
PCI 總線數目等。U-Boot 也會填寫設備樹文件中的“/chosen”節點,包含了諸如串口、根
設備(Ramdisk、硬盤或NFS 啓動)等相關信息。
4.5 片上系統SOC 節點
此節點用來描述片上系統SOC,如果處理器是SOC,則此節點必須存在。頂級SOC 節
點包含的信息對此SOC 上的所有設備可見。節點名應該包含此SOC 的單元地址,即此SOC
內存映射寄存器的基址。SOC 節點名以/soc<SOCname>的形式命名,例如MPC8349 的SOC
節點是"soc8349"。
在屬性中應該指定device_type(固定爲"soc")、ranges、bus-frequency 等屬性。ranges
屬性值以<bus_addr parent_bus_addr size>的形式指定。SOC 節點還包含目標板使用的每個
SOC 設備子節點,應該在設備樹中儘可能詳細地描述此SOC 上的外圍設備。如下給出帶有
看門狗設備的SOC 節點DTS 示例。
soc8349@e0000000 {
#address-cells = <1>;
#size-cells = <1>;
device_type = "soc";
compatible = "simple-bus";
ranges = <0 e0000000 100000>; /* size 1MB */
reg = <e0000000 00000200>;
bus-frequency = <0>; /* from bootloader */
{
device_type = "watchdog";
compatible = "mpc83xx_wdt";
reg = <200 100>; /* offset: 0x200 */
};
};
4.6 其他設備節點
分級節點用來描述系統上的總線和設備,類似物理總線拓撲,能很方便的描述設備間的
關係。對於系統上的每個總線和設備,在設備樹中都有其節點。對於這些設備屬性的描述和
定義請詳細參考IEEE 1275 標準及本文參考文獻[2]。
設備樹的中斷系統稍顯複雜,設備節點利用interrupt-parent 和interrupts 屬性描述到中
斷控制器的中斷連接。其中interrupt-parent 屬性值爲中斷控制器節點的指針,#interrupts 屬
性值描述可觸發的中斷信號,其值格式與中斷控制器的interrupt-cells 屬性值有關。一般
#interrupt-cells 屬性值爲2,interrupts 屬性就對應爲一對描述硬件中斷號和中斷觸發方式的
十六進制值。
5 扁平設備樹編譯
根據嵌入式板的設備信息寫設備樹源碼文件(.dts)通常比較簡單,但是手寫二進制的
扁平設備樹(.dtb)就顯得比較複雜了。設備樹編譯器dtc 就是用來根據設備樹源碼的文本
文件生成設備樹二進制鏡像的。dtc 編譯器會對輸入文件進行語法和語義檢查,並根據Linux
內核的要求檢查各節點及屬性,將設備樹源碼文件(.dts)編譯二進制文件(.dtb),以保證
內核能正常啓動。dtc 編譯器的使用方法如下所示[6]:
dtc [ -I dts ] [ -O dtb ] [ -o opt_file ] [ -V opt_version ] ipt_file
2.6.25 版本之後的內核源碼已經包含了dtc 編譯器。在配置編譯內核時選中
CONFIG_DTC,會自動生成設備樹編譯器dtc。將編寫的目標板設備樹文件mpc8349emitx.dts
放到內核源碼的arch/powerpc/boot/dts/目錄下,利用內核Makefile 生成blob 的簡單規則,使
用以下命令亦可完成設備樹的dtc 編譯:
$ make mpc8349emitx.dtb
6 U-Boot 相關設置說明
爲使 U-Boot 支持設備樹,需要在板子配置頭文件中設置一系列宏變量。如本文在
MPC8349E 處理器目標板中移植的U-Boot 配置如下:
/* pass open firmware flat tree */
#define CONFIG_OF_LIBFDT 1
#undef CONFIG_OF_FLAT_TREE
#define CONFIG_OF_BOARD_SETUP 1
#define CONFIG_OF_HAS_BD_T 1
#define CONFIG_OF_HAS_UBOOT_ENV 1
啓動引導代碼U-Boot 在完成自己的工作之後,會加載Linux 內核,並將扁平設備樹的
地址傳遞給內核,其代碼形式如下:
#if defined(CONFIG_OF_FLAT_TREE) || defined(CONFIG_OF_LIBFDT)
if (of_flat_tree) { /* device tree; boot new style */
/*
* Linux Kernel Parameters (passing device tree):
* r3: pointer to the fdt, followed by the board info data
* r4: physical pointer to the kernel itself
* r5: NULL
* r6: NULL
* r7: NULL
*/
(*kernel) ((bd_t *)of_flat_tree, (ulong)kernel, 0, 0, 0);
/* does not return */
}
#endif
arch/powerpc 內核的入口有且只有一個,入口點爲內核鏡像的起始。此入口支持兩種調
用方式,一種是支持Open Firmware 啓動,另一種對於沒有OF 的引導代碼,需要使用扁平
設備樹塊,如上示例代碼。寄存器r3 保存指向設備樹的物理地址指針,寄存器r4 保存爲內
核在物理內存中的地址,r5 爲NULL。其中的隱含意思爲:假設開啓了mmu,那麼這個mmu
的映射關係是1:1 的映射,即虛擬地址和物理地址是相同的。
7 Linux 內核對設備樹的解析
扁平設備樹描述了目標板平臺中的設備樹信息。每個設備都有一個節點來描述其信息,
每個節點又可以有子節點及其相應的屬性。內核源碼中include/linux/of.h 及drivers/of/base.c
等文件中提供了一些Open Firmware API,通過這些API,內核及設備驅動可以查找到相應
的設備節點,讀取其屬性值,利用這些信息正確地初始化和驅動硬件。
圖2 內核及驅動對扁平設備樹的解析
Fig2 Interaction from kernel and drivers with the FDT blob
8 結論
本文介紹了設備樹的起源及其優點,進而闡述了設備樹的數據存儲格式以及源碼描述語
法,給出了設備樹的編譯方法,最後引出了移植過程中的U-Boot 相關設置說明及內核的解
析過程分析。設備樹爲嵌入式系統向Linux 內核傳遞參數的動態接口,本文以MPC8349E
處理器目標板上的DTS 移植經歷作總結,希望對嵌入式PowerPC Linux 開發者具有一定的
參考價值,可以加快嵌入式PowerPC Linux 開發中的設備樹DTS 移植過程。
[3] DENX. Flattened Device Tree Blob [EB/OL]. http://www.denx.de/wiki/view/DULG/LinuxFDTBlob, 2009
[5] SN. The Openmware Home Page [EB/OL]. http://playground.sun.com/1275/home.html
發佈了2 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章