From: 全面解析Linux 内核 3.10.x - 本文章完全基于MIPS架构
Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在PPC 平台,已使用很长时间!.使用一种特殊语言来书写,通过Device Tree Compiler编译为Device Tree Blob.在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中).
DTS (device tree source)
.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。譬如,对于VEXPRESS而言,vexpress-v2m.dtsi就被vexpress-v2p-ca9.dts所引用, vexpress-v2p-ca9.dts有如下一行:
/include/ “vexpress-v2m.dtsi”
当然,和C语言的头文件类似,.dtsi也可以include其他的.dtsi,譬如几乎所有的ARM SoC的.dtsi都引用了skeleton.dtsi。
.dts(或者其include的.dtsi)基本元素即为前文所述的结点和属性:
设备树是一个类似树型数据结构属性的简单树!遵循key-value!节点可以包含字节点和属性!
一些常用的语法DEMO:
我选择下面的例子作为说明:!
/
{
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 = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
a./
- 一般是指单一的根节点。
b.node1 and node2
- 指的是一对子节点。
c.Child-node1 and child-node2
- 一对子孙节点。
d.属性指的是key-value组合value值可以是空的或者为字节流,数据类型不能编码数据结构,有一些可以在设备树的源文件表示!
e.文本字符串(空终止)是用双引号
表示 - 如 String-property = “a string”
f.'Cells'
- 指的是用尖括号分隔开的32位无符号整型数
如:cell-property = <0xbeef 123 0xabcd1234>
g.二进制数据与方括号分开 - 如 Binary-property = [0x01 0x23 0x45 0x67]
h.不同的数据可以使用逗号连接在一起 - 如Mixed-property = "a string",.[0x01 0x23 0x45 0x67],<0x12345678>;
i.逗号也可以创建字符串列表 - 如String-list = "red fish", "blue fish"
要device tree是如何工作的的呢?这里使用一个简单的DEMO!
/*
* XLP3XX Device Tree Source for SVP boards
*/
/dts-v1/;
/ {
model = "netlogic,XLP-SVP";
compatible = "netlogic,xlp";
soc {
compatible = "simple-bus";
ranges = <0 offset size 10 offset size>;
serial0: serial@30000 {
device_type = "serial";
compatible = "ns16550";
reg = <0 offset size>;
reg-shift = <2>;
reg-io-width = <4>;
clock-frequency = <133000000>;
interrupt-parent = <&pic>;
interrupts = <17>;
};
serial1: serial@31000 {
device_type = "serial";
compatible = "ns16550";
reg = <0 offset size>;
reg-shift = <2>;
reg-io-width = <4>;
clock-frequency = <133333333>;
interrupt-parent = <&pic>;
interrupts = <18>;
};
chosen {
bootargs = "console=ttyS0,9600 rdinit=/sbin/init";
};
};
基本设备信息:
1 个 64位mips cpu
处理器链接连线映射的串口
I2c 控制器
Spi 控制器
中断控制器
Pic 可编程控制器
看门狗等
a.compatible = "netlogic,xlp";
– 不管是节点,子节点都有这个属性!
语法解析 – compatible指定系统的名称!包含一个字符串形式”<制造商>、<模型>.指定设备是很重要的,包括制造商名称,以避免命名空间冲突.因为操作系统将使用compatible值来决定如何在机器上运行。
理论上,compatible 是一个操作系统需要的所有数据惟一地标识在机器上。如果所有机器细节是硬编码的,然后系统会去在顶层查看这个特殊的属性!
b.serial1: serial@31000
– 节点名称
语法解析 – 在每一个code list中的每个节点都遵循 [@]的命令规则!
name是一个简单的ascii字符串,长度限制为31,一般名称命名就为具体描述设备这样的名词!比如3com的网卡,命名为ethernet@509,不会3com@509这样去命令。同级节点命令必须要唯一!但是不同级可以一样!但是地址是不一样的!
c.compatible = "ns16550";
– 设备名称
语法解析 – 一般写厂商+型号的描述,但是需要注意的一点是这里没有标出的flash 的compatible是需要两个字符串的!
如下:
flash@2,0 {
compatible = “samsung,k8f1315ebm”, “cfi-flash”;
};
d.chosen
– 启动参数填充。
首先设备寻址是根据以下信息向设备树编码
reg
a> #address-cells and size-cells
Property: #address-cells, #size-cells
#address-cells and #size-cells
是属于可用于任何设备节点和子节点在设备树层次结构具体描述了子节点设备如何被编码!. #address-cells
属性定义了一个值为
用于编码地址字段在一个子节点的reg属性.#size-cells
属性定义了一个值为
用于编码大小字段在一个子节点的reg属性.#address-cells and #size-cells
属性不是继承于设备树的. 他们是被明确定义的.
编码为任意的(address,length)
组合!
详细描述 – reg属性描述设备的资源地址在地址空间是由其父总线定义的!通常这意味着内存映射输入输出寄存器块的偏移量和长度,但可能有不同的含义对于一些总线类型而言!地址空间定义为根节点的cpu真实地址。
值是一个
,由任意数量的成对的地址和长度,<地址长度>.
的数量需要指定地址和长度bus-specific
,# address-cells和# size-cells
指定的属性在设备的父节点。如果父节点指定一个值0 #size-cells,reg
长度字段的价值可以被省略!
地址范围
soc {
#address-cells = <2>;
#size-cells = <1>;
compatible = "simple-bus";
ranges = <0 0 0 0x18000000 0x04000000 // PCIe CFG
1 0 0 0x16000000 0x02000000>; // GBU chipselects
这里的ranges: 如PCIe CFG
Offset 0 from chip select 0 is mapped to address range 0x18000000...0x04000000!
中断工作方式
与地址范围所遵循的自然结构树相比,中断信号可以来自任何设备后终止.中断信号表示为节点树的独立之间的联系.四个属性用于描述中断连接:
interrupt-controller
– 空属性声明一个节点设备,接收中断信号
#interrupt-cells
– 这是一个中断控制器节点的属性.(类似于说明符#address-cells
和#size-cells
).
interrupt-parent
– 设备节点的属性包含的phandle中断控制器相连.节点没有interrupt-parent也可以继承父节点.
Interrupts
– 设备节点的属性包含中断说明符的列表,每个中断都输出信号的设备.
特殊的节点
chosen节点并不代表真正的设备,但作为一个固件和操作系统传递数据,如启动参数.选择节点中的数据并不代表硬件.通常选择的节点是空的。在启动时dts源文件并填充!
chosen {
bootargs = "console=ttyS0,9600 rdinit=/sbin/init";
};
A picture is worth a thousand words..
关于上述图有以下几点说明:
a.上述图是依据内核版本为3.7.x,当前版本(3.10.x)xlp_dt_init
此函数已经不存在了,内容被整合到plat_mem_setup
函数!
b.已知条件为boot不传参到内核!
先将新函数贴出来!
void __init plat_mem_setup(void)
{
void *fdtp;
panic_timeout = 5;
_machine_restart = (void (*)(char *))nlm_linux_exit;
_machine_halt = nlm_linux_exit;
pm_power_off = nlm_linux_exit;
/*
* If no FDT pointer is passed in, use the built-in FDT.
* device_tree_init() does not handle CKSEG0 pointers in
* 64-bit, so convert pointer.
*/
//fdtp = (void *)(long)fw_arg0;
fdtp = NULL;
if (!fdtp) {
switch (current_cpu_data.processor_id & 0xff00) {
#ifdef CONFIG_DT_XLP_SVP
case PRID_IMP_NETLOGIC_XLP3XX:
fdtp = __dtb_xlp_svp_begin;
break;
#endif
#ifdef CONFIG_DT_XLP_EVP
case PRID_IMP_NETLOGIC_XLP8XX:
fdtp = __dtb_xlp_evp_begin;
break;
#endif
default:
/* Pick a built-in if any, and hope for the best */
fdtp = __dtb_start;
break;
}
}
fdtp = phys_to_virt(__pa(fdtp));
early_init_devtree(fdtp);
}
因为boot不传参数,所以我将fdtp = (void *)(long)fw_arg0
此行代码改为fdtp = NULL
!
参数也就是通过参数来判断CPU的type,从而选择自己的dts文件进行初始化!
DT树的相关API参见内核 include/linux/of*.h
!
最后上一张比价直观的图来结束吧!
By: Keven - 点滴积累