版權: 凌雲物網智科實驗室<
www.iot-yun.com >
聲明: 本文檔由凌雲物網智科實驗室郭工編著!
作者: 郭文學< QQ: 281143292
guowenxue@gmail.com>
版本: v1.0.0
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文件了。

3. 早期Linux內核啟動
早期的Linux內核(Linux-3.0以前)里的設備信息(platform_device)和驅動信息(platform_driver)都是通過C代碼硬寫入到Linux內核里去了,這些源文件都在arch/arm/mach-xxx或plat-xxx下:

例如我們移植Linux內核代碼到FL2440開發板時,就會在設備文件
arch/arm/mach-s3c2440/mach-smdk2440.c中作大量修改的,該文件就描述了開發板上所有的設備信息。

我們在編譯Linux內核源碼之后會生成zImage文件,該文件並不能直接被u-boot啟動。之后需要使用u-boot里的mkimage工具生成uImage。

在將zImage轉換成uImage文件后,我們在u-boot下就可以直接使用tftp 下載並通過bootm 命令啟動Linux內核了。
U-Boot> tftp 30008000 linuxrom-s3c2440.bin && bootm 30008000
在前些年我們玩ARM Linux時大多是使用的這種方法。但自從Linus大神發飆之后,
ARM社區幾乎“一夜”之間將 arch/arm/mach-xxx 或 arch/arm/plat-xxx的代碼全部廢除,並不再支持。這也就是使用像S3C2440這樣的開發板,最高Linux內核版本只能到Linux-3.0的原因。而最新的內核中所有硬件信息都必須通過arch/arm/boot/dts中的DTS(Device Tree Source)文件來描述。這樣如果S3C2440想要升級到更高版本的Linux話,就必須自己重寫S3C2440的DTS文件,當然很少有人願意為一個停產的CPU做這些無用功的。
4. 設備樹啟動
Linux-3.x之后的內核統一啟用Device Tree機制之后,所有的設備硬件信息描述都會放到 arch/arm/boot/dts/ 路徑下的 xxx.dts文件中描述。這些dts(Device Tree Source)文件並不是C代碼,而是具有相應語法格式的源文件。在編譯內核時,我們可以使用 make dtbs 命令編譯生成相應開發板的dtb(Device Tree Blob)文件。因為這些源文件並不是C程序,所以不是用gcc來編譯,而是由其相應的編譯工具dtc(Device Tree Compiler)來編譯。

如下面我對Atmel SAMA5D44開發板移植Linux內核的編譯過程和結果:

很顯然,這里Linux內核uImage文件中只包含了Linux內核驅動相關的信息,而所有的設備硬件信息都在編譯生成的at91-sama5d4_xplained.dtb設備樹文件中。這也就意味着u-boot在啟動時只有uImage是不夠的,而是兩個文件都需要。對於這種情況,u-boot在啟動時需要這兩個文件,同時bootm命令里還要指定它們加載到內存中的地址。如下所示:

5 設備樹和uImage合並
參考上面的例子我們可以看到,在這里使用dtb文件會有一個很大的好處,即通過dtb文件將設備的硬件信息和Linux內核分離開了。這樣也就意味着我們只需要編譯一個Linux內核,然后加載不同的dtb文件,就可以為不同的硬件開發板服務了。譬如在上面的例子中,我使用同一個內核uImage,如果我想在Atmel的SAMA5D4 Xplained開發板上運行就只需要加載dtb文件at91-sama5d4_xplained.dtb即可; 而如果我們想啟動開發板SAMA5D3 Xplained的話,只需要將DTB文件更新為at91-sama5d3_xplained.dtb即可,而不需更新uImage。這為今后的產品升級換代提供了很大的便利。
但嵌入式是一個軟硬件高度定制的產品,我們一般很少使用這種特性。因為在生產時Linux系統內核要提供兩個文件(uImage和dtb)並下載燒錄,顯得有點繁瑣,這時我們更多地是希望將dtb和uImage打包到一個image中燒錄啟動。這時候可以分別通過Linux內核和u-boot來實現:
5.1 Linux內核append DTB
之所以Linux內核會提供這種方式是因為很多廠家都有自己的bootloader,但是這些bootloader並不都一定支持設備樹,為了實現支持設備樹啟動,就引入了這種啟動方式,即將編譯出的zImage和編譯出的設備樹鏡像文件拼成一個新的鏡像,在內核的自解壓代碼中會識別到,不會出現自解壓時導致設備樹被覆蓋。2016年在本人深圳消安做的一個LoRa物聯網網關產品使用的Atmel的處理器AT91SAM9X35+Linux-4.1內核,在該內核代碼中就是通過內核里支持的功能來合並uImage和dtb文件的。具體的實現方式是:
首先在內核
make menuconfig的“
Boot options --->”選項里要選擇:

在編譯Linux內核生成uImage和dtb文件之后,使用cat命令將他們合並,然后再使用mkimage命令生成u-boot啟動相關的uImage文件:
guowenxue@ubuntu-master: ~/linux-at91-linux4sam_5.3$ cat arch/arm/boot/dts/at91sam9x35ek.dtb >> arch/arm/boot/zImage
guowenxue@ubuntu-master: ~/linux-at91-linux4sam_5.3$ mkimage -A arm -O linux -n AT91SAM9X35EK -C NONE -a 0x20008000 -e 0x20008000 -d arch/arm/boot/zImage linuxrom-sam9x35ek.bin
guowenxue@ubuntu-master: ~/linux-at91-linux4sam_5.3$ chmod a+x linuxrom-sam9x35ek.bin
這樣,在u-boot里直接下載生成的uImage文件啟動即可。
U-Boot> tftp 22000000 linuxrom-sam9x35ek.bin && bootm 22000000
5.2 u-boot FIT image合並
最近接的馬來西亞CoherentPlus的一個NFC支付讀卡器項目,選用Atmel的Cortex A5處理器SAMA5D44,所使用的是Linux-4.9和U-Boot 2014.07。同樣嘗試上面SAM9X35的套路打包uImage和dtb文件並啟動Linux內核時失敗,U-boot啟動時提示如下錯誤。畢竟現在已經是9102年了,在這里沒有太大興趣研究這種老的打包方式,而轉向u-boot的全興工作方式FIT Image。

