我们在编译的时候,会发现要编译一下设备树文件,那么为什么要多此一举加这个文件?这个文件有什么作用呢??
一.设备树的由来(驱动框架的演进)
总体来讲,产品的开发越来越简单,架构越来越复杂,开发工作量减少。
原始架构依然很重要,里面的知识并没有被淘汰,只是被封装和继承了。
Linux 2.6版本,封装了原始架构,更加抽象,引入了设备驱动模型(sysfs)。使做产品更省事,实现了应用程序与驱动程序的分离
其实从驱动程序的演进过程我们就会发现,设备树的引入是一种必然。
为什么要使用设备树??
在Linux 2.6中,(为了适应各种平台)arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的 垃圾代码,对内核而言这些platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data绝大多数纯属垃圾冗余代码。 这个时候大神linus肯定是不允许这种事情发生的,于是引入了设备树。ARM Device Tree起源于OpenFirmware (OF)。
设备树特点:
1.设备树的主要优势:对于同一SOC的不同主板,只需更换设备树文件.dtb即可实现不同主板的无差异支持,而无需更换内核文件。
2.设备树信息被保存在一个ASCII 文本文件中,适合人类的阅读习惯,类似于xml文件, 在ARM Linux中,一个.dts文件对应一个ARM的machine放置在内核的
arch/arm/boot/dts/目录
3,设备树是一种数据结构,用于描述设备信息的语言,具体而言,是用于操作系统 中描述硬件,使得不需要对设备的信息进行硬编码(hard code)
什么是硬编码??
比较形象的举例,我们之前点灯,直接设置gpio3,现在将gpio3(写死的)替换为LED_GPIO(可变的),这个定义通过配置文件进行读入,现在你的管脚改变了,只需要改下配置文件的指定,就可以了。
4, Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身 可包含子结点。所谓属性,其实就是成对出现的name和value。
二. 设备树基本概念
DTS即Device Tree Source,是一个文本形式的文件,用于描述硬件信息,包括CPU的数量和类别、内存基地址和大小、中断控制器、总线和桥、外设、时钟和GPIO控制器等。
DTB即Device Tree Blob,是一个二进制形式的文件,由linux内核识别,为其中的设备匹配合适的驱动程序。被内核解析,用一个树形结构的结构体保存(不用说这个结构体一定是在内存中的)
源码路径 include/linux/of.h struct device_node {...}
DTC即Device Tree Compiler,将适合人类阅读和编辑的DTS文件编译成适合机器处理的DTB文件。(DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译 内核的时候主机工具dtc会被编译出来)
DTSI : device tree source include (类似于.h头文件),不同板子中有相同的节点信息提取到 DTSI中,使用 #include "xxx.dtsi" 的方式引入
dts也就是我们要编辑的文件,通过DTC工具生成二进制文件DTB,供内容识别。
bootloader 读取dtb文件并放入RAM中,并将存放地址告诉linux内核(bootm 0x41000000 - 0x42000000),内核启动以后从该地址读取相应的设备信息,匹配平台和设备驱动,分析设备树节点,并创建树形结构体 struct device_node {...},展开树。
DTS中实现了类似于这样的一个树状结构。
三.如何快速编译设备树文件
1.参考板origen的设备数文件为参考(找到厂家提供的类似的dtb)
cp arch/arm/boot/dts/exynos4412-origen.dts arch/arm/boot/dts/exynos4412-fs4412.dts
2.添加新文件需修改Makefile才能编译
vim arch/arm/boot/dts/Makefile, 在exynos4412-origen.dtb 下添加如下内容
exynos4412-fs4412.dtb
3.编译设备树文件
make dtbs
()
4.拷贝内核和设备树文件到/tftpboot目录下
cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
5.设置启动参数
set bootcmd tftp 0x41000000 uImage \; tftp 0x42000000 exynos4412-fs4412.dtb \; bootm 0x41000000 - 0x42000000
四.设备树编写规则
我们知道了怎么编译DTS文件,但是如果我们修改了外设,或者更换了平台,我们也要跟着修改设备树文件,那么我们如何修改设备树呢?
我们就需要了解设备树的结构及调用原理,理解设备树的编写规则。我们才能更好的将设备树应用到我们的项目中去。
设备树的语法框架:
/ { //根结点
node1 { //子节点1
a-string-property = "A string"; //键值对表示方式,name,value
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x23 0x34 0x56];
child-node1 { //子子节点1
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 { //子子节点2
};
};
node2 { //子节点2
an-empty-property;
a-cell-property = <1 2 3 4>;
child-node1 { //子子节点
};
};
};
这是一个简单的设备树,包括根结点,子结点,结点中又包含了对设备的一些描述(属性)。重点理解设备的描述的规则,我们就可以自由的添加自己的外设了。
下面是我们之前项目中的用到的dm9000的网卡驱动用到的设备树文件。
srom-cs1@5000000 { //结点名称
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x5000000 0x1000000>; // 对应芯片手册3Memory Map 的0x0500_0000和16 MB
ranges;
ethernet@5000000 {
compatible = "davicom,dm9000"; //内核通过该名字来匹配驱动
reg = <0x5000000 0x2 0x5000004 0x2>; //寄存器地址和数据宽度
interrupt-parent = <&gpx0>; //继承于 中断控制器gpx0
interrupts = <6 4>; //6对应中断源 DM9000_IRQ -> XEINT6 。4对应 active high
level-sensitive
davicom,no-eeprom;
mac-address = [00 0a 2d a6 55 a2];
};
}
1.结点名称
节点名称:每个节点必须有一个"<名称>[@<设备地址>"形式的名字
<名称>就是一个不超过31位的简单ascii字符串,节点的命名应该根据它所体现的是什么样的设备。比如一个3com以太网适配器的节点就应该命名为ethernet,而不应该是3com509.
<设备地址>用来访问该设备的主地址,并且该地址也在节点的reg属性中列出,同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称当然设备地址也是可选的,可以有也可以没有.
ethernet@5000000 //设备地址不是必须,可省略
2.compatible属性
指定了系统的名称,是一个字符串的列表,实际在代码中可以用于进行匹配。它包含了一个"<制造商>,<型号>"形式的字符串。
树中每个表示一个设备的节点都需要一个compatible属性
compatible = "davicom,dm9000"; //内核通过该名字来匹配驱动 ,命名要确切,避免命名冲突。不要使用带通配符的compatible值
3.#address-cells,#size-cells
#address-cells = <1> 表示address字段的长度的为1
#size-cells = <1>;表示length字段的长度为1
#address-cells = <1>; //表示address字段的长度的为1
#size-cells = <1>; //表示length字段的长度为1
reg = <0x5000000 0x1000000>; // //地址站1个cells ,长度站1个cells (reg = <0 0 0x1000>; //地址站两个cells ,长度站1个cells)
4.reg属性
reg的组织形式为req = <address1 length1 [address2 length2] [address3 length3] ..>
其中的每一组address length表明了设备使用的一个地址范围
reg = <0x5000000 0x2 0x5000004 0x2>; //寄存器地址和数据宽度
5.中断 属性
描述中断连接需要四个属性:
interrupt-controller-一个空的属性定义该节点作为一个接收中断信号的设备 //可省略
#interrupt-cells-这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中cell的个数(类似于#address-cells和#size-cells ) //可省略
interrupt-parent-这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的phandle(指向或者可以引用&)那些没有interrupt-parent的节点则从它们的父节点中继承该属性。
interrupts-一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号
interrupt-parent = <&gpx0>; //继承于 中断控制器gpx0
interrupts = <6 4>; //6对应中断源 DM9000_IRQ -> XEINT6 。4对应 active high level-sensitive
6.property 属性
简单的键-值对,它的值可以为空或者包含一个任意字节流。虽然数据类型并没有编码进数据结构,但在设备树源文件中任有几个基本的数据表示形式
- 文本字符串(无结束符)可以用双引号表示:
string-property = "a string“
- Cells是32位无符号整数,用尖括号限定:
cell-property = <Oxbeef 123 0xabcd1234>;
- 二进制数据用方括号限定
binary-property = [01 23 45 67];
- 不同表示形式的数据可以使用逗号连在一起:
mixed-property = "a string", [01 23 45 67], <0x12345678>;
- 逗号也可用于创建字符串列表:
string-list = "red fish", "blue fish";
五.常用的OF API
OF提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq. ,platform.c等等
1.根据deice_node结构的fullname参数,在全局链表 of-allnodes 中,查找合适的 device node
struct device_node *of find_node_by_path(const char *path)
2.根据property结构的name参数,在指定的 device node 中查找合适的 property
struct property *of_find_property(const struct device_node *np, const char*name,int *lenp)
3.根据compat参数与 device node的 compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)
4.获得父节点的device node
struct device_node *of get-parent(const struct device_node *node)
5.根据属性名 propname ,读出该属性的数组中sz个属性值给 out-values
int of_property_read_u32_array(const struct device_node *np,const char *propname, u8 *out _values, size t sz)
6.读取该设备的第 index 个 irq号
unsigned int irg of parse and map(struct device node *dev, int index)