韦东山Linux驱动入门实验班(5)LED驱动---驱动分层和分离,平台总线模型

news2024/11/15 10:58:23

前言

(1)前面已经已经详细介绍了LED驱动如何进行编写的代码。如果韦东山Linux驱动入门实验班(4)LED驱动已经看懂了,驱动入门实验班后面的那些模块实验,其实和单片机操作差不太多了。我就不再浪费时间进行讲解了。
(2)本文主要进行讲解驱动的分层和分离,平台总线模型。
(3)对于韦东山老师的代码,我进行了微调,因为他代码写的比较着急,所以我感觉有些地方感觉有点冗余了就自作主张的进行了调整。但是原来他的部分没有删除,如果你认为韦东山老师的比我的好就可以注释掉我微调的部分。(微调部分我会进行说明)
(4)代码仓库:gitee仓库;github仓库
(5)注意:大家下载我仓库里面的代码再阅读本文会跟好理解一点。我仓库里面的代码依旧加上了详细的注释,觉得本文过于冗余。可以看我仓库代码和正点原子的文档学习

驱动分层

什么是驱动分层

(1)通过前面的学习,我们会发现,Linux开发将一个程序分成了两个层,应用层和驱动层。但是我们在编写驱动层程序的时候,会发现,驱动还是和指定的引脚以及开发板有关。
(2)这时候肯定会有人说了,我只需要你改一下哪个数组就可以了啊,这有什么难的吗?
(3)不难,但是对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。
(4)可能有些人会说了,我之前编写的程序,也没有重复啊。不过就只是要改一下驱动程序的gpios[]结构体的定义。但是,你要知道,在开发大型项目的时候,你将设备信息存入驱动程序中。每次调整都需要打开对应的驱动程序,如果我们不小心动了驱动程序的某个地方,产生了bug,排查是非常麻烦的。所以说,为了让Linux系统不变的臃肿,同时,为了安全性,于是Linux决定将驱动程序拆分成驱动程序和硬件描述程序,驱动程序一旦编写了,基本就不再需要再打开了。

在这里插入图片描述

(5)现在我们知道了,驱动程序与硬件描述程序的剥离之后。Linux于是提出了平台总线模型。我们将驱动程序和硬件描述信息都挂载在这个总线上面。
(6)有了这个总线,我们就可以先把我们设备上的一些信息挂载在总线上。然后给这些设备命一个名字。
(7)当我们需要使用指定设备的时候,驱动就可以通过给设备命的名字来找到设备信息。一旦驱动和设备匹配上,就可以执行指定的程序了。
(8)这样做有什么好处?这样我们的驱动程序可以直接进行移植,不需要受限于任何设备。而我们拿到一块开发板之后,可以直接把驱动移植过来,然后再编写设备描述信息即可,驱动代码几乎不再需要进行什么调整。

在这里插入图片描述

设备如何挂载在平台总线上

上面我们说了,Linux发明了平台总线,我们只需要将设备和驱动挂载在总线上,如果匹配上了,就会自动执行程序。那么设备是如何挂载在平台总线上。

platform_device_register()

(1)platform_device_register()这个函数可以将设备信息挂载在平台总线上。我们只需要传入struct platform_device结构体类型指针。
(2)如下是platform_device()结构体的定义,虽然参数很多,但是真正需要关注的只有nameidresourcenum_resourcesdev
<1>name:设备的名字, 用来和驱动相匹配。 名字相同才能匹配成功
<2>id:ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备, 因为有时候有这种需求)
<3>resource:资源结构体数组。我们需要写的设备信息写在这个数组里面。
<4>num_resources:资源结构体数量。表明这个设备结构体有多少个,其实就是一个宏定义#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
<5>dev:与平台设备相关联的设备对象。这个只需要知道要给.release参数传入一个函数指针即可。当设备被卸载的时候,会调用这个函数。

// platform 设备结构体
struct platform_device led_device = {
	.name = "led_device",  //platform 设备的名字, 用来和 platform 驱动相匹配。 名字相同才能匹配成功
	.id = -1,   // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备, 因为有时候有这种需求)
	.resource = led_resource,  //指向一个资源结构体数组。 一般包含设备信息
	.num_resources = ARRAY_SIZE(led_resource),  //资源结构体数量
	.dev = {
		.release = LED_release,
		}
};

