[Linux_IMX6ULL驱动开发]-驱动的分层及实现

目录

驱动分层的思路

驱动分层的实现

上层驱动的实现

次设备号的使用

上层驱动代码

底层驱动的实现

底层驱动c文件的实现

底层驱动头文件实现

应用层文件的实现


驱动分层的思路

在上一篇文章中,博主实现了通过寄存器控制引脚,以此来达到控制LED灯亮灭。但是其实,把对寄存器的映射等步骤在驱动文件中实现并不是很好的实现,因为不同的开发板,引脚、寄存器都是不同的,为了达到兼容的目的,我们最好实现如下分层,把驱动分为上层和下层,驱动的上层完成file_operation结构体注册、class类、devices设备的注册等。下层为具体的硬件实现,包括完成寄存器的映射、初始化以及硬件控制等。


驱动分层的实现

下图是实现驱动分层的具体实现思路

上层驱动的实现

在上层驱动中,我们主要完成的是,完成file_operation结构体的构造和注册、class类、device设备的创建(退出对应的函数需要完成上述三个的注销,devices一定要先于calss类销毁,否则会造成内核异常操控空指针,驱动会崩溃

上层驱动较为重要的步骤是:

        包含对应的定义的头文件

        获取对应的结构体,以此能够调用底层驱动实现的初始化、操控等函数

        在对应要填充到file_operation结构体的函数中,实现对应的初始化、操控等步骤

次设备号的使用

假如我们这里的需求是控制两盏LED灯,那么我们就需要用到次设备号来区分这两个LED灯,次设备号不同,代表我们需要的灯就不同,那么所需要初始化的引脚和操控的引脚也不同,在这里我们传入不同的次设备号,在驱动底层我们通过传入的次设备号来判断到底是进行哪个引脚的初始化。

上层驱动代码

注意,该代码中没有进行映射内存的释放,没有在file_operation结构体中定义释放函数,所以应用层并不能通过close来关闭此设备,无法释放映射的物理地址

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>#include "file_operation.h"/* 不同板子的操作函数结构体 */
struct led_operation* p_ledopr_func;/* 确定主设备号 */
static int major = 0;
/* 缓存数组 */
static char kernal_buf[1024];
/* 节点的定义 */
static struct class *led_class;/* 读多少的宏定义 */
#define data_num(a,b) ( (a) < (b) ? (a) : (b) )/* 定义函数入口地址 */
static int led_drv_open (struct inode *node, struct file *file)
{int minor = iminor(node);printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 通过次设备号初始化灯 */p_ledopr_func->init(minor);return 0;
}static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int return_size;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_to_user(buf, kernal_buf, data_num(1024,size));return return_size;}
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int return_size;char status;/* 根据file结构体获得inode,然后获取次设备号 */struct inode* inode = file_inode(file);int minor = iminor(inode);printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return_size = copy_from_user(&status, buf, 1);/* 根据次设备号和status控制LED */p_ledopr_func->ctl(minor,status);return 1;}
static int led_drv_rease (struct inode *node, struct file *file)
{printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);return 0;
}/* 定义文件结构体读,写,打开,卸载
*/
static struct file_operations led_driver = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_rease,
};/* 把结构体注册到内核为了能够把该结构体注册到内核需要init函数
*/
static int __init led_init(void)
{int err;int i;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 注册结构体到内核后,返回主设备号 */major = register_chrdev(0, "myled", &led_driver);//创建节点 /dev/ledled_class = class_create(THIS_MODULE, "myled_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)){printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 创建失败的话摧毁内核中的led结构体 */unregister_chrdev( major, "myled");return -1;}p_ledopr_func = get_board_led_operation();/* 创建了节点后,需要创建设备 *//* 因为LED不止一个,需要多个次设备号 */for( i = 0 ; i < p_ledopr_func->num ; i++ )device_create(led_class, NULL, MKDEV(major, i), NULL, "myled%d" , i);return 0;
}/* 有注册函数就有卸载函数 */
static void __exit led_exit(void)
{int i;printk("%s %s %d \n",__FILE__,__FUNCTION__,__LINE__);/* 把device卸载 */for( i = 0 ; i < p_ledopr_func->num ; i++ )device_destroy(led_class, MKDEV(major, i));/* 把class卸载 */class_destroy(led_class);/* 把file_operation从内核中卸载 */unregister_chrdev( major, "myled");}/* 需要用某些宏表示上述两个函数分别是入口函数和出口函数 */
module_init(led_init);
module_exit(led_exit);/* 遵循GPL协议 */
MODULE_LICENSE("GPL");

