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

目录

驱动分层的思路

驱动分层的实现

上层驱动的实现

次设备号的使用

上层驱动代码

底层驱动的实现

底层驱动c文件的实现

底层驱动头文件实现

应用层文件的实现


驱动分层的思路

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


驱动分层的实现

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

上层驱动的实现

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

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

包含对应的定义的头文件

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

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

次设备号的使用

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

上层驱动代码

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

cpp 复制代码
#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/led
	led_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文件的实现

cpp 复制代码
#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;
}

底层驱动头文件实现

cpp 复制代码
#ifndef	__FILE_OPERATION_H
#define __FILE_OPERATION_H



struct 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

应用层文件的实现

cpp 复制代码
#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;
	
	//open
	fd = open(argv[1] , O_RDWR);
	if( fd < 0 )
	{
		printf("open %s file \n",argv[1]);
		return -1;
	}
	//write
	if( 0 == strcmp(argv[2] , "on") )
	{
		status = 1;
		write(fd , &status , 1);
	}
	else
	{
		status = 0;
		write(fd , &status , 1);
	}

	return 1;

}
相关推荐
hhhhhhh_hhhhhh_1 小时前
rk3568制冷项目驱动开发流程汇总(只适用于部分模块CIF DVP等,自用)
驱动开发
日记跟新中1 小时前
Ubuntu20.04 修改root密码
linux·运维·服务器
码农君莫笑1 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
BUG 4041 小时前
Linux——Shell
linux·运维·服务器
大霞上仙2 小时前
Linux 多命令执行
linux·运维·服务器
晨欣2 小时前
Kibana:LINUX_X86_64 和 DEB_X86_64两种可选下载方式的区别
linux·运维·服务器
AI青年志2 小时前
【服务器】linux服务器管理员查看用户使用内存情况
linux·运维·服务器
dessler3 小时前
Docker-run命令详细讲解
linux·运维·后端·docker
PyAIGCMaster3 小时前
ubuntu装P104驱动
linux·运维·ubuntu
奈何不吃鱼3 小时前
【Linux】ubuntu依赖安装的各种问题汇总
linux·运维·服务器