目录
驱动分层的思路
在上一篇文章中,博主实现了通过寄存器控制引脚,以此来达到控制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; }