实现一个简单的内核驱动
此次实现基于MTK8173平台Android 6.0 实现。
此次使用一个虚拟的硬件设备,这个设备只有一个4字节的寄存器,它可读可写,移植的角度来实现我们将其命名为welcome。在kernel部分要实现一个驱动主要分为,添加编译选择选项,实现对应的相关驱动,添加进入内核编译。
1,、添加编译选项
此项选择的源码存放的路径为 kernel-3.18driversmiscmediatek目录
1.1 修改Kconfig文件 再合适的位置添加
config MTK_WELCOME bool "CONFIG_MTK_WELCOME" default n help just for driver test.
1.2 修改同目录下的makefile文件
添加:obj-$(CONFIG_MTK_WELCOME) += welcome/
1.3 新建目录welcome 用于单独存放此次的驱动代码,方便管理
2、实现对应的welcome驱动
2.1 添加相应的头文件 welcome.h
#ifndef _WELCOME_KERNEL_H_ #define _WELCOME_KERNEL_H_
//选择是否需要时会用信号量 用于访问控制
//#define CONFIG_WELCOME_SEM_MUTEX
#include <linux/cdev.h> #include <linux/semaphore.h> // /sys/class/welcomc/welcomf #define WELCOME_DEVICE_NODE_NAME "welcom" // /dev/WELCOME_DEVICE_FILE_NAME #define WELCOME_DEVICE_FILE_NAME "welcomf" // /proc/WELCOME_DEVICE_PROC_NAME #define WELCOME_DEVICE_PROC_NAME "welcomp" // /sys/class/WELCOME_DEVICE_CLASS_NAME #define WELCOME_DEVICE_CLASS_NAME "welcomc" #define CONFIG_WELCOME_PROC //定义私有结构体 struct welcome_kernel_dev{ int val; #ifdef CONFIG_WELCOME_SEM_MUTEX struct semaphore sem; #endif struct cdev dev; }; #endif//_WELCOME_KERNEL_H_
2.2 新建welcome.c
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/device.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <linux/fs.h> //#include <linux/ioctl.h> #include "bug_log.h" #include "welcome.h" #ifdef CONFIG_WELCOME_PROC #include <linux/proc_fs.h> #endif // 定义主从设备号变量 static int welcome_major = 0; static int welcome_minor = 0; //设备类别和设备变量 static struct class* welcome_class = NULL; static struct welcome_kernel_dev* welcome_dev = NULL; // 传统的设备文件操作方法 static int welcome_open(struct inode* inode,struct file* filp); static int welcome_release(struct inode* inode,struct file* filp); static ssize_t welcome_read(struct file* filp,char __user *buf,size_t count,loff_t* f_pos); static ssize_t welcome_write(struct file* filp,const char __user *buf,size_t count,loff_t* f_pos); //设备文件操作方法表 static struct file_operations welcome_fops = { .owner = THIS_MODULE, .open = welcome_open, .release = welcome_release, .read = welcome_read, .write = welcome_write, }; // 属性访问设置 static ssize_t welcome_val_show(struct device* dev,struct device_attribute* attr,char* buf); static ssize_t welcome_val_store(struct device* dev,struct device_attribute* attr,const char* buf,size_t count); /* 定义两个内部使用的访问 val值的方法 */ //读寄存器val 的值到缓冲区buf中 static ssize_t _welcome_get_val(struct welcome_kernel_dev* dev, char* buf){ int val = 0; int ret = 0; //同步访问 #ifdef CONFIG_WELCOME_SEM_MUTEX if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; } #endif val = dev->val; #ifdef CONFIG_WELCOME_SEM_MUTEX up(&(dev->sem)); #endif sprintf(buf,"%d ",val); ret = 2;//strlen(buf) + 1; //ret 决定输出字符个数 return ret; } //把缓冲区buf的值写到寄存器val中 static ssize_t _welcome_set_val(struct welcome_kernel_dev* dev, const char* buf, size_t count) { int val = 0; //将字符串转换成数字 val = simple_strtol(buf,NULL,10); //同步访问 #ifdef CONFIG_WELCOME_SEM_MUTEX if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; } #endif dev->val = val; #ifdef CONFIG_WELCOME_SEM_MUTEX up(&(dev->sem)); #endif return count; } //读取设备属性 static ssize_t welcome_val_show(struct device* dev,struct device_attribute* attr,char* buf){ #if 1 //打印数字方案 struct welcome_kernel_dev* hdev = welcome_dev;//(struct welcome_kernel_dev*)dev_get_drvdata(dev); return _welcome_get_val(hdev,buf); #else //打印字符串方案 ssize_t ret = 0; unsigned int uc_reg_value; uc_reg_value = welcome_dev->val; sprintf(buf, "gt5x , firmware id : 0x%02x ", uc_reg_value); ret = strlen(buf) + 1; return ret; #endif } //写设备属性 static ssize_t welcome_val_store(struct device* dev,struct device_attribute* attr,const char* buf,size_t count){ struct welcome_kernel_dev* hdev = welcome_dev; return _welcome_set_val(hdev,buf,count); } //定义设备属性不能是666 会报bug.h 错误 static DEVICE_ATTR(val,S_IRUGO | S_IWUSR,welcome_val_show,welcome_val_store); #if ENABLE_BUG_LOG //读取设备属性 static ssize_t bugLevel_val_show(struct device* dev,struct device_attribute* attr,char* buf){ //打印字符串方案 ssize_t ret = 0; sprintf(buf, " DBUG_E:3 DBUG_D:2 DBUG_I:1: Kernel_BugLevel=%d ", Kernel_BugLevel); ret = strlen(buf) + 1; return ret; } //写设备属性 static ssize_t bugLevel_val_store(struct device* dev,struct device_attribute* attr,const char* buf,size_t count){ int val = 0; //将字符串转换成数字 Kernel_BugLevel = simple_strtol(buf,NULL,10); return count; } //定义设备属性不能是666 会报bug.h 错误 static DEVICE_ATTR(buglevel,S_IRUGO | S_IWUSR,bugLevel_val_show,bugLevel_val_store); #endif //打开设备方法 static int welcome_open(struct inode* inode,struct file* filp){ struct welcome_kernel_dev* dev; //将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用 dev = container_of(inode->i_cdev,struct welcome_kernel_dev,dev); filp->private_data = dev; return 0; } //设备文件释放时调用 static int welcome_release(struct inode* inode,struct file* filp){ return 0; } //读取设备文件寄存器val 的值 static ssize_t welcome_read(struct file* filp,char __user *buf,size_t count,loff_t* f_pos){ ssize_t err = 0; struct welcome_kernel_dev* dev = filp->private_data; //同步访问 #ifdef CONFIG_WELCOME_SEM_MUTEX if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; } #endif if(count < sizeof(dev->val)){ goto out; } //将寄存器val的值拷贝到用户提供的缓冲区 if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){ err = -EFAULT; goto out; } err = sizeof(dev->val); out: #ifdef CONFIG_WELCOME_SEM_MUTEX up(&(dev->sem)); #endif return err; } //写设备的寄存器val static ssize_t welcome_write(struct file* filp,const char __user *buf,size_t count,loff_t* f_pos){ struct welcome_kernel_dev* dev = filp->private_data; ssize_t err = 0; //同步访问 #ifdef CONFIG_WELCOME_SEM_MUTEX if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; } #endif if(count != sizeof(dev->val)){ goto out; } // 将用户提供的缓冲区写到设备寄存器 if(copy_from_user(&(dev->val),buf,count)){ err = -EFAULT; goto out; } //返回数据大小 err = sizeof(dev->val); out: #ifdef CONFIG_WELCOME_SEM_MUTEX up(&(dev->sem)); #endif return err; } /*********************proc 文件系统访问方法*********************/ //读取设备寄存器val 的值 保存在page 缓冲区 #ifdef CONFIG_WELCOME_PROC static ssize_t welcome_proc_read(struct file* filp,char __user *buf,size_t count,loff_t* f_pos){ ssize_t err = 0; char* page = NULL; int val = welcome_dev->val; struct welcome_kernel_dev* dev = welcome_dev; //同步访问 #ifdef CONFIG_WELCOME_SEM_MUTEX if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; } #endif if(count < sizeof(dev->val)){ goto out; } //将寄存器val的值拷贝到用户提供的缓冲区 if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){ err = -EFAULT; goto out; } err = sizeof(dev->val); out: #ifdef CONFIG_WELCOME_SEM_MUTEX up(&(dev->sem)); #endif return snprintf(buf, sizeof(val)+1, "%d ", val); } //把缓冲区的值buff 保存到设备寄存器val 中 static ssize_t welcome_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) { int err = 0; char* page = NULL; DBUG_E("DBUG_E welcome_write loglevel = %d ",Kernel_BugLevel); DBUG_I("DBUG_I welcome_write loglevel = %d ",Kernel_BugLevel); DBUG_D("DBUG_D welcome_write loglevel = %d ",Kernel_BugLevel); if(len > PAGE_SIZE){ DBUG_E("The buff is too large:%lu. ",len); return -EFAULT; } page = (char*)__get_free_page(GFP_KERNEL); if(!page){ DBUG_E("Failed to alloc page. "); return -ENOMEM; } //先把用户提供的缓冲区的值拷贝到内核缓冲区 if(copy_from_user(page,buff,len)){ DBUG_E("Failed to copy buff from user. "); err = -EFAULT; goto out; } err = _welcome_set_val(welcome_dev,page,len); out: free_page((unsigned long) page); return err; } static int welcome_proc_open(struct inode *inode, struct file *file) { return single_open(file, NULL, NULL); } static const struct file_operations welcome_proc_fops = { // .open = welcome_proc_open, .read = welcome_proc_read, .write = welcome_proc_write, }; // 创建/proc/welcome 文件 static void welcome_create_proc(void){ struct proc_dir_entry* entry ; //entry = create_proc_entry(WELCOME_DEVICE_PROC_NAME,0,NULL); entry = proc_create(WELCOME_DEVICE_PROC_NAME, 0666, NULL,&welcome_proc_fops); } //删除/proc/welcome 文件 static void welcome_remove_proc(void){ remove_proc_entry(WELCOME_DEVICE_PROC_NAME,NULL); } #endif /*********************proc 文件系统访问方法*********************/ //定义模块加载和卸载方法 // 初始化设备 static int __welcome_setup_dev(struct welcome_kernel_dev* dev){ int err; dev_t devno = MKDEV(welcome_major,welcome_minor); memset(dev,0,sizeof(struct welcome_kernel_dev)); cdev_init(&(dev->dev),&welcome_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &welcome_fops; //注册字符设备 err = cdev_add(&(dev->dev),devno,1); if(err){ return err; } //初始化信号量 和寄存器val #ifdef CONFIG_WELCOME_SEM_MUTEX //init_MUTEX(&(dev->sem)); 编译报错 新内核没有该API sema_init(&(dev->sem),1); #endif dev->val = 0; return 0; } //模块加载方法 static int __init welcome_init(void){ int err = -1; dev_t devno = 0; struct device* device_temp = NULL; DBUG_I("Initializing welcome device. "); //动态分配主设备号 和从设备号 err = alloc_chrdev_region(&devno,0,1,WELCOME_DEVICE_NODE_NAME); if(err < 0){ printk(KERN_ALERT"Failed to alloc char dev region. "); goto fail; } welcome_major = MAJOR(devno); welcome_minor = MINOR(devno); //分配welcome 设备机构体变量 welcome_dev = kmalloc(sizeof(struct welcome_kernel_dev),GFP_KERNEL); if(!welcome_dev){ err = -ENOMEM; DBUG_E("Failed to alloc welcome_dev. "); goto unregister; } //初始化设备 err = __welcome_setup_dev(welcome_dev); if(err){ DBUG_E("Failed to setup dev :%d . ",err); goto cleanup; } //在/sys/class 目录下创建设备类别目录welcome welcome_class = class_create(THIS_MODULE,WELCOME_DEVICE_CLASS_NAME); if(IS_ERR(welcome_class)){ err = PTR_ERR(welcome_class); DBUG_E("Failed to create welcome class. "); goto destroy_cdev; } //在/dev 目录和/sys/class/welcome 目录下分别创建设备文件welcome //device_temp = device_create(welcome_class,NULL,0,NULL,WELCOME_DEVICE_FILE_NAME);// 不会在dev目录下创建节点 device_temp = device_create(welcome_class,NULL,devno,NULL,WELCOME_DEVICE_FILE_NAME); if(IS_ERR(device_temp)){ err = PTR_ERR(device_temp); DBUG_E("Failed to create welcome device. "); goto destroy_class; } //在/sys/class/welcome/welcome 目录下创建属性文件val err =device_create_file(device_temp,&dev_attr_val); if(err < 0){ DBUG_E("Failed to create attribute val. "); goto destroy_device; } #if ENABLE_BUG_LOG err =device_create_file(device_temp,&dev_attr_buglevel); if(err < 0){ DBUG_E("Failed to create attribute buglevel. "); goto destroy_device; } #endif dev_set_drvdata(device_temp,NULL); //device_temp->driver_data =(void*) welcome_dev; //创建/proc/welcome 文件 #ifdef CONFIG_WELCOME_PROC welcome_create_proc(); #endif DBUG_I("Succed to inintialize welcome device . "); return 0;//正常退出 destroy_device: device_destroy(welcome_class,devno); destroy_class: class_destroy(welcome_class); destroy_cdev: cdev_del(&(welcome_dev->dev)); cleanup: kfree(welcome_dev); unregister: //释放设备号 unregister_chrdev_region(devno,1); fail: return err; } //模块卸载方法 static void __exit welcome_exit(void){ dev_t devno = MKDEV(welcome_major,welcome_minor); DBUG_I("Destroy hello device. "); //删除/proc/welcome 文件 #ifdef CONFIG_WELCOME_PROC welcome_remove_proc(); #endif //销毁设备类别和设备 if(welcome_class){ device_destroy(welcome_class,devno); class_destroy(welcome_class); } //删除字符设备和释放设备内存 if(welcome_dev){ cdev_del(&(welcome_dev->dev)); kfree(welcome_dev); } //释放设备号 unregister_chrdev_region(devno,1); } MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Weclcome Kernel Driver"); module_init(welcome_init); module_exit(welcome_exit);
2.3 此次驱动的打印信息采用分层打印的思想在 sys 下的节点控制打印登机 添加 bug_log.h
#ifndef __BUG_KERNEL_LOG_H__ #define __BUG_KERNEL_LOG_H__ //atlas 是否使能log enable:1 disable:0 #define ENABLE_BUG_LOG 1 /** DBUG_E:3 DBUG_D:2 DBUG_I:1**/ #if ENABLE_BUG_LOG unsigned char Kernel_BugLevel=4; // 注意if 后面的行数不带{} #define DBUG_E(fmt, args...) do { if (Kernel_BugLevel >= 3) printk(KERN_ALERT fmt, ##args); } while (0) #define DBUG_D(fmt, args...) do { if (Kernel_BugLevel >= 2) printk(KERN_ALERT fmt, ##args); } while (0) #define DBUG_I(fmt, args...) do { if (Kernel_BugLevel >= 1) printk(KERN_ALERT fmt, ##args); } while (0) #else #define DBUG_E(fmt, args...) #define DBUG_D(fmt, args...) #define DBUG_I(fmt, args...) #endif #if 0 //kernel 对应的printk 等级 #define KERN_EMERG "<0>" /* system is unusable */ #define KERN_ALERT "<1>" /* action must be taken immediately */ #define KERN_CRIT "<2>" /* critical conditions */ #define KERN_ERR "<3>" /* error conditions */ #define KERN_WARNING "<4>" /* warning conditions */ #define KERN_NOTICE "<5>" /* normal but significant condition */ #define KERN_INFO "<6>" /* informational */ #define KERN_DEBUG "<7>" /* debug-level messages */ #endif #endif
使能 ENABLE_BUG_LOG 觉得是否参与编译
Kernel_BugLevel 设置默认log等级
2.4 在welcome目录下添加Makefile 文件 添加笔译规则
obj-$(CONFIG_MTK_WELCOME) += welcome.o
如果还有其他c文件加入在后面添加即可。
3、修改对应的deconfig文件,让文件参与编译,将驱动编译近内核 添加
CONFIG_MTK_WELCOME=y
4、编译内核 make bootimage 然后下载入手机
最好是同时修改掉对应节点的权限,否则将没有权限访问对应的节点,找到对应的rc文件 添加类似 chmod 666 /dev/welcomf 即可
===============================================================
自此内核驱动编写完成 会在相应的目录下生存如下文件:
/sys/class/welcomc/welcomf
/dev/welcomf
/proc/welcomp
/sys/class/welcomc
在/sys/class/welcomc/welcomf val 节点可以直接操作查看相应的值
在/sys/class/welcomc/welcomf buglevel 节点可以修改对应的log等级