Quantcast
Channel: Sam的技术Blog
Viewing all articles
Browse latest Browse all 158

Linux设备树(Linux Device Tree)学习

$
0
0

作者: 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


 

Viewing all articles
Browse latest Browse all 158

Trending Articles