input子系統淺析

kernel version: linux-4.9.13


1. 概述

input子系統分爲三層:

  • 設備驅動層
  • 核心層
  • 事件處理層

設備驅動層包含各類輸入設備驅動(如觸摸屏、鼠標、鍵盤等等),獲取輸入事件並上報;
核心層根據輸入設備種類,分發事件至不同的事件處理器;
事件處理層包含:通用事件處理器(evdev)、鼠標事件處理器(mousedev)、搖桿事件處理器(joydev),緩存事件並提供接口等待用戶獲取。


input_list

2. 數據結構

2.1 輸入設備 —— input_dev

每個輸入設備驅動中都實例化了一個input_dev對象,用於記錄設備硬件相關信息、事件位圖設置(支持的事件類型)以及其他參數。

struct input_dev {
    const char *name;   // 設備名稱
    /*
    struct input_id {
        __u16 bustype;  // 總線 
        __u16 vendor;   // 廠商
        __u16 product;  // 產品
        __u16 version;  // 版本
    };    
    */
    struct input_id id; // 設備id,用於與input_handler匹配

    ......

    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)];   // 絕對位移事件位圖
    ......

    struct device dev;

    struct list_head    h_list;     // 內核鏈表頭
    struct list_head    node;       // 內核鏈表節點

    ......
};

2.2 事件處理器 —— input_handler

同樣,每個事件處理器也都實例化了input_handler對象,如evdev_handler、mousedev_handler、joydev_handler等等,input_handler類中提供了事件處理、與輸入設備匹配連接相關的接口。

struct input_handler {

    void *private;

    /* 事件處理接口 */
    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    void (*events)(struct input_handle *handle,
               const struct input_value *vals, unsigned int count);
    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);

    bool legacy_minors;
    int minor;
    const char *name;

    /* 輸入設備支持列表,用於與input_dev匹配 */
    const struct input_device_id *id_table;  

    struct list_head    h_list;     // 內核鏈表頭
    struct list_head    node;       // 內核鏈表節點
};

3. 輸入設備與事件處理器的匹配連接

input子系統維護了兩條重要鏈表:

  • 輸入設備鏈表 —— input_dev_list
  • 事件處理器鏈表 —— input_handler_list

兩條鏈表在覈心層input.c中靜態聲明並初始化:

static LIST_HEAD(input_dev_list);
static LIST_HEAD(input_handler_list);


input_list

當一個輸入設備註冊時,它會被加入到輸入設備鏈表(input_dev_list),同時遍歷事件處理器鏈表(input_handler_list)進行匹配:

int input_register_device(struct input_dev *dev)
{
    ......

    // 將輸入設備加入內核鏈表 
    list_add_tail(&dev->node, &input_dev_list);

    // 遍歷事件處理器鏈接匹配
    list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);

    ......
}

匹配過程以事件處理器的id_table爲判斷條件1,如果輸入設備與事件處理器匹配成功,則調用事件處理器的connect()方法進一步處理,以通用事件處理器(evdev)爲例,最終調用evdev_connect():

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    ......

    int minor;
    int dev_no;

    // 獲取次設備號
    minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
    if (minor < 0) {
        error = minor;
        pr_err("failed to reserve new minor: %d\n", error);
        return error;
    }

    // 設置設備節點名字,根據註冊順序依次爲event0、event1......
    dev_no = minor;
    /* Normalize device number if it falls into legacy range */
    if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
        dev_no -= EVDEV_MINOR_BASE;
    dev_set_name(&evdev->dev, "event%d", dev_no);

    // 以主、次設備號合成設備號
    evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
    // 關聯input設備類    
    evdev->dev.class = &input_class;
    // 繼承input_dev父類
    evdev->dev.parent = &dev->dev;
    evdev->dev.release = evdev_free;
    device_initialize(&evdev->dev);

    // 字符設備初始化及註冊
    cdev_init(&evdev->cdev, &evdev_fops);
    evdev->cdev.kobj.parent = &evdev->dev.kobj;
    error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
    if (error)
        goto err_unregister_handle;

    error = device_add(&evdev->dev);
    if (error)
        goto err_cleanup_evdev;

    return 0;
}

在上述代碼中可以看到,輸入設備調用input_register_device()函數註冊時,如果與通用事件處理器(evdev)成功配對,最後就會生成字符設備節點(/dev/input/eventN),這就是用戶空間獲取內核輸入事件的接口。

4. 輸入事件的上報流程

輸入事件的傳遞以input_event爲基本單位:

struct input_event {
    struct timeval time;    // 時間戳
    __u16 type;             // 事件總類型
    __u16 code;             // 事件子類型
    __s32 value;            // 事件值      
};


input_event

在輸入設備驅動(input_dev)中,一般通過輪詢或中斷方式獲取輸入事件的原始值(raw value),經過處理後再使用input_event()函數上報;核心層將事件數據(type、code、value)打包、分發至事件處理器;事件處理器給輸入事件加上時間戳,最後傳遞至上層用戶空間。

整個上報流程的關鍵點在於事件處理器與用戶空間的交互,以通用事件處理器(evdev)爲例,其與用戶程序組成了一對多的C/S2通信模型:


evdev_client

通用事件處理器(evdev)代表服務器,每一個打開設備節點(/dev/input/eventN)的用戶程序則代表一個連接的客戶端。

在evdev.c中有着對應描述的結構體:

// 服務器
struct evdev {
    int open;
    struct input_handle handle;
    wait_queue_head_t wait;
    struct evdev_client __rcu *grab;
    struct list_head client_list;       // 客戶端連接列表
    spinlock_t client_lock; 
    struct mutex mutex;
    struct device dev;
    struct cdev cdev;
    bool exist;
};

// 客戶端
struct evdev_client {
    unsigned int head;
    unsigned int tail;
    unsigned int packet_head; 
    spinlock_t buffer_lock;
    struct wake_lock wake_lock;
    bool use_wake_lock;
    char name[28];
    struct fasync_struct *fasync;
    struct evdev *evdev;
    struct list_head node;
    int clkid;
    unsigned int bufsize;
    struct input_event buffer[];    // 輸入事件環形緩衝區
};

設備驅動上報事件並不是直接傳遞給用戶程序,在通用事件處理器(evdev)中,事件被緩存在緩衝區中,當緩衝區不爲空時,用戶程序主動讀取緩衝區獲取事件。


producer-consumer

這個緩衝區就是在evdev_client中定義的環形緩衝區,也就是說每個打開設備節點的用戶程序,在建立客戶端連接的同時,也構建了自己的緩衝區用以獲取事件,不同的客戶端之間互不干涉。

關於緩衝區的詳細分析請參考:input子系統事件處理層(evdev)的環形緩衝區


  1. 通用事件處理器(evdev)的id_table設置爲匹配所有輸入設備
  2. Client/Server,客戶端與服務器架構
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章