platform_device_unregister()

(1)这个用于将设备从平台总线上卸载。当这个设备不需要的时候,我们就可以对他进行卸载。
(2)与platform_device_register()相同,也是只需要传入struct platform_device结构体类型指针。

设备程序的入口和出口

(1)设备程序和驱动程序一样,都是使用insmod和rmmod进行装载和卸载。程序入口都是使用module_init()宏来进行定义,程序出口也都是使用module_exit()来定义。
(2)他也必须使用调用MODULE_LICENSE(“GPL”);指定模块为GPL协议

驱动如何挂载在平台总线上

platform_driver_register()

(1)这个函数只需要传入一个static struct platform_driver类型结构体指针。
(2)这个驱动的结构体,比设备的那个结构体还是好理解很多的。
<1>我们只需要知道platform_driver结构体".driver"中的".name"和platform_device结构体中的".name"一模一样就可以了。
<2>当设备和驱动匹配上了之后,我们就会执行probe函数。如果设备和驱动一旦断开联系了,就执行remove函数。你可以这么理解,一对男女,男生和女生结婚了,那么就会颁发结婚证(执行probe函数)。有一天,他们俩因为一些事情,感情破裂了,就会给他们一个离婚证(执行remove函数)。

static struct platform_driver led_driver = {
	.driver		= {
		.name	= "led_device",   //根据这个名字,找到设备
		.owner = THIS_MODULE,
	},
	.probe		= led_drver_probe,   //注册平台之后,内核如果发现支持某一个平台设备,这个函数就会被调用。入口函数
	.remove		= led_drver_remove,  //出口函数
};

platform_driver_unregister()

这个是用于在平台上卸载驱动的。和platform_driver_register()一样,都是传入一个static struct platform_driver结构体指针。

和上一篇博客的区别

硬件信息不同

(1)这里主要就是把原来放在gpios[] 数组中的硬件描述信息,放在另外一个文件中。让驱动程序与硬件平台剥离。
(2)原来的硬件描述是存放在一个数组中,而现在的硬件描述是放在一个结构体指针里面,然后通过Linux中的平台总线获得设备信息。

/**********   原来的硬件描述  **********/
static struct gpio_desc gpios[] = {
    {131, "led0" },  //引脚编号,名字
};
/**********   现在的硬件描述  **********/
static struct gpio_desc *gpios;   //描述gpio

初始化程序不同

(1)在上一篇博客中,我们的驱动初始化函数都是放在module_init()这个宏里面。
(2)但是现在不一样了,module_init()这个宏里面的那个函数,只做一件事情,就是将static struct platform_driver结构体注册到平台总线上。

/**********   原来的module_init()中的函数任务  **********/
static int __init gpio_drv_init(void)
{
	//申请GPIO
	//将GPIO设置为输出
	//注册字符驱动程序
	//创建类
	//创建设备名/dev目录下的设备名
}
/**********   现在的module_init()中的函数任务  **********/
//注册时候的入口函数
static int __init led_drver_init(void)
{
	int ret = 0;
	// platform驱动注册到 Linux 内核
	ret = platform_driver_register(&led_driver);  //注意,这里是driver表示是驱动
	if(ret<0)
	{
		printk("platform_driver_register error \n");
		return ret;
	}
	printk("platform_driver_register ok \n");
	return ret;
}

初始化程序执行条件不同

(1)原来,我们初始化程序,只需要装载驱动程序即可。
(2)现在不一样了,我们需要注册相互匹配的设备程序和驱动程序,初始化程序probe函数才会执行。
(3)注意,设备程序和驱动程序的注册没有顺序。随便你先注册谁。

获取GPIO数量信息不同

