羅名駒,陳益民,賈志文
(廣東工業大學 信息工程學院,廣州 510006)
扁平設備樹FDT在ARM Linux中的應用研究
羅名駒,陳益民,賈志文
(廣東工業大學 信息工程學院,廣州 510006)
引入Flattened Device Tree(扁平設備樹,FDT)到ARM Linux后,Linux內核可以通過FDT獲取板級硬件的細節信息,這樣就減少了Linux 內核中arch/arm目錄下大量描述板級硬件細節信息的冗余代碼,把大多數與板級硬件特性相關的代碼放在設備樹文件和設備驅動中,提高了代碼的復用性,避免了ARM Linux內核為支持新硬件進行大量修改,提高了ARM Linux板級支持的開發速度,也使得使用現有的內核鏡像去引導具有相同芯片集的硬件平臺成為可能。
扁平設備樹;ARM;Linux kernel
在Flattened Device Tree(FDT)還沒有引入ARM Linux內核之前,Linux內核代碼arch/arm目錄下有大量重復的代碼來描述板級硬件的細節信息。正是這些用來描述板級硬件細節信息的代碼使ARM Linux內核代碼變得越來越冗余。
2011年3月17日,Linus Torvalds在ARM Linux郵件列表寫到“this whole ARM thing is a fucking pain in the ass”,這引起了ARM Linux社區激烈的討論。ARM社區為改變Linux內核代碼冗余的情況引入了FDT。使用FDT后,Linux內核可以直接通過FDT獲取硬件的細節信息,這使得ARM Linux內核中的冗余編碼大大減少,同時也使得用一個內核鏡像去引導同一類ARM芯片集的硬件平臺成為可能。
FDT是一種描述板級硬件配置的樹形數據結構,來源于 Open Firmware (OF)[1],并繼承了Open Firmware IEEE 1275 設備樹的定義[2]。
在Flattened Device Tree中,可描述板級硬件的信息包括[3]:CPU的數量和類別、內存基地址和大小、總線和橋、中斷控制器、Clock控制器、GPIO控制器和外圍設備。
Bootloader在啟動的過程中把FDT二進制代碼加載到內存,Linux內核會根據FDT傳遞進來的硬件信息展開,得到Linux內核所需的硬件設備信息。
1.1 FDT源文件(Device tree source)
FDT源文件(.dts)[4]由節點(node)和屬性(property)組成,每個節點可包含子節點和屬性。在Linux內核的arch/arm/boot/dts/目錄下存放著.dts文件,通常一個板級硬件對應著一個.dts文件。
下面是一個簡單設備樹源碼示例:
/{
model = "MyBoardName"
compatible= "MyBoardFamilyName"
#address-cells = <1>
#size-cells = <1>
aliases {
i2c0 = &i2c0;
i2s0 = &i2s0;
spi0 = &spi0;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0{
compatible = "arm,Cortex-a9";
device_type = "cpu";
reg = <0>;
};
};
memory@0{
name = "memory"
device_type = "memory"
reg = <0x00000000 0x20000000>
}
chosen{
name = "chosen"
bootargs="console=ttySAC0,115200 root=/dev/
mmcblk0p5"
}
}
從FDT源碼示例可以看到,設備樹的基本單元由節點組成,每個節點都由節點單元名稱標識,并且每一個屬性都有相對應的值。節點用節點名字標識,其形式為node-name@unit-address。
(1) 根節點
每個設備樹源碼文件里面有且僅有一個根節點,且節點名必須是“/”。根節點下有若干個節點,每個節點中的屬性(property)描述了該節點的特性。根節點下包含的屬性有:model為板級硬件模塊的名稱;#address-cells表示該目標板或者硬件模塊的地址線;#size-cells表示該目標板或者硬件模塊的地址線寬度。
(2) CPU節點
CPU節點描述了CPU的特性,每一個CPU都有一個與之一一對應的CPU節點。CPU節點下常用的屬性有[5]:#address-cells表示CPU的地址線;#size-cells表示CPU的地址線寬度;device_type為節點設備類型,必須設為“cpu”;reg用于指定CPU的編號。
(3) memory 節點
memory節點是FDT文件必備的節點,它定義了板級硬件物理內存的起始地址和大小。該節點下常用的屬性有:
device_type為節點設備類型,必須設為“memory”;
reg用
指定內存地址和大小,例如<0x00000000 0x20000000>表示內存地址從0x0000 0000開始,大小為512 MB的內存節點。(4) SoC節點
SoC節點描述了CPU上集成的外設接口,例如I2C、SPI、串口等。SoC節點包含的屬性有:device_type為節點設備類型,必須設為“soc”;ranges節點表示SoC寄存器地址。
(5) chosen節點
chosen節點不是用來描述硬件資源的信息,而是向Linux 內核傳遞一些運行時的參數。使用chosen節點可以取代Bootloader通過tag list傳遞運行參數給Linux 內核。例如:bootargs這個屬性傳遞的是command line的信息;initrd-start這個屬性傳遞是initrd的開始地址。
(6) aliases節點
aliases節點定義了一些節點的別名。在FDT源文件中引用一個節點時要指明相對于根節點的絕對路徑,例如/node-name-1/node-name-2。aliases 節點定義一些節點路徑的別名,使引用節點變得簡潔。
Linux內核開發者把SoC芯片公用的.dts文件單獨分離出來,保存為.dtsi文件。當其他.dts文件需要使用這些公共的.dtsi文件時,就把該.dtsi包含進去。大部分的ARM SoC的.dtsi文件都引用了arch/arm/boot/dts/skeleton.dtsi這個設備樹源碼文件。
1.2 FDT源碼編譯器(Device tree compiler)
FDT源碼編譯器是將FDT源碼文件(.dts)編譯為FDT二進制文件(.dtb)的工具。配置Linux內核時,選中CONFIG_DTC選項,Linux內核在編譯的過程中會自動編譯生成設備樹編譯器。在Linux內核源碼根目錄下運行make dtbs命令時,設備樹源碼編譯器會把選中的.dts編譯成.dtb。
1.3 FDT二進制文件(Device tree blob)
使用設備樹源碼編譯器把.dts格式的設備樹源碼文件編譯成.dtb格式的FDT二進制文件。在制作板級硬件啟動鏡像時,通常會在指定區域存放.dtb文件,Bootloader在引導內核的過程中會把該.dtb文件讀取到特定內存區域中。
ARM Linux未引入FDT時,ARM Linux內核在arch/arm/plat-xxx和arch/arm/mach-xxx做的主要工作是[6]:
注冊platform_device,綁定resource:
static struct resource xxx_resources[] = {
[0] = {
.start= …,
.end= …,
.flags= IORESOURCE_MEM,
},
[1] = {
.start=…,
.end=…,
.flags=IORESOURCE_IRQ,
},
};
static struct platform_device xxx_device={
name="xxx",
.id=-1,
.dev= {
.platform_data=&xxx_data,
},
.resource=xxx_resources,
.num_resources=ARRAY_SIZE(xxx_resources),
};
注冊i2c_board_info、spi_board_info等:
static struct i2c_board_info __initdataxxx_i2c_devices[]={
{I2C_BOARD_INFO("name", 0x1a), },
};
static struct spi_board_infoxxx_spi_devices[] ={
{/* DataFlash chip */
.modalias="mtd_dataflash",
.chip_select=1,
.max_speed_hz=15*1000*1000,
.bus_num=0,
},
};
填充由MACHINE_START和MACHINE_END包圍起來的的一系列callback函數:
MACHINE_START(xxx_board,"ARM-xxx")
.atag_offset=0x100,
.map_io=xxx_map_io,
.init_early=xxx_init_early,
.init_irq=xxx_init_irq,
.timer=& xxx_timer,
.handle_irq=gic_handle_irq,
.init_machine=xxx _init,
.restart=xxx_board _restart,
MACHINE_END
使用FDT后,resource信息可以從.dts節點中的reg、interrupts等屬性得到; Linux內核中板級硬件的回調函數通過調用函數of_platform_bus_probe(NULL,xxx_of_bus_ids, NULL),即可自動展開所有的platform_device。
假設有一款ARM芯片的板級硬件,Linux內核在arch/arm/mach-xxx/的板級文件中使用如下方式展開.dts文件中的設備節點對應的平臺設備信息:
static struct of_device_id xxx_of_bus_ids[]__initdata = {
{ .compatible="simple-bus", },
{},
};
void __init xxx_mach_init(void){
of_platform_bus_probe(NULL,xxx_of_bus_ids, NULL);
}
#ifdef CONFIG_ARCH_XXX
DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
……
.init_machine=xxx_mach_init,
……
MACHINE_END
#endif
把I2C、SPI等這些硬件設備信息填充到對應的FDT源碼文件中的I2C controller、SPI controller節點上去,I2C host驅動、SPI host驅動的probe函數在注冊主設備的時候獲得這些硬件信息:
i2c2: i2c@e1a00000 {
compatible="yyyy,xxxx-i2c";
reg=<0xe1a00000 0x1000>;
#address-cells=<1>;
#size-cells=<0>;
……
};
spi0: spi@e1300000 {
compatible="yyyy,xxxx-spi";
reg=<0xe1300000 0x1000>;
#address-cells=<1>;
#size-cells=<0>;
……
};
MACHINE_START和MACHINE_END包圍起來的一系列板級硬件callback函數變成為:
static const char * constxxx_dt_match[]__initconst={
"arm,xxx_board",
NULL,
};
DT_MACHINE_START(XXX_BOARD_DT, "ARM-xxx")
.dt_compat=xxx_dt_match,
.smp=smp_ops(xxx_board_smp_ops),
.map_io=xxx_dt_map_io,
.init_early=xxx_dt_init_early,
.init_irq=xxx_dt_init_irq,
.timer=&xxx_dt_timer,
.init_machine=xxx_dt_init,
.handle_irq=gic_handle_irq,
.restart=xxx_board_restart,
MACHINE_END
.dt_compat成員是用來表明相關的板級硬件與.dts文件中根節點的compatible屬性的兼容關系。如果Bootloader傳遞給內核的設備樹中根結點的compatible屬性出現在某個板級硬件的.dt_compat表中,相關的板級硬件就與對應的設備樹匹配,被執行相關板級硬件的初始化函數。
對驅動來說,當驅動與.dts中描述的設備節點匹配后,會執行驅動的probe()函數。對于平臺驅動而言,需要添加一個OF匹配表。例如.dts文件的"acme,xxx-i2c-bus"兼容I2C控制器節點的OF匹配表為:
static const struct of_device_idxxx_i2c_of_match[]={
{.compatible="yyy,xxx-i2c-bus ", },
{},
};
MODULE_DEVICE_TABLE(of,xxx_i2c_of_match);
static struct platform_driver i2c_xxx_driver={
.driver={
.name="xxx-i2c-bus ",
.owner=THIS_MODULE,
.of_match_table=xxx_i2c_of_match,
},
.probe=i2c_xxx_probe,
.remove=i2c_xxx_remove,
}
本文介紹了FDT的優點及其在ARM Linux的用法,詳細闡述了ARM Linux引入FDT后,相關內核板級支持包和驅動代碼的變化。在ARM Linux引入FDT后,刪除

