天天看點

input子系統相關

本文是我學習時所寫,非百分之百原創,望指出錯誤之處。

參考資料:

input子系統按鍵處理

INPUT輸入子系統

在系統中會出現很多的input裝置,比如:鍵盤、螢幕等等,這些實體裝置都會統一的抽象為input裝置。

用來抽象這些裝置的資料結構是struct input_dev結構,該結構如下:

input輸入子系統的架構如下圖:

input子系統相關

從中我們可以得知,input輸入子系統分為上中下三個部分。

1、下層裝置驅動層。系統中可以注冊多個input輸入裝置,每個裝置對應不同的實體硬體裝置,比如:鍵盤input裝置、滑鼠input裝置等等。

2、上層為處理程式(handlers),事件驅動層。不同的input裝置在這一層都會有不同的handlers所對應,不同的handlers在在=應用層中的接口命名方式又不一樣,例如:Mouse下的輸入裝置在應用層的接口是 /dev/input/mousen (n代表0、1、2…),Joystick下的輸入裝置在應用層的接口是 /dev/input/jsn(n代表0、1、2…),這個是在input輸入子系統中實作的。

3、中間的輸入核心層。從圖中我們可以看到,輸入核心層起到上下層中進行資料傳輸的作用,當下層發生任意輸入事件是,核心層便被激活,然後将該事件傳輸給上層的一個或多個handlers中去。

input核心層分别為裝置驅動層和事件驅動層提供了相關的API接口。

提供給裝置驅動層的API

裝置驅動層用struct input_dev結構來抽象一個硬體裝置。

以下為struct input_dev的結構:

struct input_dev {
	const char *name;
 	const char *phys;
	const char *uniq;
	struct input_id id;
	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];     //表示此input裝置支援的事件
	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)];
	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 keycodemax;
	unsigned int keycodesize;
	void *keycode;
	int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
	int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);
	struct ff_device *ff;
	unsigned int repeat_key;
	struct timer_list timer;
	int sync;
	int abs[ABS_MAX + 1];
	int rep[REP_MAX + 1];
	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 absmax[ABS_MAX + 1];
	int absmin[ABS_MAX + 1];
	int absfuzz[ABS_MAX + 1];
	int absflat[ABS_MAX + 1]; 
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	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 *grab;	//目前占有該裝置的handle
	spinlock_t event_lock;
	struct mutex mutex;
	unsigned int users;
	int going_away;
	struct device dev;
	struct list_head  h_list;//用來挂接dev上連接配接的所有handle的一個連結清單頭
	struct list_head  node;//作為一個連結清單節點将自己挂接到 全局input_dev_list 連結清單上
};
           

其中,成員unsigned long evbit[BITS_TO_LONGS(EV_CNT)],表示此input裝置支援的事件,比如:EV_SYN(同步事件)、EV_KEY(按鍵事件)、EV_SW(開關事件)、EV_ABS(絕對坐标事件)、EV_REL(相對坐标事件)、EV_LED(LED燈事件)、EV_SND(聲音事件)、EV_REP(重複按鍵事件)、EV_FF(受力事件)、EV_PWR(電源相關事件)。

成員unsigned long keybit[BITS_TO_LONGS(KEY_CNT)],表示

成員unsigned long absbit[BITS_TO_LONGS(ABS_CNT)],設定相應的位以支援某一類絕對值坐标。

核心層提供給裝置驅動層的API主要有三個,如下所示:

struct input_dev *input_allocate_device(void);//申請一個input裝置
int input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);
/*函數input_set_capability,該函數用來裝置準備注冊的input裝置所支援的上報事件的類型type,如EV_KEY;以及需要上報的事件的code,
如KEY_POWER*/
int input_register_device(struct input_dev *dev);//注冊一個已經設定好了的input裝置dev
           

我們在編寫某個輸入裝置的驅動程式時,一般會先使用函數input_allocate_device申請一個input裝置。然後使用input_set_capability函數對該input裝置進行設定,例如支援哪類事件,具體事件的code等等。然後調用input_register_device函數将input裝置注冊到核心中。

