之前我們做的按鍵驅動程式都是應用程式主動open設備/dev/buttons而現實情況不能來打開這個設備甚至不知道這個設備的存在。 解決方案:變成通用的驅動程式。接下來我們引入的輸入子系統可以完成該任務。 1.輸入子系統的簡介 1.1 引入輸入子系統的好處: (1)統一了物理形態各異的相似的輸入設備 ...
之前我們做的按鍵驅動程式都是應用程式主動open設備/dev/buttons而現實情況不能來打開這個設備甚至不知道這個設備的存在。
解決方案:變成通用的驅動程式。接下來我們引入的輸入子系統可以完成該任務。
1.輸入子系統的簡介
1.1 引入輸入子系統的好處:
(1)統一了物理形態各異的相似的輸入設備的處理功能。例如,各種滑鼠,不論PS/2、USB、還是藍牙,都被同樣處理。
(2)提供了用於分發輸入報告給用戶應用程式的簡單的事件(event)介面。你的驅動不必創建、管理/dev節點以及相關的訪問方法。因此它能夠很方便的調用輸入API以發送滑鼠移動、鍵盤按鍵,或觸摸事件給用戶空間。X windows這樣的應用程式能夠無縫地運行於輸入子系統提供的event介面之上。
(3)抽取出了輸入驅動的通用部分,簡化了驅動,並提供了一致性。例如,輸入子系統提供了一個底層驅動(成為serio)的集合,支持對串口和鍵盤控制器等硬體輸入的訪問。
1.2 分析輸入子系統實現的原理
linux系統將輸入子系統分為三層結構,主要是input driver、input core、Input handler。在網上搜刮到的兩幅經典的圖片,有助於我們理解輸入子系統。
2 代碼分析
(1)/drivers/input/input.c
input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops)//註冊一個input_fops結構體
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};//結構體內只有一個open函數
(2)怎麼去讀取按鍵?
上述的file_operations結構體內沒有read函數,我們需進一步探究。
input_open_file
struct input_handler *handler = input_table[iminor(inode) >> 5]
new_fops = fops_get(handler->fops) //這個就是我們實際使用的file_operations結構體
err = new_fops->open(inode, file)
input_table這個數組由誰去構造?
input_table[handler->minor >> 5] = handler
(3)註冊函數input_register_device和input_register_handler,向input.c註冊
input_register_device
list_add_tail(&dev->node, &input_dev_list) //放入鏈表
list_for_each_entry(handler, &input_handler_list, node)//對於每一個input_handler,都調用input_attach_handler
input_attach_handler(dev, handler)//根據input_handler的id_table判斷是否支持這個input_dev
input_register_handler
input_table[handler->minor >> 5] = handler //放入數組
list_add_tail(&handler->node, &input_handler_list) //放入鏈表
list_for_each_entry(dev, &input_dev_list, node)//對於每一個input_dev,都調用input_attach_handler
input_attach_handler(dev, handler)//根據input_handler的id_table判斷是否支持這個input_dev
input_attach_handler
id = input_match_device(handler->id_table, dev);//如果dev和id匹配,則調用connect函數建立連接
error = handler->connect(handler, dev, id);
(4)怎麼建立連接?怎麼讀按鍵?
以evdev.c為例
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);//分配一個結構體input_handle結構體
evdev->handle.dev = dev;
evdev->handle.name = evdev->name;
evdev->handle.handler = handler;
evdev->handle.private = evdev;//設置input_handle結構體handle的值
error = input_register_handle(&evdev->handle);//註冊handle結構體
input_handler->h_list = &input_handle
input_dev -> h_list = &input_handle
evdev_read
//無數據且非阻塞方式打開,則立刻返回
if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
wait_event_interruptible//休眠
誰來喚醒他?
evdev_event
wake_up_interruptible(&evdev->wait)
那evdev_event誰來調用?
gpio_keys_isr
input_event(input, type, button->code, !!state)
input_sync(input)
input_event
struct input_handle *handle
list_for_each_entry(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle, type, code, value)//調用evdev_event
3.寫代碼
3.1 代碼框架
我們只需要完成input_dev那部分,其他的內核已經為我們寫好了。
(1)分配一個input_dev結構體
(2)設置
(3)註冊
(4)硬體相關的操作,eg:在中斷程式中上報事件,參考gpio-keys.c
3.2 驅動源代碼
1 說明:輸入子系統(input subsystem)的驅動層的核心結構。
2
3 頭文件:include/linux/input.h
4
5 成員說明:
6
7 void *private;
8
9 //不清楚。
10
11 char *name;
12
13 //設備名字,如鍵盤名字。
14
15 char *phys;
16
17 //設備文件節點名,如input/kbd0。
18
19 char *uniq;
20
21 //全球唯一的ID號。
22
23 struct input_id id;
24
25 //後文作詳細介紹。
26
27 unsigned long evbit[NBITS(EV_MAX);]
28
29 //該設備驅動所能支持的事件。
30
31 //EV_SYN 同步事件
32
33 //EV_KEY 鍵盤事件
34
35 //EV_REL 相對坐標事件,用於滑鼠
36
37 //EV_ABS 絕對坐標事件,用於搖桿
38
39 //EV_MSC 其他事件
40
41 //EV_LED LED燈事件
42
43 //EV_SND 聲音事件
44
45 //EV_REP 重覆按鍵事件
46
47 //EV_FF 受力事件
48
49 //EV_PWR 電源事件
50
51 //EV_FF_STATUS 受力狀態事件
52
53 unsigned long keybit[NBITS(KEY_MAX)];
54
55 //鍵值存放表
56
57 unsigned long relbit[NBITS(REL_MAX)];
58
59 //用於存放相對坐標值等
60
61 unsigned long absbit[NBITS(ABS_MAX)];
62
63 //用於存放絕對坐標值等
64
65 unsigned long mscbit[NBITS(MSC_MAX)];
66
67 //存放其他事件類型
68
69 unsigned long ledbit[NBITS(LED_MAX)];
70
71 //存放表示各種狀態的LED值
72
73 unsigned long sndbit[NBITS(SND_MAX)];
74
75 //存放各種事件的聲音
76
77 unsigned long ffbit[NBITS(FF_MAX)];
78
79 //存放受力設備的屬性
80
81 int ff_effects_max;
82
83 //顯然與受力效果有關,具體作用還不大清楚。
84
85 unsigned int keycodemax;
86
87 unsigned int keycodesize;
88
89 void * keycode;
90
91 //這三個不是很清楚,有點模糊理解。
92
93 unsigned int repeat_key;
94
95 //存放重覆按鍵時的鍵值
96
97 struct timer_list timer;
98
99 //定時器
100
101 struct pm_dev *pm_dev;
102
103 //考慮到有些設備可能有電源管理
104
105 struct pt_regs *regs;
106
107 //不清楚
108
109 int state;
110
111 //顯然是表示一個狀態,但不清楚具體是誰的狀態
112
113 int sync;
114
115 //具體用於什麼也不大清楚
116
117 int abs[ABS_MAX + 1];
118
119 //顯然是與絕對坐標有關的,但具體的作用不清楚。
120
121 int rep[REP_MAX + 1];
122
123 //存放重覆按鍵時的延時,系統依靠這個延時時間來判斷重覆按鍵
124
125 //rep[0]表示開始要重覆按鍵時的延時時間,即第1個鍵與第2個鍵(開始重覆按鍵)之間的延時
126
127 //rep[1]此後重覆按鍵之前的延時時間,直到按鍵抬起
128
129 //通俗解釋就是,假如我按了一個“a”,並且一直按著,那麼在顯示出來的第一個a與第二個a之間的時間延時為rep[0],而此後的相鄰兩個a之間的延時為rep[1]
130
131
132
133 unsigned long key[NBITS(KEY_MAX)];
134
135 unsigned long led[NBITS(LED_MAX)];
136
137 unsigned long snd[NBITS(SND_MAX)];
138
139 //不知道有什麼用
140
141 int absmax[ABS_MAX + 1];
142
143 int absmin[ABS_MAX + 1];
144
145 int absfuzz[ABS_MAX + 1];
146
147 int absflat[ABS_MAX + 1];
148
149 //顯然與絕對坐標值有關,但不知道具體作用
150
151
152
153 int (*open)(struct input_dev *dev);
154
155 void (*close)(struct input_dev *dev);
156
157 int (*accept)(struct input_dev *dev, struct file *file);
158
159 int (*flush)(struct input_dev *dev, struct file *file);
160
161 int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
162
163 int (*upload_effect)(struct input_dev *dev, struct ff_effect *effect);
164
165 int (*erase_effect)(struct input_dev *dev, int effect_id);
166
167 //底層與硬體相關的一組操作,若有具體定義,則會在input core層被調用,具體看input.c。
168
169
170
171 struct input_handle *grab;
172
173 //該結構會在後文做具體介紹,這個指針用於占用輸入設備用,如鍵盤
174
175 struct list_head h_list;
176
177 struct list_head node;
178
179 //h_list鏈表用於與input_handler相聯繫
180
181 //node鏈表:設備向輸入子系統(input subsystem)註冊後,會將該鏈表添加到系統維護的一個鏈表中去,從而系統可以管理這個設備
input_dev結構體
1 /*
2 *輸入子系統
3 *參考gpio-keys.c
4 */
5 #include <linux/module.h>
6 #include <linux/version.h>
7
8 #include <linux/init.h>
9 #include <linux/fs.h>
10 #include <linux/interrupt.h>
11 #include <linux/irq.h>
12 #include <linux/sched.h>
13 #include <linux/pm.h>
14 #include <linux/sysctl.h>
15 #include <linux/proc_fs.h>
16 #include <linux/delay.h>
17 #include <linux/platform_device.h>
18 #include <linux/input.h>
19 #include <linux/irq.h>
20
21
22 #include <asm/gpio.h>
23 #include <asm/io.h>
24 #include <asm/arch/regs-gpio.h>
25
26
27 struct pin_desc{
28 int irq;
29 char *name;
30 unsigned int pin;
31 unsigned int key_val;
32 };
33
34 struct pin_desc pins_desc[4] = {
35 {IRQ_EINT0, "S2", S3C2410_GPF0, KEY_L},
36 {IRQ_EINT2, "S3", S3C2410_GPF2, KEY_S},
37 {IRQ_EINT11, "S4", S3C2410_GPG3, KEY_ENTER},
38 {IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
39 };
40
41 static struct input_dev *g_ptButtonsInput;
42 static struct timer_list g_tButtonsTimer;
43 static struct pin_desc *g_ptPindescTmp;
44
45
46 static irqreturn_t buttons_irq(int irq, void *dev_id)
47 {
48 /* 10ms後啟動定時器 */
49 g_ptPindescTmp = (struct pin_desc *)dev_id;
50 mod_timer(&g_tButtonsTimer, jiffies+HZ/100);
51 return IRQ_RETVAL(IRQ_HANDLED);
52 }
53
54 static void ButtonsTimerFunc(unsigned long data)
55 {
56 struct pin_desc * tPindesc = g_ptPindescTmp;
57 unsigned int uiPinval;
58
59 if(!tPindesc)
60 return;
61
62 uiPinval = s3c2410_gpio_getpin(tPindesc->pin);
63
64 if (uiPinval)
65 {
66 /* 鬆開 */
67 input_event(g_ptButtonsInput, EV_KEY, tPindesc->key_val, 0);
68 input_sync(g_ptButtonsInput);
69 }
70 else
71 {
72 /* 按下 */
73 input_event(g_ptButtonsInput, EV_KEY, tPindesc->key_val, 1);
74 input_sync(g_ptButtonsInput);
75
76 }
77 }
78
79 static int buttons_init(void)
80 {
81 int iError;
82 int i;
83 /*1.分配一個input_dev結構體*/
84 g_ptButtonsInput = input_allocate_device();
85 if (!g_ptButtonsInput)
86 return -ENOMEM;
87 /*2.設置*/
88
89 /*2.1事件類型*/
90 set_bit(EV_KEY, g_ptButtonsInput->evbit);
91 set_bit(EV_REP, g_ptButtonsInput->evbit);
92
93
94 /*2.2哪些事件*/
95 set_bit(KEY_L, g_ptButtonsInput->keybit);
96 set_bit(KEY_S, g_ptButtonsInput->keybit);
97 set_bit(KEY_ENTER, g_ptButtonsInput->keybit);
98 set_bit(KEY_LEFTSHIFT, g_ptButtonsInput->keybit);
99
100
101
102 /*3.註冊*/
103 iError = input_register_device(g_ptButtonsInput);
104 if (iError) {
105 printk("Unable to register buttons input device\n");
106 }
107
108 /*4.硬體相關的操作*/
109 init_timer(&g_tButtonsTimer);
110 g_tButtonsTimer.function = &ButtonsTimerFunc; /* timer handler */
111 add_timer(&g_tButtonsTimer);
112
113 for(i = 0; i < 4; i++)
114 {
115 request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
116 }
117 return 0;
118
119 }
120
121 static void buttons_exit(void)
122 {
123 int i;
124
125 for(i = 0;i<4;i++)
126 {
127 free_irq(pins_desc[i].irq, &pins_desc[i]);
128 }
129
130 del_timer(&g_tButtonsTimer);
131 input_unregister_device(g_ptButtonsInput);
132 input_free_device(g_ptButtonsInput);
133 }
134
135 module_init(buttons_init);
136 module_exit(buttons_exit);
137
138 MODULE_LICENSE("GPL");
buttons_input
//input驅動的測試方法
1.ls /dev/event* -l 查看現有的/dev/event*設備
2.insmod buttons_input.ko 安裝驅動
3.ls /dev/event* -l 查看buttons_input對應的設備
4.cat /dev/tty1,然後在按鍵,“l”“s”“ENTER”便會出現ls
5.如果啟動了QT,可以點開記事本,按相應的按鍵“l”“s”“ENTER”便會在記事本上出現ls
6.也可通過執行exec 0</dev/tty1 //標準輸入改為tty1,然後重覆上述操作即可。
測試方法