編寫按鍵驅動的方法很多,這裏我僅僅列舉用中斷法加上簡單地字符設備註冊來編寫,並沒有採用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);