Linux 輸入子系統

來源:互聯網
上載者:User

標籤:

Technorati 標籤: Kernel 輸入子系統 Input

     在Linux中,輸入裝置(如按鍵、鍵盤、觸控螢幕、滑鼠等)是典型的字元裝置,其一般的工作機理,是底層在按鍵、觸摸時,觸發一個中斷,或者驅動通過定時器定時查詢,通過這兩種方式通知CPU,CPU然後通過SPI、I2C或I/O介面讀取索引值、座標等資料,放入緩衝區,字元裝置驅動管理該緩衝區,向上提供read介面供應用程式使用。

     在上述的工作流程中,只有終端、讀取數值是根具體硬體裝置相關,而輸入事件的緩衝區管理以及字元裝置驅動的介面函數,都是通用的,因此,有必要統一這些不同的輸入裝置,提煉出通用部分。

    Linux的Input子系統整體架構如下:

    先介紹核心資料結構體,再介紹一個簡單的例子,然後引入準系統函數。

核心資料結構體

    Input子系統有三層,比較核心的結構體有四個,分別為 輸入事件input_event,輸入裝置input_dev,核心處理input_handle,事件處理input_handler,分屬於Input的不同層級,在上面這些結構體中,input_handle處於核心地位。如所示:

    整個Input子系統有 兩個全域鏈表,一個是input_dev_list 鏈表,裡面有當前系統下,所有的底層輸入裝置,一個是input_handler_list鏈表,裡面有當前系統下,所有的事件處理函數。

輸入裝置

struct input_dev {

…..

struct input_id id;//與input_handler匹配用的id,包括 匯流排類型、生產廠商、產品類型、版本

unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //裝置所支援的事件類型 ,如按鍵事件 EV_KEY

unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];  //裝置所支援的子事件類型,如 按索引值

int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

unsigned long key[BITS_TO_LONGS(KEY_CNT)];//反應裝置當前的按鍵狀態

struct input_handle *grab;//當前佔有該裝置的input_handle

struct list_head h_list;//該鏈表頭用於連結此裝置所關聯的input_handle

struct list_head node; //用於將此裝置連結到input_dev_list

}

事件處理器

struct input_handler{

…..

int minor;  //表示裝置的次裝置號

/*event用於處理事件*/

void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);

/*connect用於建立handler和device的聯絡*/

int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);

const struct file_operations *fops;//handler的一些處理函數

const struct input_device_id *id_table;//用於和device匹配 ,這個是事件處理器所支援的input裝置

const struct input_device_id *blacklist;//匹配黑名單,這個是事件處理器應該忽略的input裝置

struct list_head h_list;//這個鏈表用來連結他所支援的input_handle結構,input_dev與input_handler配對之後就會產生一個input_handle結構

struct list_head node; //連結到input_handler_list,這個鏈錶鏈接了所有註冊到核心的事件處理器

}

 

串連結構體

每一個 input_handle 結構體代表一個成功配對的 input_dev 和 input_handler。

struct input_handle {

void *private; //每個配對的事件處理器都會分配一個對應的裝置結構,如evdev事件處理器的evdev結構,注意這個結構與裝置驅動層的input_dev不同,初始化handle時,儲存到這裡。

int open; //開啟標誌,每個input_handle 開啟後才能操作,這個一般通過事件處理器的open方法間接設定

const char *name;

struct input_dev *dev; //關聯的input_dev結構

struct input_handler *handler; //關聯的input_handler結構

struct list_head d_node; //input_handle通過d_node串連到了input_dev上的h_list鏈表上

struct list_head h_node; //input_handle通過h_node串連到了input_handler的h_list鏈表上

};

 

資料結構之間的關係

struct input_dev物理輸入裝置的基本資料結構,包含裝置相關的一些資訊

struct input_handler 事件處理結構體,定義怎麼處理事件的邏輯

struct input_handle用來建立 input_dev 和 input_handler 之間關係的結構體

input_dev 通過全域的input_dev_list連結在一起。裝置註冊的時候實現這個操作。

input_handler 通過全域的input_handler_list連結在一起。事件處理器註冊的時候實現這個操作(事件處理器一般核心內建,一般不需要我們來寫)

input_hande 沒有一個全域的鏈表,它註冊的時候將自己分別掛在了input_dev 和 input_handler 的h_list上了。通過input_dev 和input_handler就可以找到input_handle在裝置註冊和事件處理器,註冊的時候都要進行配對工作,配對後就會實現連結。通過input_handle也可以找到input_dev和input_handler。

 

