编写按键驱动的方法很多,这里我仅仅列举用中断法加上简单地字符设备注册来编写,并没有采用misc设备来注册。(这里的注册函数都是相对古老,以后不推荐使用!)首先编写一个设备驱动程序头文件先定义了,这很容易,照搬别人的就行了。接着确定你的设备驱动程序会用到的数据结构,这里会用到一个重要的数据结构,struct button_irqs,用来表征按键的状态以及按键的标识。接着确定file_operations里会用到的函数。记得编写初始化以及退出函数。分析驱动时时第一步便是从初始化函数开始然后如果有中断的话先分析中断然后才是操作函数。确定这两个后开始按照你所熟知的原理进行编写驱动。
先列出该驱动程序会用到一些重要函数以及作用:
void s3c2410_gpio_cfgpin(unsigned int pin,unsigned int function):设置引脚功能为输入、输出、特殊功能等
request_irq(unsigned int irq, irq_handler_thandler, unsigned long flags,constchar *name, void *dev):
申请中断函数,其中参数作用如下:
unsigned int irq :请求的中断号
irqreturn_t (*handler) :安装的处理函数指针。
unsigned long flags :一个与中断管理相关的位掩码选项。
const char *dev_name :传递给 request_irq 的字符串,用来在/proc/interrupts 来显示中断的拥有者。
void *dev_id :用于共享中断信号线的指针。它是唯一的标识,在中断线空闲时可以使用它,驱动程序也可以用它来指向自己的私有数据区(来标识哪个设备产生中断)。若中断没有被共享,dev_id 可以设置为 NULL,但推荐用它指向设备的数据结构。
void disable_irq(unsigned int irq):作用是关掉中断
void free_irq(unsigned int irq, void*dev_id):释放中断,与request_irq对应。
unsigned int s3c2410_gpio_getpin(unsignedint pin):获得对应GPIO口的数据寄存器值
wake_up_interruptible(&button_wait):唤醒等待队列里的某个进程这里是button_wait。
wait_event_interruptible(button_event,ev_press):等待事件中断发生,这里的事件便是ev_press
copy_to_user(void __user *to, const void*from, unsigned long n):将内核的数据传给用户空间。
Memset:C库函数,将某缓冲区或是数据结构写入某一数值。
static inline void poll_wait(struct file *filp, wait_queue_head_t * wait_address, poll_table *p):poll_wait函数,将某进程阻塞放进poll_table列表。
static inline int register_chrdev(unsignedint major, const char *name,const struct file_operations *fops):字符设备注册函数
对应有字符设备注销函数:
static inline void unregister_chrdev(unsignedint major, const char *name)
该驱动会用到的所有函数就这些了,理解了这些函数就差不多了解整个驱动流程了,接下来我们说一下整个按键驱动执行的过程:首先程序先从初始化函数执行,注册字符设备的同时会执行file_operations的操作函数。所以首先是打开函数,打开函数里我们处理的是注册中断,一般注册中断都是在open函数里实现的,注册完后就可以等待按键事件的发生了。所以进入等待队列并阻塞。这里就用到函数poll,一旦有按键事件发生就会检测到存储按键事件的数组里有了值,所以会唤醒等待队列的进程从而读取数据到用户空间。用户空间读完值后清空数组并继续等待下一次按键的发生,这个就是用户空间应用程序里实现的了,驱动不管。记住驱动知识负责提供功能,而不管怎么应用这个功能。即DDR3说的机制(mechanism)与策略(policy)。接下来按照驱动程序的编写来讲解整个驱动的实现。
#include <linux/module.h> /*模块有关的*/
#include <linux/kernel.h> /*内核有关的*/
#include <linux/fs.h> /*文件系统有关的*/
#include <linux/init.h> /*init*/
#include <linux/delay.h> /*延迟函数相关*/
#include <linux/poll.h> /*poll函数相关*/
#include <asm/irq.h> /*中断*/
#include <linux/interrupt.h> /*linux中断相关*/
#include <asm/uaccess.h> /*copy_to_user函数相关*/
#include <asm/arch/regs-gpio.h> /*寄存器设置*/
#include <asm/hardware.h> /*hardware*/
#define DEVICE_NAME "button_lg"
/*ev_press 变量是用于检测key_value是否有值的标志*/
static volatile int ev_press = 0;
/*key_value数组存储按键值,详细见后面的程序*/
static volatile int key_values [] = {0,0,0,0,0,0};
/*这是按键的数据结构体,很重要的一部分,程序都是围绕它在展开的,其中有按键的中断号,引脚的名称,引脚对应的控制寄存器改写的值,举个例子吧,pin_reg如果是S3C2410_GPG0_EINT8,在源码中是0x02 << 0,那么这个值写入GPG0的控制寄存器即等于将寄存器的最低两位置为0x02,也就是设该引脚为特殊功能---中断引脚。还有按键的号码,从0~5,按键的名称。所以我们利用下面的结构体数组来初始化*/
static struct button_irq_descp{
int irq;
int pinname;
int pin_reg;
int pinnum;
char *name;
};
static struct button_irq_descp button_event[6]={
{IRQ_EINT8,S3C2410_GPG(0),S3C2410_GPG0_EINT8,1,"key1"},
{IRQ_EINT11,S3C2410_GPG(3),S3C2410_GPG3_EINT11,2,"key2"},
{IRQ_EINT13,S3C2410_GPG(5),S3C2410_GPG5_EINT13,3,"key3"},
{IRQ_EINT14,S3C2410_GPG(6),S3C2410_GPG6_EINT14,4,"key4"},
{IRQ_EINT15,S3C2410_GPG(7),S3C2410_GPG7_EINT15,5,"key5"},
{IRQ_EINT19,S3C2410_GPG(11),S3C2410_GPG11_EINT19,6,"key6"},
};
/*设置等待队列,DECLEAR_WAIT_QUEUE_HEAD的作用包括两个,一是定义等待队列,二是初始化等待队列,那么这个等待队列有什么用呢?通俗讲就是等待事情的发生,放在一个队列里,但是这个队列又不一样,它是一个队列头,仅仅是只为按键服务的就是只有按键一个进程的。详细的见其他参考书了*/
static DECLARE_WAIT_QUEUE_HEAD(button_wait);
/*下面我们按照刚才说的程序流程来讲各个操作函数,首先提到的是open函数,这个函数在这里就是为了实现注册中断,其中需要讲解的是如果中断注册失败的话其处理流程应该是逐个释放以前注册号的中断*/
static lg_button_open(struct inode *inode, struct file*file)//all funtion is register the irq;
{
int i,ret;
for(i=0;i<sizeof(button_event)/sizeof(button_event[0]);i++)
{
/*这个函数就是设置引脚为中断功能*/
s3c2410_gpio_cfgpin(button_event[i].pinname,button_event[i].pin_reg);
/*这个函数就是为6个按键注册中断到内核里,如果到其中某一个失败的话就跳出循环*/
ret=request_irq(button_event[i].irp,button_handler_lg,IRQ_TYPE_EDGE_BOTH,DEVICE_NAME,(void*)&button_irqs[i]);
if(ret<0)
{
printk(KERN_INFO"can`tregister button_irq %d\n",button_event[i].irq);
break;
}
}
/*如果在某个i注册失败的话就进入释放中断循环体,我们先将i--,因为这个i是在那个注册失败的按键中推出的,所以没必要为它做一次释放,释放完后返回一个出错码*/
if(ret<0)
{
i--;
for(;i>=0;i--)
{
disable_irq(button_event[i].irq);
free_irq(button_event[i].irq,(void*)&button_event[i]);
}
return-EBUSY;
}
return 0;
}
/*这是中断处理函数,既然发生中断了我们应该怎么做呢?当然是读取按键的值即是哪个键按下的并存储到数组中并置标志位并且唤醒那些等待按键中断的进程*/
staticirqreturn_t button_handler_lg(int irq,(void *)dev_id)
{
structbutton_irq_descp *button_event = (struct button_irq_descp*)dev_id;
/*从形参中传递进来的irq中断号知道是哪个引脚有中断信号,并将这个传递这个值通过结构体的内部值不一样传递s3c2410_gpio
_getpin,读取存在相应引脚数据寄存器的值,一般是按下为高电平,弹起为低电平*/
int up=s3c2410_gpio_getpin(button_event->pinname);
if(up) //按下时进入if语句,将值加上一个任意值存入数组
key_values[button_event->pinnum]=up+0x80;
else//否则是弹起状态
key_values[button_event->pinnum]=up;
//然后置标志位为1,这是为了通知后面的read程序。因为如果没有数据可读的时候read函数是阻塞的,它会在那可中断休眠*/
ev_press=1;
//唤醒等待队列的进程,说有数据可读了,因为该进程会调用read函数读取数据但是一时没有数据所以就进入可中断阻塞状态,所以需要在这唤醒,函数里头是等待队列名字*/
wake_up_interruptible(&button_wait);
return IRQ_RETVAL(IRQ_HANDLED);//这个返回值代表中断成功处理的意思
}
//这是read函数,在前头已经讲了很多了*/
static int lg_button_read(struct file *filp,char__user *buff,size_t count,loff_t *offp)
{ unsignedlong err;
//进程调用这个函数时一开始便判断标志位,若为0表示无数据可读,就进入if语句,然后再判断进程是否设置为非阻塞方式,若是的话就不等待立刻返回,若不是就等待事情的发生//
if(!ev_press)
{
if(filp->f_flags&O_NONBLOCK)
{
return-EAGAIN;
}
else
//调用该函数进入等待中断状态,形参是等待中断发生时的标志,即ev_press,另外如果中断发生了就返回到这个函数来执行*/ wait_event_interruptible(button_event,ev_press);
}
//将数据复制到用户空间
err=copy_to_user(buff,(const void*)key_values,min(sizeof(key_values),count));
//复制完后清空数组并清标志位
memset((void *)key_values,0,sizeof(key_values));
ev_press=0;
//如果复制失败就返回一个错误值
if(err)
{
return-EFAULT;
}
else return min(sizeof(key_values),count);
}
}
//阻塞函数实现步骤:初始化poll_table表,一般我们在驱动里不用做这个,然后poll方法调用的poll_wait会把当前进程挂到驱动程序提供的wait_queue_head_t中,加入能读或能写就返回,否则调用schedule_timeout函数睡眠,这是和read函数配合使用的。*/
static int lg_button_poll(struct file *file,structpoll_table_struct *wait)
{
unsigned int mask;
//将进程挂到等待队列里
poll_wait(file,&button_wait,wait);
//如果可读的话就进入if语句,将掩码置为数据可读(POLLRDNORM)与设备可读(POLLIN)
if(ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
//关闭函数
static lg_button_close(struct inode *inode, structfile *file)
{
int i;
for(i=0;i<sizeof(button_event)/sizeof(button_event[0]);i++)
{
disable_irq(button_event[i].irq);
free_irq(button_event[i].irq,(void*)&button_event[i]);
}
return 0;
}
/*这是操作函数,不多说,记住这个函数不能放在操作函数的前面否则会报错的*/
static const struct file_operations button_s3c2440={
.owner =THIS_MODULE,
.open =lg_button_open,
.read =lg_button_read,
.poll =lg_button_poll,
.release =lg_button_close;
}
static __init int init_button_lg()
{
int ret;
if(ret=register_chrdev(0,DEVICE_NAME,&lg_button)<0)
{
printk(DEVICE_NAME"can`tregister\n");
return ret;
}
Return ret;
}
static__exit void exit_button_lg()
{
unregister_chrdev(0,DEVICE_NAME);
}
module_init(init_button_lg);
module_exit(exit_button_lg);