天天看點

Linux input子系統一、Input子系統分層思想二、三個重要的結構體

一、Input子系統分層思想

    input子系統是典型的字元裝置。首先分析輸入子系統的工作機理。底層裝置(按鍵、觸摸等)發生動作時,産生一個事件(抽象),CPU讀取事件資料放入緩沖區,字元裝置驅動管理該緩沖區。不同的輸入事件的緩沖管理及字元裝置驅動的file_operations接口對輸入裝置是通用的。所有linux核心就引入了輸入子系統,由核心層統一關系這些公共的部分,這就是Linux核心的歸類思想和分層思想,把同等類型的驅動進行歸類并在核心中以子系統的方式統一管理。

    輸入子系統由輸入子系統驅動層(Input Driver),核心層(Input Core)和事件處理層(Event Handler)三部分組成。架構圖如下圖:

Linux input子系統一、Input子系統分層思想二、三個重要的結構體

    其中驅動層提供對硬體各寄存器的讀寫通路和将底層硬體對使用者輸入通路的響應轉換為标準的輸入事件,再通過核心層送出給事件處理層;核心層主要是對下提供了裝置驅動層的程式設計接口,對上提供了事件處理層的程式設計接口;而事件處理層為使用者空間的應用提供了統一通路裝置的接口和驅動層送出上來的事件報告給使用者。是以這使得輸入裝置的驅動部分不需要關心對裝置檔案的操作,而隻需要關心對各硬體寄存器的操作和所送出的輸入事件。

    各層之間通信的方式抽象為一種事件。而事件有三種屬性:類型(type),編碼(code),值(value)。Input子系統支援的事件都定義在include/linux/input.h中,包括所有支援的類型,所屬類型支援的編碼等。

事件傳遞方向:

             驅動層-->核心層-->事件處理層-->使用者層

二、三個重要的結構體

  1、input_dev結構體

   input_dev對應着實際的裝置端,定義并規定了各種裝置的資訊等。

struct input_dev {  
        const char *name;    //裝置名,将在sys/class/input/XXX/name phys 中儲存
        const char *phys;     //裝置系統層的實體路徑将在sys/class/input/XXX/phys 中儲存  
        const char *uniq;    
        struct input_id id;   //輸入裝置id 總線類型;廠商編号,産品id,産品版本與input_hander比對時會用到  
        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //事件類型标志位  
        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];   //按鍵事件支援的子事件,按鍵類型  
        unsigned long relbit[BITS_TO_LONGS(REL_CNT)];   //相對位移事件标志位  
        unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];   //絕對位移事件标志位  
        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];   //雜項事件标志位  
        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];   //led訓示燈标志位  
        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];   //聲音事件  
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //強制回報事件  
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //開關事件标志位  
  
        unsigned int hint_events_per_packet;  
        unsigned int keycodemax;  //鍵盤碼表大小  
        unsigned int keycodesize; //鍵盤碼大小  
        void *keycode;            //鍵盤碼表指針  
  
        int (*setkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int keycode);//設定鍵盤碼  
        int (*getkeycode)(struct input_dev *dev,unsigned int scancode, unsigned int *keycode);//擷取鍵盤碼  
        int (*setkeycode_new)(struct input_dev *dev,const struct input_keymap_entry *ke,unsigned int *old_keycode);   
        int (*getkeycode_new)(struct input_dev *dev,struct input_keymap_entry *ke);  
  
        struct ff_device *ff;   //強制回報裝置  
        unsigned int repeat_key;    //重複按鍵标志位  
        struct timer_list timer;    //定時器  
        int rep[REP_CNT];       //重複次數  
        struct input_mt_slot *mt;  
        int mtsize;  
        int slot;  
        struct input_absinfo *absinfo;  
        unsigned long key[BITS_TO_LONGS(KEY_CNT)];  //  
        unsigned long led[BITS_TO_LONGS(LED_CNT)];  //  
        unsigned long snd[BITS_TO_LONGS(SND_CNT)];  //  
        unsigned long sw[BITS_TO_LONGS(SW_CNT)];    //  
  
        int (*open)(struct input_dev *dev);     //open方法  
        void (*close)(struct input_dev *dev);   //close方法  
        int (*flush)(struct input_dev *dev, struct file *file);  
        int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);  
  
        struct input_handle __rcu *grab;  
        spinlock_t event_lock;  
        struct mutex mutex;  
        unsigned int users;  
        bool going_away;  
        bool sync;  
        struct device dev;      //裝置檔案  
        struct list_head    h_list; //input_handler處理器連結清單頭  
        struct list_head    node;   //input_device裝置連結清單頭  
    }; 
           

  unsigned long evbit[BITS_TO_LONGS(EV_CNT)]數組,代表這個裝置支援那類事件。事件類型:

     #define EV_SYN 0x00  //同步事件

     #define EV_KEY 0x01  //按鍵類型

     #define EV_REL 0x02  //相對位移類型

     #define EV_ABS 0x03  //絕對位移類型

     #define EV_MSC 0x04

     #define EV_SW 0x05

         #define EV_LED 0x11

     #define EV_SND 0x12

     #define EV_REP 0x14  //重複類型

     #define EV_FF 0x15  

     #define EV_PWR 0x16

     #define EV_FF_STATUS  0x17

     #define EV_MAX 0x1f

     #define CNT (EV_MAX+1)

  unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];代表這個裝置支援哪些按鍵。按鍵類型:

    #define KEY_RESERVED 0

    #define KEY_ESC 1

    #define KEY_1 2

    ...

    #define KEY_A 30

    #define KEY_S 31

    ...