底层驱动的实现

底层驱动的实现主要就是通过上层驱动传递下来的次设备号来判断,到底是初始化哪些寄存器,应该映射哪些硬件地址进行操作。同时还需把操控硬件的结构体返回给上层驱动。

底层驱动c文件的实现

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>
#include "file_operation.h"/* 一个灯为 GPIO5组的03     *//* 使能时钟 */
static volatile unsigned int* CCM_CCGR1 = NULL;
/* 引脚复用 */
static volatile unsigned int* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
/* 设置为输出模式 */
static volatile unsigned int* GPIO5_GDIR = NULL;
/* 设置输出高电平/低电平 */
static volatile unsigned int* GPIO5_DR = NULL;static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */	   
{/* 在初始化函数中,实现初始化、复用引脚设置为GPIO模式、设置为输出模式 *//* 通过次设备号判断操控哪个LED */if( 0 == which ){/* 只映射一次 */if( NULL == CCM_CCGR1 ){CCM_CCGR1 = ioremap(0x20C406C , 4 );IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014 , 4 );GPIO5_GDIR = ioremap(0x020AC004, 4);GPIO5_DR = ioremap(0x020AC000, 4);	}*CCM_CCGR1 |= (3 << 30);/* 因为引脚复用为GPIO为0101,包含0,所以先清零,防止原本的位包含1,导致|1后,还会是1 */*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf);*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 5;*GPIO5_GDIR |= (1 << 3);}/* 初始化另一盏灯 */else {}	printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);return 0;
}static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");if( 0 == which ){/* 低电平点灯 */if( 1 == status )*GPIO5_DR &= ~(1 << 3);else *GPIO5_DR |= (1 << 3);}/* 打开另一盏灯 */else{}return 0;
}static struct led_operation board_demo_led_opr = {.num = 1,.init = board_demo_led_init,.ctl  = board_demo_led_ctl,
};struct led_operation *get_board_led_operation(void)
{return &board_demo_led_opr;
}

底层驱动头文件实现

#ifndef	__FILE_OPERATION_H
#define __FILE_OPERATION_Hstruct led_operation{int num;int (*init)(int which);/* 初始化哪个LED */int(*ctl)(int which , char status);/* 控制哪个LED,以及控制的状态 */};struct led_operation* get_board_led_operation(void);#endif