[1] Grant Likely,Josh Boyer.A Symphony of Flavours: Using the device tree to describe embedded hardware[R].Ottawa,Canada:Linux Symposium,2008.
[2] SUN.The Open Firmware Home Page[EB/OL].[2016-10].http://playground.sun.com/1275/home.html,2005.
[3] 宋寶華.Linux設備驅動開發詳解:基于最新的Linux4.0內核[M].北京:機械工業出版社,2015.
[4] devicetree-specification-v0.1[EB/OL].[2016-10].https://www.devicetree.org/specifications/.
[5] 邱文華.基于扁平設備樹的Linux內核啟動方式[J].現代計算機:專業版,2009.
[6] ARM Linux 3.x的設備樹(Device Tree)[EB/OL].[2016-10].http://blog.csdn.net/21cnbao/article/details/8457546.
[7] Device Tree[EB/OL].[2016-10].http://elinux.org/Device_Tree.
羅名駒、賈志文(碩士研究生),主要研究方向為嵌入式系統應用;陳益民(副教授),主要研究方向為測試計量技術、監測自動化裝置、光伏產品監測技術。
Application of Flattened Device Tree in ARM Linux
Luo Mingju,Chen Yimin,Jia Zhiwen
(School of Information Engineering,Guangdong University of Technology,Guangzhou 510006,China)
Flattened Device Tree is introduced into ARM Linux,that can directly put the detailed description of board-level hardware information to the Linux kernel.So the arch/arm in the Linux kernel directory’s redundant codes which describing board-level hardware details are reduced.The reusability of the Linux kernel codes is improved when we write the most board-level hardware features related code in the device tree files and device drivers.It also can avoid modifying a lot of ARM Linux kernel codes to support the new hardware,and accelerate speed to develop the ARM Linux board support package.Using the existing kernel image to boot with the same chipset hardware platform is possible.
flattened device tree;ARM;Linux kernel
TP311
A
?士然
2016-10-25)