詳細的内容見include/linux/input.h

2、input_handler結構體

      input_handler屬于輸入子系統的事件層。當事件處理器接收到來自字元裝置傳來的事件時,調用用來處理事件。

struct input_handler {  
      void *private;  //私有資料  
      void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); //事件處理  
      bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value); //過濾器  
      bool (*match)(struct input_handler *handler, struct input_dev *dev); //裝置比對  
      int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); //裝置連接配接  
      void (*disconnect)(struct input_handle *handle);    //裝置斷開連接配接  
      void (*start)(struct input_handle *handle);  
      const struct file_operations *fops; //輸入操作函數集  
      int minor;  //次裝置号  
      const char *name;   //裝置名  
      const struct input_device_id *id_table; //輸入裝置 id表  
      struct list_head    h_list; //input_handler處理器連結清單頭  
      struct list_head    node;   //input_device裝置連結清單頭  
  };
           

3、input_handle結構體

    input_handle為事件input_dev和input_handler之間的溝通者。每個配對的事件處理器都會配置設定一個對應的裝置結構。 

struct input_handle{
        void *private;
        int open;
        const char *name;
 
        struct input_dev   *dev;  //指向input_dev結構體
        struct input_handler   *handler;  //指向input_handler結構體
 
        struct list_head   d_node; //通過d_node連接配接到input_dev上的h_list連結清單上
        struct list_head   h_node; //通過h_node連接配接到input_handler上的h_list連結清單上
    }
           

三、編寫Input裝置驅動的步驟

   1、配置設定一個輸入裝置。

   2、注冊一個輸入裝置。

   3、設定輸入裝置事件類型,主要是input_dev結構中的evbit和keybit。

   4、驅動事件報告。

   5、釋放和登出input裝置。

  附上一個簡單的按鍵驅動,按鍵采用中斷的方式。

#include...
static struct input_dev *button_dev;
 
static irqreturn_t button_interrupt(int irq, void *dummy)
{
    input_report_key(button_dev, BTN_0, 1);
    input_sync(button_dev);
    return IRQ_HANDLED;
}
 
