珠海网站建设创意,wordpress 企业模板 免费,天坛网站建设,装修网站建设价格目录 前言一、设备树是个啥#xff1f;二、设备树编写语法规则1.文件类型2.设备树源文件#xff08;DTS#xff09;结构3.设备树源文件#xff08;DTS#xff09;解析 三、设备树API函数1.在内核中获取设备树节点#xff08;三种#xff09;2.获取设备树节点的属性 四、… 目录 前言一、设备树是个啥二、设备树编写语法规则1.文件类型2.设备树源文件DTS结构3.设备树源文件DTS解析 三、设备树API函数1.在内核中获取设备树节点三种2.获取设备树节点的属性 四、应用示例1.设备树2.驱动层3.应用层 前言 本文主要讲解了一下设备树的概念编写语法规则API函数和使用流程最后使用LED灯闪烁实战验证了一下。 一、设备树是个啥 在Linux开源初期Linux 系统因其开源、稳定和安全而受到了各大公司和开发者的欢迎。然后随着越来越多个人和公司的使用在受到追捧的同时也让Linux内核变得越来臃肿。由于当时内核中没有规范来引导最终使之充斥着大量的屎山代码。这些垃圾的设备代码全都来自于对应公司单板启动或运行细节强绑定无法复用和移植。 终于在2012年因为Tony Lindgren内核OMAP development tree的维护者发送了一个邮件给Linus请求提交OMAP平台代码修改并附带修改以及如何解决merge conficts的这个事情。让Linus Torvalds 实在受不了了直接在推特上爆粗口“Gaah.Guys, this whole ARM thing is a fucking pain in the ass” 这个事件发生之后Linux 内核社区和维护者采取了一些措施来改善这一问题其中很关键的一条就是ARM SOC board specific的代码被移除由DeviceTree机制来负责传递硬件拓扑和硬件资源信息。其实设备树Device Tree在 Linux 内核中早在 2005 年已经引入了只不过该事件之后设备树的使用得到了更多关注和改进之后设备树的推动主要是因为其在管理复杂硬件平台方面的固有优势。 本质上Device Tree改变了原来用hardcode方式将硬件设备配置信息嵌入到内核代码的方法改用bootloader传递一个DB的形式。 如上所示就是一个大致的设备树抽象图设备树的出现大大减小了Linux内核的大小也简化了你写驱动代码的架构。
驱动代码本来是分为两部分的 1.设备端提供硬件设备的资源 — 中断、GPIO 资源等 2.驱动端驱动代码的框架 — 就是之前说过的杂项和 Linux2.6 — 他会和设备端进行匹配然后从设备端获取硬件的资源来驱动对应的硬件。 但是设备树出来之后就把设备端给替代了我们只需要简单写几行代码将设备添加到设备树中然后只写驱动端的代码就行了。简化了硬件描述和配置使得内核代码中对硬件的硬编码减少了。
设备树存放路径一般都在 arch/arm/boot/dts/下。 我是用的是
RK3588S/kernel/arch/arm64/boot/dts/rockchip/rk3588s-yyt.dts二、设备树编写语法规则
1.文件类型
dts:写驱动时主要编写修改的文件 — 类似于 C 语言的.c 文件。 dtsi设备的原始文件 — 一般是由厂商写 — 类似于 C 语言的.h文件。 dtb就是设备树编译生成的二进制文件可执行文件 — 类似于 C 语言的.o 文件。 dtc编译设备树的工具 — 类似于 C 语言的 gcc 文件。
图示
2.设备树源文件DTS结构
直接上图
3.设备树源文件DTS解析
1.属性 model:描述开发板信息的属性 – 公司 芯片的型号 — 类型是一个字符串
compatible兼容性标识符设备树属性里最重要的一个属性用来做匹配的。 驱动代码就是通过compatible找到目标节点的从而获得该设备的设备信息。类型是字符串,也可以是多个字符串 例 compatible xyd-led, rk-led; 查找顺序是先找 xyd-led再找 rk-led找到一个即可。
status设备当前的状态 类似与开关四种状态。— 类型也是一个字符串
“okay”表示设备启用并且应该被驱动程序使用。设备在系统启动时会被初始化。“disabled”表示设备被禁用。驱动程序不会加载和初始化这个设备但他仍在设备树里。“fail”表示设备初始化失败通常用于在设备初始化时出现问题的情况下。设备将不会被驱动程序使用。“reserved”表示设备被保留通常用于未来的扩展或保留给系统内部用途。
了解 chosen就是你 uboot 可以给内核传递的一些参数
bootargs earlyconuart8250,mmio32,0xff690000 consolettyFIQ0
androidboot.basebandN/A androidboot.veritymodeenforcing
androidboot.hardwarerk30board androidboot.consolettyFIQ0
androidboot.selinuxpermissive init/init kpti0;bootargs在 uboot 启动的时会把后边的这些信息传递给内核内核就可以带着这些信息去找你的文件系统这些信息也可以在设备树里设置。 aliases就是给其他的节点取一个别名你操作这个别名就相对于操作该节点 #address-cells 2 — 设备寄存器的地址 #size-cells 2 — 设备寄存器的长度 – 就是大小 以上两个都是一个无符号 32 位数值
address-cells 1 — 代表这个地址的是 2 – 代表的是 2 个字节 size-cells 1 — 代表这个地址长度是 2 – 代表的是 2 个字节 其实以上两个值是给 reg 属性使用的
reg — 这里你就可以填写硬件设备的地址以及他的长度 地址和长度具体是几个字节的是有 address-cells 和 size-cells 例reg 0xff690000 0x20
三、设备树API函数
1.在内核中获取设备树节点三种
struct device_node *of_find_node_by_name(struct device_node
*from, const char *name);函数功能通过节点名获取设备树节点 函数头文件#include linux/of.h 函数参数 from:直接写 NULL — 代表从根节点开始找 name想要从设备树上获取的节点名 函数返回值成功返回指向一个 struct device_node 指针失败返回NULL。
struct device_node *of_find_compatible_node(struct device_node
*from, const char *type, const char *compatible)函数功能通过兼容性标识符获取设备树节点 函数头文件#include linux/of.h 函数参数 device_node写 NULL type写 NULL compatible设备节点上的 兼容性标识符属性的值 函数返回值成功返回指向一个 struct device_node 指针失败返回NULL。
struct device_node *of_find_node_by_path(const char *path);函数功能通过给定路径获取设备树节点 函数头文件#include linux/of.h 函数参数 path:写你要查找节点的路径 比如你要找 xyd_led 就填写 “/xyd_led” —表示从设备树上的根目录开始找 xyd_led 的节点。 函数返回值成功返回指向一个 struct device_node 指针失败返回NULL。
struct device_node 是设备树的一个重要结构体用于描述设备树中的一个节点。一般我们只使用该结构中的const char *name; — 从设备树上找到的设备的节点名。
2.获取设备树节点的属性
int of_get_named_gpio(struct device_node *np,const char*propname, int index);函数功能从设备树Device Tree中获取 GPIO 的编号 函数头文件#include linux/of_gpio.h 函数参数 np指向 device_node 结构的指针表示设备树中的设备节点。 propname指向包含 GPIO 编号的属性名称的字符串。例“gpios”。 index属性中 GPIO 编号的索引获取— 就是你要获取第几个GPIO口。 函数返回值成功获取得到的 gpio 口的编号 失败返回负数
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value);函数功能从设备树中的设备节点属性中读取一个 32 位无符号整数。 函数头文件#include linux/of.h 函数参数 np:指向 device_node 结构的指针表示设备树中的设备节点。 propname节点里具体哪一个属性 — 属性的名字 index获取这个属性里的一个值 — 如果一个默认就写 0 out_value:保存获取得到信息 函数返回值成功返回 0失败返回负数。
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
函数功能从设备树中的设备节点属性中读取一个字符串 函数头文件#include linux/of.h 函数参数 np指向 device_node 结构的指针表示设备树中的设备节点。 propname获取的节点属性名。 out_string保存获取得到的属性值 函数返回值成功返回 0失败返回负数。
四、应用示例
目标利用设备树字符设备驱动实现对LED等的控制
1.设备树
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/** Copyright (c) 2021 Rockchip Electronics Co., Ltd.**/
/dts-v1/;
#include rk3588s-evb4-lp4x-yyt.dtsi
#include rk3588-linux.dtsi
/ {model Rockchip RK3588S EVB4 LP4X V10 Board;compatible rockchip,rk3588s-evb4-lp4x-v10, rockchip,rk3588;xydled:xyd_leds {compatible xyd_led;leds-gpios gpio0 RK_PC5 GPIO_ACTIVE_HIGH,gpio0 RK_PC6 GPIO_ACTIVE_HIGH;status okay;};
};没什么好说的就是在一个子节点中添加了两个LED灯的GPIO口默认高电平。 添加完需在瑞芯微编写好的脚本去编译烧录。就是在RK3588s文件路径下的中断中使用./build.sh kernel命令重新编译boot.img并重新烧录。 使用该命令默认就编译设备树文件了并且他把编译生成的设备树的 dtb 文件给继承了到 boot.img 镜像里。 开发板设备树节点的位置 /sys/firmware/devicetree/base/xyd_led /proc/device-tree/xyd_led
为什么有两个路径 1.不同的访问接口: /sys/firmware/devicetree/base 和 /proc/device-tree 提供不同的接口来访问设备树数据适用于不同的需求和使用场景。sysfs 更适合用来与用户空间程序交互而 procfs 更适合获取原始数据。
2.数据更新方式: /sys/firmware/devicetree/base 的数据可能会受到内核的处理和格式化影响而 /proc/device-tree 则提供的是设备树的原始视图。
3.兼容性: 一些旧的工具或脚本可能仍然依赖于 /proc/device-tree而较新的工具和机制可能使用 /sys/firmware/devicetree/base。同时procfs 和 sysfs 具有不同的历史背景和设计目标。
2.驱动层
#include linux/module.h
#include linux/kernel.h
#include linux/miscdevice.h
#include linux/fs.h
#include linux/cdev.h
#includelinux/device.h
#include linux/gpio.h
#include linux/fs.h
#include linux/of_gpio.h
#include linux/of.hconst char *out_string NULL;
dev_t dev;//设备号
struct cdev mydev;
struct class *myclass NULL;
int gpio_value[2] {0};
int i0;
int my_open (struct inode *inode, struct file *fp)
{gpio_set_value(21,1);gpio_set_value(22,1);printk(led_Open ok\n);return 0;
}int my_release (struct inode *inode, struct file *fp)
{gpio_set_value(21,0);gpio_set_value(22,0);printk(led_close ok\n);return 0;
}struct file_operations my_filop {.open my_open,.release my_release,
};static int __init my_open_init(void)
{struct device_node *nodeof_find_node_by_name(NULL,xyd_leds);//寻找指定的设备节点并将找到的设备节点返回给node里if(node NULL){printk(该节点不存在\n);return -1;}printk(节点的名字:%s\n,node-name);//获取设备树节点的属性for(i0;i2;i){gpio_value[i] of_get_named_gpio(node, leds-gpios, i);printk(gpio_value[%d]:%d\n,i,gpio_value[i]);}//获取设备节点上的属性of_property_read_string(node,status,out_string);printk(设备节点上的属性out_string:%s\n,out_string);if(strcmp(okay, out_string)!0){printk(设备节点不可用\n);return 0;}gpio_request(gpio_value[0],led1);gpio_request(gpio_value[1],led2);gpio_direction_output(gpio_value[0],1);gpio_direction_output(gpio_value[1],1);alloc_chrdev_region(dev,0, 1, led_tree);//注册索取设备号printk(设备号注册成功!\n);printk(主设备号:%d\n,MAJOR(dev));printk(次设备号:%d\n,MINOR(dev));cdev_init(mydev,my_filop);cdev_add(mydev,dev,1);myclass class_create(THIS_MODULE, class_led);if(myclass NULL){printk(class_creat error!\n);return -1;}device_create(myclass,NULL,dev,NULL,led_shine);return 0;
}static void __exit my_open_exit(void)
{device_destroy(myclass,dev);//销毁设备节点class_destroy(myclass);//销毁设备类cdev_del(mydev);//删除字符设备unregister_chrdev_region(dev,1);//释放设备号printk(设备注销成功\n);gpio_free(gpio_value[0]);gpio_free(gpio_value[1]);
}module_init(my_open_init);
module_exit(my_open_exit);
MODULE_LICENSE(GPL);入口函数
设备树 1.of_find_node_by_name寻找指定的设备节点并将找到的设备节点返回给node里。 2.of_get_named_gpio获取节点上的属性本例获取的就是GPIO的编号。 3.of_property_read_string获取节点上的属性本例目的就是获取status的值。
LED灯gpio配置 1.先使用gpio_request申请GPIO口所需的资源。 2.配置工作模式。由于是控制LED灯所以这里将模式设置为输出模式gpio_direction_output。 3.使用alloc_chrdev_region注册设备号。 4.使用cdev_init初始化设备。 5.使用cdev_add向内核申请linux2.6字符设备。 下面是自动创建节点的部分 6.使用class_create创建一个类方便管理注册的设备。 7.使用device_create创建设备节点。
出口函数 1.device_destroy销毁设备节点。 2.class_destroy销毁设备类。 3.cdev_del删除字符设备。 4.unregister_chrdev_region释放设备号。 5.gpio_free释放GPIO口的资源。
3.应用层
#include stdio.h
#include fcntl.h
#include unistd.h
#include string.h
int main(int argc,char *argv[])
{int fd 0;char buffer[100];const char *data Hello, this is a test write!;if(argc2){printf(请输入正确的参数\n);return -1;}fd open(argv[1],O_RDWR);if(fd0){perror(open);return -1;}while(1){fd open(argv[1],O_RDWR); // --- 底层的open函数sleep(1);close(fd);//底层的closesleep(1);} return 0;
}结果