linux input輸入子系統分析《一》:初識input輸入子系統


主要講述本人在學習Linux內核input子系統的全部過程,如有分析不當,多謝指正。以下交流方式,文章歡迎轉載,保留聯繫信息,以便交流。

郵箱:[email protected]

主頁:www.ielife.cn(愛嵌論壇——嵌入式技術學習交流)

博客:blog.csdn.net/ielife

1      開發環境

主  機:ubuntu10.04

開發板:mini2440

內  核:linux-2.6.22.6

編譯器:arm-linux-gcc(3.4.5)

2      linux輸入子系統

本節從整體上講解了輸入子系統的框架結構。有助於讀者從整體上認識linux的輸入子系統。在陷入代碼分析的過程中,通過本節的知識能夠找準方向,明白原理。

本節重點:

  •          輸入子系統的框架結構
  •          各層對應內核中的文件位置
  •          輸入子系統的事件處理機制
  •          輸入子系統的驅動層基本操作流程
  •          輸入子系統的驅動層常用函數

本節難點:

  •          輸入子系統的事件處理機制
  •          輸入子系統的驅動工作流程

2.1    初識linux輸入子系統

linux輸入子系統(linux input subsystem)從上到下由三層實現,分別爲:輸入子系統事件處理層(EventHandler)、輸入子系統核心層(InputCore)和輸入子系統設備驅動層。

對於輸入子系統設備驅動層而言,主要實現對硬件設備的讀寫訪問,中斷設置,並把硬件產生的事件轉換爲核心層定義的規範提交給事件處理層。

對於核心層而言,爲設備驅動層提供了規範和接口。設備驅動層只要關心如何驅動硬件並獲得硬件數據(例如按下的按鍵數據),然後調用核心層提供的接口,核心層會自動把數據提交給事件處理層。

對於事件處理層而言,則是用戶編程的接口(設備節點),並處理驅動層提交的數據處理。

對於linux輸入子系統的框架結構如下圖1所示:


圖1  linux輸入子系統框架結構

 

由上圖所展現的內容就是linux輸入子系統的分層結構。

/dev/input目錄下顯示的是已經註冊在內核中的設備編程接口,用戶通過open這些設備文件來打開不同的輸入設備進行硬件操作。

事件處理層爲不同硬件類型提供了用戶訪問及處理接口。例如當我們打開設備/dev/input/mice時,會調用到事件處理層的Mouse Handler來處理輸入事件,這也使得設備驅動層無需關心設備文件的操作,因爲Mouse Handler已經有了對應事件處理的方法。

輸入子系統由內核代碼drivers/input/input.c構成,它的存在屏蔽了用戶到設備驅動的交互細節,爲設備驅動層和事件處理層提供了相互通信的統一界面。

下圖2簡單描述了linux輸入子系統的事件處理機制:


圖2  linux輸入子系統事件處理機制

 

由上圖可知輸入子系統核心層提供的支持以及如何上報事件到input event drivers。

作爲輸入設備的驅動開發者,需要做以下幾步:

Ÿ           在驅動加載模塊中,設置你的input設備支持的事件類型,類型參見表1設置

Ÿ           註冊中斷處理函數,例如鍵盤設備需要編寫按鍵的擡起、放下,觸摸屏設備需要編寫按下、擡起、絕對移動,鼠標設備需要編寫單擊、擡起、相對移動,並且需要在必要的時候提交硬件數據(鍵值/座標/狀態等等)

Ÿ           將輸入設備註冊到輸入子系統中

 

表1  Linux輸入子系統支持的數據類型

EV_SYN     0x00    同步事件

EV_KEY     0x01    按鍵事件

EV_REL     0x02    相對座標(如:鼠標移動,報告相對最後一次位置的偏移)

EV_ABS     0x03    絕對座標(如:觸摸屏或操作杆,報告絕對的座標位置)

EV_MSC     0x04    其它

EV_SW      0x05    開關

EV_LED     0x11    按鍵/設備燈

EV_SND     0x12    聲音/警報

EV_REP     0x14    重複

EV_FF      0x15    力反饋

EV_PWR    0x16    電源

EV_FF_STATUS    0x17   力反饋狀態

EV_MAX    0x1f    事件類型最大個數和提供位掩碼支持

由表1可知,設備所能表示的事件種類,一個設備可以選擇一個或多個事件類型上報給輸入子系統。

Linux輸入子系統提供了設備驅動層上報輸入事件的函數,在include/linux/input.h中:

voidinput_report_key(struct input_dev *dev, unsigned int code, int value);      //上報按鍵事件

voidinput_report_rel(struct input_dev *dev, unsigned int code, int value);       //上報相對座標事件

voidinput_report_abs(struct input_dev *dev, unsigned int code, int value);              //上報絕對座標事件

……

當提交輸入設備產生的輸入事件之後,需要調用下面的函數來通知輸入子系統,以處理設備產生的完整事件:

void input_sync(struct input_dev *dev);

