一.什么是设备树?
设备树是一种描述硬件资源的数据结构。它通过bootloader将硬件资源传给内核,使得内核和硬件资源描述相对独立。
有了初步概念之后,我们再来一起探讨设备树的起源。
二.设备树的由来
要想了解为什么会有设备树,设备树是怎么来的,我们就要先来回顾一下在没有设备树之前我们是怎么来写一个驱动程序的。以字符设备驱动代码框架为例,我们一起来回顾下。
任何的设备驱动的编写,Linux已经为我们打好了框架,我们只要像做完形填空一样填写进去就可以了。
字符设备驱动框架:
杂项设备驱动框架:
通过这些框架,我们可以很容易编写我们的驱动代码,但是,当我们用这个框架非常熟练的时候,我们就会发现虽然这个方法很简单,但是非常不容易扩展,当我们有很多很多相似设备的时候,如果我们都是按照这个框架来完成,那就要写很多遍这个流程,但是多个相似设备之间真正有差异的地方只有框架的第四步,也就是初始化硬件的部分,其他步骤的代码基本都是一样的。这样就会造成大量的重复代码。但是,我们在编写驱动代码的时候,我们要尽量做到代码的复用,也就是一套驱动尽量可以兼任很多设备,如果我们还按照这个来编写就不太符合我们的规则了。
为了实现这个目标,我们就要把通用的代码和有差异的代码分离出来,来增强我们驱动代码的可移植性。所以,设备驱动分离的思想也就应运而生了,在Linux中,我们是在写代码的时候进行分离,分离是把一些不相似的东西放到了dev.c,把相似的东西放在了dri.c,如果我们有很多相似的设备或者平台,我们只要修改dev.c就可以了,这样我们重复性的工作就大大的减少了。这个就是平台总线的由来。
平台总线这个方法有什么弊端呢?
当我们用这个方法用习惯以后就会发现,假如soc不变,我们每换一个平台,都要修改C文件,并且还要重新编译。而且会在arch/arm/plat-xxx和arch/arm/mach-xxx下面留下大量的关于板级细节的代码。并不是说这个方法不好,只是从Linux的发展来看,这些代码相对于Linux内核来说就是“垃圾代码”,而且这些“垃圾代码”非常多,于是就有了Linux Torvalds那句简单粗暴的话:
为了改变这个现状,设备树也就被引进到Linux上了,用来剔除相对内核来说的“垃圾代码”,即用设备树文件来描述这些设备信息,也就是代替dev.c文件,虽然拿到了内核外面,但platform匹配上基本不变,并且相比于之前的方法,使用设备树不仅可以去掉大量“垃圾代码”,并且采用文本格式,方便阅读和修改,如果需要修改部分资源,我们也不用在重新编译内核了,只需要把设备树源文件编译成二进制文件,在通过bootloader传递给内核就可以了。内核在对其进行解析和展开得到一个关于硬件的拓扑图。我们通过内核提供的接口获取设备树的节点和属性就可以了。即内核对于同一soc的不同主板,只需更换设备树文件dtb即可实现不同主板的无差异支持,而无需更换内核文件。
三.设备树的基本概念
1.为什么叫设备树呢?
因为他的语法结构像树一样,所以管它叫设备树
2. 常用名词解释
<1>DT:Device Tree //设备树
<2>FDT:Flattened Device Tree //展开设备树//开放固件,设备树起源于OF,所以我们在设备树中可以看到很多有of字母的函数
<3>device tree source(dts) //设备树代码
<4>device tree source include(dtsi): //更通用的设备树代码,也就是相同芯片但不能平台都可以使用的代码
<5>device tree blob(dtb) //DTS编译后得到的DTB文件
<6>device tree compiler(dtc) //设备树编译器
设备树基本框架
<1>设备树从根节点开始,每个设备都是一个节点。
<2>节点和节点之间可以互相嵌套,形成父子关系。
<3>设备的属性用key-value对(键值对)来描述,每个属性用分号结束
设备树语法
2.1节点
什么是节点呢?节点就好比一颗大树,从树的主干开始,然后有一节一节的树枝,这个就叫节点。在代码中的节点是什么样子的呢。我们把上面模板中的根节点摘出来,如下图所示,这个就是根节点。相当于大树的树干。
/{
};//分号
而树枝就相当于设备树的子节点,同样我们把子节点摘出来就是根节点里面的node1和node2,如下图所示:
/{ //根节点
node1//子节点node1
{
};
node2//子节点node2
{
};
};//分号
一个树枝是不是也可以继续分成好几个树枝呢,也就是说子节点里面可以包含子子节点。所以child-node1和child-node2是node1和node1的子节点,如下图所示:
/{ //根节点
node1//子节点node1
{
child-node1 //子子节点
{
};
};
node2//子节点node2
{
child-node2 //子子节点
{
};
};
};//分号
2.2节点名称
节点的命名有一个固定的格式。
格式:<名称>[@<设备地址>]
<名称>节点的名称也不是任意起的,一般要体现设备的类型而不是特点的型号,比如网口,应该命名为ethernet,而不是随意起一个,比如111。
<设备地址>就是用来访问该设备的基地址。但并不是说在操作过程中来描述一个地址,他主要用来区分用。
注意事项:
<1>同一级的节点只要地址不一样,名字是可以不唯一的。
<2>设备地址是一个可选选项,可以不写。但为了容易区分和理解,一般是都写的。
2.3节点别名
当我们找一个节点的时候,我们必须书写完整的节点路径,如果我们的节点名很长,那么我们在引用的时候就十分不方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名)。比如一个动漫人物的名字是蒙其·D·路飞,他的小名是路飞,那是不是小名要比我们的全名更容易记忆了。这个就是别名。
举例:
uart8: serial@02288000
其中,uart8就是这个节点名称的别名,serial@02288000就是节点名称。
2,4, 节点的引用
一般我往一个节点里面添加内容的时候,不会直接把添加的内容写到节点里面,而是通过节点的引用来添加。
举例
&uart8 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okay";
};
&uart8表示引用节点别名为uart8的节点,并往这个节点里面添加以下内容:
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart8>;
status = "okay";
注意事项:
编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写,使用引用可以避免移植者四处找节点。如dts和dtsi里面都有根节点,但最终会合并成一个根节点。
2.5属性
(1)reg属性
reg属性用来描述一个设备的地址范围。
格式:
reg=<add1 length1 [add2 length2]......>
举例
serial@02288000 {
reg = <101F2000 0x1000>;
};
其中101F2000就是起始地址,0x1000就是长度。
(2)#address-cells和#size-cells属性
address-cells用来设置子节点中reg地址的数量
#size-cells用来设置子节点中reg地址长度的数量。
举例
cpu{
#address-cells = <1>;
#size-cells = <1>;
serial@101F2000{
compatible = "serial";
reg = <101F2000 0x1000>;
};
};
其中#address-cells和#size-cell均为1,也就是说我们子节点里面的reg属性里这个寄存器组的起始地址只有一个,长度也只有一个。所以101F2000是起始地址,0x1000是长度。
(3)compatible属性
compatible是一个字符串列表,可以在代码中进行匹配。
举例:
compatible = "led";
(4)status属性
status属性的值类型是字符串,这里我们只要记住俩个常用的即可,一个是okay,表示设备可以正常使用,一个是disable,表示设备不能正常使用。
在设备树中添加自定义节点
查看设备树节点
ls /proc/device-tree
ls /sys/firmware/devicetree/base/
两个是一样的
在/home/samba/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/boot/dts/imx6ull-14x14-evk.dts中添加自定义节点
test1:test{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0x020ac000 0x00000004>;
status = "okay";
};
编译设备树文件
如果环境没有dtc工具,需要安装dtc工具
安装命令:
apt-get install device-tree-compiler
然后我们输入以下命令设置交叉编译器和编译设备树
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs
但是我们只想编译 imx6ull-14x14-evk.dts 文件, 可以将上述命令的 dtbs 替换为 imx6ull-14x14-evk.dts, 因为我们 imx6ull 烧写的是 topeet_emmc_4_3.dtb 的设备树, 所以编译命令如下图所示, 如果用户烧写的是其他屏幕的设备树文件, 可以修改为对应的命令。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- topeet_emmc_4_3.dtb
编译烧录到开发板上查看
设备树中常用的of操作函数
设备都是以节点的形式“挂”到设备树上的,因此要想获取这个设备的其他属性信息,必须先获取到这个设备的节点。Linux 内核使用 device_node 结构体来描述一个节点,此结构体定义在文件include/linux/of.h 中,定义如下
struct device_node
{
const char *name; /* 节点名字 */
const char *type; /* 设备类型 */
phandle phandle;
const char *full_name; /* 节点全名 */
struct fwnode_handle fwnode;
struct property *properties; /* 属性 */
struct property *deadprops; /* removed 属性 */
struct device_node *parent; /* 父节点 */
struct device_node *child; /* 子节点 */
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux 内核中使用结构体 property 表示属性,此结构体同样定义在文件 include/linux/of.h 中,内容如下:
struct property
{
char *name; /* 属性名字 */
int length; /* 属性长度 */
void *value; /* 属性值 */
struct property *next; /* 下一个属性 */
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr;
};
或者设备树文件节点里面资源的步骤:
步骤一:查找我们要找的节点。
步骤二:获取我们需要的属性值。
1.查找节点的常用of函数:
<1>of_find_node_by_path函数
<2>of_get_parent函数
<3>of_get_next_child函数
2.查找节点属性的常用of函数:
<1>of_find_property函数
<2>
of_property_read_u8函数
of_property_read_u16函数
of_property_read_u32函数
of_property_read_u64函数
<3>
of_property_read_u8_array函数
of_property_read_u16_array函数
of_property_read_u32_array函数
of_property_read_u64_array函数
<4>of_property_read_string函数
<5> of_iomap函数 #include <linux/of_address.h>
参数index的值举例:想映射哪段就填哪段
of 函数实验
driver.c
#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/of.h>
struct device_node *test_device_node; //节点
struct property *test_node_property; //节点属性
int size;
u32 out_values[2] = {0};
char *out_string = "";
static int hello_init(void)
{
int ret = 0;
/* 内核打印函数不能用printf,因为内核没有办法使用C语言库 */
printk("hello world\n"); // 内核模块加载的时候打印hello world
test_device_node = of_find_node_by_path("/test"); //查找根节点下的test节点
if(test_device_node == NULL)
{
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node name is %s\n", test_device_node->name);
test_node_property = of_find_property(test_device_node, "compatible", &size); //查找节点的compatible属性
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 is %s\n", (char *)test_node_property->value);
//读取属性中u32类型的数组数据, 比如大多数的reg属性都是数组数据,可以使用这个函数一次读取出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]);
ret = of_property_read_string(test_device_node, "status", &out_string); //获取status属性内容
if(ret < 0)
{
printk("of_property_read_string is error\n");
return -1;
}
printk("status is %s\n", out_string);
return 0;
}
static void hello_exit(void)
{
printk("byby\n"); // 内核模块卸载的时候打印"byb byb
}
module_init(hello_init); // 驱动模块的入口
module_exit(hello_exit); // 驱动模块的出口
MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证
Makefile
obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
在开发板上查看
设备树下的platform驱动
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
const struct of_device_id *of_match_table; 用来设置 platform 驱动匹配表
平台总线匹配:
1.struct device_driver中的const struct of_device_id *of_match_table中的compatible
2.struct platform_driver中的const struct platform_device_id *id_table中的name
3.struct device_driver driver中的name
优先级: 1>2>3
Platform 驱动程序
先查看开发板设备数节点中的值是不是正确
driver.c
#include <linux/init.h> //包含宏定义的头文件
#include <linux/module.h> //包含初始化加载模块的头文件
#include <linux/platform_device.h> //平台设备所需要的头文件
#include <linux/of.h> //of函数
#include <linux/of_address.h> //of_iomap函数
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
struct device_node *test_device_node; //节点
u32 out_values[2] = {0}; //读取到的数组值
unsigned int *vir_gpio5_dr;
int misc_open(struct inode *inode, struct file *file)
{
printk("hello misc_open\n");
return 0;
}
int misc_release(struct inode *inode, struct file *file)
{
printk("hello mise_release bye bye\n");
return 0;
}
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = "heheh";
if (copy_to_user(ubuf, kbuf, strlen(kbuf) + 1) != 0)
{
printk("copy_to_user error\n");
return -1;
}
printk("hello misc_read bye bye\n");
return 0;
}
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
char kbuf[64] = {0};
if (copy_from_user(kbuf, ubuf, size) != 0)
{
printk("copy_from_user error\n");
return -1;
}
printk("hello misc_write bye bye\n");
if(kbuf[0] == 1) //打开蜂鸣器
{
*vir_gpio5_dr |= 0x02;
}else if(kbuf[0] == 0)//关闭蜂鸣器
{
*vir_gpio5_dr &= ~0x02;
}
return 0;
}
/* 文件操作集 */
struct file_operations misc_fops = {
.owner = THIS_MODULE, // 当前模块
.open = misc_open,
.release = misc_release,
.write = misc_write,
.read = misc_read,
};
/* 杂项设备结构体 */
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, // 动态分配次设备号
.name = "hello_mise", // 设备节点的名字
.fops = &misc_fops // 文件操作集
};
int beep_probe(struct platform_device *pdev)
{
int ret = 0;
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"); //查找根节点下的test节点
if(test_device_node == NULL)
{
printk("of_find_node_by_path is error\n");
return -1;
}
printk("test_device_node name is %s\n", test_device_node->name);
//读取属性中u32类型的数组数据, 比如大多数的reg属性都是数组数据,可以使用这个函数一次读取出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]);
ret = misc_register(&misc_dev); // 注册杂项设备
if (ret < 0)
{
printk("misc register is error!\n");
return -1;
}
printk("mise register is ok!\n");
vir_gpio5_dr = of_iomap(test_device_node, 0); //直接内存映射
if(vir_gpio5_dr == NULL)
{
printk("of_iomap is error\n");
return -1;
}
return 0;
}
int beep_remove(struct platform_device *platform_device)
{
printk("beep_remove\n");
return 0;
}
struct platform_device_id beep_id_table = {
.name = "123"
};
struct of_device_id of_match_table_test[] = { //与设备树的 compatible 匹配
{
.compatible = "test1234"
},
{
}
};
/* platform 驱动结构体 */
struct platform_driver beep_platform_driver = {
.probe = beep_probe,
.remove = beep_remove,
.driver = {
.owner = THIS_MODULE,
.name = "beep_test", //匹配优先级3
.of_match_table = of_match_table_test, //中的.compatible匹配优先级1
},
.id_table = &beep_id_table //中的.name匹配优先级2
};
static int beep_driver_init(void)
{
int ret = 0;
printk("hello world\n");
ret = platform_driver_register(&beep_platform_driver); //注册平台驱动
if(ret < 0)
{
printk("platform_driver_register is error\n");
return ret;
}
return 0;
}
static void beep_driver_exit(void)
{
printk("byby\n"); // 内核模块卸载的时候打印"byb byb
platform_driver_unregister(&beep_platform_driver); //卸载 platform 驱动
misc_deregister(&misc_dev); // 注销杂项设备
iounmap(vir_gpio5_dr); //卸载杂项设备
}
module_init(beep_driver_init); // 驱动模块的入口
module_exit(beep_driver_exit); // 驱动模块的出口
MODULE_LICENSE("GPL"); // 声明模块拥有开源许可证
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int fd;
char buf[64] = {0};
if(argc < 2)
{
printf("Usage: %s <1:beep open / 0:beep close>\n", argv[0]);
return -1;
}
fd = open("/dev/hello_mise", O_RDWR);
if (fd < 0)
{
perror("open error\n");
return fd;
}
buf[0] = atoi(argv[1]); //字符串转int
write(fd, buf, strlen(buf)+1);
close(fd);
return 0;
}
Makefile
obj-m +=driver.o
KDIR:=/home/mzx/imx6ull/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
标签:node,struct,Linux,14,device,printk,节点,设备
From: https://www.cnblogs.com/mzx233/p/18158793