那麼為什麼一個input_device和input_handler中擁有的是h_list而不是一個handle呢?因為一個device可能對應多個handler,而一個handler也不能只處理一個device,比如說一個滑鼠,它可以對應even handler,也可以對應mouse handler,因此當其註冊時與系統中的handler進行匹配,就有可能產生兩個執行個體,一個是evdev,另一個是mousedev,而任何一個執行個體中都只有一個handle。至於以何種方式來傳遞事件,就由使用者程式開啟哪個執行個體來決定。後面一個情況很容易理解,一個事件驅動不能只為一個甚至一種裝置服務,系統中可能有多種裝置都能使用這類handler,比如event handler就可以匹配所有的裝置。在input子系統中,有8種事件驅動,每種事件驅動最多可以對應32個裝置,因此dev執行個體總數最多可以達到256個。

 

 

Input Driver例子

下面以一個簡單驅動為例子來介紹

#include <asm/irq.h>

#include <asm/io.h>

static struct input_dev *button_dev;   /*輸入裝置結構體*/
static irqreturn_t button_interrupt(int irq, void *dummy)     /*中斷處理函數*/
{
          input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);  /*向輸入子系統報告產生按鍵事件*/
        input_sync(button_dev);       /*通知接收者,一個報告發送完畢*/
        return IRQ_HANDLED;

}

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(); /*分配一個裝置結構體*/

        //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_MASK(EV_KEY);   /*設定按鍵資訊*/

         button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);

       //分別用來設定裝置所產生的事件以及上報的按索引值。Struct iput_dev中有兩個成員,一個是evbit.一個是keybit.分別用

       //表示裝置所支援的動作和索引值。

         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);

     這個demo代碼,在button_init()中,首先註冊了中斷處理函數,然後調用input_allocate_device()函數分配一個input_dev結構體,並調用input_register_device函數對其進行註冊。在中斷處理函數中,demo將接受到的按鍵資訊上報給Input子系統,Input子系統向使用者態程式提供按鍵輸入資訊。

    在上述這個簡單的驅動裡面,涉及到幾個問題

    1. 輸入裝置如何傳遞事件到核心層

    2. 核心層如何找到對應事件的事件處理函數

    3. 底層輸入裝置驅動是如何和事件處理層聯絡上的

 

準系統函數

下面從使用流程入手,簡要介紹以下input的整體流程,這裡只會標註出主要代碼流程。

    1. 分配一個輸入裝置

   

     從注釋中可知,釋放一個還未註冊的輸入裝置,使用input_free_device,釋放一個已經註冊的裝置,使用input_unregister_device。

     由於沒有輸入參數,因此,可以猜測出這個分配出來的input_dev的一些配置都是預設配置。

     這裡面,比較重要的有兩個鏈表h_list和node,分配後,我們需要在預設配置的基礎上,添加自己的配置資訊。

   2. 註冊一個輸入裝置

    

     從注釋上,可以知道,傳入參數必須為input_allocate_device的傳回值。

     這個函數裡面會設定input_dev所支援的基本事件類型,注意,一個裝置可以支援一種或者多種事件類型。Input子系統需要在sysfs檔案系統中體現出現,因此,input在sysfs中的device名稱會在這裡面設定。

然後將底層輸入裝置input_dev添加到全域裝置鏈表input_dev_list中,對全域鏈表input_handler_list中的每一個handler函數,調用

input_attach_handler()。

每一次input_dev的註冊,都會遍曆事件處理鏈表input_handler_list,尋找輸入裝置對應的事件處理常式。

每一次input_hanlder的註冊,都會遍曆裝置鏈表input_dev_list,尋找事件處理常式對應的輸入裝置。

上面這兩個操作幾乎是對稱的,機制同platform中的device和device driver的相互尋找類型。

具體代碼如下:

 

platform機制中的尋找,是根據裝置名稱和裝置驅動名稱來匹配的,這裡也不例外,匹配的過程,通過對比input_dev和input_handler的id成員,具體為id成員的匯流排類型、裝置廠商、裝置號、裝置版本是否一致來判斷是否匹配成功。

    3. 輸入裝置找到事件處理常式

    正常情況下,使用 事件處理常式中的 id_table和輸入裝置input_dev.id成員進行匹配。id_table指向該事件處理常式支援的裝置列表。

    匹配成功後,調用handler->connect,將handler和input_dev串連起來。

   4. 向Input核心層報告輸入事件

 

  這裡面的核心函數為 input_handle_event(dev,type,code,value),裡面是一個大的switch,一級為事件類型type,二級switch為event code,這裡只分析按鍵相關:

 

