Linux驱动(五):Linux2.6驱动编写之设备树
目录
- 前言
- 一、设备树是个啥?
- 二、设备树编写语法规则
- 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 = "earlycon=uart8250,mmio32,0xff690000 console=ttyFIQ0
androidboot.baseband=N/A androidboot.veritymode=enforcing
androidboot.hardware=rk30board androidboot.console=ttyFIQ0
androidboot.selinux=permissive init=/init kpti=0";
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>
#include<linux/device.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/of_gpio.h>
#include <linux/of.h>const char *out_string = NULL;
dev_t dev;//设备号
struct cdev mydev;
struct class *myclass = NULL;
int gpio_value[2] = {0};
int i=0;
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 *node=of_find_node_by_name(NULL,"xyd_leds");//寻找指定的设备节点,并将找到的设备节点返回给node里if(node == NULL){printk("该节点不存在\n");return -1;}printk("节点的名字:%s\n",node->name);//获取设备树节点的属性for(i=0;i<2;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(argc<2){printf("请输入正确的参数\n");return -1;}fd = open(argv[1],O_RDWR);if(fd<0){perror("open");return -1;}while(1){fd = open(argv[1],O_RDWR); // --- 底层的open函数sleep(1);close(fd);//底层的closesleep(1);} return 0;
}
结果:


相关文章:
Linux驱动(五):Linux2.6驱动编写之设备树
目录 前言一、设备树是个啥?二、设备树编写语法规则1.文件类型2.设备树源文件(DTS)结构3.设备树源文件(DTS)解析 三、设备树API函数1.在内核中获取设备树节点(三种)2.获取设备树节点的属性 四、…...
算法【Java】 —— 前缀和
模板引入 一维前缀和 https://www.nowcoder.com/share/jump/9257752291725692504394 解法一:暴力枚举 在每次提供 l 与 r 的时候,都从 l 开始遍历数组,直到遇到 r 停止,这个方法的时间复杂度为 O(N * q) 解法二:前…...
python网络爬虫(四)——实战练习
0.为什么要学习网络爬虫 深度学习一般过程: 收集数据,尤其是有标签、高质量的数据是一件昂贵的工作。 爬虫的过程,就是模仿浏览器的行为,往目标站点发送请求,接收服务器的响应数据,提取需要的信息,…...
tio websocket 客户端 java 代码 工具类
为了更好地组织代码并提高可复用性,我们可以将WebSocket客户端封装成一个工具类。这样可以在多个地方方便地使用WebSocket客户端功能。以下是使用tio库实现的一个WebSocket客户端工具类。 1. 添加依赖 确保项目中添加了tio的依赖。如果使用的是Maven,可以…...
通过卷积神经网络(CNN)识别和预测手写数字
一:卷积神经网络(CNN)和手写数字识别MNIST数据集的介绍 卷积神经网络(Convolutional Neural Networks,简称CNN)是一种深度学习模型,它在图像和视频识别、分类和分割任务中表现出色。CNN通过模仿…...
【A题第二套完整论文已出】2024数模国赛A题第二套完整论文+可运行代码参考(无偿分享)
“板凳龙” 闹元宵路径速度问题 摘要 本文针对传统舞龙进行了轨迹分析,并针对一系列问题提出了解决方案,将这一运动进行了模型可视化。 针对问题一,我们首先对舞龙的螺线轨迹进行了建模,将直角坐标系转换为极坐标系࿰…...
一份热乎的数据分析(数仓)面试题 | 每天一点点,收获不止一点
目录 1. 已有ods层⽤⼾表为ods_online.user_info,有两个字段userid和age,现设计数仓⽤⼾表结构如 下: 2. 设计数据仓库的保单表(⾃⾏命名) 3. 根据上述两表,查询2024年8⽉份,每⽇,…...
3 html5之css新选择器和属性
要说css的变化那是发展比较快的,新增的选择器也很多,而且还有很多都是比较实用的。这里举出一些案例,看看你平时都是否用过。 1 新增的一些写法: 1.1 导入css 这个是非常好的一个变化。这样可以让我们将css拆分成公共部分或者多…...
【Kubernetes】K8s 的鉴权管理(一):基于角色的访问控制(RBAC 鉴权)
K8s 的鉴权管理(一):基于角色的访问控制(RBAC 鉴权) 1.Kubernetes 的鉴权管理1.1 审查客户端请求的属性1.2 确定请求的操作 2.基于角色的访问控制(RBAC 鉴权)2.1 基于角色的访问控制中的概念2.1…...
保研 比赛 利器: 用AI比赛助手降维打击数学建模
数学建模作为一个热门但又具有挑战性的赛道,在保研、学分加分、简历增色等方面具有独特优势。近年来,随着AI技术的发展,特别是像GPT-4模型的应用,数学建模的比赛变得不再那么“艰深”。通过利用AI比赛助手,不仅可以大大…...
秋招校招,在线性格测评应该如何应对
秋招校招,如果遇到在线测评,如何应对? 这里写个总结稿,希望对大家有些帮助。在线测评是企业深入了解求职人的渠道,如果是性格测试,会要求测试者能够快速答出,以便于反应实际情况(时间…...
chrome 插件开发入门
1. 介绍 Chrome 插件可用于在谷歌浏览器上控制当前页面的一些操作,可自主控制网页,提升效率。 平常我们可在谷歌应用商店中下载谷歌插件来增强浏览器功能,作为开发者,我们也可以自己开发一个浏览器插件来配合我们的日常学习工作…...
揭开面纱--机器学习
一、人工智能三大概念 1.1 AI、ML、DL 1.1.1 什么是人工智能? AI:Artificial Intelligence 人工智能 AI is the field that studies the synthesis and analysis of computational agents that act intelligently AI is to use computers to analog and instead…...
Python中的私有属性与方法:解锁面向对象编程的秘密
在Python的广阔世界里,面向对象编程(OOP)是一种强大而灵活的方法论,它帮助我们更好地组织代码、管理状态,并构建可复用的软件组件。而在这个框架内,私有属性与方法则是实现封装的关键机制之一。它们不仅有助…...
开篇_____何谓安卓机型“工程固件” 与其他固件的区别 作用
此系列博文将分析安卓系列机型与一些车机 wifi板子等工程固件的一些常识。从早期安卓1.0起始到目前的安卓15,一些厂家发布新机型的常规流程都是从工程机到量产的过程。在其中就需要调试各种参数以便后续的量产参数可以固定到最佳,工程固件由此诞生。 后…...
DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed
DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed 文章目录 DBeaver 连接 MySQL 报错 Public Key Retrieval is not allowed问题解决办法 问题 使用 DBeaver 连接 MySQL 数据库的时候, 一直报错下面的错误 Public Key Retrieval is not allowed详细…...
三个月涨粉两万,只因为知道了这个AI神器
大家好,我是凡人,最近midjourney的账号到期了,正准备充值时,被一个国内AI图片的生成神器给震惊了,不说废话,先上图看看生成效果。 怎么样还不错吧,是我非常喜欢的国风画,哈哈&#x…...
vulhub GhostScript 沙箱绕过(CVE-2018-16509)
1.搭建环境 2.进入网站 3.下载包含payload的png文件 vulhub/ghostscript/CVE-2018-16509/poc.png at master vulhub/vulhub GitHub 4.上传poc.png图片 5.查看创建的文件...
李宏毅机器学习笔记——反向传播算法
反向传播算法 反向传播(Backpropagation)是一种用于训练人工神经网络的算法,它通过计算损失函数相对于网络中每个参数的梯度来更新这些参数,从而最小化损失函数。反向传播是深度学习中最重要的算法之一,通常与梯度下降…...
内推|京东|后端开发|运维|算法...|北京 更多岗位扫内推码了解,直接投递,跟踪进度
热招岗位 更多岗位欢迎扫描末尾二维码,小程序直接提交简历等面试。实时帮你查询面试进程。 安全运营中心研发工程师 岗位要求 1、本科及以上学历,3年以上的安全相关工作经验; 2、熟悉c/c、go编程语言之一、熟悉linux网络编程和系统编程 3、…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...