static int __init button_init(void)
{
    int err;
    if(err = request_irq(IRQ_EINT(16), button_interrupt, 0,”BUTTON_0”, NULL) )
    {
        return -EBUSY;  
    }
 
    button_dev = input_allocate_device(); //配置設定一個input_dev結構體
  
    set_bit(EV_KEY, button_dev->evbit); //支援按鍵類事件
    set_bit(EV_REP, button_dev->evbit); //支援重複類事件
    set_bit(BTN_0, button_dev->keybit); 
 
    err = input_register_device(button_dev); //注冊一個輸入裝置
    if(err)
    {
        goto fail;
    }
    return 0;
fail:
    input_free_device(button_dev);
    return err;
}
static void button_exit(void)
{
    free_irq(IRQ_EINT(16), button_interrupt);
    input_unregister_device(button_dev);
}
 
modult_init(button_init);
modult_exit(button_exit);
           

二、重要函數分析

    Input子系統的核心在drivers/input/input.c和evdev.c中,詳細内容需要自己去解讀。

  1、input_allocate_device()

struct input_dev *input_allocate_device(void)
{
    struct input_dev *dev;
    //配置設定一個input_dev結構體,并初始化為0
    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
    if(dev)
    {
        dev->dev.type = &input_dev_type; /*初始化裝置類型*/
        dev->dev.class = &input_class;  /*設定輸入裝置類*/
        device_initialize(&dev->dev);  /*初始化device 結構*/
        mutex_init(&dev->mutex); /*初始化互斥鎖*/
        spin_lock_init(&dev->event_lock); /*初始化事件自旋鎖*/
        INIT_LIST_HEAD(&dev->h_list); /*初始化連結清單 */
        INIT_LIST_HEAD(&dev->node); /*初始化連結清單*/<pre name="code" class="csharp">static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    /*輸入裝置的指針,該結構體表示裝置的辨別,辨別中存儲了裝置的資訊*/
    const struct input_device_id *id;
    int error;
    
    /*根據input_handler的id_table判斷能否支援這個裝置*/
    id = input_match_device(handler, dev);
    if(!id)
        return -ENODEV;
    /*調用input_handler的connect函數,建立連接配接*/
    error = handler->connect(handler, dev, id); /*連接配接裝置和處理函數*/
    if(error && error != -ENODEV)
        pr_err(“failed to attach handler %s to device %s, error: %d\n”,handler->name, kobject_name(&dev->dev.kobj), error);
    return error;
}
           

__module_get(THIS_MODULE); } return dev;}

  2、input_register_device函數

    初始化一些預設的值,将自己的結構體添加到linux裝置模型中,将input_dev添加到input_dev_list連結清單中。然後調用input_attach_handler根據input_handler的id_table判斷是否支援這個裝置。

int input_register_device(struct input_dev *dev)
{
    ...
    struct input_handler *handler;
    ...
    int error;
    __set_bit(EV_SYN, dev->evbit);  //設定輸入裝置支援同步類事件
    ...
    error = device_add(&dev->dev);  //将device添加到linux裝置模型中
    ...
    list_add_tail(&dev->node, &input_dev_list); //把input_dev放入到input_dev_list連結清單
    ...
    list_for_each_entry(handler,&input_handler_list, node)
        input_attach_handler(dev,handler);
    /*對于input_handler_list連結清單的每一項,都調用input_attach_handler,根據input_handler的id_table判斷是否支援input_dev*/
}
           

   3、input_attach_handler

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    /*輸入裝置的指針,該結構體表示裝置的辨別,辨別中存儲了裝置的資訊*/
    const struct input_device_id *id;
    int error;
    
    /*根據input_handler的id_table判斷能否支援這個裝置*/
    id = input_match_device(handler, dev);
    if(!id)
        return -ENODEV;
    /*調用input_handler的connect函數,建立連接配接*/
    error = handler->connect(handler, dev, id); /*連接配接裝置和處理函數*/
    if(error && error != -ENODEV)
        pr_err(“failed to attach handler %s to device %s, error: %d\n”,handler->name, kobject_name(&dev->dev.kobj), error);
    return error;
}
           

  4、evdev_connect函數

    evdev_connect函數在driver/input/evdev.c中。主要用來連接配接input_dev和input_handler,把事件建立起來,這樣事件才能知道被誰處理,或者處理後将推向誰傳回結果。

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)  
{  
        struct evdev *evdev;  
        int minor;  
        int error;  
        /*第 07~13 行,for 循環中的 EVDEV_MINORS 定義為 32,表示 evdev_handler 所表示的 32 個裝置檔案。 evdev_talbe 是一個 struct evdev 類型的數組,struct evdev 是子產品使用的封裝結構,與具體的輸入裝置有關。第 08 行,這一段代碼的在 evdev_talbe 找到為空的那一項,當找到為空的一項,便結束 for 循環。這時,minor 就是數組中第一項為空的序号。第 10 到13 行,如果沒有空閑的表項,則退出。*/  
      for (minor = 0; minor < EVDEV_MINORS; minor++)  
        if (!evdev_table[minor])  
                break;  
  
        if (minor == EVDEV_MINORS) {  
                printk(KERN_ERR "evdev: no more free evdev devices\n");  
                return -ENFILE;  
        }  
  
        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);/*第 14~16 行,配置設定一個 struct evdev 的空間,如果配置設定失敗,則退出。*/  
        if (!evdev)  
                return -ENOMEM;  
