实现一个简单的内核驱动

  此次实现基于MTK8173平台Android 6.0 实现。

  此次使用一个虚拟的硬件设备,这个设备只有一个4字节的寄存器,它可读可写,移植的角度来实现我们将其命名为welcome。在kernel部分要实现一个驱动主要分为,添加编译选择选项,实现对应的相关驱动,添加进入内核编译。

  1,、添加编译选项

    此项选择的源码存放的路径为 kernel-3.18\drivers\misc\mediatek目录

  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\n",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\n", 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\n", 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\n", 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 \n",Kernel_BugLevel);
    DBUG_I("DBUG_I welcome_write loglevel = %d \n",Kernel_BugLevel);
    DBUG_D("DBUG_D welcome_write loglevel = %d \n",Kernel_BugLevel);
    if(len > PAGE_SIZE){
        DBUG_E("The buff is too large:%lu.\n",len);
        return -EFAULT;
    }
    page = (char*)__get_free_page(GFP_KERNEL);
    if(!page){
        DBUG_E("Failed to alloc page. \n");
        return -ENOMEM;
    }
    //先把用户提供的缓冲区的值拷贝到内核缓冲区
    if(copy_from_user(page,buff,len)){
        DBUG_E("Failed to copy buff from user.\n");
        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.\n");
    //动态分配主设备号 和从设备号
    err = alloc_chrdev_region(&devno,0,1,WELCOME_DEVICE_NODE_NAME);
    if(err < 0){
        printk(KERN_ALERT"Failed to alloc char dev region.\n");
        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.\n");
        goto unregister;
    }
    //初始化设备
    err = __welcome_setup_dev(welcome_dev);
    if(err){
        DBUG_E("Failed to setup dev :%d .\n",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.\n");
        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.\n");
        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. \n");
        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. \n");
        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 . \n");

    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.\n");
    //删除/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等级