(1)下面这里有些人可能对pdev有疑问,这个是啥玩意?这个东西很简单,pdev就是被匹配上的设备struct platform_device结构体指针。
(2)当驱动和设备匹配上之后,执行probe函数。而这个函数的传入值struct platform_device *pdev就是被匹配上的设备struct platform_device结构体指针。
(3)因为struct platform_device结构体的num_resources记录了资源结构体的数量。所以可以通过pdev -> num_resources获取GPIO数量。
(4)我们看韦东山老师的代码,会发现好长,好麻烦。一开始我也是这么想的,命名一条指令就可以解决,搞这么长干嘛?后面简单的思考了一下。代码些这么长,还是一定作用的。
(5)我们来看platform_get_resource()这个函数,就是用于返回struct platform_device结构体中资源resource中flags被标记为IORESOURCE_IRQ的GPIO的GPIO信息。当我们这个GPIO的flags被标记为IORESOURCE_IRQ,就会返回一个指针指向这里。
(6)如果我们的设备,有些GPIO的flags没有被标记为了IORESOURCE_IRQ,那么就不会被count统计在内。

/**********   原来获取GPIO数量  **********/
int count = sizeof(gpios)/sizeof(gpios[0]);  //统计有多少个GPIO
/**********   现在获取GPIO数量(作者的方法)  **********/
count = pdev -> num_resources;
/**********   现在获取GPIO数量(韦东山老师的方法)  **********/
while (1)
{
	/* 下面这9行,是用于统计有多少个GPIO的
	 * dev:一个指向 platform_device 结构的指针,表示要获取资源信息的设备。
	 * type:一个无符号整数,表示要获取的资源类型。在 Linux 内核中,资源类型使用常量来表示,
	        例如 IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源等。你可以根据需要选择适当的资源类型。
	 * num:一个无符号整数,表示要获取的资源的索引号。在一个设备中可能存在多个相同类型的资源,通过索引号可以区分它们。
	 * 返回值:返回一个指向 resource 结构的指针,表示获取到的资源信息。
	           resource 结构包含了资源的起始地址、大小等信息。如果没有找到指定的资源,函数将返回 NULL。
	*/
	led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, count);
	if (led_resource)
	{
		count++;
	}
	else
	{
		break;
	}
}

设备结构体gpios空间分配不同

(1)在原来的代码里面,我们的gpios被定义成了一个数组gpios[]。所以当我们在这个数组里面写入设备信息的时候,这个数组会自动改变空间大小。
(2)但是这里的设备信息不在驱动文件里面了,因此我们需要使用内存的动态分配了。我们知道GPIO有多少个之后,调用内存动态分配函数获取一个空间,然后把这个空间的首地址指针返回给gpios。
(3)需要注意的一点是,驱动的动态内存分配不能使用malloc,而是需要使用kmalloc。这个和驱动不能使用printf函数,只能使用printk函数是一个道理。

/**********   原来gpios空间分配  **********/
static struct gpio_desc gpios[] = {
    {131, "led0" },  //引脚编号,名字
};
/**********   现在gpios空间分配  **********/
static struct gpio_desc *gpios;   //描述gpio
/* 作用 :  kmalloc是Linux内核中的一个内存分配函数,用于在内核空间中动态分配内存。
 *        它类似于C语言中的malloc函数,但是在内核中使用kmalloc而不是 malloc,因为内核空间和用户空间有不同的内存管理机制。
 * size : 要分配的内存大小,以字节为单位。
 * flags : 分配内存时的标志,表示内存的类型和分配策略,是一个 gfp_t 类型的值。常常采用GFP_KERNEL
 *          GFP_KERNEL是内存分配的标志之一,它表示在内核中以普通的内核上下文进行内存分配。
 * 返回值 : 如果内存分配成功,返回指向分配内存区域的指针。如果内存分配失败(例如内存不足),返回NULL。
*/
gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL); 

设备结构体gpios硬件信息获取不同

(1)原来,我们直接向gpios[]数组里面写入硬件信息就可以了。
(2)因为现在我们将硬件信息写入了设备程序里面了,所以需要其他办法获得。从设备程序中获得硬件信息有两种方法。
<1>通过pedv指针直接获得硬件信息。
<2>通过platform_get_resource()函数获得。
(3)
<1>我们对比下面的代码会发现,led_resource和pdev->resource[i]是一个东西啊。
<2>可以这么说,但是还是有一定的区别。如果pdev->resource[i]的flags不是IORESOURCE_IRQ,那么就不会被提取出来成led_resource。

