文章目录
linux驱动之设备树
设备树的由来—什么是设备树
- Open Firmware Device Tree 开发固件设备树
(1)Device Tree可以描述的信息包括 CPU 的数量和类别、内存基地址和大小、总线和桥、外设连接、中断控制器和中断的使用情况、GPIO 控制器和GPIO 的使用情况、Clock 控制器和 Clock 使用情况。
(2)设备树信息被保存再一个 ASCII文本文件中,适合人类的阅读习惯、类似于 xml 文件,在 ARM Linux 中,一个 .dts 文件对应一个ARM的 machine ,放置在内核的 arch/arm/boot/dts/ 目录(2.6版本之前没有)。
(3)设备树是一种数据结构,用于描述设备信息的语言,具体而言,是用于操作系统中描述硬件,使得不需要对设备的信息进行硬编码(hard code)
(4)Device Tree 由一系列被命名的节点(node)和属性(property)组成,而节点本身可包含子结点。所谓属性,其实就是成对出现的 name 和 value。
(5)设备树源文件 dts 被编译成 dtb 二进制文件,在 bootloader 运行时传递给操作系统,操作系统对其进行解析展开(Flattened),从而产生一个硬件设备的拓扑图,有了这个拓扑图,在编程的过程中可以直接通过系统提供的接口获取到设备树中的节点和属性信息。
设备树其实是一个文件,这个文件包含很多的节点,这些节点是专用来描述设备的信息,包括CPU的信息,GPIO的信息等。信息里面包括很多的属性。属性中包括各种值 value,这些 value 是传递给内核使用的。内核可以解析出这些文件信息,然后给程序员使用。
Linux设备树的由来—为什么会有设备树
-
Linux 2.6 中,arch/arm/plat-xxx 和 arch/arm/mach-xxx 中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过只是垃圾,如板上的 platform 设备、resource、i2c_board_info、spi_board_info 以及各种硬件 platform_data.常见的 s3c2410、s3c6410 等板级目录、代码量在数万行。所以会使内核代码非常庞大,管理麻烦,这就叫硬编码方式。
2.6内核描述设备配置 类似 arch/arm/plat-samsung/dev-i2c0.c
arch/arm/mach-s5vp210/mach-smdkv210.c
-
Linus Torvalds 对于此种情况大发雷霆,在2011年 ARM Linux邮件列表宣称 this whole ARM thing is a fucking 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-sndkv310.dtb \
exynos4412-origen.dtb \
- 我们可以单独编译 Device Tree 文件。当我们在Linux 内核下运行 make dtbs 时,若我们之前选择了 ARCH_EXYNOS ,上述 .dtb 都会由对应的 .dts编译出来
快速了解设备树—编译设备树文件
- 参考板 origen 的设备树文件做参考
cp arch/arm/boot/dts/exynos4410-origen.dts arch/arm/boot/dts/exynos4412-fs4412.dts
- 添加新文件需修改 Makefile 才能编译
vim arch/arm/boot/dts/Makefile ,在 exynos4410-origen.dtb \ 下添加如下内容
exynos4412-fs4412.dtb
- 编译设备树文件
make dtbs
- 拷贝内核和设备树文件到 /tftpboot 目录下
- 设置启动参数
set bootcmd tftp 0x41000000 uImage \; tftp 0x42000000 exynos4410-fs4412.dtb \; bootm 0x41000000 - 0x42000000
dtb使用的过程
设备树语法及内部构成
- 部分名词
DT: Device Tree
FDT: Flattened Device Tree
OF: Open Firmware
DTS: device tree source
DTSI: device tree source include
DTB: device tree blob
DTC: device tree compiler
设备树语法
(1)节点
(2)属性
(3)根节点
(4)compatible 属性
(5)reg 属性
(6)#address-cells 和 #address-siz 属性
(7)中断信息属性—interrupts 和 interrupts
- 简单的设备树内容
/ {
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{
};
};
};
- 节点(node)
节点名称:每个节点必须有一个 “<名称>[@<设备地址>]” 形式的名字
<名称> 就是一个不超过31位的简单 ascii 字符串,节点的命名应该根据它所体现的是什么样的设备。比如一个 3 com 以太网适配器的节点应该命名为 ethernet,而不应该是 3com509
<设备地址>用来访问该设备的主地址,并且该地址也在节点的reg属性中列出,同级节点命名必须是唯一的,但只要地址不同,多个节点也可以使用一样的通用名称,当然设备地址也是可选的,可以有也可以没有。
树中每个表示一个设备的节点都需要一个 compatible 属性 - 属性 property
- 常见的属性 — compatible 属性
- 常见属性—#address-cells 和 #size-cells
- 常见属性 —reg属性
- 常见属性—中断信息
举例:类似
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;
serial@101f0000 {
compatible = "arm,pl011";
reg = <0x10170000,0x1000>;
interrupts = <1 0>;
};
intc:interrupt-controller@10140000{
compatible = "arm,pl190";
reg = <0x10140000,0x1000>;
interrupt-controller;
#interrupt-cells = <2>;
};
}
对于arm架构,标志为具体含义 Documentation/devicetree/bindings/arm/gic.txt
设备树实战
- 在 对应的板子上添加如下信息:(只是测试用)
test_node@123456 {
compatible = "farsight,test";
reg = <0x114001E0 0x24
0x11400c20 0x24>;
testprop,mytest;
test_list_string = "read fish","blue fish";
interrupt-parent = <&gpx1>; //因为按键接到了 gpx1_1
interrupts = <2 2>;
//因为按键接到了gpx1_1,如果接到了gpx2_1,那么就是 <2 2>
};
之后 make dtbs
下载到开发板后 在 /proc/device_tree/device_tree 可以看到很多节点,可以看到有test_node@123456 节点的文件夹
- 常用的 OF API
OF 提供的函数主要集中在 /drivers/of/ 目录下
- 解析设备树过程程序设计
#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)
{
printk("key pressed\n");
return IRQ_HANDLED;
}
static int __init dt_drv_init(void)
{
/*
test_node@123456 {
compatible = "farsight,test";
reg = <0x12345678 0x24
0x87654321 0x24>;
testprop,mytest;
test_list_string = "read fish","blue fish";
};
*/
//在代码中获取节点的所有信息
//先把节点获取到
struct device_node *np = NULL;
struct property *prop = NULL;
np = of_find_node_by_path("/test_node@123456");
if(np)
{
printk("find test node ok\n");
printk("find name = %s\n",np->name);
printk("find full_name = %s\n",np->full_name);
}
else
{
printk("find test node failed\n");
}
//获取到节点中的属性
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"))
{
printk("we have a compatible name called farsight,test\n");
}
else
{
printk("This compatible is none\n");
}
//获取到属性中整数的数组
u32 regdata[U32_DATA_LEN];
int ret;
ret = of_property_read_u32_array(np,"reg",regdata,U32_DATA_LEN);
if(!ret)
{
printk("get reg data succeed\n");
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)
{
free_irq(irqno,NULL);
}
module_init(dt_drv_init);
module_exit(dt_drv_exit);
MODULE_LICENSE("GPL");