应用层文件的实现

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> int main(int argc, char** argv)
{char status = 0;if( argc != 3 ){printf("./ledtest /dev/myled on \n");printf("./ledtest /dev/myled off \n");return -1;}int fd;//openfd = open(argv[1] , O_RDWR);if( fd < 0 ){printf("open %s file \n",argv[1]);return -1;}//writeif( 0 == strcmp(argv[2] , "on") ){status = 1;write(fd , &status , 1);}else{status = 0;write(fd , &status , 1);}return 1;}

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

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

相关文章

华为OD技术面试-爬楼计数(动态规划)

背景 2024-03-16 华为od 技术面试&#xff0c;记录题目和模型 题目 分析 入门级的 动态规划算法&#xff0c;直接写就行了 缓存递归 代码 DZs {} def climbStairs(n):if n<0:return 0if DZs.get(n, 0)>0 :return DZs[n]if n2:jf 2elif n1:jf 1;else:jf1 climb…

R语言绘制一次和二次相关性热图

在数据探索的过程中&#xff0c;我们往往会对数据与数据的相关性进行分析&#xff0c;例如我们常用的corrplot包&#xff0c;或者psych包中的corr.test函数&#xff0c;对两两变量间的相关性进行分析。我们常常会看到这样的相关性热图&#xff1a; 但有时变量间的关系并非线性…

systemctl start docker报错(code=exited, status=1/FAILURE)

运行systemctl start docker报错内容如下: 输入systemctl status docker.service显示以下内容&#xff1a; 本次启动不起来与docker服务无关 具体解决问题是修改 /etc/docker/daemon.json&#xff0c;vim /etc/docker/daemon.json # 添加如下内容 {"registry-mirrors&qu…

【2024】使用Rancher管理k8s集群和创建k8s集群

Rancher管理k8s集群及创建k8s集群。 Rancher版本为:2.8.2目录 rancher管理k8s集群rancher创建k8s集群rancher管理k8s集群 使用rancher管理已经存在的k8s集群。 本部分内容需要自行准备好k8s集群及rancher平台,部署请看本人其他文章 。 登录到rancher平台后,点击集群管理,…

千视电子携NDI 6前沿技术,亮相北京CCBN展呈现轻量化媒体解决方案

千视携NDI 6技术闪耀2024 CCBN展会&#xff0c;呈现轻量化媒体解决方案 2024年4月24日至26日&#xff0c;北京首钢会展中心将举办第三十届中国国际广播电视网络技术展览会&#xff08;CCBN2024&#xff09;。这是中国广播电视行业的一项重要盛会&#xff0c;将有国内外超600家…

51-基于GitHubActions的CI实战

在Go项目开发中&#xff0c;我们要频繁地执行静态代码检查、测试、编译、构建等操作。如果每一步我们都手动执行&#xff0c;效率低不说&#xff0c;还容易出错。所以&#xff0c;我们通常借助CI系统来自动化执行这些操作。 当前业界有很多优秀的CI系统可供选择&#xff0c;例…

4-云原生监控体系-Grafana-基本使用

1. 介绍 使用Grafana&#xff0c;您可以通过漂亮、灵活的仪表板创建、探索和共享所有数据。查询、可视化、提醒和理解您的数据&#xff0c;无论数据存储在何处。 图片出处&#xff1a; https://grafana.com/grafana/ 官方网站 2. 界面介绍 Connections 可以配置数据源&#x…

Java后端基础知识(String类型)

String类的创建方式 String的特点 1.引用数据类型 2.是final类&#xff0c;一旦创建内容不可修改 3.String类对象相等的判断用equals&#xff08;&#xff09;方法完成&#xff0c;是判断地址数值 String的创建方式 1.直接创建 String str"hello";注意&#xff…

pom.xml爆红怎么处理?

其实很简单&#xff0c;右键点击pom.xml 点击Add as Maven Project 即可&#xff0c;等待加载完毕就行啦&#xff0c;会变成蓝色M标志&#xff0c;表示成功

个人博客系统项目(SpringBoot+Linux部署上线)

在学完SpringBoot框架、MyBatis后&#xff0c;直接开始做第一个项目&#xff1a;博客系统 首先&#xff0c;该博客系统包含核心功能有&#xff1a; 一、登录、注册、退出登录功能。 二、没有登陆前可以查看博客首页以及博客展示的分页处理&#xff0c;以及点击查看博客可以…

计算机网络知识等汇总补充

计算机网络知识汇总补充 一、四次挥手1、为什么TCP要等待2MSL2、如果说一个系统中&#xff0c;有大量的time_wait和close_wait&#xff0c;会是什么原因&#xff1f; 二、你是怎么解决粘包问题&#xff1f;三、你觉得哪些场景适合redis四、redis的持久化策略五、你会怎么保证my…

U盘怎么加密?U盘加密的方法有哪些?

U盘作为一种便携式的存储设备&#xff0c;广泛应用于日常生活和工作中。但由于其易于携带和使用的特性&#xff0c;U盘中的数据也面临着被未经授权访问的风险。因此&#xff0c;对U盘进行加密成为了保护数据安全的重要措施。本文将介绍几种常见的U盘加密方法&#xff0c;帮助用…