本文是我學習時所寫,非百分之百原創,望指出錯誤之處。
參考資料:
input子系統按鍵處理
INPUT輸入子系統
在系統中會出現很多的input裝置,比如:鍵盤、螢幕等等,這些實體裝置都會統一的抽象為input裝置。
用來抽象這些裝置的資料結構是struct input_dev結構,該結構如下:
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,是以不必向上層傳遞事件