我們上一章完成了input子系統的裝置構成,并且在使用者空間通過hexdump指令拿到了一堆不知道是什麼的資訊。今天我們就要借助input_event這個結構體來了解核心怎麼通過那個結構體了解輸入事件。
可能有心人已經發現了,上一章我們在加載完子產品以後在/dev/input路徑下生成了一個新的event檔案

在按鍵被按下以後,驅動就會向核心上報一個輸入事件,而不同的輸入裝置都會有不同的實際内容上報給核心。是以Linux核心就整定了一個通用的結構體來描述每個事件。我們在使用者态的程式就可以通過上面圖中的event1來了解按鍵的實際狀态。
input_event結構
想要知道event1檔案在按鍵按下時列印的内容
首先要知道input_event結構體的内容(include/uapi/linux/input.h,注意路徑,不要搞錯了!)
1 /*
2 * The event structure itself
3 */
4
5 struct input_event {
6 struct timeval time;
7 __u16 type;
8 __u16 code;
9 __s32 value;
10 };
input_event結構體的内容很簡單,先是一個timval結構體,這個結構體也可以展開看看!
1 struct timeval {
2 __kernel_time_t tv_sec; /* seconds */
3 __kernel_suseconds_t tv_usec; /* microseconds */
4 };
一個秒,一個微秒,查到底兩個變量都是long型的資料,也就是32位的資料。
type就是事件類型,在我們驅動中定義的是EV_KEY。16位
code,事件碼,在按鍵驅動中就是我們定義的鍵值。16位
value,值,在驅動中用來描述按鍵是否被按下或松開。32位
可以注意到,這個input_event結構體裡後三個成員跟前面input_event函數中後面三個參數是一樣的。按照上面的資料結構可以分析一下列印出來的資訊,可以發現是這種結構的
編号 timeval_sec timeval_usec type code value
0000000 2217 0000 d132 0005 0001 000b 0001 0000
0000010 2217 0000 d132 0005 0000 0000 0000 0000
0000020 2217 0000 e29b 0006 0001 000b 0000 0000
0000030 2217 0000 e29b 0006 0000 0000 0000 0000
上面的資訊都是由16進制來表示的,是以每個0都表示了4個bit。
第一組是編号,表示
第2、3清單示秒,一共8個數字,對應32位
第4、5清單示微秒
第6列為事件類型,對應EV_KEY,第一行是0001,就是對應的EV_KEY的值,第2行的0000對應EV_SYN事件
#define EV_SYN 0x00
#define EV_KEY 0x01
就是說按鍵在按下的時候,會上報一個EV_KEY事件,然後在定時處理函數裡還調用了一個同步函數
input_sync(dev->inputdev);
可以轉到這個函數的定義看一下,這個函數會上報一個EV_SYN信号
1 static inline void input_sync(struct input_dev *dev)
2 {
3 input_event(dev, EV_SYN, SYN_REPORT, 0);
4 }
是以就會有第2行那個0000,對應的type就是EV_SYN。
第7列是code,對應我們定義的key_value,我們在初始化input_dev的時候定義的事件code是keybit,值為KEY_0,KEY_0是個宏
#define KEY_0 11
十進制值為11,對應的code就是000b。
最後是value的值,表示我們上報的按鍵值,1對應按鍵按下,0表示釋放。但是在不松開的時候這個值是1和2來回變化,我不知道是怎麼來的。(這個值我感覺是個小端模式,0001在左邊,包括前面那個時間的資料,感覺也是左邊4個數先跳,後面的再變)。是以列印出來的這4行資料分别表示了按鍵按下事件——同步事件——按鍵彈起事件——同步事件。
應用程式編寫
在前面寫驅動的時候我們把檔案操作集合以及對應的函數都删掉了,通過input子系統操作的輸入裝置對應的檔案就是前面我們關注的/dev/input/路徑下的event檔案,也就是如果我們想要擷取按鍵的狀态,就要在使用者态程式中将前面通過hexdump指令列印出的資料解析出來。
先放代碼
1 /**
2 * @file inputAPP.c
3 * @author your name ([email protected])
4 * @brief input子系統測試APP
5 * @version 0.1
6 * @date 2022-08-28
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <fcntl.h>
14 #include <unistd.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <sys/ioctl.h>
18 #include <linux/input.h>
19
20 /**
21 * @brief
22 *
23 * @param argc //參數個數
24 * @param argv //參數
25 * @return int
26 */
27 int main(int argc,char *argv[])
28 {
29 char *filename; //檔案名
30 int ret = 0; //初始化操作傳回值
31 int f = 0; //初始化檔案句柄
32
33 struct input_event inputevent; //inputevent事件結構體
34
35 if(argc != 2){
36 printf("format err!\r\n");
37 return -1;
38 }
39
40 filename = argv[1]; //
41 f = open(filename, O_RDWR); //打開檔案
42 if(f < 0){
43 printf("file open error\r\n");
44 return -1;
45 }
46
47 while(1){
48 ret = read(f,&inputevent,sizeof(inputevent)); //讀取文集
49 if(ret<0){
50 //讀取錯誤
51 printf("data read err\r\n");
52 }
53 else{
54 switch(inputevent.type) {
55 case EV_SYN:
56 break;
57 case EV_KEY:
58 printf("btn event\r\n");
59 if(inputevent.code< BTN_MISC){
60 //按鍵鍵值為KEY_xxx
61 printf("key %d %s\r\n",inputevent.code,inputevent.value?"pressed":"released");
62 }
63 else{
64 //按鍵鍵值為BTN_xxx
65 printf("button %d %s\r\n",inputevent.code,inputevent.value?"pressed":"released");
66 }
67 break;
68 case EV_REL:
69 break;
70 }
71 }
72 }
73 close(f); //關閉檔案
74 return 0;
75 }
整個應用程式的思路還是比較清晰度,主要就是在主程式裡聲明了一個inputevent結構體變量(教程裡是按照全局變量的方法聲明在主函數外部的)。打開檔案什麼的就不說了,我們要關注的就是從54行開始的switch結構,如果inputevent的type是EV_KEY的話,就按照鍵值讀取。并且在case裡做了一個判斷,inputevent的value在0~255之間是對應的是鍵盤的鍵值,否則是button(如果我們隻是當個按鍵使用不建議把按鈕模拟成鍵盤,是以在寫驅動的時候可以把bitkey的值放在btn區間)。
有意思的是即便我們并沒有在驅動或應用程式中定義檔案讀寫的阻塞或非阻塞的模式,但是我感覺實際狀态下應該是按照阻塞模式讀取的檔案,可以在程式以背景模式運作以後通過top指令查詢一下
/lib/modules/4.1.15 # ./inputAPP /dev/input/event1 &
可以看出來,程式并沒有占用過多的資源。