/**********   原来gpios获得硬件信息  **********/
static struct gpio_desc gpios[] = {
    {131, "led0" },  //引脚编号,名字
};
/**********   现在gpios获得硬件信息  **********/
//通过pedv指针直接获得硬件信息。
gpios[i].gpio = pdev->resource[i].start;
sprintf(gpios[i].name, "%s", pdev->resource[i].name);   //将platform_device.resource.name传递给gpios[i].name

//通过platform_get_resource()函数获得。
struct resource *led_resource;
led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, i);  //从节点里面取出第i项	
gpios[i].gpio = led_resource->start; 						   //将需要操作的IO号传递给gpios[i].gpio
sprintf(gpios[i].name, "%s", led_resource->name);   //将platform_device.resource.name传递给gpios[i].name


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

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

相关文章

Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包

Netty Bootstrap和ServerBootstrapFuture和ChannelFutureChannelSelectorNioEventLoop和NioEventLoopGroupByteBuf示例代码 Channel相关组件入站详情出站详情对象编解码ProtoBuf和ProtoStuffnetty实现群聊系统粘包和拆包TCP协议特点举个例子 Bootstrap和ServerBootstrap Boots…

【管理设计篇】聊聊分布式配置中心

为什么需要配置中心 对于一个软件系统来说&#xff0c;除了数据、代码&#xff0c;还有就是软件配置&#xff0c;比如操作系统、数据库配置、服务配置 端口 ip 、邮箱配置、中间件软件配置、启动参数配置等。如果说是一个小型项目的话&#xff0c;可以使用Spring Boot yml文件…

Nginx解决文件服务器文件名显示不全的问题

Nginx可以搭建Http文件服务器&#xff0c;但默认的搭建会长文件名显示不全&#xff0c;比如如下&#xff1a; 问题&#xff1a;显示不全&#xff0c;出现...&#xff0c;需要进行解决 这里使用重新编绎nginx的方式&#xff0c;见此文&#xff1a; https://unix.stackexchange…

CS5265国产Typec转HDMI音视频转换芯片可替代RTD2172

集睿致远/ASL推出的CS5265是一款高性能Type-C/DP1.4至HDMI2.0b转换器IC&#xff0c;设计用于将USB type c源或DP1.4源连接至HDMI2.0b接收器。CS5265集成了DP1.4兼容接收机和HDMI2.0b兼容发射机。此外&#xff0c;CC控制器还用于CC通信&#xff0c;以实现DP Alt模式。DP接口包括…

ARM 常见汇编指令学习 9 - 缓存管理指令 DC 与 IC

文章目录 ARM64 DC 与 IC 指令 上篇文章&#xff1a;ARM 常见汇编指令学习 8 - dsb sy 指令及 dsb 参数介绍 ARM64 DC 与 IC 指令 AArch64指令集中有两条关于缓存维护&#xff08;cache maintenance&#xff09;的指令&#xff0c;分别是IC和DC。 IC 是用于指令缓存操作&…

设备管理升级:揭秘设备健康管理的核心优势

随着工业企业迎来数字化转型的浪潮&#xff0c;设备管理在实现升级和卓越运营方面扮演着关键角色。传统的设备管理方式已经难以适应复杂多变的生产环境&#xff0c;因此设备健康管理作为数字化转型的核心优势应运而生。本文将深入探讨设备健康管理的核心优势&#xff0c;以揭示…

了解ai绘画软件哪个好,我分享这几款你看看

以前绘画是一项需要花费长时间学习的艺术&#xff0c;绘画创作需要耗费许多时间和人工成本。但人工智能的发展&#xff0c;让ai绘画工具成为学习画画、创作的另一种新形式。这些绘画工具仅仅通过输入文字描述就可以生成个性化的创作&#xff0c;帮助我们快速实现绘画创作的梦想…

使用ansible playbook编写lnmp架构

使用ansible playbook编写lnmp架构 - name: nginx playgather_facts: falsehosts: lnmpremote_user: roottasks: - name: stop firewalldservice: namefirewalld statestopped- name: syslinuxcommand: /usr/sbin/setenforce 0ignore_errors: true- name: nginx.repocopy: src/…

Spring基于注解管理bean及全注解开发

