作者: Sam (甄峰) sam_code@hotmail.com
0. Linux Device Tree基础:
在早期的ARM Linux Kernel中,板级细节使用代码形式,存放在arch/arm/目录。名字通常为:plat-xxxx或者mach-xxxx,内容多为特定板上细节信息,如platform, i2c_board_info, spi_board_info和各类资源数据。(Sam在学习I2C时,Borad就在类似目录mach-tegra)
但这类目录对Linux Kernel来说,意义非常有限,因为板级代码只对相应开发板有用。使用这种方法显然不是一个好办法。与此同时PowerPC等其它架构已经在使用一种新的方法:Flattened Device Tree(FDT)。Device Tree是一种描述硬件的数据结构。它包含板级硬件细节信息,这样,通过Device Tree,就可以把硬件信息传递给Kernel,而不需要再硬编码了。
1. 无Linux Device Tree之前的状况:
在没有使用Linux Device Tree之前,要porting Linux 到一个新的ARM开发板上时,工作流程如下:
A:构建Bootloader,copy kernel到内存,并传递参数。并统一Bootloader和Kernel中的machine type ID。这个Machine Type ID与此开发板一一对应。
DT_MACHINE_START(ARDBEG, "ardbeg")
ARDBEG:就是Machine ID。
定义在arch/arm/tools/mach-types 文件中。
B:在kernel/arch/arm目录下创建mach-xxxx目录。这个目录下的文件就是板级代码,只与开发板硬件相关。
DT_MACHINE_START(ARDBEG, "ardbeg")
.atag_offset = 0x100,
.smp =
smp_ops(tegra_smp_ops),
.map_io =
tegra_map_common_io,
.reserve =
tegra_ardbeg_reserve,
.init_early =
tegra_ardbeg_init_early,
.init_irq = irqchip_init,
.init_time =
clocksource_of_init,
.init_machine =
tegra_ardbeg_dt_init,
.restart =
tegra_assert_system_reset,
.dt_compat =
ardbeg_dt_board_compat,
.init_late
= tegra_init_late
MACHINE_END
在tegra_ardbeg_dt_init()中,初始化和定义了板级设备。如memory, display, uart, usb, i2c, audio等。
可以看到,每个ARM开发板,均需要对应的Machine ID对应,并用代码实现对应的初始化和功能定义。
2.无Linux Device Tree之前ARM Board 存在的问题:
很明显,其实Board Info独立于Kernel之外。所以每次Kernel升级,都需要Board Info的实现者对应做升级和测试工作。又因为ARM 对应开发板极其众多,所以这堆代码(arch/arm/)越来越庞大。各个Table之间可能会有冲突。且很多代码有重合和冲突的可能。
在一次Kernel升级后,OMAP平台的维护者Tony Lindgren提交了OMAP的升级。但长期被ARM板级代码困扰的Linus终于爆发了:Gaah. Guys, this whole ARM thing is a f*cking pain in the ass.
ARM Linux维护者也提出了各种不同意见和改进建议。
分析下来,同一件事,在不同的人看来,有不同的层次和意义(屁股决定大脑):
A. 内核维护者(与CPU体系结构无关的代码)
B. 维护ARM系统结构者。
C. 维护ARM sub architecture者(来自ARM SOC vendor)
ARM Sub Architecture维护者来自 ARM芯片提供厂商,他们的第一要素是尽快把SOC推出市场,而不是代码多么合理和美观。
维护ARM体系结构的人能力不容置疑,但他们面对的是不断有新SOC加入的局面。且这也是ARM能够与Intel抗衡的根本。
内核维护者对整个系统更加高屋建瓴。他们注意到每次kernel升级,ARM代码变化量占整个ARCH目录的60%,他们认为这明显意味着ARM Linux 代码存在问题。
如此人员和架构设计,会导致ARM sub architecture缺乏沟通,代码有重复。且质量不高。
3. 解决方案:
针对重复的代码问题,如果不同的SOC使用了相同的IP block(例如I2C controller),那么这个driver的code要从各个arch/arm/mach-xxx中独立出来,变成一个通用的模块供各个SOC specific的模块使用。移动到哪个目录呢?对于I2C或者USB OTG而言,这些HW block的驱动当然应该移动到kernel/drivers目录。因为,对于这些外设,可能是in-chip,也可能是off-chip的,但是对于软件而言,它们是没有差别的(或者说好的软件抽象应该掩盖底层硬件的不同)。对于那些system level的code呢?例如clock control、interrupt control。其实这些也不是ARM-specific,应该属于linux kernel的核心代码,应该放到linux/kernel目录下,属于core-Linux-kernel frameworks。当然对于ARM平台,也需要保存一些和framework交互的code,这些code叫做ARM SoC core architecture code。OK,总结一下:
1、ARM的核心代码仍然保存在arch/arm目录下
2、ARM SoC core architecture code保存在arch/arm目录下
3、ARM SOC的周边外设模块的驱动保存在drivers目录下
4、ARM SOC的特定代码在arch/arm/mach-xxx目录下
5、ARM SOC board specific的代码被移除,由Device Tree机制来负责传递硬件拓扑和硬件资源信息。
4. Device Tree使用概述:
从前面的文字看来,Device Tree是用来替代之前将Hardware配置信息直接写入Code的办法,改用写入一个DB的方法。
之前习惯于每个ARM platform编译一个Kernel Image。但从长远来看,未来将会希望所有Platform对应一个Image。那么Bootloader在启动kernel,需要指出识别出的Platform信息。
那Device Tree从生成到使用流程如何呢?
A: 充分了解硬件配置和系统运行参数,并将信息写入Device Tree source file.
B: 使用Device Tree Compiler(DTC)将Device Tree Source File编译成适合机器处理的Device Tree binary file(又叫:Device Tree Blob----DTB).
C: 在系统启动时,Boot Program(BIOS,Bootloader)可以将Flash中的DTB copy到内存。
D: 把DTB内存地址传递给后续程序(bootloader或Kernel)。
5. Device Tree Source file:
Device Tree Source file中并不需要描述系统中所有硬件,一些可以动态探测到的设备不需要被描述,如USB Device。但对于SOC上的USB Host Controller,因为无法自动识别,需要在Device Tree Source file中描述。
其实这和Device Tree 出现前的Board file时的准则是一致的。
Device Tree Source file是由一个或多个node组成的,node格式是:
[label:] node-name[@unit-address] {
[properties
definitions]
[child nodes]
}
以kernel/arch/arm/boot/dts/skeleton.dtsi为例(dtsi: device tree source include):
/ {
#address-cells = <1>;
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0
0>; };
};
可以看到,"/"是跟节点的node name, {}之间是该node的具体定义,其内容包括各种属性和child node.
属性包括:
#address-cells = <1>;
#size-cells = <1>;
子node包括:
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0
0>; };
6. Device Tree Compiler:
DTC 源码在scripts/dtc目录下。而某个Platform编译时,哪些dtb会被编译出来,则可以在arch/arm/boot/dts/Makefile中看到。
这里可以看出,dtb其实是和Linux Kernel分离的。所以,它可以单独被编译出来:
make dtbs
7. Device Tree对BSP和Device Driver造成的影响和变更:
如上所述,Device Tree就是为了取代板级信息代码而推出的。所以,大量在arch/arm/plat-xxxx, arch/arm/mach-xxxx中做的工作不再是必要的了。
A: 注册platform_device,绑定resource,即内存、IRQ等板级信息
形如
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),
};
之类的platform_device代码都不再需要,其中platform_device会由kernel自动展开。而这些resource实际来源于.dts中设备结点的reg、interrupts属性。典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device。譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:
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
B. 注册i2c_board_info,指定IRQ等board级别信息:
static struct i2c_board_info __initdata
inv_mpu9250_i2c0_board_info[] = {
{
I2C_BOARD_INFO(MPU_GYRO_NAME,
MPU_GYRO_ADDR),// MPU_GYRO_ADDR=0x68
.platform_data =
&mpu9250_gyro_data,
},
{
I2C_BOARD_INFO(MPU_BMP_NAME,
MPU_BMP_ADDR),
.platform_data =
&mpu_bmp_pdata,
},
{
I2C_BOARD_INFO(MPU_COMPASS_NAME,
MPU_COMPASS_ADDR),
.platform_data =
&mpu_compass_data,
},
};
inv_mpu9250_i2c0_board_info[0].irq = gpio_to_irq(MPU_GYRO_IRQ_GPIO);
i2c_register_board_info(gyro_bus_num, inv_mpu9250_i2c0_board_info, ARRAY_SIZE(inv_mpu9250_i2c0_board_info));
可以在dtsi中修改为:
/ {
i2c@7000c400
{
mpu6515@69 {
compatible =
"invensense,mpu6515";
reg =
<<STRONG>0x68>;
interrupt-parent
= <&gpio>;
interrupts
= <169 0x01>;
invensense,int_config
= <0x10>;
invensense,level_shifter
= <0>;
invensense,orientation
= [ff 00 00 00 01 00 00 00 ff];
invensense,sec_slave_type
= <0>;
invensense,key
= [4e cc 7e eb f6 1e 35 22
00 34 0d 65 32 e9 94 89];
vlogic-supply
= <&palmas_smps8>;
vdd-supply =
<&palmas_smps9>;
};
ak8963c@0d {
compatible =
"ak,ak8963";
reg =
<0x0c>;
orientation =
[ff 00 00 00 01 00 00 00 ff];
config =
"mpu";
vid-supply =
<&palmas_smps8>;
vdd-supply =
<&palmas_smps9>;
};
};
};
内容参考于:
http://blog.csdn.net/21cnbao/article/details/8457546