鍵盤在所有的驅動之中最為簡單的一種,但它卻包含了驅動的基本框架,對以後繼續深入學習其他復雜的驅動大有裨益,以下便為你逐步剖析驅動的開發。采用的是查詢方式。一.內核模塊的注冊和撤銷
在加載模塊的時候,首先運行的是內核模塊的注冊函數。它的功能包括內核注冊設備以及變量的初始化。
static int head,tail;
int _init Keypad_init(void)
{
int result;
result=register_chrdev(KEY_LED_MAJOR,KEY_LED_NAME,&Keypad_fops);
Keypad_clear();
init_waitqueue_head(&queue);
prink("%s %s initialized.\n",KEY_LED_NAME,KEY_LED_VERSION);//不能用prinf
return 0;
}
module_init(Keypad_init);//加載模塊
void _exit Keypad_cleanup(void)
{
del_timer(&timer);
unregister_chrdev(KEY_LED_MAJOR,KEY_LED_NAME);
prink("Keypad driver removed \n");
}
module_exit(Keypad_cleanup);//卸載該模塊
二.虛擬文件系統與硬件驅動的接口
static struct file_operations Keypad_fops={
open:Keypad_open,
read:Keypad_read,
poll:Keypad_poll,
fasync:Keypad_fasync,
release:Keypad_release,
};
該接口定義完之後一些便是對這幾個具體函數的實現了!現在我們一起進入下一步吧,是不是覺得其實沒什麼難度的呢?別那麼早開心著呢?這幾個函數的實現時候,涉及到很多技術,包括內核定時器,*等待隊列的具體實現(阻塞方式),異步方式的具體實現技巧,循環隊列。看到這麼多技術你是否感到很興奮呢?以下本人將以通俗的方式為你講解,希望你能理解。
(更多的資訊歡迎登錄:qiangren.blog.edu.cn)
三.設備的打開操作接口函數具體實現(Keypad_open)
設備打開一般包括兩大操作,一是完成設備的初始化,二是設備引用計數器加1
static int Keypad_open(struct inode *inode,struct file *filp)
{
read_xy();
try_module_get(THIS_MODULE);//此函數為linux 2.6內核增加的,不同於2.4內核,功能是計數器的值加1
return 0;
}
static void read_xy(void)
{
new_data();//獲取鍵值函數
keypad_starttimer();//開啟內核定時器,在固定周期時間內獲取鍵盤新的變化
}
以下實現鍵盤鍵值獲取函數read_xy()
主要是從KEY_CS(對應的讀入地址,之前可以根據具體的硬件設備定義,比如#define kEY_CS(*(volatile unsigned short *)(0xf820000))此處應該根據具體的不同而不同!
將讀入的鍵值存入buf[]緩存中,環形緩沖的寫指針是head,讀指針是tail,前面已經定義過了
////////////////////////////////鍵盤事件的數據結構定義/////////////////////////////////
typedef struct{
ulong status;//按鍵的值
ulong click;//是否有按鍵按下,1表示有,0表示沒有
}KEY_EVENT
static KEY_EVENT cur_data,buf[BUFSIZE];//BUFSIZE為宏定義,用於定義環形緩沖的大小
static void new_data(void)
{
if((KEY_CS & 0xff)!=0xff) //從KEY_CS地址讀入數據,若有一個為0則表示有一個按鍵被按下了(此處硬件電路為低電平有效)
{
switch(KEY_CS & 0xff){
case ~KEY0 & 0xff:
cur_data.status=1;///////1被按下
break;
case ~KEY1 & 0xff:
cur_data.status=2;//2被按下
break;
/////////其他一樣添加,懂嗎??
}
cur_data.click=1;
}
else if(KEY_CS & 0xff==0xff){
cur_data.click=0;
cur_data.status=0;
}
if(head!=tail){////////循環隊列緩沖區的應用在此開始了^_^
int last=head--;
if(last<0)////////若已經到了對首之前,則跳到隊尾,以實現循環隊列
last=BUFSIZE-1;
}
//////按鍵信息存入循環隊列緩沖區中
buf[head]=cur_data;
if(++head==BUFSIZE)
head=0;
if(head==tail && tail++=BUFSIZE)
tail=0;
if(fasync)
kill_fasync(&fasyc,SIGIO,POLL_IN);
wake_up_interruptible(&queue);
}
今天先寫到這裡先,以下部分會陸續推出,敬請關注
接下來我們介紹其他幾個文件接口函數的實現
四.先介紹關閉函數keypad_release(),為什麼先介紹它呢?道理很簡單,應該它比較簡單,先讓大家做下熱身運動,在介紹完這個之後,繼續會介紹一個比較復雜的函數,看你吃得消沒有哦
關閉操作主要實現的是:關閉設備異步通知,設備計數器減1,刪除定時器信號中斷
static int Keypad_release(struct inode *inode,struct)
{
Keypad_fasync(-1,filp,0);
module_put(THIS_MODULE);
del_timer(&timer);
return 0;
}
五.設備讀取操作接口函數實現Keypad_read()
主要作用是從緩沖區讀取鍵值,通過調用get_data()實現,通過copy_to_user()函數將鍵值復制到用戶的數據區中
static ssize_t Keypad_read(struct file *filp,char *buf,ssize_t count,loff_t *l)
{
DECLEARE_WAITQUEUE(wait,current);//聲明等待隊列,將當前進程加入到等待隊列中
KEY_EVENT t;
ulong out_buf[2];
if(head==tail)//當前循環隊列中沒有數據可以讀取
{
if(filp->f_flags & O_NONBLOCK)//假如用戶采用的是非堵塞方式讀取
return _EAGAIN;
add_wait_queue(&queue,&wait);//將當前進程加入等待隊列
current->state=TASK_INTERRUPTIBLE;//設置當前進程的狀態
while((head==tail)&&!signal_pending(current))//假若還沒有數據到循環隊列並且當前進程沒有受到信號
{
shedule();//進程調度
current->state=TASK_INTERRUPTIBLE;
}
current->state=TASK_RUNNING;
remove_wait_queue(&queue,&wait);
if(head==tail)
return count;
t=get_data();//調用get_data()函數,得到緩沖區中的數據,下面將給予詳細的 介紹
out_buf[0]=t.status;
out_buf[1]=t.click;
copy_to_user(buf,&out_buf,sizeof(out_buf));//將得到的鍵值拷貝到用戶數據區
return count;
}
}
很自然我們就應該要介紹get_data()函數的實現了,該函數的功能就是從我們定義的循環隊列緩沖區中讀出我們要的鍵值,所以其實很簡單的如果理解循環隊列的原理,在此不多加解釋,大家應該具備一般的數據結構相關的知識吧
static KEY_EVENT get_data(void)
{
int last=tail
if(++tail==BUFSIZE)
tail=0;
return buf[last];
}
上面如果你看得懂得話,那麼可以進入下面的學習了,主要介紹的是內核定時器的使用,利用等待隊列實現阻塞型I/O,poll系統調用,異步通知方式,介紹完之後,我將給出一個應用實例,對於有使用過文件操作系統調用的來說,對我們所寫的鍵盤驅動來說,他們基本上是一樣的。廢話少說,我們馬上開始我們精彩的驅動開發!