Linux驅動之輸入子系統
1.1 輸入子系統簡介
1.1.1 概念
在Linux中,輸入子系統是由輸入子系統裝置驅動層、輸入子系統核心層(Input Core)和輸入子系統事件處理層(Event Handler)組成。
其中裝置驅動層提供對硬體各寄存器的讀寫通路和将底層硬體對使用者輸入通路的響應轉換為标準的輸入事件,再通過核心層送出給事件處理層;而核心層對下提供了裝置驅動層的程式設計接口,對上又提供了事件處理層的程式設計接口;而事件處理層就為我們使用者空間的應用程式提供了統一通路裝置的接口和驅動層送出來的事件處理。是以這使得我們輸入裝置的驅動部分不在用關心對裝置檔案的操作,而是要關心對各硬體寄存器的操作和送出的輸入事件。
1.1.2 輸入子系統的好處
(1)統一了實體形态各異的相似的輸入裝置的處理功能。例如,各種滑鼠,不論PS/2、USB、還是藍牙,都被同樣處理。
(2)提供了用于分發輸入報告給使用者應用程式的簡單的事件(event)接口。你的驅動不必建立、管理/dev節點以及相關的通路方法。是以它能夠很友善的調用輸入API以發送滑鼠移動、鍵盤按鍵,或觸摸事件給使用者空間。X windows這樣的應用程式能夠無縫地運作于輸入子系統提供的event接口之上。
(3)抽取出了輸入驅動的通用部分,簡化了驅動,并提供了一緻性。例如,輸入子系統提供了一個底層驅動(成為serio)的集合,支援對序列槽和鍵盤控制器等硬體輸入的通路。
1.1.3 輸入子系統的接口
/dev/input或者/dev目錄下顯示的是已經注冊在核心中的裝置程式設計接口,使用者通過open這些裝置檔案來打開不同的輸入裝置進行硬體操作。
輸入子系統的接口:/dev/input目錄。
[root@XiaoLong /]# ls /dev/input/* -l crw-rw---- 1 root root 13, 64 May 16 01:44 /dev/input/event0 crw-rw---- 1 root root 13, 65 May 16 01:44 /dev/input/event1 crw-rw---- 1 root root 13, 63 May 16 01:44 /dev/input/mice crw-rw---- 1 root root 13, 32 May 16 01:44 /dev/input/mouse0 |
輸入子系統由核心代碼 drivers/input/input.c 構成,它的存在屏蔽了使用者到裝置驅動的互動
細節,為裝置驅動層和事件處理層提供了互相通信的統一界面。
有的系統的輸入子系統的節點在 /dev/目錄下:
[root@XiaoLong /]# ls /dev/* -l crw-rw---- 1 root root 13, 64 May 16 01:44 /dev/event0 crw-rw---- 1 root root 13, 65 May 16 01:44 /dev/event1 crw-rw---- 1 root root 13, 63 May 16 01:44 /dev/mice crw-rw---- 1 root root 13, 32 May 16 01:44 /dev/mouse0 |
事件處理層為不同硬體類型提供了使用者通路及處理接口。例如當我們打開裝置/dev/input/mice時,會調用到事件處理層的Mouse Handler來處理輸入事件,這也使得裝置驅動層無需關心裝置檔案的操作,因為Mouse Handler已經有了對應事件處理的方法。
輸入子系統由核心代碼drivers/input/input.c構成,它的存在屏蔽了使用者到裝置驅動的互動細節,為裝置驅動層和事件處理層提供了互相通信的統一界面。
1.1.4 輸入子系統的總體架構
圖1-1
1.1.5 輸入子系統的分層
Linux輸入子系統包括三個層次,有上到下别是事件處理層(Event Handler)、核心層(Input Core)和驅動層(Input Driver)。
1.事件處理層:負責與使用者程式打交道,将硬體驅動層傳來的事件報告給使用者程式。
2.核心層:是連結其他兩個層之間的紐帶與橋梁,向下提供驅動層的接口,向上提供事件處理層的接口。
3.驅動層:負責操作具體的硬體裝置,這層的代碼是針對具體的驅動程式的,鍵盤、滑鼠、觸摸屏等字元裝置驅動功能的實作工作主要在這層。
在Input子系統三層架構中對應3個結構體。
1.結構體input_dev表示底層硬體裝置,是所有輸入裝置的抽象。
2.handle是搖桿的意思,結構體input_handle表示連接配接杆,連接配接底層硬體和上層事件處理層。
3.結構體input_handler表示事件處理器,是對事件處理的抽象。
事件處理層代碼:
drivers\Input\Evdev.c
1.2 輸入子系統的核心結構
1.2.1 input_dev結構
在驅動層需要實作struct input_dev結構,實作輸入子系統的注冊與登出。
在input.h定義了如下結構。
struct input_dev { const char *name; //裝置名字--比如:鍵盤的名字 const char *phys; //裝置在系統中的路徑。比如:input/key0 const char *uniq; //全球唯一ID号 struct input_id id; //用于比對事件處理層handler 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)]; //記錄izhic的相對坐标 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 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_slot *mt; int mtsize; int slot; int trkid; struct input_absinfo *absinfo; 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 (*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; spinlock_t event_lock; struct mutex mutex; unsigned int users; bool going_away; bool sync; //最後一次同步後沒有新的事件置1 struct device dev; struct list_headh_list; //handle連結清單,用于與input_handler相聯系 struct list_headnode; // input_dev連結清單 /* 裝置向輸入子系統(input subsystem)注冊後,會将該連結清單添加到系統維護的一個連結清單中去,進而系統可以管理這個裝置*/ |
1.2.2 input_event事件結構
struct input_event 結構一般在應用層定義使用,用來接收事件層上報的事件。
struct input_event { struct timeval time; //本次上報時間戳 __u16 type; //本次資料的事件類型 (按鍵事件、相對坐标、絕對坐标) __u16 code; //具體數值,如果是按鍵事件,則是鍵值 __s32 value; //和code相關标志,如果是按鍵,代表按下還是松開。 }; |
:事件的類型。(比如:按鍵事件EV_KEY ,絕對坐标EV_ABS)
:上報的按鍵值。
如果上報的是EV_KEY事件,code表示按鍵值。
如果上報的是EV_ABS事件,code表示坐标的類型(X或者Y)。
value :
如果上報的是EV_KEY事件,value就表示狀态值。(0或者1或者重複值2)
如果上報的是EV_ABS事件,value就表示具體的坐标值。
1.2.3 時間結構體
struct timeval { __kernel_time_ttv_sec;/*秒 */ __kernel_suseconds_ttv_usec;/* 微秒 */ }; |
1.3 輸入子系統API函數
1.3.1 動态配置設定input_dev結構體
函數原型 | struct input_dev *input_allocate_device(void) |
函數功能 | 該函數為struct input_dev結構體配置設定記憶體,并初始化該結構體的部分成員 |
函數參數 | 空 |
函數傳回值 | 成功:struct input_dev結構體指針,指向配置設定的結構體 失敗:NULL |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
1.3.2 釋放input_dev結構體
函數原型 | void input_free_device(struct input_dev *dev) |
函數功能 | 釋放input_allocate_device函數配置設定的 input_dev結構體 |
函數參數 | struct input_dev結構體指針 |
函數傳回值 | 無 |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
1.3.3 注冊輸入子系統
函數原型 | int input_register_device(struct input_dev *dev) |
函數功能 | 該函數用于向輸入子系統核心注冊輸入裝置 |
函數參數 | struct input_dev結構體指針 |
函數傳回值 | 成功:傳回0 失敗:一個負的錯誤碼 |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
1.3.4 登出輸入子系統
函數原型 | void input_unregister_device(struct input_dev *) |
函數功能 | 該函數用于登出一個輸入裝置 |
函數參數 | struct input_dev結構體指針,指向要登出的裝置對應的輸入裝置結構體 |
函數傳回值 | 空 |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
1.3.5 填充input_dev結構體
方法1:使用設定位的函數實作填充input_dev 結構體
static inline void __set_bit(int nr, volatile unsigned long *addr); //設定指定的位 static inline void __clear_bit(int nr, volatile unsigned long *addr); //清除指定的位 |
參數:
設定的值
設定的位址
示例:
__set_bit(EV_KEY, key_input->evbit); //設定支援按鍵事件
__set_bit(KEY_1,key_input->keybit); // 設定上報的按鍵值
方法2:通過input_set_capability函數
函數原型 | void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) |
函數功能 | 設定輸入子系統上報的事件類型和具體的按鍵值 |
函數參數 | struct input_dev :結構體指針,指向要登出的裝置對應的輸入裝置結構體 unsigned int type :事件類型 unsigned int code :事件類型對應的具體值 |
函數傳回值 | 空 |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
示例:
input_set_capability(input_dev,EV_KEY,KEY_1); //設定的上報事件類型和具體的值
1.3.6 向應用層上報事件
函數原型 | void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) |
函數功能 | 該函數用于向應用層上報輸入事件 |
函數參數 | *dev:input_dev裝置結構體 Type:上報的事件類型 Code:具體上報的發生事件,根據Type的不同而含義不同。 例如: Type為EV_KEY時,code表示鍵盤code或者滑鼠按鍵值。 Type為EV_REL時,code表示操作的是哪個坐标軸,如:REL_X,REL_Y。(因為滑鼠有x,y兩個軸向,是以一次滑鼠移動,會産生兩個input_event) Value:根據Type的不同而含義不同 例如: Type為EV_KEY時,value: 0表示按鍵擡起。1表示按鍵按下。(4表示持續按下等)。 Type為EV_REL時,value: 表明移動的值和方向(正負值)。 Type為EV_ABS時,value表示具體的坐标值。 |
函數傳回值 | 空 |
所在頭檔案 | include/linux/input.h |
函數定義檔案 | drivers/input/input.c |
1.3.7 同步上報事件
函數原型 | static inline void input_sync(struct input_dev *dev) |
函數功能 | 該函數用于同步發生的事件,告訴應用層本次事件已經完成(必須的) |
函數參數 | struct input_dev結構體指針,指向要同步的結構體 |
函數傳回值 | 空 |
所在頭檔案 | include/linux/input.h |
1.4 程式設計架構
1.4.1 上報普通按鍵事件步驟
- 定義input_dev結構體指針
static struct input_dev *key_input; |
- 動态配置設定input_dev結構體
key_input=input_allocate_device(); |
- 設定事件類型和對應的值
__set_bit(EV_KEY, key_input->evbit); //設定事件類型 __set_bit(KEY_1,key_input->keybit); // 設定事件類型對應的具體值 可以設定重複上報事件 |
或者:
input_set_capability(key_input,EV_KEY,KEY_1); |
- 注冊輸入子系統
input_register_device(key_input); |
- 在具體的地方上報事件(比如中斷)
input_event(key_input,EV_KEY,KEY_1,1); //表示按鍵按下 input_event(key_input,EV_KEY,KEY_1,0); //表示按鍵松開 |
- 同步事件
input_sync(key_input); //同步事件--必須同步否則應用層收不到資料 |
- 登出輸入裝置(一般在驅動出口調用)
input_unregister_device(key_input); |
- 釋放input_dev結構體(一般在驅動出口調用)
input_free_device(key_input); |
1.4.2 上報觸摸屏事件步驟
- 動态配置設定input_dev 結構體
static struct input_dev *touch_input = NULL; //輸入子系統的結構體 |
- 設定事件類型
/*設定上報事件類型為觸摸屏事件--->絕對值事件 EV_ABS*/ __set_bit(EV_ABS,touch_input->evbit); __set_bit(EV_KEY,touch_input->evbit); |
- 設定事件對應的值
__set_bit(ABS_X,touch_input->absbit); /*上報X坐标*/ __set_bit(ABS_Y,touch_input->absbit); /*上報Y坐标*/ __set_bit(ABS_PRESSURE,touch_input->absbit); /*置為壓力事件*/ __set_bit(BTN_TOUCH,touch_input->keybit); |
- 設定上報的事件範圍
input_set_abs_params(touch_input,ABS_PRESSURE,0,1,0,0); //壓力分為兩個級别 input_set_abs_params(touch_input,ABS_X,0,800,0,0); //x坐标範圍為0~800 input_set_abs_params(touch_input,ABS_Y,0,480,0,0); //y坐标範圍為0~480 |
- 注冊輸入子系統
input_register_device(touch_input); |
- 上報觸摸屏坐标
input_event(touch_input,EV_ABS,ABS_X,x0); input_event(touch_input,EV_ABS,ABS_Y,y0); |
- 上報觸摸屏狀态
按下: input_event(touch_input,EV_ABS,ABS_PRESSURE,1); input_event(touch_input,EV_ABS,BTN_TOUCH,1); 松開: input_event(touch_input,EV_ABS,BTN_TOUCH,0); input_event(touch_input,EV_ABS,ABS_PRESSURE,0); |
1.4.2 重新定義終端的輸入
exec 0</dev/tty1 //重定義标準輸入
标準:
0:檔案描述符0 -- 标準輸入
1:檔案描述符1 -- 标準輸出
2:檔案描述符2 -- 标準錯誤
重定義标準輸入之後,在CRT終端就可以使用普通按鍵模拟标準鍵盤,實作敲打指令。
- 檢視系統的标準檔案描述符:
Unix/Linux/BSD 都有三個特别檔案,分别
1)标準輸入 即 STDIN , 在 /dev/stdin ,
一般指鍵盤輸入, shell裡代号是 0
2) 标準輸出 STDOUT, 在 /dev/stdout,
一般指終端(terminal), 就是顯示器, shell裡代号是 1
3) 标準錯誤 STDERR, 在 /dev/stderr
也是指終端(terminal), 不同的是, 錯誤資訊送到這裡
裡代号是 2
[root@wbyq test_20180702]# ls /dev/std* -l lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdout -> /proc/self/fd/1 |
1.5 滑鼠與鍵盤驅動
USB滑鼠驅動代碼路徑:
drivers\hid\usbhid\usbmouse.c
usb鍵盤驅動的源代碼目錄:
drivers/usb/input/usbkbd.c
1.6 輸入子系統核心代碼分析
核心層:\drivers\input\input.c
1.6.1 輸入子系統的注冊
在input.c檔案裡調了input_init函數注冊輸入子系統,主裝置号為13。
檔案操作集合:
static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, .llseek = noop_llseek, }; |
這裡隻實作了一個open函數,其他的檔案操作接口不在這裡,在事件處理實作。
在這個input_open_file函數裡實作了到事件處理層的接口轉換。
使用者層打開輸入子系統事件節點的時候,會調用input_open_file函數,然後在input_open_file函數裡改變了檔案操作集合的指針指向,最終指向了事件處理層的檔案操作集合。
input_open_file函數代碼如下:
static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler; const struct file_operations *old_fops, *new_fops = NULL; int err; err = mutex_lock_interruptible(&input_mutex); if (err) return err; /* No load-on-demand here? */ handler = input_table[iminor(inode) >> 5]; if (handler) new_fops = fops_get(handler->fops); //得到事件層的檔案操作集合指針 mutex_unlock(&input_mutex); /* * That's _really_ odd. Usually NULL ->open means "nothing special", * not "no device". Oh, well... */ if (!new_fops || !new_fops->open) { fops_put(new_fops); err = -ENODEV; goto out; } old_fops = file->f_op; file->f_op = new_fops; /*将事件層實作的open函數指派給檔案指針*/ err = new_fops->open(inode, file); /*調用事件層的OPEN函數*/ if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); out: return err; } |
1.6.2 事件處理層
源碼:\drivers\input\evdev.c
圖1-1 注冊了輸入事件處理程式
圖1-2 事件處理層調用的檔案操作集合
圖1-3 事件處理層的檔案操作結合
從檔案操作集合可以看出,輸入子系統支援多種方式讀取按鍵值。
可以通過read函數直接讀、poll機制、異步通知機制等。
1.6.3 應用層程式設計架構
1.6.3.1 通過poll機制
#include <poll.h> struct input_event ev; struct pollfd fds[1]; fds[0].fd = fb; fds[0].events = POLLIN; while(1) { 等待資料*/ 讀取發生的事件 |