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:设备当前的状态 ,类似与开关,四种状态。— 类型也是一个字符串

  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);//底层的closesleep(1);}	return 0;
}

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

在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2000-2022年中国河流水系变化矢量数据集(矢量/30m)含:河道水面宽度、水面中心线、水系网络节点

2000-2022年中国河流水系变化矢量数据集 数据介绍 河网水系是是受气候变化和人类活动影响最显著的水生态环境之一。联合国可持续发展目标6&#xff08;SDG 6&#xff09;亚目标SDG 6.6.1是指与水有关的生态系统范围随时间的变化。本数据产品面向SDG 6.6.1&#xff0c;利用卫星…

盘点:当养生茶遇上互联网,都有哪些打法?

健康行业电商大战早已拉开序幕&#xff0c;作为健康行业的一个大类——养生茶还能缺席么&#xff1f;三好夫人、同仁堂、东韵、九芝堂、余庆堂等等各路豪杰齐聚养生茶电商&#xff0c;看他们如何各显神通吧&#xff01; 三好夫人——一生只送一人 三好夫人以爱之名创立&#x…

网络编程入门-实现服务器与客户端通讯

概念学习 TCP概念&#xff1a; TCP&#xff08;Transmission Control Protocol&#xff09;协议指的是传输控制协议&#xff0c;是一个面向连接的传输协议&#xff0c;他是一个能提供高可靠性的通信协议&#xff0c;所谓高可靠性指的是数据无丢失、数据无误、数据无失序、数据…

Keil导入包出错

1.菜单栏找不到GD系列&#xff1f; 随便新建一个工程&#xff0c;将project用记事本打开后如图2所示。再将别人给的代码工程用记事本打开&#xff0c;发现别人给的工程少了这两行&#xff0c;所以复制粘贴到别人给的工程记事本中&#xff0c;保存刷新后重新打开&#xff0c;就…

剑指offer JZ23 链表中环的入口结点

问题描述&#xff1a; 给定一个长度为n的链表&#xff0c;首先判断其是否有环&#xff0c;然后找到环的入口。 要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复杂度 O(n)。 思路&#xff1a; 1. 投机一点的做法 从头遍历链表&#xff0c;如果有环&#xff0c;那么有些节…

解读:以RTC为基,AI为脑的“超拟人”AI实时互动解决方案

我们打造了一款满足想象与应用的智能体——AI实时互动。 谈谈AI智能体 当AI变得足够聪明时&#xff0c;用户与AI的交互将变得真实自然。于是&#xff0c;构建高拟真AI与用户的实时交互&#xff0c;已经成为企业提升数智化生产力的新思路。 在这个交互过程中&#xff0c;存在一…

@开发者极客们,网易2024低代码大赛来啦

极客们&#xff0c;网易云信拍了拍你 9月6日起&#xff0c;2024网易低代码大赛正式开启啦&#xff01; 低代码大赛是由网易主办的权威赛事&#xff0c;鼓励开发者们用低代码开发的方式快速搭建应用&#xff0c;并最终以作品决出优胜。 从2022年11月起&#xff0c;网易低代码大赛…

python_openCV_计算图片中的区域的黑色比例

希望对原始图片进行处理&#xff0c;然后计算图片上的黑色和白色的占比 上图&#xff0c; 原始图片 import numpy as np import cv2 import matplotlib.pyplot as pltdef cal_black(img_file):#功能&#xff1a; 计算图片中的区域的黑色比例#取图片中不同的位置进行计算&…

Qt:关于使用player->state()导致的程序崩溃

前言 最近想做一个白噪声播放器&#xff0c;中间就用到了QMediaplayer这个类&#xff0c;其中遇到两个问题&#xff0c;一个是调用player->duration()第一次获取媒体时长为0的问题(这个问题留到下一个文章去说)&#xff1b;还有一个就是未初始化好就调用player->state()…

解决Win10版Township进度保存问题

解决Win10版Township进度保存问题 问题描述问题分析解决步骤1.WinR打开运行&#xff0c;输入regedit点击确定打开注册表2.进入注册表“计算机\HKEY_CURRENT_USER\Software\Classes\LocalSettings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings”目录3.在这…

逆向学习系列(一)安装模拟器

从今天开始&#xff0c;学习逆向APP的知识并记录。 首先&#xff0c;从最简单的环境搭建开始&#xff0c;我的环境&#xff08;LENOVO&#xff09;&#xff0c;win7&#xff0c;64位。 &#xff08;一&#xff09;安装mumu模拟器。 官网地址&#xff1a;MuMu模拟器官网_安卓…

【idea】设置文件模板

搜索 File and Code Templates 。 添加模板。 在任意文件目录下右键&#xff0c;new->找到添加的模板。 参考链接&#xff1a; IDEA创建模板文件_edit file templates-CSDN博客

Rocketmq 概述消息队列的应用场景

Message Queue &#xff08;消息 队列&#xff09;&#xff0c;从字⾯上理解&#xff1a;⾸先它是⼀个队列。 FIFO 先进先出的数据结构—— 队列。消息队列就是所谓的存放消息的队列。 消息队列解决的不是存放消息的队列的⽬的&#xff0c;解决的是通信问题。 先来看看没有使用…

rancher搭建k8s及jenkins自动化部署

1、准备环境 角色IP用途k8s-rancher-master192.168.3.63master节点k8s-rancher-node01192.168.3.64node节点k8s-rancher-node02192.168.3.66node节点k8s-rancher-server192.168.2.33rancher-server节点注: 服务器名需要配置不同,相同服务器名不能加入node节点 在所有节点进行…

海外直播对网速、带宽、安全的要求

要满足海外直播的要求&#xff0c;需要拥有合适的网络配置。在全球化的浪潮下&#xff0c;海外直播正逐渐成为企业、个人和各类组织的重要工具。不论是用于市场推广、品牌宣传&#xff0c;还是与观众互动&#xff0c;海外直播都为参与者带来了丰富的机会。然而&#xff0c;确保…

【小项目】python贪吃蛇小游戏设计

引入pygame库 添加pygame库&#xff0c;在cmd中输入以下代码&#xff0c;进行安装。如果输入pip install pygame出现以下报错&#xff0c;可以尝试在前面加入python3 -m。 python3 -m pip install pygame 贪吃蛇代码 import pygame import time import random# 初始化 Pygam…

【原创】java+springboot+mysql学生信息管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

IQ Tools---Radar Pulses/Chirps

本文将详细介绍IQ Tool中的Radar Pulses/Chirps模块的使用方法和实现原理。 1. 参数配置 图1. Radar Pulses/Chirps参数配置界面 该模块可配参数如下&#xff1a;   (1) Sample Rate(Hz): 系统采样率&#xff0c;单位:Hz   (2) Repeat interval(s): 脉冲重复周期&#xff…

数据库类型有哪些?

根据存储方式的不同&#xff0c;数据库可以分为不同种类。每种类型的数据库&#xff0c;都有各自使用场景以及不同的产品。 ​ 关系型数据库 关系型数据库&#xff08;RDBMS&#xff09;基于关系模型&#xff0c;通过表&#xff08;Table&#xff09;的形式来组织数据&#xf…

java中普通代码块和静态代码块之间的区别(java小知识点)

文章目录 1.普通代码块&#xff08;实例代码块&#xff09;1.1用法 2.静态代码块2.1用法 3.总结 1.普通代码块&#xff08;实例代码块&#xff09; 实例代码块是一段未包含在任何方法或构造器中的代码。它再每次创建类的实例时候执行&#xff0c;并且优先于构造器执行. 用途一般…