Input_subsystem_從三個基礎結構到事件上報流程
從三個基礎結構到事件上報流程
宣告:這篇部落格大部分來自
https://blog.csdn.net/u014545515/article/details/53507567?utm_source=blogxgwz1
我跟著這篇部落格的思路一路下來,覺得非常有收貨,也寫入自己的感悟。
廣泛,並且深入
-沉默犀牛
三個層級
事件處理層:evdev.c 、mousedev.c
核心層:input.c
裝置驅動層:touchscreen、mouse
事件處理層:通過核心層的API獲取輸入事件上報的資料,定義API與應用層互動
核心層:為事件處理層和裝置驅動層提供介面API
裝置驅動層:採集輸入裝置的資料資訊,通過核心層提供的API上報資料
基礎資料結構
struct input_dev:會在具體input driver 層中被填充
struct input_handle:會在event handler層和input driver層註冊裝置時通過input_dev或input_handler間接呼叫
struct input_handler:會在event handler層(如evdev.c)中被例項化
input_dev
struct input_dev { const char *name; /*匯出到使用者空間的相關資訊,在sys檔案可以看到*/ const char *phys; const char *uniq; struct input_id id; /*與input_handler匹配用的id*/ unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; /*輸入裝置的事件支援點陣圖*/ 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, const struct input_keymap_entry *ke, unsigned int *old_keycode); int (*getkeycode)(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 *mt; struct input_absinfo *absinfo; unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //反應裝置當前的按鍵狀態 unsigned long led[BITS_TO_LONGS(LED_CNT)]; //反應裝置當前的led狀態 unsigned long snd[BITS_TO_LONGS(SND_CNT)]; //反應裝置當前的聲音輸入狀態 unsigned long sw[BITS_TO_LONGS(SW_CNT)]; //反應裝置當前的開關狀態 int (*open)(struct input_dev *dev); //第一次開啟裝置時呼叫,初始化裝置用 void (*close)(struct input_dev *dev); //最後一個應用程式釋放裝置時用,關閉裝置 int (*flush)(struct input_dev *dev, struct file *file); //用於處理傳遞給裝置的事件,如LED事件 int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); struct input_handle __rcu *grab; //當前佔有該裝置的input_handle spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; struct device dev; struct list_head h_list; //該連結串列頭用於連結此input_dev所關聯的input_handle struct list_head node; //用於將此input_dev連結到input_dev_list unsigned int num_vals; unsigned int max_vals; struct input_value *vals; bool devres_managed; };
input_handler
struct input_handler { void *private; /*event用於處理事件*/ 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); /*connect用於建立handler和device的聯絡*/ int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); /*disconnect用於解除handler和device的聯絡*/ void (*start)(struct input_handle *handle); void (*start)(struct input_handle *handle); bool legacy_minors; int minor; //次裝置號 const char *name; const struct input_device_id *id_table; //用於和input_dev匹配 struct list_head h_list; //用於連結和此input_handler相關的input_handle struct list_head node; //用於將該input_handler鏈入input_handler_list };
input_handle
struct input_handle {
void *private;
int open; //記錄裝置的開啟次數(有多少個應用程式訪問裝置)
const char *name;
struct input_dev *dev; //指向所屬的device
struct input_handler *handler; //指向所屬的handler
struct list_head d_node; //用於將此input_handle鏈入所屬input_dev的h_list連結串列
struct list_head h_node; //用於將此input_handle鏈入所屬input_handler的h_list連結串列
};
這個圖中的紅線和綠線連錯了
input_handle中的d_node 和 h_node的任務是把該input_handle鏈入到所屬的input_dev和input_handler中,所以紅線和綠線應該連在同一列的input_dev和input_handler中才對
三大結構體關係淺析
1.input_handle
是連線 input_device
和 input_handler
的橋樑
2.input_device
可以通過input_handle
找到input_handler
,同樣的input_handler
可以通過input_handle
找到input_device
3.一個device
可能對應多個handler
,而一個handler
也不能只處理一個device
,比如說一個滑鼠,它可以對應evdev_handler
,也可以對應mouse_handler
,因此當其註冊時與系統中的handler
進行匹配,就有可能產生兩個例項,一個是evdev
,另一個是mousedev
,而任何一個例項中都只有一個handle
,至於以何種方式來傳遞事件,就由使用者程式開啟哪個例項來決定
4.後面一個情況很容易理解,一個事件驅動不能只為一個甚至一種裝置服務,系統中可能有多種裝置都能使用這類handler
,比如event handler
就可以匹配所有的裝置
5.在input子系統中,有8
種事件驅動,每種事件驅動最多可以對應32
個裝置,因此dev
例項總數最多可以達到256
個
1) input_dev
以下用synapticsTP驅動的程式碼為例子,分析程式碼如何實現。無關input subsystem的省略了
static int synaptics_rmi4_probe
(struct i2c_client *client, const struct i2c_device_id *dev_id)
{
int retval;
unsigned char intr_status[4];
struct synaptics_rmi4_data *rmi4_data;
const struct synaptics_rmi4_platform_data *platformdata =
client->dev.platform_data;
/* Allocate and initialize the instance data for this client */
rmi4_data = kcalloc(2, sizeof(struct synaptics_rmi4_data), //開闢該驅動的資料結構空間
GFP_KERNEL);
if (!rmi4_data)
return -ENOMEM;
rmi4_data->input_dev = input_allocate_device(); //分配一個input_dev,其中設定了一些互斥鎖、連結串列頭、和名字
if (rmi4_data->input_dev == NULL) {
retval = -ENOMEM;
goto err_input;
}
...
...
...
rmi4_data->input_dev->name = DRIVER_NAME;
rmi4_data->input_dev->phys = "Synaptics_Clearpad";
rmi4_data->input_dev->id.bustype = BUS_I2C;
rmi4_data->input_dev->dev.parent = &client->dev;
input_set_drvdata(rmi4_data->input_dev, rmi4_data);
//設定引數,用的MT_PROTOCOL_B
set_bit(EV_SYN, rmi4_data->input_dev->evbit);
set_bit(EV_KEY, rmi4_data->input_dev->evbit);
set_bit(EV_ABS, rmi4_data->input_dev->evbit);
input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_X, 0,
rmi4_data->sensor_max_x, 0, 0);
input_set_abs_params(rmi4_data->input_dev, ABS_MT_POSITION_Y, 0,
rmi4_data->sensor_max_y, 0, 0);
input_set_abs_params(rmi4_data->input_dev, ABS_MT_TOUCH_MAJOR, 0,
MAX_TOUCH_MAJOR, 0, 0);
input_mt_init_slots(rmi4_data->input_dev,
rmi4_data->fingers_supported, 0);
...
retval = request_threaded_irq(client->irq, NULL, //設定中斷處理函式 synaptics_rmi4_irq
synaptics_rmi4_irq,
platformdata->irq_type,
DRIVER_NAME, rmi4_data);
if (retval) {
dev_err(&client->dev, "%s:Unable to get attn irq %d\n",
__func__, client->irq);
goto err_query_dev;
}
retval = input_register_device(rmi4_data->input_dev); //註冊到input core中
if (retval) {
dev_err(&client->dev, "%s:input register failed\n", __func__);
goto err_free_irq;
}
...
return retval;
...
...
}
關注一下input_register_device()函式
int input_register_device(struct input_dev *dev)
{
...
/* Every input device generates EV_SYN/SYN_REPORT events. */
__set_bit(EV_SYN, dev->evbit);
/* 事件型別由input_dev的evbit成員來表示,
在這裡將其EV_SYN置位,表示裝置支援所有的事件。
注意,一個裝置可以支援一種或者多種事件型別
#define EV_SYN 0x00 //表示裝置支援所有的事件
#define EV_KEY 0x01 //鍵盤或者按鍵,表示一個鍵碼
#define EV_REL 0x02 //滑鼠裝置,表示一個相對的游標位置結果
#define EV_ABS 0x03 //手寫板產生的值,其是一個絕對整數值
#define EV_MSC 0x04 //其他型別
#define EV_LED 0x11 //LED燈裝置
#define EV_SND 0x12 //蜂鳴器,輸入聲音
#define EV_REP 0x14 //允許重複按鍵型別
#define EV_PWR 0x16 //電源管理事件
*/
/* KEY_RESERVED is not supposed to be transmitted to userspace. */
__clear_bit(KEY_RESERVED, dev->keybit);
...
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
/*
* rep主要是處理重複按鍵,如果沒有定義dev->rep[REP_DELAY]和 dev->rep[REP_PERIOD],
* 則將其賦值為預設值。dev->rep[REP_DELAY]是指第一次按下多久算一次,這裡是250ms,
* dev->rep[REP_PERIOD]指如果按鍵沒有被擡起,每33ms算一次。
*/
//如果dev沒有定義getkeycode和setkeycode,則賦預設值。
//他們的作用一個是獲得鍵的掃描碼,一個是設定鍵的掃描碼
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
error = device_add(&dev->dev); /*將input_dev封裝的dev註冊到sysfs*/
if (error)
goto err_free_vals;
...
/*將input_dev掛在input_dev_list上*/
list_add_tail(&dev->node, &input_dev_list); //將新分配的input裝置連線到input_dev_list連結串列上
list_for_each_entry(handler, &input_handler_list, node) //巨集for迴圈
input_attach_handler(dev, handler);
//input_attach_handler的主要功能就是呼叫了兩個函式,
//一個input_match_device進行配對,
//一個connect處理配對成功後續工作,遍歷input_handler_list連結串列,配對 input_dev 和 input_handler
//input_attach_handler 這個函式是配對的關鍵
/*
* 這裡先把input_dev掛載到input_dev_list 連結串列上,然後對每個掛載到input_handler_list 的handler
* 呼叫input_attach_handler(dev, handler); 去匹配。 所有的input_dev掛載到input_dev_list 連結串列上
* 所有的handler掛載到input_handler_list 上
*/
...
}
這個函式就是為input_dev
設定預設值,然後將其掛在input_dev_list
上,與掛在input_handler_list
中的handler
相匹配,如果匹配成功,就呼叫handler
的connect
函式
接下來看看 input_attach_handler
是怎麼做的
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
const struct input_device_id *id;
int error;
id = input_match_device(handler, dev); /*匹配id*/
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id); /*如果匹配,則呼叫具體的handler的connect函式*/
//evdev_connect函式做配對後的善後工作,分配一個evdev結構體,
//並初始化相關成員,evdev結構體中有input_handle結構,初始化並註冊之。
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;
}
關於input_match_device
函式,它是通過匹配id
來確認匹配的,看handler
的id
是否支援,在事件處理層有這麼一段程式碼,以evdev.c
為例:
static const struct input_device_id evdev_ids[] = {
{ .driver_info = 1 }, /* Matches all devices */
{ }, /* Terminating zero entry */
};
這是對 input_handler 的成員id_table 的初始化
evdev_ids沒有定義flag,也沒有定義匹配屬性值
這個evdev_ids的意思就是:evdev_handler可以匹配所有input_dev裝置
也就是所有的input_dev發出的事件,都可以由evdev_handler來處理
特別注意,input_attach_handler
中通過handler
呼叫的connect
函式,是在事件處理層定義並實現的,以evdev.c
為例,則connect
函式就是evdev_connect
evdev_connect
()函式主要用來連線input_dev
和input_handler
,這樣事件的流通鏈才能建立,流通鏈建立後,事件才知道被誰處理,或者處理後將向誰返回結果
2) input_handler
input_handler 是在event handler層進行例項化的,在evdev.c中:
static struct input_handler evdev_handler = {
.event = evdev_event,
.events = evdev_events,
.connect = evdev_connect,
.disconnect = evdev_disconnect,
.legacy_minors = true,
.minor = EVDEV_MINOR_BASE,
.name = "evdev",
.id_table = evdev_ids,
};
static int __init evdev_init(void)
{
return input_register_handler(&evdev_handler);
}
static void __exit evdev_exit(void)
{
input_unregister_handler(&evdev_handler);
}
module_init(evdev_init);
module_exit(evdev_exit);
可以看出,evdev_handler
的註冊是放在平臺初始化函式中進行的,也就是說,evdev.c
一旦載入,就會執行evdev_init
函式,則input_register_handler
函式立即執行
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int error;
error = mutex_lock_interruptible(&input_mutex);
if (error)
return error;
INIT_LIST_HEAD(&handler->h_list);
list_add_tail(&handler->node, &input_handler_list);
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
由此可見,input_register_handler
和input_register_device
本質上做了一樣的事情(都是在找與自己匹配的input_dev
/input_handler
),input_attach_handler
也會呼叫handler->connect
接下來看看evdev.c
中的connect
函式:
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id)
{
struct evdev *evdev;
int minor;
int dev_no;
int error;
/*
* EVDEV_MINORS定義為32.表示evdev_handler所表示的32個裝置檔案.
* evdev_table是一個struct evdev型別的全域性陣列.struct evdev是模組使用的封裝結構
* 在evdev_table找到為空的那一項,當找到為空的一項,便結束for迴圈。
* 這時,minor就是陣列中第一項為空的序號
*/
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;
}
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
if (!evdev) {
error = -ENOMEM;
goto err_free_minor;
}
/*
* 對分配的evdev結構進行初始化,
* 主要對連結串列、互斥鎖和等待佇列做必要的初始化。
* 在evdev中,封裝了一個handle結構,這個結構與handler是不同的。
* 可以把handle看成是handler和input device的資訊集合體,
* 這個結構用來聯絡匹配成功的handler和input device。
*/
INIT_LIST_HEAD(&evdev->client_list);
spin_lock_init(&evdev->client_lock);
mutex_init(&evdev->mutex);
init_waitqueue_head(&evdev->wait);
evdev->exist = true;
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->handle.dev = input_get_device(dev);
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler;
evdev->handle.private = evdev;
/*
* 在裝置驅動模型中註冊一個evdev->dev的裝置,並初始化一個evdev->dev的裝置
* 這裡,使evdev->dev所屬的類指向input_class
* 這樣在/sysfs中建立的裝置目錄就會在/sys/class/input/下顯示
*/
evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
evdev->dev.class = &input_class;
evdev->dev.parent = &dev->dev;
evdev->dev.release = evdev_free;
device_initialize(&evdev->dev);
/*
*input_register_handle完成的主要功能是:
*list_add_tail_rcu(&handle->d_node, &dev->h_list);
*list_add_tail(&handle->h_node, &handler->h_list);
*/
error = input_register_handle(&evdev->handle);
if (error)
goto err_free_evdev;
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); //將evdev->device註冊到sysfs檔案系統中
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);
err_free_minor:
input_free_minor(minor);
return error;
}
如上,input_dev和input_handler註冊時都要呼叫的handler->connect,所要完成的最主要的任務,就是填充並註冊input_handle(順便初始化了一些連結串列頭、鎖、等待佇列)
input_register_handle函式把handle通過d_node掛到input_dev的h_list,通過h_node掛到handler的h_list上,這樣input_dev、input_handler、input_handle就聯絡起來了,可與前面的框圖對照
事件上報流程
在input driver
中,完成對input_dev
引數的設定,把input_dev
註冊到input core
中,然後在適當的時候(比如,外部觸發中斷)由input_driver
層把鍵值填充成包,傳送給input core
層,input core
會把資料交給(與傳送資料的input_dev
匹配的)input_handler
處理,處理後的結果上報給使用者空間
在此用幾句話,總結一下上面講述的內容:
Q1:input_dev
怎麼註冊到input core
?
A1:以TP為例,在driver的probe中填充input_dev
必要的成員,設定好input需要的引數,呼叫input_register_device
註冊到input core
上
Q2:input_handler
怎麼註冊到input core
?
A2:在平臺初始化期間,以模組的形式,註冊到core上
Q3:input_dev
和 input_handler
怎麼匹配?通過什麼緊緊的聯絡在一起?
A3:input_dev
註冊到input core
後,就開始遍歷input_handler_list
上跟自己匹配的handler
,同理,input_handler
註冊到input core
後,也開始遍歷input_dev_list
上跟自己匹配的dev,(用id匹配)成功匹配好後,就呼叫handler->connect
函式來生成一個input_handle
,把input_dev
和input_handler
聯絡到一起
Q4:之前說過,把input_dev
把鍵值上報給core後,交由匹配的handler
來處理,那是怎麼處理的呢?
A4:
1.event handler
層與上層互動,用到了一個例項化的file_operations
,以evdev為例,這個例項化物件就是evdev_fops
,這裡面的成員函式就是對上層提供的介面,當上層要進行讀操作,就會呼叫evdev_read
函式
2.這個evdev_read
函式的最後,實現了一個等待佇列來等待事件,沒有事件就阻塞。(我們只有在觸控按鍵事件發生,中斷到來,我們採取上報按鍵事件,並喚醒阻塞,讓event handler
層的evdev_read
將鍵值最終通過copy_to_user
送到使用者空間)
3.既然把鍵值交由給了input_handler
來處理,那肯定是input_handler
控制喚醒了阻塞,實現把鍵值上報給使用者空間了,接下來看看怎麼控制喚醒了阻塞
4.在TP的driver中斷服務函式中,一定呼叫了input_sync
來把鍵值上報給core,然後經歷以下的呼叫流程:input_sync
->input_event
->input_handle_event
->input_pass_values
->input_to_handler
,在input_to_handler
中就呼叫了handler
->events
和handler
->event
,這裡出現的handler
->events
和handler
->event
就是event handler
層定義的evdev_handler
的成員函式evdev_events
、evdev_event
。evdev_event
最終也呼叫了evdev_events
,然後呼叫evdev_pass_values
,evdev_pass_values
這個函式最終呼叫了wake_up_interruptible
函式,喚醒了睡眠,這就與上面說的evdev_read
函式聯絡上了。
我們在evdev_read函式中通過等待佇列實現了阻塞,等待輸入事件的隨機發生
而在觸控按鍵事件隨機發生後,裝置驅動層程式碼通過input_report_abs等函式上報資料,逐級呼叫,通過在事件處理層evdev.c裡定義的evdev_handler->events處理事件,最終呼叫到evdev_pass_values並喚醒睡眠
一旦在evdev_read中的等待佇列被喚醒,則進入下一輪迴圈上報新的鍵值給使用者空間