一、DTS的加载过程
如果要使用Device Tree,首先用户要了解自己的硬件配置和系统运行参数,并把这些信息组织成Device Tree source file。通过DTC(Device Tree Compiler),可以将这些适合人类阅读的Device Tree source file变成适合机器处理的Device Tree binary file(device tree blob)。
在系统启动时,boot program(例如:firmware、bootloader)可以将保存在flash中的DTB copy到内存(当然也可以通过其他方式,例如通过bootloader的交互式命令加载DTB,或者firmware可以探测到device的信息,组织成DTB保存在内存中),并把DTB的起始地址传递给client program(例如OS kernel,bootloader或者其他特殊功能的程序)。
对于计算机系统(computer system),一般是firmware->bootloader->OS,对于嵌入式系统,一般是bootloader->OS。
它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。
Device Tree是否要描述系统中的所有硬件信息?答案是否定的。基本上,不需要描述那些可以动态探测到的设备,例如USB device。不过对于SOC上的usb hostcontroller,它无法被动态识别,需要在device tree中描述。
同理,在computersystem中,PCI device可以被动态探测到,不需要在device tree中描述,但是PCI bridge如果不能被探测,那么就需要描述它。
.dts文件是一种ASCII 文本格式的Device Tree描述,此文本格式非常人性化,适合人类的阅读习惯。
基本上,在ARM Linux中,一个.dts文件对应一个ARM的machine,一般放置在内核的arch/arm/boot/dts/目录。
由于一个SoC可能对应多个machine(一个SoC可以对应多个产品和电路板),势必这些.dts文件需包含许多共同的部分,Linux内核为了简化,把SoC公用的部分或者多个machine共同的部分一般提炼为.dtsi,类似于C语言的头文件。其他的machine对应的.dts可以include这个.dtsi。
譬如,对于RK3288而言, rk3288.dtsi就被rk3288-chrome.dts所引用,rk3288-chrome.dts有如下一行:#include“rk3288.dtsi”。
再如rtd1195, 在 rtd-119x-nas.dts中就包含了/include/ "rtd-119x.dtsi"。
当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi,即#include"skeleton.dtsi“
或者 /include/ "skeleton.dtsi"
{
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
a-byte-data-property = \[0x01 0x23 0x34 0x56\];
child-node1 {
first-child-property;
second-child-property = <>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = < >; /\* each number (cell) is a uint32 \*/
child-node1 {
};
};
};
下面以一个最简单的machine为例来看如何写一个.dts文件。假设此machine的配置如下:
1、1个双核ARM Cortex-A9 32位处理器;
2、ARM的local
bus上的内存映射区域分布了2个串口(分别位于0x101F1000 和
0x101F2000)、GPIO控制器(位于0x101F3000)、SPI控制器(位于0x10115000)、中断控制器(位于0x10140000)和一个external
bus桥;
3、External bus桥上又连接了SMC SMC91111 Ethernet(位于0x10100000)、I2C控制器(位于0x10160000)、64MB NOR Flash(位于0x30000000);
4、External bus桥上连接的I2C控制器所对应的I2C总线上又连接了Maxim DS1338实时钟(I2C地址为0x58)。
其对应的.dts文件为:
{
compatible = "acme,coyotes-revenge";
#address-cells = <>;
#size-cells = <>;
interrupt-parent = <&intc>;
cpus {
#address-cells = <>;
#size-cells = <>;
cpu@ {
compatible = "arm,cortex-a9";
reg = <>;
};
cpu@ {
compatible = "arm,cortex-a9";
reg = <>;
};
};
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < >;
};
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < >;
};
gpio@101f3000 {
compatible = "arm,pl061";
reg = <0x101f3000 0x1000
0x101f4000 0x0010>;
interrupts = < >;
};
intc: interrupt-controller@ {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <>;
};
spi@ {
compatible = "arm,pl022";
reg = <0x10115000 0x1000 >;
interrupts = < >;
};
external-bus {
#address-cells = <>
#size-cells = <>;
ranges = < 0x10100000 0x10000 // Chipselect 1, Ethernet
0x10160000 0x10000 // Chipselect 2, i2c controller
0x30000000 0x1000000>; // Chipselect 3, NOR Flash
ethernet@, {
compatible = "smc,smc91c111";
reg = < 0x1000>;
interrupts = < >;
};
i2c@, {
compatible = "acme,a1234-i2c-bus";
#address-cells = <>;
#size-cells = <>;
reg = < 0x1000>;
rtc@ {
compatible = "maxim,ds1338";
reg = <>;
interrupts = < >;
};
};
flash@, {
compatible = "samsung,k8f1315ebm", "cfi-flash";
reg = < 0x4000000>;
};
};
};
上述.dts文件中, root结点"/"的compatible 属性compatible = "acme,coyotes-revenge";定义了系统的名称,它的组织形式为:
Linux内核透过root结点"/"的compatible 属性即可判断它启动的是什么machine。
如在arch/arm/boot/dts/vexpress-v2m.dtsi中的Flash结点:
flash@, {
compatible = "arm,vexpress-flash", "cfi-flash";
reg = < 0x00000000 0x04000000>,
< 0x00000000 0x04000000>;
bank-width = <>;
};
compatible属性的第2个字符串"cfi-flash"明显比第1个字符串"arm,vexpress-flash"涵盖的范围更广。
ranges是地址转换表,其中的每个项目是一个子地址、父地址以及在子地址空间的大小的映射。映射表中的子地址、父地址分别采用子地址空间的#address-cells和父地址空间的#address-cells大小。
对于本例而言,子地址空间的#address-cells为2,父地址空间的#address-cells值为1,因此0
0 0x10100000
0x10000的前2个cell为external-bus后片选0上偏移0,第3个cell表示external-bus后片选0上偏移0的地址空间被映射到CPU的0x10100000位置,第4个cell表示映射的大小为0x10000。ranges的后面2个项目的含义可以类推。
static struct i2c\_board\_info \_\_initdata afeb9260\_i2c\_devices\[\] = {
{
I2C\_BOARD\_INFO("tlv320aic23", 0x1a),
}, {
I2C\_BOARD\_INFO("fm3130", 0x68),
}, {
I2C\_BOARD\_INFO("24c64", 0x50),
}
};
之类的i2c_board_info代码,目前不再需要出现,现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的
i2c@, {
compatible = "acme,a1234-i2c-bus";
…
rtc@ {
compatible = "maxim,ds1338";
reg = <>;
interrupts = < >;
};
};
Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter); 然后被自动展开。
从设备树中读取相关GPIO的配置编号和标志,返回值为 gpio number。
dtb-$(CONFIG_ARCH_VEXPRESS) += vexpress-v2p-ca5s.dtb \
vexpress-v2p-ca9.dtb \
vexpress-v2p-ca15-tc1.dtb \
vexpress-v2p-ca15_a7.dtb \
xenvm-4.2.dtb
当我们在Linux内核下运行make
dtbs时,若之前选择了ARCH_VEXPRESS,上述.dtb都会由对应的.dts编译出来。因为arch/arm/Makefile中含有一个dtbs编译target项目。当然也可以单独编译Device
Tree文件。命令由读者自行去找。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章