实现一个简单的内核驱动
此次实现基于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文件
+= 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 文件 添加笔译规则
+= 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等级