编写按键驱动的方法很多,这里我仅仅列举用中断法加上简单地字符设备注册来编写,并没有采用misc设备来注册。(这里的注册函数都是相对古老,以后不推荐使用!)首先编写一个设备驱动程序头文件先定义了,这很容易,照搬别人的就行了。接着确定你的设备驱动程序会用到的数据结构,这里会用到一个重要的数据结构,struct button_irqs,用来表征按键的状态以及按键的标识。接着确定file_operations里会用到的函数。记得编写初始化以及退出函数。分析驱动时时第一步便是从初始化函数开始然后如果有中断的话先分析中断然后才是操作函数。确定这两个后开始按照你所熟知的原理进行编写驱动。
先列出该驱动程序会用到一些重要函数以及作用:
void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function):设置引脚功能为输入、输出、特殊功能等
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *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(unsigned int 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(unsigned int major, const char *name,const struct file_operations *fops):字符设备注册函数
对应有字符设备注销函数:
static inline void unregister_chrdev(unsigned int 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>
#include <linux/delay.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#define DEVICE_NAME "button_lg"
static volatile int ev_press = 0;
static volatile int key_values [] = {0,0,0,0,0,0};
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"},
};
static DECLARE_WAIT_QUEUE_HEAD(button_wait);
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);
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`t register button_irq %d\n",button_event[i].irq);
break;
}
}
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;
}
static irqreturn_t button_handler_lg(int irq,(void *)dev_id)
{
struct button_irq_descp *button_event = (struct button_irq_descp*)dev_id;
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)
{unsigned long 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,struct poll_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, struct file *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`t register\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);