1. 程式人生 > >驅動學習回顧——按鍵(Button)驅動的理解和總結

驅動學習回顧——按鍵(Button)驅動的理解和總結

當時在學習按鍵驅動時,便覺得其中有幾個點是比較難理解的,在整體上自己感到並沒有很好地掌握。現在經過了一段時間地學習,再將按鍵驅動裡的疑難點加以理解之後,總結記錄一下。
關於驅動程式碼之前已經給出,點選:這裡,但只是關於程式碼語句的註釋,下面將從功能塊上著重分析這個驅動。

一、中斷

也許現在提起中斷,都可以聯想到這個圖:
這裡寫圖片描述
中斷,本質上就是一種特殊的訊號,由某個裝置發個CPU,CPU接到這個訊號後,作業系統負責處理由這個中斷帶來的資訊。中斷是隨時可以發生的,不同的裝置也對應著不同的中斷。這時就需要一個標識——中斷號。具體地說叫:中斷請求(IRQ)線。
在響應中斷時,核心會執行一個函式——中斷服務程式。中斷服務程式是被核心呼叫來響應中斷的,而它們運行於我們稱之為中斷上下文的特殊上下文中。
回到按鍵裡具體的程式碼:

static irqreturn_t s3c_button_intterupt(int irq,void *de_id)                
{
    int i;
    int found = 0;
    struct s3c_button_platform_data *pdata = button_device.data;

    for(i=0; i<pdata->nbuttons; i++)
    {
        if(irq == pdata->buttons[i].nIRQ)
        {
            found = 1
; break; } } if(!found) /* An ERROR interrupt */ return IRQ_NONE; /* Only when button is up then we will handle this event */ if(BUTTON_UP == button_device.status[i]) { button_device.status[i] = BUTTON_UNCERTAIN; //未決狀態 mod_timer(&(button_device.timers
[i]), jiffies+TIMER_DELAY_DOWN);//啟動定時器 } return IRQ_HANDLED; } static void button_timer_handler(unsigned long data) { struct s3c_button_platform_data *pdata = button_device.data; int num =(int)data; int status = s3c2410_gpio_getpin( pdata->buttons[num].gpio ); if(LOWLEVEL == status) { if(BUTTON_UNCERTAIN == button_device.status[num]) /* Come from interrupt */ { //dbg_print("Key pressed!\n"); button_device.status[num] = BUTTON_DOWN; printk("%s pressed.\n", pdata->buttons[num].name); /* Wake up the wait queue for read()/poll() */ button_device.ev_press = 1; wake_up_interruptible(&(button_device.waitq)); } /* Cancel the dithering */ mod_timer(&(button_device.timers[num]), jiffies+TIMER_DELAY_UP); } else { //dbg_print("Key Released!\n"); button_device.status[num] = BUTTON_UP; } return ; }

乍一看好像有兩個中斷服務程式?這就牽扯到一個重要的知識點:中斷的上半部下半部
上半部:接受到一箇中斷,它就立即執行,但只做嚴格時限的工作,這些工作都是在所有中斷被禁止的情況下完成的。同時,能夠被允許稍後完成的工作推遲到下半部(bottom half)去,此後,下半部會被執行。
下半部:這才是中斷髮生後真正要進行處理的地方。這裡採用了定時器的方式實現,所以button_timer_handler 函式裡才是判斷按鍵行為的地方。
對於這個例子,我們找了一個定時器來幫助處理中斷。總的來說,從中斷服務程式獲得了具體的裝置號且為有效按鍵中斷之後就由定時器來全盤接手。在超時之後,進入定時器原本設定好的定時器中斷處理程式button_timer_handler中正式的處理這個中斷請求。也就是說中斷處理程式s3c_button_intterupt()只是確認按鍵是否有效,真正處理中斷的程式是定時器裡面的button_timer_handler( )
稍後會在button_open 函式中看一看是怎麼回事。