/*第 17~20 行,對配置設定的 evdev 結構進行初始化,主要對連結清單、互斥鎖和等待隊列做必要的封裝了一個 handle 結構,這個結構與 handler 是不同的。可以把 handle初始化。 evdev 中,在這個結構用來聯系比對成功的 handler 和 input 看成是 handler 和 input device 的資訊集合體,device。*/  
        INIT_LIST_HEAD(&evdev->client_list);  
        spin_lock_init(&evdev->client_lock);  
        mutex_init(&evdev->mutex);  
        init_waitqueue_head(&evdev->wait);  
        /*第 21 行,對 evdev 命一個名字,這個裝置的名字形如 eventx, 如 event1、 event2 和 event3等。最大有 32 個裝置,這個裝置将在/dev/input/目錄下顯示。*/  
       dev_set_name(&evdev->dev, "event%d", minor);  
       evdev->exist = 1;  
        /*第 23~27 行,對 evdev 進行必要的初始化。其中,主要對 handle 進行初始化,這些初始化的目的是使 input_dev 和 input_handler 聯系起來。*/  
        evdev->minor = minor;  
  
        evdev->handle.dev = input_get_device(dev);  
        evdev->handle.name = dev_name(&evdev->dev);  
        evdev->handle.handler = handler;  
        evdev->handle.private = evdev;  
        /*第 28~33 行,在裝置驅動模型中注冊一個 evdev->dev 的裝置,并初始化一個 evdev->dev 的裝置。這裡,使 evdev->dev 所屬的類指向 input_class。這樣在/sysfs 中建立的裝置目錄就會在/sys/class/input/下顯示。*/  
        evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);  
        evdev->dev.class = &input_class;  
        evdev->dev.parent = &dev->dev;  
        evdev->dev.release = evdev_free;  
        device_initialize(&evdev->dev);  
  
        error = input_register_handle(&evdev->handle);/*第 34 行,調用 input_register_handle()函數注冊一個 input_handle 結構體。*/  
        if (error)  
        goto err_free_evdev;  
  
        error = evdev_install_chrdev(evdev);/*第 37 行,注冊 handle,如果成功,那麼調用 evdev_install_chrdev 将 evdev_table 的 minor項指向 evdev.。*/  
        if (error)  
                goto err_unregister_handle;  
  
        error = device_add(&evdev->dev);/*将 evdev->device 注冊到 sysfs 檔案系統中。*/  
        /*第 41~50 行,進行一些必要的錯誤處理。*/  
        if (error)  
                goto err_cleanup_evdev;  
  
        return 0;  
  
        err_cleanup_evdev:  
        evdev_cleanup(evdev);  
        err_unregister_handle:  
        input_unregister_handle(&evdev->handle);  
        err_free_evdev:  
        put_device(&evdev->dev);  
        return error;  
}  
           

繼續閱讀