【Linux驱动】设备树的开发


前言

掌握设备树是 Linux 驱动开发人员必备的技能!因为在新版本的 Linux 中,ARM 相关的驱动全部采用了设备树(也有支持老式驱动的,比较少),最新出的 CPU 其驱动开发也基本都是基于设备树的,比如 ST 新出的 STM32MP157、NXP 的 I.MX8 系列等
本篇文章主要是让我们了解如何通过设备树进行Linux驱动的开发

硬件: RV1126
Linux内核: 4.19

一、设备树

1.设备树介绍

随着智能手机的发展,每年新出的ARM架构芯片少说都在数十、数百款,Linux内核下板级信息文件将会成指数级增长!这些板级信息文件都是.c或.h文件,都会被硬编码进Linux内核中,导致Linux内核“虚胖”。就好比你喜欢吃自助餐,然后花了100多到一家宣传看着很不错的自助餐厅,结果你想吃的牛排、海鲜、烤肉基本没多少,全都是一些凉菜、炒面、西瓜、饮料等小吃,相信你此时肯定会脱口而出一句“骗子!”。同样的,内核也是如此,所以ARM社区就引入了PowerPC等架构已经采用的设备树(FlattenedDeviceTree),将这些描述板级硬件信息的内容都从Linux内中分离开来,用一个专属的文件格式来描述,这个专属的文件就叫做设备树,文件扩展名为.dts。

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如 CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等,如下图所示:
在这里插入图片描述

在设备树中,可描述的信息包括:
a. CPU数量和类型
b.内存基地址和大小
c.总线和桥
d.外设连接
e.中断控制器和中断使用情况
f.GPIO控制器和GPIO使用情况
g.时钟控制器和时钟使用情况

bootload会将这些信息传递给内核,内核开始识别这些树,并解析成linux内核中platform_device,i2c_client,spi_device等设备,而这些设备使用的内存资源,中断等信息也传递给内核。内核会将这些资源绑定给相应的设备。

设备树的主要优势: 对于同一SOC的不同主板,只需更换设备树文件.dtb即可实现不同主板的无差异支持,而无需更换内核文件。

2.设备树组成

在这里插入图片描述
DTS文件是一种ASCII文本对Device Tree的描述,放置在内核的/arch/arm/boot/dts目录。一般而言,一个*.dts文件对应一个ARM的machine。·DTSI文件作用:由于一个SOC可能有多个不同的电路板,而每个电路板拥有一个 .dts。这些dts势必会存在许多共同部分,为了减少代码的冗余,设备树将这些共同部分提炼保存在.dtsi文件中,供不同的dts共同使用。*.dtsi的使用方法,类似于C语言的头文件,在dts文件中需要进行include *.dtsi文件。当然,dtsi本身也支持include 另一个dtsi文件。 dtsi由厂商提供·DTC是将.dts编译为.dtb的工具,相当于gcc,
·DTB文件是 dts 被 DTC 编译后的二进制格式的设备树文件,它可以被linux内核解析
设备树文件路径:rv1126_rv1109_v2.2.0_20210825/kernel/arch/arm/boot/dts

3.设备树语法

设备树中的基本单元,被称为“node”,其格式为:

[label:] node-name[@unit-address] {
    [properties definitions]
    [child nodes]
};

label: 是标号,可以省略。label的作用是为了方便地引用node;
node-name: 是设备节点的名称,为ASCII字符串,节点名字应该能够清晰的描述出节点的功能,比如“uart1”就表示这个节点是UART1外设;
@unit-address: 一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话 “unit-address” 可以不要;
注:根节点没有node-name 或者 unit-address,它被定义为 /

在这里插入图片描述
注意:
1》设备树文件都由根节点开始,每个设备只有一个根节点(如果包含多个文件,根节点则会合并),其它所有设备都作为子节点存在,由节点名和一组节点属性构成。
2》节点属性都是有key-value的键值对来描述,并以 ; 结束
3》节点间可以嵌套形成父子关系,这样可以方便描述设备间的关系

3.1标准属性

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性。除了用户自定义属性,有很多属性是标准属性,Linux 下的很多外设驱动都会使用这些标准属性。
在这里插入图片描述

3.2compatible 属性详解

compatible也叫做“兼容性”属性,这是非常重要的一个属性! compatible 属性的值是一个字符串列表, compatible 属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,
compatible 属性的值格式:“manufacturer,model”,其中manufacturer 表示厂商, model 一般是模块对应的驱动名字。
例如:compatible = “fsl,mpc8641”, “ns16550”;
上面的compatible有两个属性,分别是 “fsl,mpc8641” 和 “ns16550”;其中 “fsl,mpc8641” 的厂商是 fsl(飞思卡尔);设备首先会使用第一个属性值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件;
如果没找到,就使用第二个属性值查找,以此类推,直到查到到对应的驱动程序 或者 查找完整个 Linux 内核也没有对应的驱动程序为止。

二、设备树开发

1.设备树节点的操作函数

Linux 驱动程序往往需要去读取到 Linux 内核中附带的 dts 文件,并操作设备树 DTS 的相关节点.

1.查找指定节点名称的节点

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
from:起始节点。从这个节点开始向下搜索,查找具有指定名称的节点。如果传入 NULL,则从设备树的根节点开始搜索。
name:要查找的节点名称。
返回值为找到的设备树节点的指针,如果没有找到匹配的节点,则返回 NULL

2.用于通过设备树路径(path)查找设备树中的节点