disposition 的取值有如下幾種,它表示使用什麼樣的方式處理該輸入事件。
#define INPUT_IGNORE_EVENT                0         // 表示忽略事件,不對其進行處理
#define INPUT_PASS_TO_HANDLERS         1        // 表示將事件交給 handler 處理
#define INPUT_PASS_TO_DEVICE             2        // 表示將事件交給 input_dev 處理
#define INPUT_PASS_TO_ALL                 (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)

 

如果該事件是傳遞給裝置自身,則調用裝置驅動自身的event函數來處理事件。

如果該事件時傳遞給上層事件處理函數,則調用input_pass_event來傳遞事件,將調用輸入裝置對於的handler的event()函數來處理輸入事件。

注意:只有在handle被開啟的情況下,才會接收到事件。

 

5. Input子系統輸入事件處理層

      輸入事件處理層是在系統初始化時,註冊進系統的。

     輸入子系統的事件處理層核心資料結構為 input_handler,所有輸入子系統的事件處理常式都掛在input_handler_list中。在

6. 輸入事件處理層註冊

    

     系統定義了8個輸入事件處理層, 這些事件處理層通過handler->h_list串連起來,同時,也儲存在全域input_table數組(他們在數組中的索引為裝置號右移5位的值)和全域input_handler_list鏈表中。

      同輸入裝置註冊一樣,輸入事件處理註冊時,需要尋找對應的輸入裝置。代碼如下:

在input_attach_handler中,最後會調用error = handler->connect(handler, dev, id);也就是evdev_handler->connect,也就是

evdev_connect函數,在這裡面初始化input handle,並且註冊到系統。

在這裡面,會將handle掛到所對應input device的h_list鏈表上.還將handle掛到對應的handler的hlist鏈表上,因此,可以把handle看成是 handler和 input device的資訊集合體 .在這個結構裡集合了匹配成功的 handler和 input device。就這樣,handler和input dev匹配到一起。

7. 事件層處理來自核心層的事件

      這裡,會呼叫事件處理層的event函數,也就是evdev_event。每當input dev上報一個事件時,會將其交給和它匹配的handler的event函數來處理,在這裡,又會通過遍曆鏈表來調用evdev_pass_event來處理。

這裡的操作,就是將event上傳的資料儲存到client->buffer中。client->head是當前的資料位元置,這裡是一個環形緩衝區,

寫資料是從client->head寫.而讀資料則是從client->tail中讀.

寫完之後,通過向上層發起SIGIO訊號來通知有事件發生,可以從緩衝區中讀取資料了。

7. 輸入事件處理函數的檔案提供者

        輸入裝置在上層表現為主裝置號為INPUT_MAJOR的裝置檔案,對他的讀寫會通過VFS,最後傳遞到evdev_fops的檔案操作結構體中去。

       

   1:  static ssize_t evdev_read(struct file *file, char __user *buffer,
   2:                size_t count, loff_t *ppos)
   3:  {
   4:      struct evdev_client *client = file->private_data;
   5:      struct evdev *evdev = client->evdev;
   6:      struct input_event event;
   7:      int retval;
   8:   
   9:      if (count < input_event_size())
  10:          return -EINVAL;
  11:   
  12:      if (client->head == client->tail && evdev->exist &&
  13:          (file->f_flags & O_NONBLOCK))
  14:          return -EAGAIN;
  15:   
  16:      retval = wait_event_interruptible(evdev->wait,
  17:          client->head != client->tail || !evdev->exist);
  18:      if (retval)
  19:          return retval;
  20:   
  21:      if (!evdev->exist)
  22:          return -ENODEV;
  23:   
  24:      while (retval + input_event_size() <= count &&
  25:             evdev_fetch_next_event(client, &event)) {
  26:   
  27:          if (input_event_to_user(buffer + retval, &event))
  28:              return -EFAULT;
  29:   
  30:          retval += input_event_size();
  31:      }
  32:   
  33:      return retval;
  34:  }

     首先,它判斷緩衝區大小是否足夠.在讀取資料的情況下,可能當前緩衝區內沒有資料可讀.在這裡先睡眠等待緩衝

區中有資料.如果在睡眠的時候,.條件滿足.是不會進行睡眠狀態而直接返回的. 然後根據read()提夠的緩衝區大小.將

client中的資料寫入到使用者空間的緩衝區中.

 

參考文獻:

http://blog.csdn.net/lbmygf/article/details/7360084

http://blog.chinaunix.net/uid-27717694-id-3758334.html

Linux 輸入子系統

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.