kernel version: linux-4.9.13
1. 概述
input子系統分爲三層:
- 設備驅動層
- 核心層
- 事件處理層
設備驅動層包含各類輸入設備驅動(如觸摸屏、鼠標、鍵盤等等),獲取輸入事件並上報;
核心層根據輸入設備種類,分發事件至不同的事件處理器;
事件處理層包含:通用事件處理器(evdev)、鼠標事件處理器(mousedev)、搖桿事件處理器(joydev),緩存事件並提供接口等待用戶獲取。
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_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_dev)中,一般通過輪詢或中斷方式獲取輸入事件的原始值(raw value),經過處理後再使用input_event()函數上報;核心層將事件數據(type、code、value)打包、分發至事件處理器;事件處理器給輸入事件加上時間戳,最後傳遞至上層用戶空間。
整個上報流程的關鍵點在於事件處理器與用戶空間的交互,以通用事件處理器(evdev)爲例,其與用戶程序組成了一對多的C/S2通信模型:
通用事件處理器(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)中,事件被緩存在緩衝區中,當緩衝區不爲空時,用戶程序主動讀取緩衝區獲取事件。
這個緩衝區就是在evdev_client中定義的環形緩衝區,也就是說每個打開設備節點的用戶程序,在建立客戶端連接的同時,也構建了自己的緩衝區用以獲取事件,不同的客戶端之間互不干涉。
關於緩衝區的詳細分析請參考:input子系統事件處理層(evdev)的環形緩衝區