2.2    輸入設備驅動的簡單案例

在Linux內核文檔的documentation/input下,有一個input-programming.txt文件,講解了編寫輸入設備驅動程序的核心步驟。

提供的案例代碼描述了一個button設備,產生的事件通過BUTTON_PORT引腳獲取,當有按下/釋放發生時,BUTTON_IRQ被觸發,以下是驅動的源代碼:

#include <linux/input.h>                                                                                                        
 #include <linux/module.h>
 #include <linux/init.h>

 #include <asm/irq.h>
 #include <asm/io.h>

 static struct input_dev *button_dev;

 static void button_interrupt(int irq, void*dummy, struct pt_regs *fp)
 {
        input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);
        input_sync(button_dev);
 }      

 static int __init button_init(void)
 {
        int error;
        
        if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button",NULL)) {
                 printk(KERN_ERR"button.c: Can't allocate irq %d\n", button_irq);
                 return -EBUSY;
        }      
        
         button_dev = input_allocate_device();
        if (!button_dev) {
                 printk(KERN_ERR"button.c: Not enough memory\n");
                 error = -ENOMEM;
                 goto err_free_irq;
        }

        button_dev->evbit[0] = BIT(EV_KEY);
        button_dev->keybit[LONG(BTN_0)] = BIT(BTN_0);

        error = input_register_device(button_dev);
        if (error) {
                 printk(KERN_ERR"button.c: Failed to register device\n");
                 goto err_free_dev;
        }

        return 0;

 err_free_dev:
        input_free_device(button_dev);
 err_free_irq:
        free_irq(BUTTON_IRQ, button_interrupt);
        return error;
 }

 static void __exit button_exit(void)
 {
       input_unregister_device(button_dev);
        free_irq(BUTTON_IRQ, button_interrupt);
}

module_init(button_init);
module_exit(button_exit);

編寫基於輸入子系統的設備驅動程序需要包含<linux/input.h>,因爲它包含了輸入子系統的接口和所有的宏定義,這些內容在編寫輸入設備驅動程序時需要用到。

button_init函數說明:

當模塊加載(insmod)或內核引導過程中,button_init函數會被調用。首先做的工作是獲取能夠正確控制硬件設備的硬件資源(例如內存、IO內存、中斷和DMA),在代碼中BUTTON_IRQ作爲BUTTON設備的中斷資源,通過request_irq()函數被申請註冊。當有按鍵按下/釋放時,調用button_interrupt()中斷處理函數獲取按鍵值BUTTON_PORT(BUTTON設備的I/O資源)。

那麼輸入子系統怎麼能夠知道這個設備爲輸入設備呢?通過第8行爲設備定義一個用於描述一個輸入設備對象。

static struct input_dev *button_dev;

定義了button_dev之後,如何通知輸入子系統有新的輸入設備了呢?或者說如何把一個新的輸入設備加入到輸入子系統中呢?可以通過輸入子系統核心層input.c中提供的函數分配一個輸入設備,在代碼的第25行。

button_dev= input_allocate_device();

有了輸入設備的描述,當事件產生時,輸入子系統怎麼能夠知道設備產生的事件類型呢?通過32和33行的代碼。

button_dev->evbit[0]= BIT(EV_KEY);
button_dev->keybit[LONG(BTN_0)]= BIT(BTN_0);

其中evbit和keybit成員分別代表設備產生的事件類型和上報的按鍵值。其中輸入子系統的一些位操作NBITS、BIT、LONG經常被用到:

#defineNBITS(x) (((x)/BITS_PER_LONG)+1)                 //通過位x獲取數組的長度
#defineBIT(x)       (1UL<<((x)%BITS_PER_LONG))       //返回位x在數組中的位域
#defineLONG(x) ((x)/BITS_PER_LONG)                        //返回位x的索引

以上的工作做完之後,即可註冊爲輸入設備了,代碼的35行。

input_register_device(button_dev);

這個函數把button_dev輸入設備掛入輸入設備鏈表中,並且通知事件處理層調用connect函數完成設備和事件處理的綁定,當用戶打開設備時,便能夠調用到相應的事件處理接口獲得硬件上報的數據了。input_register_device()函數是會睡眠的函數,因此不能夠在中斷上下文和持有自旋鎖的代碼中調用。

當我們把上面的工作做完之後,設備驅動中唯一值得關注的就是button_interrupt()中斷處理函數了。當按鍵動作發生,button_interrupt()函數被調用,完成事件的上報由其中的兩條語句完成。

input_report_key(button_dev, BTN_1, inb(BUTTON_PORT) & 1);
input_sync(button_dev);

其中input_report_key上報了這是一個按鍵事件,且它的值爲inb(BUTTON_PORT) & 1,由於案例代碼只產生一個按鍵的值,因此input_sync()在這裏不起關鍵作用。但如果是一個觸摸屏,即有x座標和y座標,則需要通過input_sync()函數把x和y座標完整地傳遞給輸入子系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章