文章目录 spring概述Spring定义Spring核心Spring Framework的特点 基于注解管理bean依赖开启组件扫描使用注解定义Bean案例:Autowired注入属性注入set注入形参上注入只有一个构造函数&#xff0c;无注解Autowire注解和Qualifier注解联合 Resource注入Spring全注解开发 spring概…

信息系统项目管理的计算机基础知识

一、信息化发展 &#xff08;一&#xff09;信息与信息化 1、信息 信息是确定性的增加。单位为比特&#xff08;bit&#xff09;。 2、信息系统 信息系统是通过输入数据&#xff0c;然后进行加工处理&#xff0c;最后产生信息的系统。面向管理和支持生产是信息系统的显著特…

【PHP】简记问题:使用strtotime(‘-1 month‘, time)获取上个月第一天时间戳出错

发生场景 在7月31号是查看统计上个月订单购买总金额&#xff0c;查询结果为0 $preMonthStart strtotime(date(Ym01, strtotime("-1 month"))); $curMonthStart strtotime(date(Ym01)); # 统计上月份实际订单金额 $sql "SELECT count(money) FROM orders WH…

Flowable-服务-服务任务

文章目录 定义图形标记XML内容界面操作 定义 服务任务&#xff08;Service Task&#xff09;是一个自动化任务&#xff0c;无须人为参与&#xff0c;一般被用作调用服务。当流程执行到服务任务 时&#xff0c;可以自动执行编写的 Java 程序实现自定义的业务逻辑&#xff0c;完…

SpringMVC中的相关注解

文章目录 RequestMappingGetMappingPostMappingResponseBody传参RequestParamPathVariableRequestBodyRequestPartCookieValueSessionAttributeRequestHeader总结 RequestMapping 支持 GET/POST 类型的请求。&#xff08;&#xff09;内写请求的访问地址。 即可以修饰类也可以修…

springboot2实现图片文件上传与mysql存储路径并回显

环境介绍 技术栈 springbootmybatismysql 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis 2.3.1 springboot是基于spring 4.0&#xff0c;springboot2是基于spring5.0,springboot2由pivotal公司在2018发布,这个框架主要用来…

Pytorch深度学习-----神经网络之池化层用法详解及其最大池化的使用

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

一些类型推导相关的功能(C++)

目录 auto关键的新用法&#xff08;C11起&#xff09; 背景介绍 用法示例 注意事项 typeid运算符 type_info类 typeid的用法 decltype运算符 用法阐述 用法示例 用法建议 function类模板 用法阐述 用法示例 function较函数指针的优势 std::function和decltype的…

苍穹外卖day10——订单状态定时处理(Spring Task)、来单提醒和客户催单(WebSocket)

预期效果 对于超时没处理的需要定时程序处理。基于SpringTask实现。 来单提醒和客户催单。基于WebSocket实现。 Spring Task 介绍 Cron表达式 周几通常不能和日一起指定。 cron表达式在线生成器 在线Cron表达式生成器 入门案例 创建定时任务类 /*** 定义定时任务类*/ Slf4j…

OBS推流工具介绍及桌面录屏推流功能实现

OBS推流工具介绍及桌面录屏推流功能实现 文章目录 OBS推流工具介绍及桌面录屏推流功能实现1 OBS工具介绍2 OBS工具安装及简单使用2.1 安装步骤2.2 简单使用介绍 3 OBS实现桌面录屏推流工具实现4 总结 流媒体开发工程中&#xff0c;我们除了使用ffmpeg等工具辅助调试外&#xff…

《工具箱-数据库相关》Dbeaver数据导入“CSV格式“、“Txt格式“导入配置

《工具箱-数据库相关》DBeaver线下数据导入数据库表 Dbeaver数据导入"CSV格式"、"Txt格式"导入配置 使用CSV、Txt导入的时候&#xff0c;数据格式不同&#xff0c;在导入数据的时候&#xff0c;要根据数据编码样式设置不同的配置。 一、Txt格式导入 1.1 …

汇编语言基础知识

目录 前言&#xff1a; 汇编语言的产生 汇编语言的组成 内存 指令和数据 cpu对内存的读写操作 地址总线 数据总线 控制总线 内存地址空间 前言&#xff1a; 汇编语言是直接在硬件之上工作的 编程语言&#xff0c;我们首先了解硬件系统的机构&#xff0c;才能有效地应用…