扁平设备树FDT在ARM Linux中的应用研究

2017-04-14 09:42罗名驹陈益民贾志文
单片机与嵌入式系统应用 2017年3期
关键词:源码内核代码

罗名驹,陈益民,贾志文

(广东工业大学 信息工程学院,广州 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芯片集的硬件平台成为可能。

1 FDT组成和结构

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文件读取到特定内存区域中。

2 FDT在ARM Linux中的应用

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)

猜你喜欢
源码内核代码
面向数据可靠传输的高译码率带反馈的LT码
强化『高新』内核 打造农业『硅谷』
企业如何保护源码
基于嵌入式Linux内核的自恢复设计
Linux内核mmap保护机制研究
创世代码
创世代码
创世代码
创世代码
微生物内核 生态型农资