文章目录
DTS设备树的由来和使用
Linux设备树的由来-为什么会有设备树
- 在Linux 2.6中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件platform_data。常见的s3c2410、s3c6410等板级目录,代码量在数万行。
- Linus Torvalds对于此种情况大发雷霆,在2011年的ARM Linux邮件列表宣称this whole ARM thing is a f*cking pain in the ass”
- 所以Linux开发社区就开始整改,设备树最早用于PowerPC等其他体系架构,ARM架构开发社区就开始采用设备树来描述设备的信息
快速编译设备树—DTC (device tree compiler)
-
将.dts编译为.dtb的工具。
-
DTC的源代码位于内核的scripts/dtc目录,在Linux内核使能了Device Tree的情况下,编译内核的时候主机工具dtc会被编译出来
-
在Linux内核的arch/arm/boot/dts/Makefile中,描述了当某种SoC被选中后,哪些.dtb文件会被编译出来,如与EXYNOS对应的.dtb包括:
dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ exynos4210-smdkv310.dtb \ exynos4412-origen.dtb \
-
我们可以单独编译Device Tree文件。当我们在Linux内核下运行make dtbs时,若我们之前选择了ARCH_EXYNOS,上述.dtb都会由对应的.dts编译出来
快速了解设备树—编译设备树文件
参考板origen的设备数文件为参考
cp arch/arm/boot/dts/exynos4412-origen.dts arch/arm/boot/dts/exynos4412-fs4412.dts
添加新文件需修改Makefile才能编译
vim arch/arm/boot/dts/Makefile,在exynos4412-origen.dtb \ 下添加如下内容 exynos4412-fs4412.dtb \
编译设备树文件
make dtbs
拷贝内核和设备树文件到/tftpboot目录下
cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
设置启动参数
set bootcmd tftp 0x41000000 uImage \; tftp 0x42000000 exynos4412-fs4412.dtb \; bootm 0x41000000 - 0x42000000
Linx设备树的由来–什么是设备树
Open Firmware Device Tree 开发固件设备树
-
Device Tree可以描述的信息包括CPU的数量和类别、内存基地址和大小、总线和桥、
外设连接、中断控制器和中断使用情况、GPIO控制器和GPIO使用情况、Clock控制器和Clock使用情况。 -
设备树信息被保存在一个ASCII 文本文件中,适合人类的阅读习惯,类似于xml文件,
在ARM Linux中,一个.dts文件对应一个ARM的machine放置在内核的
arch/arm/boot/dts/目录 -
设备树是一种数据结构,用于描述设备信息的语言,具体而言,是用于操作系统
中描述硬件,使得不需要对设备的信息进行硬编码(hard code) -
Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身
可包含子结点。所谓属性,其实就是成对出现的name和value -
设备树源文件dts被编译成dtb二进制文件,在bootloader运行时传递给操作系统,
操作系统对其进行解析展开(Flattened),从而产生一个硬件设备的拓扑图有了这个拓
扑图,在编程的过程中可以直接通过系统提供的接口获取到设备树中的节点和属性
信息
运行流程分析
设备树源文件(dts) --> (DTC编译) --> 设备树二进制文件 --> (u-boot加载到内存,内核解析展开) --> 树形结构体
我们只要通过在dts源文件添加设备树节点,最后就可以读取展开的设备树节点了。
节点 node
节点名称:每个节点必须有一个“<名称>[@<设备地址>]”形式的名字
<名称> 就是一个不超过31位的简单 ascii 字符串,节点的命名应该根据它所体现的
是什么样的设备。比如一个 3com 以太网适配器的节点就应该命名为
ethernet,而不应该是 3com509。
<设备地址>用来访问该设备的主地址,并且该地址也在节点的 reg 属性中列出,同级
节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称,
当然设备地址也是可选的,可以有也可以没有
树中每个表示一个设备的节点都需要一个 compatible 属性
property
简单的键-值对,它的值可以为空或者包含一个任意字节流。虽然数据类型并没有编码进数据
结构,但在设备树源文件中任有几个基本的数据表示形式 :
文本字符串(无结束符)可以用双引号表示:
string-property = "a string"
Cells是 32 位无符号整数,用尖括号限定:
cell-property = <0xbeef 123 0xabcd1234>
二进制数据用方括号限定:
binary-property = [01 23 45 67];
不同表示形式的数据可以使用逗号连在一起:
mixed-property = "a string", [01 23 45 67], <0x12345678>;
逗号也可用于创建字符串列表:
string-list = "red fish", "blue fish";
compatible属性
指定了系统的名称,是一个字符串的列表,实际在代码中可以用于进行匹配,当前你选择的是
哪个机器,它包含了一个“<制造商>,<型号>”形式的字符串。重要的是要指定一个确切的设备,
并且包括制造商的名子,以避免命名空间冲突,
不要使用带通配符的 compatible 值,比如“fsl,mpc83xx-uart”或类似情况
/ {
compatible = "acme,coyotes-revenge";
};
#address-cells和#address-siz属性
#address-cells = <1> 表示address字段的长度的为1
#size-cells = <1>; 表示length字段的长度为1
external-bus {
#address-cells = <2>
#size-cells = <1>;
ethernet@0,0 {
compatible = "smc,smc91c111";
reg = <0 0 0x1000>; // 地址站两个cells, 长度站1个cells
nterrupts = < 5 2 >;
};
}
reg属性
reg的组织形式为reg = <address1 length1 [address2 length2] [address3 length3] … >
其中的每一组address length表明了设备使用的一个地址范围
/ {
compatible = “acme,coyotes-revenge”;
#address-cells = <1>;
#size-cells = <1>;
serial@101f2000 {
compatible = "arm,pl011";
reg = <0x101f2000 0x1000 >;
interrupts = < 2 0 >;
};
}
中断信息属性–interrupts和interrupts
描述中断连接需要四个属性:
interrupt-controller - 一个空的属性定义该节点作为一个接收中断信号的设备
#interrupt-cells - 这是一个中断控制器节点的属性。它声明了该中断控制器的
中断指示符中 cell 的个数(类似于 #address-cells 和 #size-cells)
interrupt-parent - 这是一个设备节点的属性,包含一个指向该设备连接的中断控制
器的 phandle(指向或者可以引用&)那些没有 interrupt-parent 的节点则从它们
的 父节点中继承该属性。
interrupts - 一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个
中断输出信号
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
intc: interrupt-controller@10140000 {
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;
#interrupt-cells = <2>;
};
}
设备树基本语法
/{ --->>> 根节点
node{ --->>> 根节点的子节点
key = value; --->>> 属性,键值对
...
child_node{ --->>> node节点的子节点
key = value;
};
...
};
...
};
Key : 属性名
Value : 属性值
设备树语法-键值对
[1]. 字符串信息
compatible = "farsight,hqyj";
[2]. 32位无符号整形数组
word-array = <32 45 67 89>;
[3]. 二进制数组
bi-array = [0c 20 11 24];
[4]. 字符数组
string-list = "aaa" , "bbb" , "ccc";
节点名命名规范
<name>[@<unit-address>]
eg: gpio@101F3000
名字是ASCII字符串(字母、数字、 "-"、等等构成)
最长可以是31个字符
一般应该以设备类型命名
unit-address一般的是设备地址
节点别名
节点引用
节点合并
设备树编程
常用OF API
OF提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等
-
根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
-
根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
-
根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)
-
获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)
-
根据属性名propname,读出该属性的数组中sz个属性值给out_values
int of_property_read_u32_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
-
读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
测试代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#define NODE_PATH "/test_node@12345678"
#define U32_DATA_LEN 4
static int is_good; //标志位
static int irqno; //中断号
/*
test_node@12345678{
compatible = "fs4412,test";
reg = <0x12345678 0x24
0x87654321 0x24>;
testprop,mytest;
test_list_string = "red fish","fly fish", "blue fish";
interrupt-parent = <&gpx1>;
interrupts = <1 4>;
};
*/
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("------------------------key pressed \n");
return IRQ_HANDLED;
}
static int __init dt_drv_init(void)
{
struct device_node *np = NULL;
struct property *prop = NULL;
u32 regdata[U32_DATA_LEN];
int ret;
int i;
const char *pstr[3];
int flags;
printk("-----%s------------\n", __FUNCTION__);
//step1:获取结点信息(按照节点路径查找)
np = of_find_node_by_path(NODE_PATH);
if(np)
{
printk("find test node ok\n");
printk("node name = %s\n", np->name);
printk("node full name = %s\n", np->full_name);
}
else
{
printk("find test node failed\n");
}
//step2: 获取到节点中的属性
prop = of_find_property(np,"compatible", NULL);
if(prop)
{
printk("find compatible ok\n");
printk("compatible value = %s\n", prop->value);
printk("compatible name = %s\n", prop->name);;
}
else
{
printk("find compatible failed\n");
}
//step3:匹配compatible属性
if(of_device_is_compatible(np, "fs4412,test"))
{
printk("we have a compatible named fs4412,test\n");
}
else
{
printk("we not have a compatible named fs4412,test\n");
}
//step4:读取到属性中的整数的数组
ret = of_property_read_u32_array(np, "reg", regdata, U32_DATA_LEN );
if(!ret)
{
for(i=0; i<U32_DATA_LEN; i++)
printk("----regdata[%d] = 0x%x\n", i,regdata[i]);
}
else
{
printk("get reg data failed\n");
}
//step5:读取到属性中的字符串的数组
for(i=0; i<3; i++)
{
ret = of_property_read_string_index(np, "test_list_string", i, &pstr[i]);
if(!ret)
{
printk("----pstr[%d] = %s\n", i,pstr[i]);
}
else
{
printk("get pstr data failed\n");
}
}
// step7:属性的值为空,实际可以用于设置标志
if(of_find_property(np, "testprop,mytest", NULL))
{
is_good = 1;
printk("is_good = %d\n", is_good);
}
// steo8: 获取到中断的号码
irqno = irq_of_parse_and_map(np, 0);
printk("-----irqno = %d\n", irqno);
//step9:验证中断号码是否有效
flags = IRQF_TRIGGER_FALLING ;
ret = request_irq(irqno, key_irq_handler, flags,"key_irq", NULL);
if(ret)
{
printk("request_irq error\n");
return -EBUSY;
}
return 0;
}
static void __exit dt_drv_exit(void)
{
printk("-----%s------------\n", __FUNCTION__);
free_irq(irqno, NULL);
}
module_init(dt_drv_init);
module_exit(dt_drv_exit);
MODULE_LICENSE("GPL");
终端信息
(1)添加设备树(DTS)节点,描述test_node信息
(2)将编译后的设备树文件cp到tftp跟目录,板子上电后内核会解析 proc/device-tree/ 查看解析记录
linux虚拟机运行:
开发板运行:
(3)编写驱动代码,API获取节点-属性-值,并拿中断号申请中断资源
(4)终端信息成功打印属性信息