設(shè)備樹的引入減少了內(nèi)核為支持新硬件而需要的改變,提高代碼重用,加速了Linux支持包的開發(fā),使得單個內(nèi)核鏡像能支持多個系統(tǒng)。作為U-Boot 和Linux 內(nèi)核之間的動態(tài)接口,本文闡述了設(shè)備樹的數(shù)據(jù)存儲格式以及源碼描述語法,進(jìn)而分析了U-Boot 對扁平設(shè)備樹的支持設(shè)置,Linux 內(nèi)核對設(shè)備樹的解析流程。
IBM、Sun 等廠家的服務(wù)器最初都采用了Firmware(一種嵌入到硬件設(shè)備中的程序,用于提供軟件和硬件之間的接口),用于初始化系統(tǒng)配置,提供操作系統(tǒng)軟件和硬件之間的接口,啟動和運行系統(tǒng)。后來為了標(biāo)準(zhǔn)化和兼容性,IBM、Sun 等聯(lián)合推出了固件接口IEEE 1275標(biāo)準(zhǔn),讓他們的服務(wù)器如IBM PowerPC pSeries,Apple PowerPC,Sun SPARC 等均采用OpenFirmware,在運行時構(gòu)建系統(tǒng)硬件的設(shè)備樹信息傳遞給內(nèi)核,進(jìn)行系統(tǒng)的啟動運行[1]。這樣做的好處有,減少內(nèi)核對系統(tǒng)硬件的嚴(yán)重依賴,利于加速支持包的開發(fā),降低硬件帶來的變化需求和成本,降低對內(nèi)核設(shè)計和編譯的要求。
隨著 Linux/ppc64 內(nèi)核的發(fā)展,內(nèi)核代碼從原來的arch/ppc32 和arch/ppc64 逐漸遷移到統(tǒng)一的arch/powerpc 目錄,并在內(nèi)核代碼引入Open Firmware API 以使用標(biāo)準(zhǔn)固件接口[2]。Linux 內(nèi)核在運行時,需要知道硬件的一些相關(guān)信息。對于使用ARCH=powerpc 參數(shù)編譯的內(nèi)核鏡像,這個信息需要基于Open Firmware 規(guī)范,以設(shè)備樹的形式存在[3]。這樣內(nèi)核在啟動時讀取掃描Open Firmware 提供的設(shè)備樹,從而獲得平臺的硬件設(shè)備信息,搜索匹配的設(shè)備驅(qū)動程序并將該驅(qū)動程序綁定到設(shè)備。
在嵌入式 PowerPC 中,一般使用U-Boot 之類的系統(tǒng)引導(dǎo)代碼,而不采用Open Firmware。早期的U-Boot 使用include/asm-ppc/u-boot.h 中的靜態(tài)數(shù)據(jù)結(jié)構(gòu)struct bd_t 將板子基本信息傳遞給內(nèi)核,其余的由內(nèi)核處理。這樣的接口不夠靈活,硬件發(fā)生變化就需要重新定制編譯燒寫引導(dǎo)代碼和內(nèi)核,而且也不再適應(yīng)于現(xiàn)在的內(nèi)核。為了適應(yīng)內(nèi)核的發(fā)展及嵌入式PowerPC平臺的千變?nèi)f化,吸收標(biāo)準(zhǔn)Open Firmware 的優(yōu)點,U-Boot 引入了扁平設(shè)備樹FDT 這樣的動態(tài)接口,使用一個單獨的FDT blob(二進(jìn)制大對象,是一個可以存儲二進(jìn)制文件的容器)存儲傳遞給內(nèi)核的參數(shù)[3]。一些確定信息,例如cache 大小、中斷路由等直接由設(shè)備樹提供,而其他的信息,例如eTSEC 的MAC 地址、頻率、PCI 總線數(shù)目等由U-Boot 在運行時修改。U-Boot 使用扁平設(shè)備樹取代了bd_t,而且也不再保證對bd_t 的后向兼容。
2 設(shè)備樹概念
簡單的說,設(shè)備樹是一種描述硬件配置的樹形數(shù)據(jù)結(jié)構(gòu),有且僅有一個根節(jié)點[4]。它包含了有關(guān)CPU、物理內(nèi)存、總線、串口、PHY 以及其他外圍設(shè)備信息等。該樹繼承了OpenFirmware IEEE 1275 設(shè)備樹的定義。操作系統(tǒng)能夠在啟動時對此結(jié)構(gòu)進(jìn)行語法分析,以此配置內(nèi)核,加載相應(yīng)的驅(qū)動。
3 設(shè)備樹存儲格式
U-Boot 需要將設(shè)備樹在內(nèi)存中的存儲地址傳給內(nèi)核。該樹主要由三大部分組成:頭(Header)、結(jié)構(gòu)塊(Structure block)、字符串塊(Strings block)。設(shè)備樹在內(nèi)存中的存儲布局圖1 如下:
圖1 設(shè)備樹存儲格式圖
Fig1 The layout of a DT block
3.1 頭(header)
頭主要描述設(shè)備樹的基本信息,如設(shè)備樹魔數(shù)標(biāo)志、設(shè)備樹塊大小、結(jié)構(gòu)塊的偏移地址等,其具體結(jié)構(gòu)boot_param_header 如下。這個結(jié)構(gòu)中的值都是以大端模式表示,并且偏移地址是相對于設(shè)備樹頭的起始地址計算的。
3.2 結(jié)構(gòu)塊(structure block)
扁平設(shè)備樹結(jié)構(gòu)塊是線性化的樹形結(jié)構(gòu),和字符串塊一起組成了設(shè)備樹的主體,以節(jié)點形式保存目標(biāo)板的設(shè)備信息。在結(jié)構(gòu)塊中,節(jié)點起始標(biāo)志為常值宏OF_DT_BEGIN_NODE,節(jié)點結(jié)束標(biāo)志為宏OF_DT_END_NODE;子節(jié)點定義在節(jié)點結(jié)束標(biāo)志前。一個節(jié)點可以概括為以O(shè)F_DT_BEGIN_NODE 開始,包括節(jié)點路徑、屬性列表、子節(jié)點列表,最后以O(shè)F_DT_END_NODE 結(jié)束的序列,每一個子節(jié)點自身也是類似的結(jié)構(gòu)。
3.3 字符串塊(Strings block)
為了節(jié)省空間,將一些屬性名,尤其是那些重復(fù)冗余出現(xiàn)的屬性名,提取出來單獨存放到字符串塊。這個塊中包含了很多有結(jié)束標(biāo)志的屬性名字符串。在設(shè)備樹的結(jié)構(gòu)塊中存儲了這些字符串的偏移地址,這樣可以很容易地查找到屬性名字符串。字符串塊的引入節(jié)省了嵌入式系統(tǒng)較為緊張的存儲空間。
4 設(shè)備樹源碼DTS 表示
設(shè)備樹源碼文件(.dts)以可讀可編輯的文本形式描述系統(tǒng)硬件配置設(shè)備樹,支持C/C++方式的注釋,該結(jié)構(gòu)有一個唯一的根節(jié)點“/”,每個節(jié)點都有自己的名字并可以包含多個子節(jié)點。設(shè)備樹的數(shù)據(jù)格式遵循了Open Firmware IEEE standard 1275。本文只簡述設(shè)備樹數(shù)據(jù)布局及語法,Linux 板級支持包開發(fā)者應(yīng)該詳細(xì)參考IEEE 1275 標(biāo)準(zhǔn)[5]及其他文獻(xiàn)[2] [4]。為了說明,首先給出基于PowerPC MPC8349E 處理器的最小系統(tǒng)的設(shè)備樹源碼示例。
可以看到,這個設(shè)備樹中有很多節(jié)點,每個節(jié)點都指定了節(jié)點單元名稱。每一個屬性后面都給出相應(yīng)的值。以雙引號引出的內(nèi)容為ASCII 字符串,以尖括號給出的是32 位的16 進(jìn)制值。這個樹結(jié)構(gòu)是啟動Linux 內(nèi)核所需節(jié)點和屬性簡化后的集合,包括了根節(jié)點的基本模式信息、CPU 和物理內(nèi)存布局,它還包括通過/chosen 節(jié)點傳遞給內(nèi)核的命令行參數(shù)信息。
/ {
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 根節(jié)點
設(shè)備樹的起始點稱之為根節(jié)點"/"。屬性model 指明了目標(biāo)板平臺或模塊的名稱,屬性compatible 值指明和目標(biāo)板為同一系列的兼容的開發(fā)板名稱。對于大多數(shù)32 位平臺,屬性
#address-cells 和#size-cells 的值一般為1。
4.2 CPU 節(jié)點
/cpus 節(jié)點是根節(jié)點的子節(jié)點,對于系統(tǒng)中的每一個CPU,都有相應(yīng)的節(jié)點。/cpus 節(jié)點沒有必須指明的屬性,但指明#address-cells = <1>和 #size-cells = <0>是個好習(xí)慣,這同時指明了每個CPU 節(jié)點的reg 屬性格式,方便為物理CPU 編號。
此節(jié)點應(yīng)包含板上每個CPU 的屬性。CPU 名稱一般寫作PowerPC,,例如Freescale 會使用PowerPC,8349 來描述本文的MPC8349E 處理器。CPU 節(jié)點的單元名應(yīng)該是cpu@0 的格式,此節(jié)點一般要指定device_type(固定為"cpu"),一級數(shù)據(jù)/指令緩存的表項
大小,一級數(shù)據(jù)/指令緩存的大小,核心、總線時鐘頻率等。在上面的示例中通過系統(tǒng)引導(dǎo)代碼動態(tài)填寫時鐘頻率相關(guān)項。
4.3 系統(tǒng)內(nèi)存節(jié)點
此節(jié)點用于描述目標(biāo)板上物理內(nèi)存范圍,一般稱作/memory 節(jié)點,可以有一個或多個。當(dāng)有多個節(jié)點時,需要后跟單元地址予以區(qū)分;只有一個單元地址時,可以不寫單元地址,默認(rèn)為0。
此節(jié)點包含板上物理內(nèi)存的屬性,一般要指定device_type(固定為"memory")和reg屬性。其中reg 的屬性值以<起始地址空間大小>的形式給出,如上示例中目標(biāo)板內(nèi)存起始地址為0,大小為256M 字節(jié)。
4.4 /chosen 節(jié)點
這個節(jié)點有一點特殊。通常,這里由Open Firmware 存放可變的環(huán)境信息,例如參數(shù),默認(rèn)輸入輸出設(shè)備。
這個節(jié)點中一般指定bootargs 及l(fā)inux,stdout-path 屬性值。bootargs 屬性設(shè)置為傳遞給內(nèi)核命令行的參數(shù)字符串。linux,stdout-path 常常為標(biāo)準(zhǔn)終端設(shè)備的節(jié)點路徑名,內(nèi)核會以此作為默認(rèn)終端。
U-Boot 在1.3.0 版本后添加了對扁平設(shè)備樹FDT 的支持,U-Boot 加載Linux 內(nèi)核、Ramdisk 文件系統(tǒng)(如果使用的話)和設(shè)備樹二進(jìn)制鏡像到物理內(nèi)存之后,在啟動執(zhí)行Linux內(nèi)核之前,它會修改設(shè)備樹二進(jìn)制文件。它會填充必要的信息到設(shè)備樹中,例如MAC 地址、PCI 總線數(shù)目等。U-Boot 也會填寫設(shè)備樹文件中的“/chosen”節(jié)點,包含了諸如串口、根設(shè)備(Ramdisk、硬盤或NFS 啟動)等相關(guān)信息。
4.5 片上系統(tǒng)SOC 節(jié)點
此節(jié)點用來描述片上系統(tǒng)SOC,如果處理器是SOC,則此節(jié)點必須存在。頂級SOC 節(jié)點包含的信息對此SOC 上的所有設(shè)備可見。節(jié)點名應(yīng)該包含此SOC 的單元地址,即此SOC內(nèi)存映射寄存器的基址。SOC 節(jié)點名以/soc的形式命名,例如MPC8349 的SOC
節(jié)點是"soc8349"。
在屬性中應(yīng)該指定device_type(固定為"soc")、ranges、bus-frequency 等屬性。ranges屬性值以的形式指定。SOC 節(jié)點還包含目標(biāo)板使用的每個SOC 設(shè)備子節(jié)點,應(yīng)該在設(shè)備樹中盡可能詳細(xì)地描述此SOC 上的外圍設(shè)備。如下給出帶有看門狗設(shè)備的SOC 節(jié)點DTS 示例。
soc8349@e0000000 {
#address-cells = <1>;
#size-cells = <1>;
device_type = "soc";
compatible = "simple-bus";
ranges = <0 e0000000 100000>; /* size 1MB */
reg = ;
bus-frequency = <0>; /* from bootloader */
{
device_type = "watchdog";
compatible = "mpc83xx_wdt";
reg = <200 100>; /* offset: 0x200 */
};
};
4.6 其他設(shè)備節(jié)點
分級節(jié)點用來描述系統(tǒng)上的總線和設(shè)備,類似物理總線拓?fù)?,能很方便的描述設(shè)備間的關(guān)系。對于系統(tǒng)上的每個總線和設(shè)備,在設(shè)備樹中都有其節(jié)點。對于這些設(shè)備屬性的描述和定義請詳細(xì)參考IEEE 1275 標(biāo)準(zhǔn)及本文參考文獻(xiàn)[2]。
設(shè)備樹的中斷系統(tǒng)稍顯復(fù)雜,設(shè)備節(jié)點利用interrupt-parent 和interrupts 屬性描述到中斷控制器的中斷連接。其中interrupt-parent 屬性值為中斷控制器節(jié)點的指針,#interrupts 屬性值描述可觸發(fā)的中斷信號,其值格式與中斷控制器的interrupt-cells 屬性值有關(guān)。一般
#interrupt-cells 屬性值為2,interrupts 屬性就對應(yīng)為一對描述硬件中斷號和中斷觸發(fā)方式的十六進(jìn)制值。
5 扁平設(shè)備樹編譯
根據(jù)嵌入式板的設(shè)備信息寫設(shè)備樹源碼文件(.dts)通常比較簡單,但是手寫二進(jìn)制的扁平設(shè)備樹(.dtb)就顯得比較復(fù)雜了。設(shè)備樹編譯器dtc 就是用來根據(jù)設(shè)備樹源碼的文本文件生成設(shè)備樹二進(jìn)制鏡像的。dtc 編譯器會對輸入文件進(jìn)行語法和語義檢查,并根據(jù)Linux內(nèi)核的要求檢查各節(jié)點及屬性,將設(shè)備樹源碼文件(.dts)編譯二進(jìn)制文件(.dtb),以保證內(nèi)核能正常啟動。dtc 編譯器的使用方法如下所示[6]:dtc [ -I dts ] [ -O dtb ] [ -o opt_file ] [ -V opt_version ] ipt_file2.6.25 版本之后的內(nèi)核源碼已經(jīng)包含了dtc 編譯器。在配置編譯內(nèi)核時選中CONFIG_DTC,會自動生成設(shè)備樹編譯器dtc。將編寫的目標(biāo)板設(shè)備樹文件mpc8349emitx.dts放到內(nèi)核源碼的arch/powerpc/boot/dts/目錄下,利用內(nèi)核Makefile 生成blob 的簡單規(guī)則,使
用以下命令亦可完成設(shè)備樹的dtc 編譯:
$ make mpc8349emitx.dtb
6 U-Boot 相關(guān)設(shè)置說明
為使 U-Boot 支持設(shè)備樹,需要在板子配置頭文件中設(shè)置一系列宏變量。如本文在
MPC8349E 處理器目標(biāo)板中移植的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
啟動引導(dǎo)代碼U-Boot 在完成自己的工作之后,會加載Linux 內(nèi)核,并將扁平設(shè)備樹的
地址傳遞給內(nèi)核,其代碼形式如下:
#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 內(nèi)核的入口有且只有一個,入口點為內(nèi)核鏡像的起始。此入口支持兩種調(diào)用方式,一種是支持Open Firmware 啟動,另一種對于沒有OF 的引導(dǎo)代碼,需要使用扁平設(shè)備樹塊,如上示例代碼。寄存器r3 保存指向設(shè)備樹的物理地址指針,寄存器r4 保存為內(nèi)
核在物理內(nèi)存中的地址,r5 為NULL。其中的隱含意思為:假設(shè)開啟了mmu,那么這個mmu的映射關(guān)系是1:1 的映射,即虛擬地址和物理地址是相同的。
7 Linux 內(nèi)核對設(shè)備樹的解析
扁平設(shè)備樹描述了目標(biāo)板平臺中的設(shè)備樹信息。每個設(shè)備都有一個節(jié)點來描述其信息,每個節(jié)點又可以有子節(jié)點及其相應(yīng)的屬性。內(nèi)核源碼中include/linux/of.h 及drivers/of/base.c等文件中提供了一些Open Firmware API,通過這些API,內(nèi)核及設(shè)備驅(qū)動可以查找到相應(yīng)
的設(shè)備節(jié)點,讀取其屬性值,利用這些信息正確地初始化和驅(qū)動硬件。
圖2 內(nèi)核及驅(qū)動對扁平設(shè)備樹的解析
Fig2 Interaction from kernel and drivers with the FDT blob
8 結(jié)論
本文介紹了設(shè)備樹的起源及其優(yōu)點,進(jìn)而闡述了設(shè)備樹的數(shù)據(jù)存儲格式以及源碼描述語法,給出了設(shè)備樹的編譯方法,最后引出了移植過程中的U-Boot 相關(guān)設(shè)置說明及內(nèi)核的解析過程分析。設(shè)備樹為嵌入式系統(tǒng)向Linux 內(nèi)核傳遞參數(shù)的動態(tài)接口,本文以MPC8349E
處理器目標(biāo)板上的DTS 移植經(jīng)歷作總結(jié),希望對嵌入式PowerPC Linux 開發(fā)者具有一定的參考價值,可以加快嵌入式PowerPC Linux 開發(fā)中的設(shè)備樹DTS 移植過程。
?
學(xué)會Linux設(shè)備樹dts移植
- Linux(218417)
- DTS(16744)
- 設(shè)備樹(3545)
相關(guān)推薦
熱點推薦
電子發(fā)燒友App













評論