這裡還有一個知識點:消抖。按鍵畢竟是一種外設,因此可能會由於外界的因素或硬體問題而引起電平狀態突然改變(毛刺,時間極短)。對按鍵來說,它可能會認為這是有效的中斷觸發(下降沿),這不是我們所希望的。因此,我們同樣用了定時器。按下按鍵,但先設定為未決定狀態,同時延時一下,根據在這一小段的延遲時間裡,是不是仍然還保持著按下的狀態,如果還是,才認為“真正”按下了按鍵,否則會認為是毛刺。

小結:
1)中斷快速響應,快速返回。一旦產生中斷,CPU會停止做的事情,來處理你的這個中斷,在處理中斷時是把別的中斷遮蔽掉的,別的中斷是不能響應的(但有中斷優先順序的話會優先處理級別高的中斷,如在STM32微控制器上,中斷能夠巢狀),所以不應該在中斷服務程式裡有sleep等這類處理出現。
2)Linux實現下半部的機制:
軟中斷:
1、軟中斷是在編譯期間靜態分配的。
2、最多可以有32個軟中斷。
3、軟中斷不會搶佔另外一個軟中斷,唯一可以搶佔軟中斷的是中斷處理程式。
4、可以併發執行在多個CPU上(即使同一型別的也可以)。所以軟中斷必須設計為可重入的函式(允許多個CPU同時操作), 因此也需要使用自旋鎖來保護其資料結構。
5、目前只有兩個子系統直接使用軟中斷:網路和SCSI。

tasklet:
1、tasklet是使用兩類軟中斷實現的:HI_SOFTIRQ和TASKLET_SOFTIRQ。
2、可以動態增加減少,沒有數量限制。
3、同一類tasklet不能併發執行。
4、不同型別可以併發執行。
5、大部分情況使用tasklet。

工作佇列:
1、由核心執行緒去執行,換句話說總在程序上下文執行。
2、可以睡眠,阻塞。

看open裡的部分程式碼,可以看到藉助了定時器:

static int button_open(struct inode *inode, struct file *file)
{
…… ……
for(i=0; i<pdata->nbuttons; i++) //初始化button資訊
    {
        /* Initialize all the buttons status to UP  */
        pdev->status[i] = BUTTON_UP; //按鍵未按下

        /* Initialize all the buttons' remove dithering timer */
        setup_timer(&(pdev->timers[i]), button_timer_handler, i);//設定定時器及呼叫函式。把每個最初的定時器賦予超時便返回到指定的處理函式的功能

        /* Set all the buttons GPIO to EDGE_FALLING interrupt mode */
        s3c2410_gpio_cfgpin(pdata->buttons[i].gpio, pdata->buttons[i].setting);//配置成中斷模式
        irq_set_irq_type(pdata->buttons[i].nIRQ, IRQ_TYPE_EDGE_FALLING);//下降沿觸發中斷

        /* Request for button GPIO pin interrupt  */
        result = request_irq(pdata->buttons[i].nIRQ, s3c_button_intterupt, IRQF_DISABLED, DEV_NAME, (void *)i);//註冊給核心,一旦發生pdata->buttons[i].nIRQ中斷號的中斷之後,呼叫s3c_button_intterupt中斷處理程式

}
…… ……

二、等待佇列、poll

button_open函式裡其實已經涉及到了:

static int button_open(struct inode *inode, struct file *file)
{
…… ……
init_waitqueue_head(&(pdev->waitq));   //初始化等待佇列頭
…… ……
}

我們在定義button_device 結構體的時候已經在裡面定義了這個變數:

struct button_device    //定義一個button_device的結構體
{
    unsigned char                      *status;      /* The buttons Push down or up status */
    struct s3c_button_platform_data    *data;        /* The buttons hardware information data */

    struct timer_list                  *timers;      /* The buttons remove dithering timers */
    wait_queue_head_t                  waitq;        /* Wait queue for poll()*/
    volatile int                       ev_press;     /* Button pressed event */

    struct cdev                        cdev;
    struct class                       *dev_class;
} button_device;

而在button_timer_handler函式中:

 /* Wake up the wait queue for read()/poll() */
     button_device.ev_press = 1;
     wake_up_interruptible(&(button_device.waitq));

ev_press 是一個按鍵按下而發生的標識,按下,設為1。此時喚醒等待佇列讓裝置進行讀取。ev_press引數值也用來設定工作模式,選定阻塞或者是非阻塞模式。
看到button_read 讀取按鍵資訊這個函式裡:

static int button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)  
{   
    struct button_device *pdev = file->private_data;  
    struct s3c_button_platform_data *pdata;  
    int   i, ret;  
    unsigned int status = 0;  

    pdata = pdev->data;  

    dbg_print("ev_press: %d\n", pdev->ev_press);  
    if(!pdev->ev_press) //ev_press為按鍵識別符號,即按下時才為1.結合下面等待佇列程式  
    {  
         if(file->f_flags & O_NONBLOCK)  
         {  
             dbg_print("read() without block mode.\n");// O_NONBLOCK是設定為非阻塞模式  
             return -EAGAIN;//若沒有按鍵按下,還採用非阻塞模式則會一直浪費CPU的時間等待。  
         }  
         else  
         {  
             /* Read() will be blocked here */  
             dbg_print("read() blocked here now.\n");  
             wait_event_interruptible(pdev->waitq, pdev->ev_press); //在阻塞模式讀取且沒按鍵按下則讓等待佇列進入睡眠,直到ev_press為1  
         }  
    }  

    pdev->ev_press = 0;//清除標識,準備下一次  
  …… ……
  }

