1. 程式人生 > >Input_subsystem_從三個基礎結構到事件上報流程

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_deviceinput_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相匹配,如果匹配成功,就呼叫handlerconnect函式

接下來看看 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來確認匹配的,看handlerid是否支援,在事件處理層有這麼一段程式碼,以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_devinput_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_handlerinput_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_devinput_handler怎麼匹配?通過什麼緊緊的聯絡在一起?
A3:input_dev註冊到input core後,就開始遍歷input_handler_list上跟自己匹配的handler,同理,input_handler註冊到input core後,也開始遍歷input_dev_list上跟自己匹配的dev,(用id匹配)成功匹配好後,就呼叫handler->connect函式來生成一個input_handle,把input_devinput_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->eventshandler->event,這裡出現的handler->eventshandler->event就是event handler層定義的evdev_handler的成員函式evdev_eventsevdev_eventevdev_event最終也呼叫了evdev_events,然後呼叫evdev_pass_valuesevdev_pass_values這個函式最終呼叫了wake_up_interruptible函式,喚醒了睡眠,這就與上面說的evdev_read函式聯絡上了。

我們在evdev_read函式中通過等待佇列實現了阻塞,等待輸入事件的隨機發生
而在觸控按鍵事件隨機發生後,裝置驅動層程式碼通過input_report_abs等函式上報資料,逐級呼叫,通過在事件處理層evdev.c裡定義的evdev_handler->events處理事件,最終呼叫到evdev_pass_values並喚醒睡眠
一旦在evdev_read中的等待佇列被喚醒,則進入下一輪迴圈上報新的鍵值給使用者空間