我們知道,Linux kernel在ARM架構中引入device tree(全稱是Flattened Device Tree,后續將會以FDT代稱)的時候,其實懷揣了一個Unify Kernel的夢想----同一個Image,可以支持多個不同的平台。隨着新的ARM64架構將FDT列為必選項,並將和體系結構有關的代碼剝離之后,這個夢想已經接近實現。Device Tree在ARM架構中普及之后,u-boot也馬上跟進、大力支持,畢竟,美好的Unify kernel的理想,需要bootloader的成全。為了支持基於device tree的unify kernel,u-boot需要一種新的Image格式,這種格式需要具備如下能力:
-
Image中需要包含多個dtb文件;
-
可以方便的選擇使用哪個dtb文件boot kernel;
是不是這樣就感覺跟Linux內核一樣Niubility了?沒錯!要的就是這種感覺。綜合上面的需求,u-boot推出了全新的image格式----FIT uImage,其中FIT是flattened image tree的簡稱。它利用了Device Tree Source files(DTS)的語法,生成的image文件也和dtb文件類似(稱作itb),下面是我們項目中的示例代碼。:
guowenxue@ubuntu-master:~/sama5d4-sdk/linux-bsp/linux-at91$ cat linuxrom-sama5d4.its
/* U-Boot uImage source file for "sama5d4_xplained" */ /dts-v1/; / { description = "U-Boot uImage source file for SAMA5D4 Xplained"; #address-cells = <1>; images { kernel@sama5d4 { description = "Linux kernel for SAMA5D4 Xplained"; data = /incbin/("arch/arm/boot/zImage"); type = "kernel"; arch = "arm"; os = "linux"; compression = "none"; load = <0x20008000>; entry = <0x20008000>; }; fdt@sama5d4 { description = "Flattened Device Tree blob for SAMA5D4 Xplained"; data = /incbin/("arch/arm/boot/dts/at91-sama5d4_xplained.dtb"); type = "flat_dt"; arch = "arm"; compression = "none"; }; }; configurations { default = "conf@sama5d4"; conf@sama5d4 { description = "Boot Linux kernel with FDT blob"; kernel = "kernel@sama5d4"; fdt = "fdt@sama5d4"; }; }; };
上面的代碼是不是很眼熟?沒錯,就是跟Linux內核樹里的DTS文件語法一樣,里面的一些參數就是mkimage制作uImage時的一些參數。在編譯生成Linux內核zImage和dtb文件之后,我們只需要使用mkimage命令就可以生成相應的itb文件了。當然,上面的文件遵循dts語法,那他的編譯就需要dtc編譯器,默認ubuntu並沒有安裝該命令,所以在使用之前還得安裝相應的命令,好在ubuntu下都提供了,如果沒有可以在u-boot或linux內核下去找:
guowenxue@ubuntu-master:~/sama5d4-sdk/linux-bsp/linux-at91$ sudo apt-get install u-boot-tools device-tree-compiler
guowenxue@ubuntu-master:~/sama5d4-sdk/linux-bsp/linux-at91$ mkimage -f linuxrom-sama5d4.its linuxrom-sama5d4.itb
FIT description: U-Boot uImage source file for SAMA5D4 Xplained Created: Fri Aug 23 21:43:12 2019 Image 0 (kernel@sama5d4) Description: Linux kernel for SAMA5D4 Xplained Created: Fri Aug 23 21:43:12 2019 Type: Kernel Image Compression: uncompressed Data Size: 4879744 Bytes = 4765.38 kB = 4.65 MB Architecture: ARM OS: Linux Load Address: 0x20008000 Entry Point: 0x20008000 Image 1 (fdt@sama5d4) Description: Flattened Device Tree blob for SAMA5D4 Xplained Created: Fri Aug 23 21:43:12 2019 Type: Flat Device Tree Compression: uncompressed Data Size: 32670 Bytes = 31.90 kB = 0.03 MB Architecture: ARM Default Configuration: 'conf@sama5d4' Configuration 0 (conf@sama5d4) Description: Boot Linux kernel with FDT blob Kernel: kernel@sama5d4 FDT: fdt@sama5d4
既然是全新的東西,u-boot默認並不一定支持。如果要U-boot支持FIT Image啟動的話,我們還得在u-boot的配置文件中添加它的支持,即加上 CONFIG_FIT 宏定義即可:
guowenxue@ubuntu-master:~/sama5d4-sdk/linux-bsp/u-boot-at91$ vim include/configs/sama5d4_xplained.h
/* add by guowenxue, 2019.08.22 */ #define CONFIG_FIT 1 #define CONFIG_ENV_OVERWRITE 1 #define CONFIG_ETHADDR 42:96:ab:be:a7:5e #define CONFIG_IPADDR 192.168.2.199 #define CONFIG_SERVERIP 192.168.2.2
編譯升級u-boot之后,我們則可以直接啟動該itb文件了。啟動過程如下所示:

6 PS:
本文檔主要是講解Linux內核設備樹的使用,並不涉及到Device Tree Source的語法和原理,如果有需要的請自行百度、參考Linux內核里的設備樹文件學習。