#input_register_device注冊函數中重要的調用關系如下:
int input_register_device(struct input_dev *dev)  #input裝置注冊函數
	__set_bit(EV_SYN, dev->evbit); #首先将該裝置将EV_SYN置位,表示支援所有事件
	init_timer(&dev->timer); #初始化一個定時器,該定時器在處理重複事件時發揮作用
	device_add(&dev->dev); #将input_dev内置的struct device dev結構注冊到Linux裝置模型中去
	list_add_tail(&dev->node, &input_dev_list);#将我們注冊的dev加入到input_dev_list連結清單中
#input核心層維護兩個全局連結清單,分别為input_dev_list和input_handler_list,用力啊存放所有的struct input_dev和struct input_handler
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);#這句和上一句是一個循環,周遊input_handler_list上的handler,用于和我們現在的dev比對。
		
#input_attach_handler比對函數的調用關系如下:
input_attach_handler(struct input_dev *dev, struct input_handler*handler)
	if (handler->blacklist&& input_match_device(handler->blacklist, dev)) 
                  return -ENODEV; 
    #以上連句表示先判斷handler中的blacklist字段是否定義,定了則先對handler->blacklist和dev->id進行比對
    #handler->blacklist是struct input_device_id結構的資料,dev->id是struct input_id結構的資料
    id = input_match_device(handler->id_table, dev);
    #這一句将handler中的id_table字段和dev->id進行比對,比對成功,傳回struct input_device_id類型資料指針
    #handler->id_table也是struct input_device_id結構的資料
    handler->connect(handler, dev, id);#當該input裝置和某個handler比對成功後會調用對應handler中的connect()函數
	#connect()函數的作用就是将handler和input_dev使用struct input_handle結構連接配接起來
	#并通過struct input_handle結構組成的連結清單維護所有的已經比對成功的handler和input_dev
           

提供給裝置驅動層的API還有如下幾個:

static inline void input_set_abs_params(struct input_dev *dev, int axis, int min, int max, int fuzz, int flat);
input_set_abs_params(input_dev, ABS_RX, 0, 23040, 0, 0);
/*函數input_set_abs_params,參數dev表示input裝置,支援絕對值x坐标,并設定它在坐标系中的最大值和最小值,以及幹擾值和平焊位置等。
就是通過設定absbit成員實作的。*/

static void input_handle_event(structinput_dev *dev,unsigned int type,unsigned int code, int value);
/*函數input_handle_event,該函數向核心層上報事件,參數dev是經過注冊了的input裝置指針,參數type表示事件類型,如:EV_KEY、EV_SYN等*/
           

在input_handle_event函數中,會判斷發生的事件應該是繼續往上發送給handler還是發送給裝置的,如讓 LED 燈點亮事件、蜂鳴器鳴叫事件等,這些事件就要發送給裝置,需要調用struct input_dev結構中的event()函數;如果是發送給handler層處理,則調用 input_pass_event()函數。

提供給事件驅動層的API

提供給事件驅動層的API主要由以下兩個:

int input_register_handler(struct input_handler *handler);//向核心層注冊handler
int input_register_handle(struct input_handle *handle);//注冊
           

以下為struct input_handler結構:

struct input_handler {
    void *private;//  私有資料

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    //handler用于向上層上報輸入事件的函數
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);
    //match 函數用來比對handler 與 input_dev 裝置
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
    //當handler 與 input_dev 比對成功之後調用該函數
    void (*disconnect)(struct input_handle *handle);
    //斷開handler 與 input_dev 之間的連接配接
    void (*start)(struct input_handle *handle);

    const struct file_operations *fops;//  一個file_operations 指針,指向這個handler的操作函數
    int minor;
    //該handler 的編号 (在input_table 數組中用來計算數組下标) input_table數組就是input子系統用來管理注冊的handler的一個資料結構
    const char *name;//handler的名字

    const struct input_device_id *id_table;//指向一個 input_device_id類型的數組,用來進行與input裝置比對時用到的資訊
	/*id_table會和struct input_dev結構中的id字段進行比較*/
	const struct input_device_id *blacklist; //指向一個 input_device_id類型的數組,這個數組包含 handler 應該忽略的裝置
    struct list_head    h_list;
    //用來挂接handler上連接配接的所有handle的一個連結清單頭
    struct list_head    node;
    /*作為一個連結清單節點将自己挂接到 input_handler_list 連結清單上
    (input_handler_list 連結清單是一個由上層handler參維護的一個用來挂接所有注冊的handler的連結清單頭)*/
};
           

