概述
在Linux 2.6中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件platform_data。常见的s3c2410、s3c6410等板级目录,代码量在数万行。
后来Linux开发社区就开始整改,设备树最早用于PowerPC等其他体系架构,ARM架构开发社区就开始采用设备树来描述设备的信息。
采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
本文重点是如果添加设备树节点和程序通过设备树节点获取硬件信息的方法。关于设备树设备树更全面的了解请参考文章
https://blog.csdn.net/21cnbao/article/details/8457546
运行流程分析
设备树源文件dts被编译成dtb二进制文件,在bootloader运行时传递给操作系统,操作系统对其进行解析展开(Flattened),从而产生一个硬件设备的拓扑图有了这个拓扑图,在编程的过程中可以直接通过系统提供的接口获取到设备树中的节点和属性信息
设备树源文件(dts) --> (DTC编译) --> 设备树二进制文件 --> (u-boot加载到内存,内核解析展开) --> 树形结构体
我们只要通过在dts源文件添加设备树节点,最后就可以读取展开的设备树节点了。
节点(node)和属性(property)
- 节点:节点用以归类描述一个硬件信息或是软件信息(好比文件系统的目录)
- 属性:节点内描述了一个或多个属性,属性是键值对(key - value),描述具体的软/硬信息
DTS描述键值对(key - value)的语法
- 字符串信息 string-property = "a string"
- 32bits无符号整型数组信息 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 = "acme,coyotes-revenge";
#address-cells = <1> 描述子节点reg属性值的地址表中首地址cell数量
#size-cells = <1>; 描述子节点reg属性值的地址表中地址长度cell数量
interrupt-controller - 一个空属性用来声明这个node接收中断信号;
#interrupt-cells - 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符;
interrupt-parent - 标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
interrupts - 一个中断标识符列表,表示每一个中断输出信号。
获取设备树节点信息的一些API
在 linux/of.h 文件中可以看见这些接口函数
1.获取节点
//通过路径索引节点
struct device_node *of_find_node_by_path(const char *path)
2.通过节点获取属性
//提取属性的值
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
//获取属性的整形数组
int of_property_read_u32_array(const struct device_node *np,const char *propname, u8* out_values, size_t sz)
//获取属性中的字符串数组
static inline int of_property_read_string_index(struct device_node *np,const char *propname, int index,const char **out_string)
//从节点中获取中断号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
编译设备树
1.以参考板origen的设备树为模板,创建dts文件
root@linux:linux-3.14-fs4412# 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.编译设备树动作
root@linux:linux-3.14-fs4412# make dtbs
4.将编译后的dtb文件cp到tftp根目录下
root@linux:linux-3.14-fs4412# cp arch/arm/boot/dts/exynos4412-fs4412.dtb /tftpboot/
5.设置板子uboot的启动参数
FS4412 # set bootcmd tftp 0x41000000 uImage \; tftp 0x42000000 exynos4412-fs4412.dtb \; bootm 0x41000000 - 0x42000000
FS4412 # saveenv
实验过程
1.添加设备树(DTS)节点,描述按键信息
test_nod@12345678{
compatible = "farsight,test";
reg = <0x12345678 0x24
0x87654321 0x24>;
testprop,mytest;
test_list_string = "red fish","fly fish", "blue fish";
interrupt-parent = <&gpx1>;
interrupts = <1 4>;
}
2.将编译后的设备树文件cp到tftp跟目录,板子上电后内核会解析 proc/device-tree/ 查看解析记录
3.编写驱动代码,API获取节点-属性-值,并拿中断号申请中断资源
//dt_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#define U32_DATA_LEN 4
static int is_good; //标志位
static int irqno; //中断号
irqreturn_t key_irq_handler(int irqno, void *devid); //中断处理方法
/*
test_nod@12345678{
compatible = "farsight,test";
reg = <0x12345678 0x24
0x87654321 0x24>;
testprop,mytest;
test_list_string = "red fish","fly fish", "blue fish";
interrupt-parent = <&gpx1>;
interrupts = <1 4>;
}
*/
static int __init dt_drv_init(void)
{
struct device_node *np = NULL;
//获取结点信息(按照节点路径查找)
np = of_find_node_by_path("/test_nod@12345678");
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");
}
//获取到节点中的属性
struct property *prop = NULL;
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");
}
if(of_device_is_compatible(np, "farsight,test"))
{//匹配compatible属性
printk("we have a compatible named farsight,test\n");
}
u32 regdata[U32_DATA_LEN];
int ret;
//读取到属性中的整数的数组
ret = of_property_read_u32_array(np, "reg", regdata, U32_DATA_LEN);
if(!ret)
{
int i;
for(i=0; i<U32_DATA_LEN; i++)
printk("----regdata[%d] = 0x%x\n", i,regdata[i]);
}else{
printk("get reg data failed\n");
}
const char *pstr[3];
int i;
//读取到属性中的字符串的数组
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");
}
}
// 属性的值为空,实际可以用于设置标志
if(of_find_property(np, "testprop,mytest", NULL))
{
is_good = 1;
printk("is_good = %d\n", is_good);
}
// 获取到中断的号码
irqno = irq_of_parse_and_map(np, 0);
printk("-----irqno = %d\n", irqno);
//验证中断号码是否有效
ret = request_irq(irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
"key_irq", NULL);
if(ret)
{
printk("request_irq error\n");
return -EBUSY;
}
return 0;
}
static void __exit dt_drv_exit(void)
{
}
irqreturn_t key_irq_handler(int irqno, void *devid)
{
printk("------------------------key pressed \n");
return IRQ_HANDLED;
}
module_init(dt_drv_init);
module_exit(dt_drv_exit);
MODULE_LICENSE("GPL");
4.实验结果展示