在Linux系統中,設備樹是一種描述硬體配置的樹形資料結構,它起源於Open Firmware標準,用於提供作業系統軟體和硬體之間的接口,啟動和運行系統。設備樹可以減少核心為支援新硬體所需的改變,提高程式碼重複使用,加速Linux支援包的開發,使得單一核心鏡像能支援多個系統。本文將介紹Linux設備樹dts移植的基本步驟和方法,包括設備樹的資料儲存格式、原始碼描述語法、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 頭(頭)
頭主要描述設備樹的基本訊息,如設備樹魔數標誌、設備樹塊大小、結構塊的偏移位址
等,其具體結構boot_param_header 如下。這個結構中的值都是以大端模式表示,且偏移
3.2 結構塊(structure block)
#
扁平設備樹結構塊是線性化的樹狀結構,和字串塊一起組成了設備樹的主體,以節點
形式保存目標板的設備資訊。在結構塊中,節點起始標誌為常值宏OF_DT_BEGIN_NODE,
節點結束標誌為宏OF_DT_END_NODE;子節點定義在節點結束標誌前。一個節點可以概
括為以OF_DT_BEGIN_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 = ; /* 32bit address */ \#size-cells = ; /* 4GB size */ cpus { \#address-cells = ; \#size-cells = ; PowerPC,8349@0 { device_type = "cpu"; reg = ; d-cache-line-size = ; /* 32 Bytes */ i-cache-line-size = ; d-cache-size = ; /* L1 dcache, 32K */ i-cache-size = ; timebase-frequency = ; /* from bootloader */ bus-frequency = ; clock-frequency = ; }; }; memory { device_type = "memory"; reg = ; /* 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 = 和 #size-cells = 是个好习惯,这同时指
明了每个CPU 节点的reg 属性格式,方便为物理CPU 编号。
此节点应包含板上每个CPU 的属性。CPU 名称一般写作PowerPC,,例如
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的形式命名,例如MPC8349 的SOC
节点是”soc8349″。
在属性中应该指定device_type(固定为”soc”)、ranges、bus-frequency 等属性。ranges
属性值以
SOC 设备子节点,应该在设备树中尽可能详细地描述此SOC 上的外围设备。如下给出带有
看门狗设备的SOC 节点DTS 示例。
soc8349@e0000000 { \#address-cells = ; \#size-cells = ; device_type = "soc"; compatible = "simple-bus"; ranges = ; /* size 1MB */ reg = ; bus-frequency = ; /* from bootloader */ { device_type = "watchdog"; compatible = "mpc83xx_wdt"; reg = ; /* 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 结论
通过本文,你应该对Linux设备树dts移植有了一个基本的了解,它是一种描述和传递硬件配置信息的有效方式,可以适应嵌入式Linux系统的多样化需求。当然,设备树也不是一成不变的,它需要根据具体的硬件平台和内核版本进行定制和修改。总之,设备树是Linux系统中不可或缺的一个组件,值得你深入学习和掌握。
以上是Linux設備樹dts移植:如何描述和傳遞硬體配置訊息的詳細內容。更多資訊請關注PHP中文網其他相關文章!