struct device_node *of_find_node_by_path(const char *path)
path:设备树节点的路径,是一个以斜杠分隔的字符串,表示设备树中节点的层次结构路径。
返回值为找到的设备树节点的指针,如果没有找到匹配的节点,则返回 NULL

2.向设备树中添加节点

在这里插入图片描述
设备树文件路径:kernel/arm/arch/boot/dts
注意设备树节点的添加要在根节点内。
添加完结点之后重新编译内核文件生成dst.bin文件烧录到开发板中。

3.设备树节点的使用

3.1平台总线驱动端的注册

在这里插入图片描述

注意:
这里因为我添加的设备是蜂鸣器,在总线上没有对应的物理总线,所以我们用的是虚拟总线(平台设备总线)去挂载我们的设备。

平台设备驱动端注册函数:
User int platform_driver_register(struct platform_driver *drv)

该函数用于将一个 struct platform_driver 结构体中的驱动程序信息注册到内核,使得内核能够正确地与该平台设备驱动程序进行关联。
以下是该函数的参数和简要说明:
drv:指向 struct platform_driver 结构体的指针,其中包含了平台设备驱动程序的相关信息,包括探测、移除等操作的回调函数、设备驱动信息等。
返回值为注册成功时返回0,失败时返回一个负数,表示注册失败的错误码。
该函数通常由平台设备驱动程序的初始化代码调用,以便在系统启动时注册驱动程序,从而使内核能够正确地管理该平台设备。注册成功后,当系统探测到与该平台设备驱动程序匹配的硬件设备时,相应的探测函数 (probe 字段指定的函数) 将被调用,完成设备的初始化工作。

struct platform_driver *drv结构体详解
在这里插入图片描述
struct platform_driver *drv结构体内struct device_driver driver结构体详解
在这里插入图片描述
struct device_driver driver结构体内struct of_device_id *of_match_table详解
在这里插入图片描述

3.2字符设备的注册

这里我们采用杂项字符设备的注册方式

杂项字符设备注册函数:int misc_register(struct miscdevice * misc)

misc:指向 struct miscdevice 结构体的指针,包含了与杂项字符设备相关的信息,如设备号、设备名称、设备操作函数等。
返回值为注册成功时返回0,失败时返回一个负数,表示注册失败的错误码。

在这里插入图片描述

4.代码示例

代码如下:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/of_gpio.h>

int my_probe(struct platform_device *);
int my_remove(struct platform_device *);
ssize_t my_write (struct file *, const char __user *, size_t, loff_t *);


struct of_device_id	match_table={
	.compatible="TestBeep"//设备名,与设备树中添加的名字保持一致,也就是compatible的属性
};//设备树匹配结构体

struct platform_driver mydrv={
	.probe=my_probe,//设备探测函数
	.remove=my_remove,//设备移除函数
	.driver={
		.name="test",//在这里这个名字不用于设备名的匹配,用compatible属性去匹配设备
		.of_match_table=&match_table//用于设备名的匹配
	}//设备驱动结构体,存放设备信息
};//平台驱动端核心结构体

struct file_operations ops={
	.write=my_write//写函数接口,为应用层提供write接口
};//字符指针操作集核心结构体

struct miscdevice  my_misc={
	.minor=255,//设备号,255默认由内核分配次设备节点号,杂项主设备号均为10。
	.name="dev_beep",//设备名
	.fops=&ops//文件操作指针集结构体
};//杂项字符设备设备核心结构体

int beepgpio;//定义一个全局变量去承接GPIO号
ssize_t my_write (struct file *f, const char __user *buf, size_t s, loff_t *l)
{
	char data;
	int ret;
	/*我们不能直接对用户层数据进行操作,
	所以我们这里用copy_from_user函数安全地从用户空间复制数据到内核空间。*/
	ret = copy_from_user(&data,buf,s);
	gpio_set_value(beepgpio,data);/*通过获取到用户空间的数据对gpio口进行设置*/
	return 0;
}

int my_probe(struct platform_device *dev)//设备探测函数
{
	printk("设备探测函数执行......\n");
	//字符设备注册得到设备节点
	misc_register(&my_misc);
	beepgpio = of_get_named_gpio(dev->dev.of_node,"beep_gpio",0);//获取gpio号
	gpio_request(beepgpio,"beep");//请求gpio
	gpio_direction_output(beepgpio,0);//设置gpio方向
	return 0;
}
int my_remove(struct platform_device *dev)//设备移除函数
{
	printk("设备移除函数执行......\n");
	misc_deregister(&my_misc);//注销字符设备
	gpio_free(beepgpio);//释放gpio口
	return 0;
}
static int __init My_Enter(void)
{
	printk("模块加载成功!\n");
	//1.平台设备驱动端注册
	if(platform_driver_register(&mydrv) == 0)
	{
		printk("平台设备驱动端注册成功!\n");
	}
	return 0;
}

static void __exit My_Exit(void)
{
	printk("模块卸载成功!\n");
	platform_driver_unregister(&mydrv);
}

module_init(My_Enter);
module_exit(My_Exit);
MODULE_LICENSE("GPL");

总结

通过上面的代码示例我们可以简单的了解设备树的开发流程,设备树的开发是每个 Linux 驱动开发工程师必须掌握的技能,特别是如今的 Linux 内核版本都是支持设备树的。设备树的出现极大的方便了各种设备的驱动代码编写,同时降低了 Linux 内核的冗余。

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值