1:初识设备树
1.1 什么是设备树,设备树的意义
设备树(Device Tree)是 Linux 内核中用于描述硬件设备的一种数据结构。它为操作系统提供了一种抽象的方法,使其能够识别和配置硬件设备,而无需将硬件细节硬编码到内核中。设备树最常用于嵌入式系统和其他复杂硬件平台上,尤其是在 ARM 架构下。
使用设备树的目的就是去精华、优化内核的重复代码。
设备树文件存放的位置:
统一放在内核架构文件夹里面/boot/dts 里面
以 32bit ARM 芯片 内核/arch/arm/boot/dts
以 64bit ARM 芯片 内核/arch/arm64/boot/dts/rockchip
我所使用的开发板为rk3588s所对应的设备文件:
ls *dtb 当前设备树谁被编译成了 二进制文件
确定我使用的设备文件:
rk3588s-yyt.dts
设备树的意义:
降低了内核的重复性和臃肿性。
2:设备树的模型
因为有了设备树之后,几乎芯片的所有的驱动,甚至是 CPU 的频率 时钟 电压都采用了这
种方式。写一个通用的驱动,通过设备树传参,带来不同的效果
设备树的模型类似 树状结构体 最开始处就是 根节点 ,往下延申各种节点信息
3:常见节点
在设备树文件中主要由分为三类文件:
dts:
用户所修改裁剪的设备树文件
dtsi:
相当于头文件->一般被dtsi包含
dtb:
会被 DTC工具编译成二进制文件,编译完成后的文件名称一般和设备树一样。
设备树内部的内容分为两个关键的点:
3. 1 设备树的所有的信息都被归纳为节点(node)
以一个LED灯的为例:
led: led{
compatible = "led";
gpios =<&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>,
<&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
status = "okay";
};
节点一般用{}引用,节点都属于根节点内部的子节点。
3.2 设备节点里面的内容
节点里面的内容被称之为属性。
compatible = "led";
status = "okay";
规范为:
1、节点的属性结尾用;
2、一个属性的多个属性之间用,分隔
3、属性一般有两种可能:字符串 数字
3.3 特殊节点
设备树里面常见的特殊节点: / : 所有的设备树的起始一定是 / 根节点 aliases: 定义一些节点的别名 chosen: 这个大部分情况下无用 可以当作给内核的传参->类似 uboot 的 bootargs |
4:常见属性
节点里面常见属性:
name:(已被弃用)
就是个标签作用,基本没有其他的作用了
model:
真标签,为了给别人看你写的设备的作用
里面会写厂商的名字 芯片 型号....
compatible:(最重要属性没有之一)
他是节点的真标识,他也是驱动匹配的方法,表示、名字
驱动怎么找到设备树从而获取设备树信息
全靠 compatible ->字符串
status: (重点之一)
表示设备树的状态
"disalbed"->该信息失效/失能
"okay
5:查找节点
在内核中查找节点有三种接口函数:
函数的功能:获取设备树节点 通过 name
函数头文件: <linux/of.h>
函数的原型:
struct device_node *of_find_node_by_name( struct device_node *from, const char *name );
函数的参数:
from:
你要从哪个节点往下搜寻
一般传入 NULL ->代表从 根节点往下搜索!
name:
你要搜寻的节点的名字
举个节点为例子:
hehehe {
compatible = "hehe","test_hehe";
status = "disabled";
testnum = <0xFFFF>;
};
节点名字 "hehehe"
函数返回值:
返回的就是在内核的节点抽象的结构体
函数的功能:获取设备的节点->通过 compatible 获取节点
函数头文件:同上
函数的原型:
struct device_node *of_find_compatible_node( struct device_node *from, const char *type, const char *compatible )
函数的参数:
from:
从哪个节点往下查找 /->NULL
type:
NULL
compatible:
你想查找的节点对应 compatible 属性值
函数返回值:
返回值即为你要的节点
函数的功能:通过路径寻找节点
函数头文件:同上
函数的原型:
struct device_node *of_find_node_by_path(const char *path)
函数的参数:
path:
提供节点的绝对路径->/开始
举个例子:
hehehe 节点为例路径: "/hehehe"
函数返回值:返回的即为节点
6:获取节点属性
函数的功能:通用获取节点属性的信息的函数
函数头文件:同上
函数的原型:
property *of_find_property( const struct device_node *np, const char *name, int *lenp )
函数的参数:
np:
获取的属性来自哪个节点
name:
获取属性的属性名
lenp:
获取属性的长度->不提供
函数返回值:
内核封装的属性返回值的结构体
函数的功能:读取一个 U32 类型属性值
函数头文件:同上
函数的原型:
int of_property_read_u32_index( const struct device_node *np, const char *propname, u32 index, u32 *out_value )
函数的参数:
np:
设备节点
propname:
你要查找的属性名
index:
索引号,下标号
out_value:
把查找到属性值给他放入里面
函数返回值:
成功返回 0
失败返回 非 0
函数的功能:获取属性的字符串信息
函数头文件:同上
函数的原型:
int of_property_read_string( struct device_node *np, const char *propname, const char **out_string )
函数的参数:
np:
节点
propname:
属性名字
out_string:
就会把返回的字符串传入其中
函数返回值:
成功返回 0
失败返回 非 0
简单举例:用设备树写一个LED灯的驱动
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/of.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/of_gpio.h"
#include "linux/device/class.h"
#include "linux/device.h"
struct class cls;
struct device_node * node;
dev_t devnum;
struct cdev xydledcdev;
const struct file_operations ops;
struct xyd_led_gpio{
int gpio_num;
int gpio_flag;
char name[32];
};
struct xyd_led_gpio myxyd_led_info;
int led_open (struct inode *i, struct file *f)
{
gpio_set_value(myxyd_led_info.gpio_num,myxyd_led_info.gpio_flag);
return 0;
}
int led_close (struct inode *i, struct file *f)
{
gpio_set_value(myxyd_led_info.gpio_num,!myxyd_led_info.gpio_flag);
return 0;
}
static int +__init tree_led_init(void)
{
//1:先确定设备树LED灯设备信息是否存在
node = of_find_compatible_node(NULL, NULL,"xyd_led");
if(node == NULL)
{
printk("未成功设置设备树信息\r\n");
return -EINVAL;
}
//2:判断status状态
const char * out_string;
int ret = of_property_read_string(node, "status", &out_string);
if(ret!=0 || strcmp("okay",out_string))
{
printk("信息不全,或者未使能! \r\n");
return -EINVAL;
}
//3.获取设备树的GPIO信息
myxyd_led_info.gpio_num = of_get_named_gpio(node, "xyd-gpios", 0);
if(myxyd_led_info.gpio_num < 0)
{
printk("该设备树未提供 GPIO 信息! \r\n");
return -EINVAL;
}
//4.获取GPIO的有效点平
enum of_gpio_flags flags =0;
of_get_named_gpio_flags(node,"xyd-gpios" , 0,&flags);
if(flags == OF_GPIO_ACTIVE_LOW)
{
myxyd_led_info.gpio_flag=0;
}
else
{
myxyd_led_info.gpio_flag=1;
}
//5:封装设别名
sprintf(myxyd_led_info.name,"xyd_led_%d",myxyd_led_info.gpio_num);
//6:初始化GPIO
gpio_request(myxyd_led_info.gpio_num,myxyd_led_info.name);
gpio_direction_output(myxyd_led_info.gpio_num,!myxyd_led_info.gpio_flag);
//7:申请一个设备号
alloc_chrdev_region(&devnum, 0,1,myxyd_led_info.name);
//8:初始化cdev
ops.open = led_open;
ops.release = led_close;
ops.owner= THIS_MODULE;
cdev_init(&xydledcdev,&ops );
//9:添加到内核
cdev_add(&xydledcdev,devnum,1);
//10:生成类结构体
cls = class_create(THIS_MODULE,myxyd_led_info.name);
//11:生成设备文件
device_create(cls, NULL,devname,NULL,myxyd_led_info.name);
return 0;
}
static void __exit tree_led_exit(void)
{
//根据倒序的思想
//1:销毁设备文件
device_destroy(cls, myxyd_led_info.gpio_num);
//2:销毁类文件
class_destroy(cls);
//3:从内核中删除
cdev_del(&xydledcdev);
//4:释放设备号
unregister_chrdev_region(devnum, 1);
//5:注销GPIO设备
gpio_free(myxyd_led_info.gpio_num);
}
module_init(tree_led_init);
module_exit(tree_led_exit);
MODULE_LICENSE("GPL");