這裡我們要知道的:
1)阻塞操作: 是指在執行裝置操作時,若不能獲得資源,則掛起程序直到滿足可操作的條件後再進行操作,被掛起的程序會進入休眠狀態,被從排程器的執行佇列中移除,直到條件被滿足。
2)非阻塞操作: 是指在執行裝置操作時,若不能獲得資源,並不掛起,或者放棄他或者不停地的查詢,直到可執行。

而我們驅動中等待佇列使用的則是阻塞的方式,首先申請一個等待佇列,一旦阻塞該程序就進入休眠將資源讓給其他程序的,然後直到滿足條件再喚醒等待佇列,重新執行程式。而之前,可以看到是在button_timer_handler 中被喚醒,也就是在處理中斷時進行。
在這裡我的理解是,我們使用者運行了按鍵的應用程式,在open時設定了定時器、配置成中斷模式、初始化了等待佇列頭;在read要獲取按鍵資訊時,若我們沒進行按下按鍵(ev_press=0)的操作,這個程序就掛起進入休眠;按下了按鍵(ev_press=1)時,也就是進入了中斷時,等待佇列被喚醒,得到了資源這個程序就再執行下去。read將按鍵的狀態儲存到status ,並將按鍵資訊通過copy_to_user 傳送到使用者空間。

驅動中的poll函式,其實就是對應到使用者空間我們使用的select或poll系統呼叫,呼叫select或poll實際上進到驅動裡就是調的poll。我以前寫的button例項就是用到了select。

static unsigned int button_poll(struct file *file, poll_table * wait)
{ 
    struct button_device *pdev = file->private_data;
    unsigned int mask = 0;

    poll_wait(file, &(pdev->waitq), wait);//新增佇列到等待佇列中
    if(pdev->ev_press)
    {
        mask |= POLLIN | POLLRDNORM; /* The data aviable */ 
    }

    return mask;
}
/*fops*/
static struct file_operations button_fops = { 
    .owner = THIS_MODULE,
    .open = button_open, 
    .read = button_read,
    .poll = button_poll, 
    .release = button_release, 
};

用法:先呼叫巨集FD_ZERO將指定的fd_set清零,然後呼叫巨集FD_SET將需要測試的fd加入fd_set,接著呼叫函式select測試fd_set中的所有fd,最後用巨集FD_ISSET檢查某個fd在函式select呼叫後,相應位是否仍然為1。
具體可以參考:http://blog.csdn.net/lingfengtengfei/article/details/12392449

到此就列出了當初學習按鍵驅動時感覺比較難懂的地方,或許還是經驗不夠,對一些地方的分析還不是很到位。但是還是希望以它為基礎,逐漸提高自己的理解和完善學習。