一、设备树的由来以及基本概念
1、设备树的由来
(1)设备树是一种描述硬件资源的数据结构,它通过bootloader将硬件资源传给内核。
(2)设备树的由来:在平台总线模型里面,将硬件资源相关的内容放在device.c(与具体开发板相关的、需要改动的),将驱动的部分放在driver.c(共性的、不需要特别改动的)。当我们每换一个平台(开发板),我们都要修改device.c文件(硬件描述的部分),并且需要重新编译,这导致linux会留下许多“垃圾代码”。
(3)引入设备树的好处:设备树实际就是平台总线模型里面的device.c部分,它使得开发板相关的硬件资源信息本身不在linux内核里面,只是在开发板启动的时候通过bootloader传给内核,不用编译到内核了,这使得剔除了linux内核与硬件平台有关的冗余代码。
总结:设备树相当于把原来的device.c换成设备树文本文件,这个文件不需要编译到内核里面,需要用到的时候通过bootloader传递给内核。
2、设备树的基本概念
of开头的函数基本上是和设备树相关的函数
.dts后缀是设备树源码,描述了硬件平台
.dtsi后缀是更为通用的设备树文件,由于一个 SOC(芯片) 可能对应多个 ARM 设备,这些 dts 文件势必包含许多共同的部分,Linux 内核为了简化,把 SOC 公用的部分或者多个设备共同的部分提炼为.dtsi 文件
dtc是指设备树编译器
.dtb后缀由设备树源文件编译得到的文件
二、设备树的基本语法
设备树(Device Tree,DT)语法结构像树一样
1、设备树基本框架
(1)设备树从根节点开始,每个设备都是一个节点
(2)节点和节点之间可以嵌套,形成了父子关系。
(3)设备的属性(就是每一个节点)用key-value的键值对来描述,每个属性用分号结束。
2、设备树的语法
(1)根节点/{};和其子节点
(2)节点名称
格式:<名称>[@<设备地址>],其中名称是不可以随意写成123、abd的,要与具体的设备相关,例如serial@02288000。此外,我们也可以给设备节点起别名,例如uart8: serial@02288000 ,其中uart8 就是这个节点名称的别名,serial@02288000 就是节点名称。
往设备节点里面写入信息:一般我往一个节点里面添加内容的时候,不会直接把添加的内容写到节点里面,而是通过节点的引用来添加,&uart8表示节点的引用
(3)节点的属性
不同的平台,不同的总线,地址位长度可能不同,有 32 位地址,有 64 位地址,为了适应这个,规范规定一个 32 位的长度为一个 cell。
#address-cells属性和#size-cells属性:#address-cells"属性用来表示设备节点的地址(起始地址)需要几个 cell 表示,该属性本身是 u32 类型的,所以如果总线地址是32位的,这里#address_cells设置为1(1个字节32位);#size-cells"属性用来表示设备节点的地址空间的长度需要几个 cell 表示。
reg属性:"reg"属性用来表示节点地址资源的,比如常见的就是寄存器的起始地址及大小。要想表示一块连续地址,必须包含起始地址和空间大小两个参数,如果有多块地址,那么就需要多组这样的值表示。对于'reg' 属性,每个元素是一个二元组,包含起始地址和大小。
其地址和大小用几个 u32 表示由父节点的"#address-cells","#size-cells"属性确定。
例如:子节点 dcp: dcp@02280000 的 reg 属性值为<0x02280000 0x4000>,相当于设置了起始地址为 0x02280000,地址长度为 0x40000。这由它的父节点aips3的"#address-cells","#size-cells"属性确定。
compatible属性:用于匹配driver.c,去寻找驱动,类似平台总线模型device.c中platform_device结构体里面的.name
status属性:例如,ok表示硬件正常工作,disable表示当前硬件不可用
三、在设备树中添加自定义节点
1、内核源码中的设备树文件
设备树文件在内核源码路径下的/arch/arm/boot/dts,根据自己实际的开发板的型号选取对应的设备树文件,这里以讯为的topeet_emmc_4_3.dts为例,如图为对应的dts文件
可以根据头文件的包含关系和父子节点关系,找到根节点所在的地方,这里的根节点在imx6ull-14x14-evk.dts里面
2、添加设备树节点
我们在根节点文件imx6ull-14x14-evk.dts的如下图位置添加自定义的节点
(1)如何查看是否添加成功
在开发板上cd /proc/device-tree,ls或者cd /sys/firmware/devicetree/base/
(2)添加自定义的test节点
在根节点下面添加test(别名test1)的设备节点
(3)安装设备树编译dtc工具
sudo apt install device-tree-compiler -y
设置交叉编译器环境变量export PATH=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin:$PATH ,export CROSS_COMPILE=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf
在内核源码目录下编译make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs或 make ARCH=arm CROSS_COMPILE=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- dtbs
这样会编译所有的设备树文件,编译的时候要退到内核原目录下
如果编译单独的设备树文件,只需要
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- ***.dtb
例如make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-imx6ull-14x14-evk.dtb
编译成功后在内核源码目录的arch/arm/boot/dts下面生成对应的dtb文件,例如
(4)烧写到开发板
我们的板子没有屏幕uboot下面默认lcdtype=hdmi,所以我们的设备树操作要找到topeet_emmc_hdmi.dtb这个文件
例如make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- topeet_emmc_hdmi.dtb
然后将这个topeet_emmc_hdmi.dtb烧录到开发板下,在/proc/device-tree查看节点
或者直接利用讯为提供的脚本./creatsh就行了,里面本身就编译了内核和设备树
(5)修改节点的信息
如果需要修改节点信息或者补充节点信息,用引用
&test1{
compatible = "123";
};
这样会覆盖掉之前的信息
四、设备树中常用的of函数
1、设备树中常用的of函数
设备树描述了设备的硬件信息,我们需要获得硬件信息,这就需要在驱动里面使用of函数来访问设备树的硬件信息
Linux内核使用device_node结构体来描述节点,用property结构体来描述节点的属性
(1)查找节点的常用of函数
of_find_node_by_path(const char * path)函数,成功返回节点struct device_node的指针,失败返回NULL,这里要输入节点的完整路径,从根节点/开始
of_get_parent(struct device_node*node),用于查找节点node的父节点
of_get_next_child(const struct device_node*node,struct device_node* prev)用于查找节点的子节点,node参数为父节点,prev为前一个子节点,表示从哪一个子节点开始迭代查找下一个子节点
(2)获得属性的of函数
of_find_property(设备节点device_node,要读取的属性名称,属性值的字节数)用于获得节点的指定属性
of_property_read_u8 函数
of_property_read_u16 函数
of_property_read_u32 函数
of_property_read_u64 函数
读取只有一个整型值的属性,读取整型数据的属性
of_property_read_u8_array 函数
of_property_read_u16_array 函数
of_property_read_u32_array 函数
of_property_read_u64_array 函数
读取数组类型的属性
of_property_read_string读取字符类型的属性
(3)of_iomap用于内存映射,和驱动里的ioremap功能一样
2、示例
利用of函数对前文添加的test节点进行操作
driver.c的完整代码如下
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
int size;
struct device_node * test_device_node;//定义结构体表示我们的节点
struct property *test_node_property;//定义结构体表示我们的节点属性
u32 out_values[2]={0};
const char *str;
static int hello_init(void){
int ret;
printk("hello_world\n");
/*of函数查找设备节点*/
test_device_node = of_find_node_by_path("/test");
if(test_device_node == NULL){
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node is %s\n",test_device_node->name);
/*of函数查找设备节点属性*/
//1、查找compatible属性
test_node_property = of_find_property(test_device_node,"compatible",&size);
if(test_node_property == NULL){
printk("of_find_property is error\n");
return -1;
}
printk("test_node_property name is %s\n",test_node_property->name);
printk("test_node_property value %s\n",(char *)test_node_property->value);
//2、查找reg属性
ret = of_property_read_u32_array(test_device_node,"reg",out_values,2);
if(ret < 0){
printk("of_property_read_u32_array is error\n");
return -1;
}
printk("out_values[0] is 0x%08x\n",out_values[0]);
printk("out_values[1] is 0x%08x\n",out_values[1]);
//3、获取status属性
ret = of_property_read_string(test_device_node, "status", &str);
if (ret < 0)
{
printk("of_property_read_string is error \n");
return -1;
}
printk("status is %s \n", str);
return 0;
}
static int hello_exit(void){
printk("bye_bye\n");
return 0;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
Makefile的完整代码如下
obj-m += driver.o #先写生成的中间文件的名字是什么,-m的意思是把我们的驱动编译成模块
KDIR:=/home/zyd/topeet/driver/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd) #获取当前目录的变量
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- #make会进入内核源码的路径,然后把当前路径下的代码编译成模块
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.symvers *.order
五、设备树下的平台总线模型
设备树就充当了device.c的部分,我们就要考虑driver.c怎么写
1、driver.c如何与设备树节点进行匹配?
driver.c里面使用of_device_id的结构体
2、probe函数
driver.c与设备树匹配之后会进入probe函数,如何从设备树中拿资源,同样两种直接利用probe的参数指针和利用of函数(见上文)
完整的probe函数如下:
//platform_driver结构体对应的probe函数
int beep_probe(struct platform_device* pdev){
int ret;
printk("beep_probe\n");
/*********************方法一:直接获取节点**************************/
//printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
/*of函数查找设备节点*/
test_device_node = of_find_node_by_path("/test");
if(test_device_node == NULL){
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node is %s\n",test_device_node->name);
/*of函数查找设备节点属性*/
//1、查找compatible属性
test_node_property = of_find_property(test_device_node,"compatible",&size);
if(test_node_property == NULL){
printk("of_find_property is error\n");
return -1;
}
printk("test_node_property name is %s\n",test_node_property->name);
printk("test_node_property value %s\n",(char *)test_node_property->value);
//2、查找reg属性
ret = of_property_read_u32_array(test_device_node,"reg",out_values,2);
if(ret < 0){
printk("of_property_read_u32_array is error\n");
return -1;
}
printk("out_values[0] is 0x%08x\n",out_values[0]);
printk("out_values[1] is 0x%08x\n",out_values[1]);
//3、获取status属性
ret = of_property_read_string(test_device_node, "status", &str);
if (ret < 0)
{
printk("of_property_read_string is error \n");
return -1;
}
printk("status is %s \n", str);
/*********************注册一个杂项设备**************************/
ret = misc_register(&beep_dev);
if(ret<0)
{
printk("misc registe is error\n");
}
printk("misc register is succeed\n");
/*********************映射物理地址**************************/
//vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
vir_gpio_dr = of_iomap(test_device_node, 0);
//第二项用于标识设备树节点中 reg 属性的某一项,reg都是二元组
if (vir_gpio_dr == NULL)
{
printk("of_iomap is error \n");
return -1;
}
return 0;
}
3、完整的driver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/fs.h> // 包含file_operations
#include <linux/miscdevice.h> // 包含miscdevice相关结构
#include <linux/uaccess.h> // 确保包含copy_from_user函数
#include <linux/of_address.h>
#include <linux/io.h> // 包含 iounmap 函数
int size;
struct device_node * test_device_node;//定义结构体表示我们的节点
struct property *test_node_property;//定义结构体表示我们的节点属性
u32 out_values[2]={0};
const char *str;
unsigned int * vir_gpio_dr;
int beep_open(struct inode* inode,struct file* file){
printk("beep_open\n");
return 0;
}
int beep_release(struct inode* inode,struct file* file){
printk("beep_release\n");
return 0;
}
ssize_t beep_read(struct file *file, char __user * ubuf, size_t size, loff_t * loff_t){
printk("beep_read\n ");
return 0;
}
ssize_t beep_write(struct file *file, const char __user * ubuf, size_t size, loff_t * loff_t){
//上层应用传入参数1为打开蜂鸣器,传入参数0为关闭蜂鸣器
char kbuf[64]={0};
if(copy_from_user(kbuf,ubuf,size)!=0)
{
printk("copy_from_user error\n ");
return -1;
}
if(kbuf[0]==1){
//打开蜂鸣器,第二位数据置为1(1左移1位为10,与虚拟地址或第二位置为1)
*vir_gpio_dr|=(1<<1);
}else if(kbuf[0]==0){
//关闭蜂鸣器,第二位数据置为0
*vir_gpio_dr&=~(1<<1);
}
printk("beep_write\n ");
return 0;
}
struct file_operations misc_fops={
.owner = THIS_MODULE, //宏,指向当前模块
.open = beep_open,
.release = beep_release,
.read = beep_read,
.write = beep_write,
};
struct miscdevice beep_dev = {
.minor = MISC_DYNAMIC_MINOR, //宏,自动分配空闲的次设备号
.name = "beep",
.fops = &misc_fops,
};
//platform_driver结构体对应的probe函数
int beep_probe(struct platform_device* pdev){
int ret;
printk("beep_probe\n");
/*********************方法一:直接获取节点**************************/
//printk("node name is %s\n",pdev->dev.of_node->name);
/*********************方法二:通过函数获取硬件资源**************************/
/*of函数查找设备节点*/
test_device_node = of_find_node_by_path("/test");
if(test_device_node == NULL){
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node is %s\n",test_device_node->name);
/*of函数查找设备节点属性*/
//1、查找compatible属性
test_node_property = of_find_property(test_device_node,"compatible",&size);
if(test_node_property == NULL){
printk("of_find_property is error\n");
return -1;
}
printk("test_node_property name is %s\n",test_node_property->name);
printk("test_node_property value %s\n",(char *)test_node_property->value);
//2、查找reg属性
ret = of_property_read_u32_array(test_device_node,"reg",out_values,2);
if(ret < 0){
printk("of_property_read_u32_array is error\n");
return -1;
}
printk("out_values[0] is 0x%08x\n",out_values[0]);
printk("out_values[1] is 0x%08x\n",out_values[1]);
//3、获取status属性
ret = of_property_read_string(test_device_node, "status", &str);
if (ret < 0)
{
printk("of_property_read_string is error \n");
return -1;
}
printk("status is %s \n", str);
/*********************注册一个杂项设备**************************/
ret = misc_register(&beep_dev);
if(ret<0)
{
printk("misc registe is error\n");
}
printk("misc register is succeed\n");
/*********************映射物理地址**************************/
//vir_gpio_dr = of_iomap(pdev->dev.of_node, 0);
vir_gpio_dr = of_iomap(test_device_node, 0);
//第二项用于标识设备树节点中 reg 属性的某一项,reg都是二元组
if (vir_gpio_dr == NULL)
{
printk("of_iomap is error \n");
return -1;
}
return 0;
}
//platform_driver结构体对应的remove函数
int beep_remove(struct platform_device* pdev ){
printk("beep_remove\n");
return 0;
}
//platform_driver结构体对应的id_table
const struct platform_device_id beep_idtable = {
.name="test123"
};
const struct of_device_id of_match_table_test[] = {
{.compatible = "123"},
{}
};
//描述平台总线driver的结构体platform_driver
struct platform_driver beep_driver ={
.probe=beep_probe,//当driver和device匹配的时候会执行probe函数
.remove=beep_remove,//当device和driver任意一个remove时(卸载模块rmmod device或driver,执行remove函数)
.driver={
.owner=THIS_MODULE,
.name="beep_test",//与device的name匹配
//of_match_table 优先级高于id_table,id_table的优先级高于.name
//设置 platform 驱动匹配表
.of_match_table = of_match_table_test,
},
.id_table=&beep_idtable,//id_table表保存了很多id的信息
};
static int beep_driver_init(void){
int ret = 0;
//注册driver.c到内核
ret = platform_driver_register(&beep_driver);
if(ret<0){
printk("platform_driver_register error\n");
}
printk("platform_driver_register ok\n");
return 0;
}
static int beep_driver_exit(void){
// 释放映射的内存
if (vir_gpio_dr){
iounmap(vir_gpio_dr);
}
misc_deregister(&beep_dev);
//注销driver.c
platform_driver_unregister(&beep_driver);
printk("bye_bye\n");
return 0;
}
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL");
4、测试app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char const* argv[]){
int fd;//定义一个句柄
char buf[64] = {0};
fd = open("/dev/beep",O_RDWR);//打开设备节点
if(fd < 0)
{
perror("open error \n");
return fd;
}
buf[0]=atoi(argv[1]);
write(fd,buf,sizeof(buf));
close(fd);
return 0;
}