diff options
Diffstat (limited to 'drivers/input/misc/keychord.c')
-rw-r--r-- | drivers/input/misc/keychord.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/drivers/input/misc/keychord.c b/drivers/input/misc/keychord.c new file mode 100644 index 00000000000..3ffab6da411 --- /dev/null +++ b/drivers/input/misc/keychord.c | |||
@@ -0,0 +1,387 @@ | |||
1 | /* | ||
2 | * drivers/input/misc/keychord.c | ||
3 | * | ||
4 | * Copyright (C) 2008 Google, Inc. | ||
5 | * Author: Mike Lockwood <lockwood@android.com> | ||
6 | * | ||
7 | * This software is licensed under the terms of the GNU General Public | ||
8 | * License version 2, as published by the Free Software Foundation, and | ||
9 | * may be copied, distributed, and modified under those terms. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/poll.h> | ||
19 | #include <linux/slab.h> | ||
20 | #include <linux/module.h> | ||
21 | #include <linux/init.h> | ||
22 | #include <linux/spinlock.h> | ||
23 | #include <linux/fs.h> | ||
24 | #include <linux/miscdevice.h> | ||
25 | #include <linux/keychord.h> | ||
26 | #include <linux/sched.h> | ||
27 | |||
28 | #define KEYCHORD_NAME "keychord" | ||
29 | #define BUFFER_SIZE 16 | ||
30 | |||
31 | MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); | ||
32 | MODULE_DESCRIPTION("Key chord input driver"); | ||
33 | MODULE_SUPPORTED_DEVICE("keychord"); | ||
34 | MODULE_LICENSE("GPL"); | ||
35 | |||
36 | #define NEXT_KEYCHORD(kc) ((struct input_keychord *) \ | ||
37 | ((char *)kc + sizeof(struct input_keychord) + \ | ||
38 | kc->count * sizeof(kc->keycodes[0]))) | ||
39 | |||
40 | struct keychord_device { | ||
41 | struct input_handler input_handler; | ||
42 | int registered; | ||
43 | |||
44 | /* list of keychords to monitor */ | ||
45 | struct input_keychord *keychords; | ||
46 | int keychord_count; | ||
47 | |||
48 | /* bitmask of keys contained in our keychords */ | ||
49 | unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; | ||
50 | /* current state of the keys */ | ||
51 | unsigned long keystate[BITS_TO_LONGS(KEY_CNT)]; | ||
52 | /* number of keys that are currently pressed */ | ||
53 | int key_down; | ||
54 | |||
55 | /* second input_device_id is needed for null termination */ | ||
56 | struct input_device_id device_ids[2]; | ||
57 | |||
58 | spinlock_t lock; | ||
59 | wait_queue_head_t waitq; | ||
60 | unsigned char head; | ||
61 | unsigned char tail; | ||
62 | __u16 buff[BUFFER_SIZE]; | ||
63 | }; | ||
64 | |||
65 | static int check_keychord(struct keychord_device *kdev, | ||
66 | struct input_keychord *keychord) | ||
67 | { | ||
68 | int i; | ||
69 | |||
70 | if (keychord->count != kdev->key_down) | ||
71 | return 0; | ||
72 | |||
73 | for (i = 0; i < keychord->count; i++) { | ||
74 | if (!test_bit(keychord->keycodes[i], kdev->keystate)) | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | /* we have a match */ | ||
79 | return 1; | ||
80 | } | ||
81 | |||
82 | static void keychord_event(struct input_handle *handle, unsigned int type, | ||
83 | unsigned int code, int value) | ||
84 | { | ||
85 | struct keychord_device *kdev = handle->private; | ||
86 | struct input_keychord *keychord; | ||
87 | unsigned long flags; | ||
88 | int i, got_chord = 0; | ||
89 | |||
90 | if (type != EV_KEY || code >= KEY_MAX) | ||
91 | return; | ||
92 | |||
93 | spin_lock_irqsave(&kdev->lock, flags); | ||
94 | /* do nothing if key state did not change */ | ||
95 | if (!test_bit(code, kdev->keystate) == !value) | ||
96 | goto done; | ||
97 | __change_bit(code, kdev->keystate); | ||
98 | if (value) | ||
99 | kdev->key_down++; | ||
100 | else | ||
101 | kdev->key_down--; | ||
102 | |||
103 | /* don't notify on key up */ | ||
104 | if (!value) | ||
105 | goto done; | ||
106 | /* ignore this event if it is not one of the keys we are monitoring */ | ||
107 | if (!test_bit(code, kdev->keybit)) | ||
108 | goto done; | ||
109 | |||
110 | keychord = kdev->keychords; | ||
111 | if (!keychord) | ||
112 | goto done; | ||
113 | |||
114 | /* check to see if the keyboard state matches any keychords */ | ||
115 | for (i = 0; i < kdev->keychord_count; i++) { | ||
116 | if (check_keychord(kdev, keychord)) { | ||
117 | kdev->buff[kdev->head] = keychord->id; | ||
118 | kdev->head = (kdev->head + 1) % BUFFER_SIZE; | ||
119 | got_chord = 1; | ||
120 | break; | ||
121 | } | ||
122 | /* skip to next keychord */ | ||
123 | keychord = NEXT_KEYCHORD(keychord); | ||
124 | } | ||
125 | |||
126 | done: | ||
127 | spin_unlock_irqrestore(&kdev->lock, flags); | ||
128 | |||
129 | if (got_chord) | ||
130 | wake_up_interruptible(&kdev->waitq); | ||
131 | } | ||
132 | |||
133 | static int keychord_connect(struct input_handler *handler, | ||
134 | struct input_dev *dev, | ||
135 | const struct input_device_id *id) | ||
136 | { | ||
137 | int i, ret; | ||
138 | struct input_handle *handle; | ||
139 | struct keychord_device *kdev = | ||
140 | container_of(handler, struct keychord_device, input_handler); | ||
141 | |||
142 | /* | ||
143 | * ignore this input device if it does not contain any keycodes | ||
144 | * that we are monitoring | ||
145 | */ | ||
146 | for (i = 0; i < KEY_MAX; i++) { | ||
147 | if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit)) | ||
148 | break; | ||
149 | } | ||
150 | if (i == KEY_MAX) | ||
151 | return -ENODEV; | ||
152 | |||
153 | handle = kzalloc(sizeof(*handle), GFP_KERNEL); | ||
154 | if (!handle) | ||
155 | return -ENOMEM; | ||
156 | |||
157 | handle->dev = dev; | ||
158 | handle->handler = handler; | ||
159 | handle->name = KEYCHORD_NAME; | ||
160 | handle->private = kdev; | ||
161 | |||
162 | ret = input_register_handle(handle); | ||
163 | if (ret) | ||
164 | goto err_input_register_handle; | ||
165 | |||
166 | ret = input_open_device(handle); | ||
167 | if (ret) | ||
168 | goto err_input_open_device; | ||
169 | |||
170 | pr_info("keychord: using input dev %s for fevent\n", dev->name); | ||
171 | |||
172 | return 0; | ||
173 | |||
174 | err_input_open_device: | ||
175 | input_unregister_handle(handle); | ||
176 | err_input_register_handle: | ||
177 | kfree(handle); | ||
178 | return ret; | ||
179 | } | ||
180 | |||
181 | static void keychord_disconnect(struct input_handle *handle) | ||
182 | { | ||
183 | input_close_device(handle); | ||
184 | input_unregister_handle(handle); | ||
185 | kfree(handle); | ||
186 | } | ||
187 | |||
188 | /* | ||
189 | * keychord_read is used to read keychord events from the driver | ||
190 | */ | ||
191 | static ssize_t keychord_read(struct file *file, char __user *buffer, | ||
192 | size_t count, loff_t *ppos) | ||
193 | { | ||
194 | struct keychord_device *kdev = file->private_data; | ||
195 | __u16 id; | ||
196 | int retval; | ||
197 | unsigned long flags; | ||
198 | |||
199 | if (count < sizeof(id)) | ||
200 | return -EINVAL; | ||
201 | count = sizeof(id); | ||
202 | |||
203 | if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK)) | ||
204 | return -EAGAIN; | ||
205 | |||
206 | retval = wait_event_interruptible(kdev->waitq, | ||
207 | kdev->head != kdev->tail); | ||
208 | if (retval) | ||
209 | return retval; | ||
210 | |||
211 | spin_lock_irqsave(&kdev->lock, flags); | ||
212 | /* pop a keychord ID off the queue */ | ||
213 | id = kdev->buff[kdev->tail]; | ||
214 | kdev->tail = (kdev->tail + 1) % BUFFER_SIZE; | ||
215 | spin_unlock_irqrestore(&kdev->lock, flags); | ||
216 | |||
217 | if (copy_to_user(buffer, &id, count)) | ||
218 | return -EFAULT; | ||
219 | |||
220 | return count; | ||
221 | } | ||
222 | |||
223 | /* | ||
224 | * keychord_write is used to configure the driver | ||
225 | */ | ||
226 | static ssize_t keychord_write(struct file *file, const char __user *buffer, | ||
227 | size_t count, loff_t *ppos) | ||
228 | { | ||
229 | struct keychord_device *kdev = file->private_data; | ||
230 | struct input_keychord *keychords = 0; | ||
231 | struct input_keychord *keychord, *next, *end; | ||
232 | int ret, i, key; | ||
233 | unsigned long flags; | ||
234 | |||
235 | if (count < sizeof(struct input_keychord)) | ||
236 | return -EINVAL; | ||
237 | keychords = kzalloc(count, GFP_KERNEL); | ||
238 | if (!keychords) | ||
239 | return -ENOMEM; | ||
240 | |||
241 | /* read list of keychords from userspace */ | ||
242 | if (copy_from_user(keychords, buffer, count)) { | ||
243 | kfree(keychords); | ||
244 | return -EFAULT; | ||
245 | } | ||
246 | |||
247 | /* unregister handler before changing configuration */ | ||
248 | if (kdev->registered) { | ||
249 | input_unregister_handler(&kdev->input_handler); | ||
250 | kdev->registered = 0; | ||
251 | } | ||
252 | |||
253 | spin_lock_irqsave(&kdev->lock, flags); | ||
254 | /* clear any existing configuration */ | ||
255 | kfree(kdev->keychords); | ||
256 | kdev->keychords = 0; | ||
257 | kdev->keychord_count = 0; | ||
258 | kdev->key_down = 0; | ||
259 | memset(kdev->keybit, 0, sizeof(kdev->keybit)); | ||
260 | memset(kdev->keystate, 0, sizeof(kdev->keystate)); | ||
261 | kdev->head = kdev->tail = 0; | ||
262 | |||
263 | keychord = keychords; | ||
264 | end = (struct input_keychord *)((char *)keychord + count); | ||
265 | |||
266 | while (keychord < end) { | ||
267 | next = NEXT_KEYCHORD(keychord); | ||
268 | if (keychord->count <= 0 || next > end) { | ||
269 | pr_err("keychord: invalid keycode count %d\n", | ||
270 | keychord->count); | ||
271 | goto err_unlock_return; | ||
272 | } | ||
273 | if (keychord->version != KEYCHORD_VERSION) { | ||
274 | pr_err("keychord: unsupported version %d\n", | ||
275 | keychord->version); | ||
276 | goto err_unlock_return; | ||
277 | } | ||
278 | |||
279 | /* keep track of the keys we are monitoring in keybit */ | ||
280 | for (i = 0; i < keychord->count; i++) { | ||
281 | key = keychord->keycodes[i]; | ||
282 | if (key < 0 || key >= KEY_CNT) { | ||
283 | pr_err("keychord: keycode %d out of range\n", | ||
284 | key); | ||
285 | goto err_unlock_return; | ||
286 | } | ||
287 | __set_bit(key, kdev->keybit); | ||
288 | } | ||
289 | |||
290 | kdev->keychord_count++; | ||
291 | keychord = next; | ||
292 | } | ||
293 | |||
294 | kdev->keychords = keychords; | ||
295 | spin_unlock_irqrestore(&kdev->lock, flags); | ||
296 | |||
297 | ret = input_register_handler(&kdev->input_handler); | ||
298 | if (ret) { | ||
299 | kfree(keychords); | ||
300 | kdev->keychords = 0; | ||
301 | return ret; | ||
302 | } | ||
303 | kdev->registered = 1; | ||
304 | |||
305 | return count; | ||
306 | |||
307 | err_unlock_return: | ||
308 | spin_unlock_irqrestore(&kdev->lock, flags); | ||
309 | kfree(keychords); | ||
310 | return -EINVAL; | ||
311 | } | ||
312 | |||
313 | static unsigned int keychord_poll(struct file *file, poll_table *wait) | ||
314 | { | ||
315 | struct keychord_device *kdev = file->private_data; | ||
316 | |||
317 | poll_wait(file, &kdev->waitq, wait); | ||
318 | |||
319 | if (kdev->head != kdev->tail) | ||
320 | return POLLIN | POLLRDNORM; | ||
321 | |||
322 | return 0; | ||
323 | } | ||
324 | |||
325 | static int keychord_open(struct inode *inode, struct file *file) | ||
326 | { | ||
327 | struct keychord_device *kdev; | ||
328 | |||
329 | kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL); | ||
330 | if (!kdev) | ||
331 | return -ENOMEM; | ||
332 | |||
333 | spin_lock_init(&kdev->lock); | ||
334 | init_waitqueue_head(&kdev->waitq); | ||
335 | |||
336 | kdev->input_handler.event = keychord_event; | ||
337 | kdev->input_handler.connect = keychord_connect; | ||
338 | kdev->input_handler.disconnect = keychord_disconnect; | ||
339 | kdev->input_handler.name = KEYCHORD_NAME; | ||
340 | kdev->input_handler.id_table = kdev->device_ids; | ||
341 | |||
342 | kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT; | ||
343 | __set_bit(EV_KEY, kdev->device_ids[0].evbit); | ||
344 | |||
345 | file->private_data = kdev; | ||
346 | |||
347 | return 0; | ||
348 | } | ||
349 | |||
350 | static int keychord_release(struct inode *inode, struct file *file) | ||
351 | { | ||
352 | struct keychord_device *kdev = file->private_data; | ||
353 | |||
354 | if (kdev->registered) | ||
355 | input_unregister_handler(&kdev->input_handler); | ||
356 | kfree(kdev); | ||
357 | |||
358 | return 0; | ||
359 | } | ||
360 | |||
361 | static const struct file_operations keychord_fops = { | ||
362 | .owner = THIS_MODULE, | ||
363 | .open = keychord_open, | ||
364 | .release = keychord_release, | ||
365 | .read = keychord_read, | ||
366 | .write = keychord_write, | ||
367 | .poll = keychord_poll, | ||
368 | }; | ||
369 | |||
370 | static struct miscdevice keychord_misc = { | ||
371 | .fops = &keychord_fops, | ||
372 | .name = KEYCHORD_NAME, | ||
373 | .minor = MISC_DYNAMIC_MINOR, | ||
374 | }; | ||
375 | |||
376 | static int __init keychord_init(void) | ||
377 | { | ||
378 | return misc_register(&keychord_misc); | ||
379 | } | ||
380 | |||
381 | static void __exit keychord_exit(void) | ||
382 | { | ||
383 | misc_deregister(&keychord_misc); | ||
384 | } | ||
385 | |||
386 | module_init(keychord_init); | ||
387 | module_exit(keychord_exit); | ||