想要向核心層注冊handler時,就需要将以上結構體中,必要的字段進行填充,然後調用input_register_handler函數。

#input_register_handler函數的調用關系如下:
int input_register_handler(struct input_handler *handler)
	list_add_tail(&handler->node,&input_handler_list);
	#以上這句将 handler 加入全局的 input_handler_list 連結清單中,該連結清單包含了系統中所有的 input_handler
	 list_for_each_entry(dev,&input_dev_list, node)
     	input_attach_handler(dev, handler);
     #以上兩句是一個循環,之前介紹input_register_device時介紹過,周遊全局input_dev_list連結清單,與之和handler對比
           

input_register_handle函數在什麼地方調用呢,在struct input_handler結構中,有一個函數connect,這個函數作用就是當handler和input_device比對成功之後就調用該函數,将通過struct input_handle結建構立起關系,并向核心層注冊這個struct input_handle結構。

struct input_handle的結構如下:

struct input_handle {
    void *private;//handle  的私有資料

    int open;//  這個用來做打開計數的
    const char *name;//   該handle 的名字

    struct input_dev *dev;//  用來指向該handle 綁定的input_dev 結構體
    struct input_handler *handler;//  用來指向該handle 綁定的 handler 結構體

    struct list_head    d_node;//  作為一個連結清單節點挂接到與他綁定的input_dev->hlist 連結清單上
    struct list_head    h_node;//  作為一個連結清單節點挂接到與他綁定的handler->hlist 連結清單上
};
           

在input_register_handle函數中,将handle挂到對應input_dev的h_list連結清單中,使用d_node挂載;也會将handle挂到對應handler的h_list中,使用h_node挂載。

#input_register_handle函數調用關系如下
int input_register_handle(struct input_handle *handle);
	  list_add_tail_rcu(&handle->d_node,&dev->h_list);#将d_node挂載到h_list下
	  list_add_tail(&handle->h_node,&handler->h_list);#将h_ndde挂載到h_list下
	  if (handler->start)
      	handler->start(handle);
      	#以上兩句表示,如果handler定義了start()函數,則執行該函數
           

通過以上步驟,便将handler和input_device關聯起來了。

事件上報

之前我們說過input_handle_event函數是核心層提供的事件上報函數,那接下來就跟一下這個函數,如下:

#input_handle_event函數調用關系如下
input_handle_event(struct input_dev *dev,unsigned int type,unsigned int code, int value);
	 if ((disposition &INPUT_PASS_TO_DEVICE) && dev->event)
     	dev->event(dev,type, code, value);
     #以上兩句表示,如果該事件需要傳遞給裝置,則調用裝置的event函數
     if(disposition & INPUT_PASS_TO_HANDLERS)
     	input_pass_event(dev, type, code, value);
     #以上兩句表示,如果事件需要傳遞給handler處理,則調用input_pass_event函數處理
#input_pass_event函數調用關系如下
input_pass_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);
	 handle= rcu_dereference(dev->grab);
	 #以上一句表示,通過struct input_dev的grab字段獲得與該input_dev占有的handle
	  if(handle)
      		handle->handler->event(handle,type, code, value);
        else
        	list_for_each_entry_rcu(handle,&dev->h_list, d_node)  #一般情況下走這裡
	 			if (handle->open) 
     				handle->handler->event(handle,type,code, value);
     #以上三局句組成循環,第一句周遊dev中的h_list,該連結清單是和該input_dev相關聯的所有handle的集合
     #如果引用計數非0,則調用handler中的event函數繼續想上層傳遞事件
     #如果引用計數為0,表示該handler沒有被打開,使用者程序沒有使用該handler,是以不必向上層傳遞事件