Linux驱动(五):Linux2.6驱动编写之设备树

news2024/9/22 19:35:57

目录

  • 前言
  • 一、设备树是个啥?
  • 二、设备树编写语法规则
    • 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:设备当前的状态 ,类似与开关,四种状态。— 类型也是一个字符串

  1. “okay”:表示设备启用并且应该被驱动程序使用。设备在系统启动时会被初始化。
  2. “disabled”:表示设备被禁用。驱动程序不会加载和初始化这个设备,但他仍在设备树里。
  3. “fail”:表示设备初始化失败,通常用于在设备初始化时出现问题的情况下。设备将不会被驱动程序使用。
  4. “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);//底层的close
		sleep(1);
	}	
	return 0;
}

结果:
在这里插入图片描述

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024 World Conference of Computer and Information Security(WCCIS 2024)

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网&#xff1a;https://ais.cn/u/vEbMBz提交检索&#xff1a;EI Compendex、IEEE Xplore、Scopus截稿日期&#xff1a;2024年9月4日2024年9月27-29日 广西桂…

Rust模块std::thread

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust多线程编程概述-CSDN博客 12.…

合碳智能 × Milvus:探索化学合成新境界——逆合成路线设计

合碳智能&#xff08;C12.ai&#xff09;成立于2022年&#xff0c;致力于运用AI和具身智能技术&#xff0c;为药物研发实验室提供新一代智能化解决方案&#xff0c;推动实验室从自动化迈向智能化&#xff0c;突破传统实验模式与人员的依赖&#xff0c;解决效率和成本的瓶颈&…

1. GIS开发工程师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…

Leetcode每日刷题之76.最小覆盖子串(C++)

1.题目解析 本题的题目是给定两个字符串 s 和 t &#xff0c;找出在 s 中的某个最小子串保证该子串中包含所以 t 中出现的字母即可&#xff0c;并且该结果是唯一答案&#xff0c;找不到结果就直接返回空串即可 2.算法原理 关于本题的核心思路就是"滑动窗口"&#xff…

【Python 千题 —— 算法篇】首字母大写

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 在文本格式化和处理过程中,常常需要将字符串的首字母大写。这在各种场景中都有实际应用,例如在标题格式化、用户输入校验、生成显示友好的文本等场景中。…

CC6链漏洞

CC6链漏洞 一 cc链简介 CC链是Apache Commons Collections反序列化漏洞利用链的简称&#xff0c;它涉及将可以执行命令的函数&#xff08;如Runtime.getRuntime().exec("calc.exe")&#xff09;序列化为对象流并转化为文件流存储在文件中&#xff0c;然后通过反序列…

深度学习5从0到1理解RNN(包括LTSM,GRU等):内容丰富(上)

循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09; 是一种经典的深度学习网络结构&#xff0c;具有广泛的应用。其中&#xff0c;槽填充&#xff08;Slot Filling&#xff09;&#xff08;即识别自然语言中的特定信息&#xff09; 是其中一个应用场景&#x…

香橙派开启vnc

1连接香橙派 2. 更新系统 在SSH会话中&#xff0c;首先更新系统软件包列表并升级现有软件包&#xff1a; sudo apt update sudo apt upgrade3. 安装VNC服务器 安装VNC服务器软件&#xff0c;这里以x11vnc为例&#xff1a; sudo apt install x11vnc 出现如图输入如下代码即可…

Python爬虫:通过js逆向获取某瓜视频的下载链接

爬虫:通过js逆向获取某瓜视频的下载链接 1. 前言2. 获取script标签下的视频加密数据3. 第一步:获取解密后的视频下载链接4. 第二步:模拟生成加密的webid值 1. 前言 就小编了解&#xff0c;某瓜视频这个网站对应视频下载链接加密处理至少经过三个版本。之前在CSDN发布了一篇关于…

船舶机械设备5G智能工厂物联数字孪生平台,推进制造业数字化转型

船舶机械设备5G智能工厂物联数字孪生平台&#xff0c;推进制造业数字化转型。在当今数字化浪潮推动下&#xff0c;船舶制造业正经历着前所未有的变革。为了应对市场的快速变化&#xff0c;提升生产效率&#xff0c;降低成本&#xff0c;并增强国际竞争力&#xff0c;船舶机械设…

Docker 详解及详细配置讲解

Docker 简介 2008 年LXC(LinuX Contiainer)发布&#xff0c;但是没有行业标准&#xff0c;兼容性非常差 docker2013年首次发布&#xff0c;由Docker, Inc开发 什么是 Docker Docker是管理容器的引擎&#xff0c;为应用打包、部署平台&#xff0c;而非单纯的虚拟化技术&#xf…

【springboot】使用swagger生成接口文档

1. 添加依赖 <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.6.0</version></dependency> 这里我老是添加不上这个依赖&#xff0c;搜索了下发现阿里…

《2024网络安全十大创新方向》

网络安全是创新驱动型产业&#xff0c;技术创新可以有效应对新的网络安全挑战&#xff1b;或是通过技术创新降低人力成本投入&#xff0c;提升企业运营效率。为推动行业技术创新、产品创新与应用创新&#xff0c;数说安全发布《2024年中国网络安全十大创新方向》&#xff0c;涵…

K8s高可用集群部署----超详细(Detailed Deployment of k8s High Availability Cluster)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

sM4040B科学级显微制冷相机特性

sM4040B科学级显微制冷相机特性 sM4040B搭载了 GSENSE4040BSI 3.2 英寸图像传感器&#xff0c;针对传感器固有的热噪声&#xff0c;专门设计了高效制冷模块&#xff0c;使得相机传感器的工作温度比环境温度低达 35-40 度。针对制冷相机常见的低温结雾现象设计了防结雾机制&…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.3 存储器

这一章&#xff0c;前面不难&#xff0c;后面难。 教你别这么连线连出问题。 看结果说话&#xff0c;延迟两个时刻输出。 先不管要求&#xff0c;输出一个稳定的信号&#xff0c;看看之前给了延迟元件正好延迟一刻&#xff0c;然后作为输入和那个稳定的信号做一个逻辑运算改变…

逻辑导论前传

人类的逻辑运算建立在已有的数据库上&#xff0c; 我们无法处理逻辑问题&#xff0c;是因为宇宙的意志不允许我们得出正确答案&#xff0c;每个人都是一个答案&#xff0c;当你认知到了所有人&#xff0c;你也就得到了所有正确答案&#xff0c;这时候宇宙智慧采取正确答案的逻辑…

绿色无广告,纯净体验——2024年优质免费视频剪辑软件

如果你习惯一个视频网站的时候&#xff0c;工作上遇到问题也会第一时间在视频网站上进行搜索解决方案。就比如我同事就很喜欢在短视频网站上搜索Office软件的一些操作步骤。如果你也想分享这类视频&#xff0c;那么我们一起探讨下有哪些适合抖音剪辑的视频剪辑工具。 1.福昕视…

Linux下安装Docker-ce ,配置nginx容器

引言 直接在windows系统中使用nginx服务&#xff0c;面临着如下问题&#xff1a; 1.性能瓶颈 高并发处理能力有限&#xff0c;资源利用率不高。 2.兼容性和稳定性问题 Nginx最初是为Linux等Unix-like系统设计的&#xff0c;虽然在Windows上也有版本&#xff0c;但可能不是…