diff options
author | Anssi Hannula <anssi.hannula@gmail.com> | 2006-07-19 01:40:22 -0400 |
---|---|---|
committer | Dmitry Torokhov <dtor@insightbb.com> | 2006-07-19 01:40:22 -0400 |
commit | 509ca1a9383601fdc5612d3d3ba5b981f6eb6c8b (patch) | |
tree | 4d7b63c2b108510c11a89dc0ea45efe788fed779 /drivers/input | |
parent | 806d41b756fecc1b13584e2b806b76d8934b1679 (diff) |
Input: implement new force feedback interface
Implement a new force feedback interface, in which all non-driver-specific
operations are separated to a common module. This includes handling effect
type validations, locking, etc.
The effects are now file descriptor specific instead of the previous strange
half-process half-fd specific behaviour. The effect memory of devices is not
emptied if the root user opens and closes the device while another user is
using effects. This is a minor change and most likely no force feedback
aware programs are affected by this negatively.
Otherwise the userspace interface is left unaltered.
Signed-off-by: Anssi Hannula <anssi.hannula@gmail.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/Makefile | 2 | ||||
-rw-r--r-- | drivers/input/evdev.c | 32 | ||||
-rw-r--r-- | drivers/input/ff-core.c | 367 | ||||
-rw-r--r-- | drivers/input/input.c | 6 |
4 files changed, 389 insertions, 18 deletions
diff --git a/drivers/input/Makefile b/drivers/input/Makefile index e539a309df8a..abdc9d435705 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile | |||
@@ -5,7 +5,7 @@ | |||
5 | # Each configuration option enables a list of files. | 5 | # Each configuration option enables a list of files. |
6 | 6 | ||
7 | obj-$(CONFIG_INPUT) += input-core.o | 7 | obj-$(CONFIG_INPUT) += input-core.o |
8 | input-core-objs := input.o | 8 | input-core-objs := input.o ff-core.o |
9 | 9 | ||
10 | obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o | 10 | obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o |
11 | obj-$(CONFIG_INPUT_JOYDEV) += joydev.o | 11 | obj-$(CONFIG_INPUT_JOYDEV) += joydev.o |
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 4bf48188cc91..12c7ab876c34 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c | |||
@@ -391,8 +391,10 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd, | |||
391 | struct evdev *evdev = list->evdev; | 391 | struct evdev *evdev = list->evdev; |
392 | struct input_dev *dev = evdev->handle.dev; | 392 | struct input_dev *dev = evdev->handle.dev; |
393 | struct input_absinfo abs; | 393 | struct input_absinfo abs; |
394 | struct ff_effect effect; | ||
394 | int __user *ip = (int __user *)p; | 395 | int __user *ip = (int __user *)p; |
395 | int i, t, u, v; | 396 | int i, t, u, v; |
397 | int error; | ||
396 | 398 | ||
397 | if (!evdev->exist) | 399 | if (!evdev->exist) |
398 | return -ENODEV; | 400 | return -ENODEV; |
@@ -460,27 +462,22 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd, | |||
460 | return 0; | 462 | return 0; |
461 | 463 | ||
462 | case EVIOCSFF: | 464 | case EVIOCSFF: |
463 | if (dev->upload_effect) { | 465 | if (copy_from_user(&effect, p, sizeof(effect))) |
464 | struct ff_effect effect; | 466 | return -EFAULT; |
465 | int err; | ||
466 | |||
467 | if (copy_from_user(&effect, p, sizeof(effect))) | ||
468 | return -EFAULT; | ||
469 | err = dev->upload_effect(dev, &effect); | ||
470 | if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) | ||
471 | return -EFAULT; | ||
472 | return err; | ||
473 | } else | ||
474 | return -ENOSYS; | ||
475 | 467 | ||
476 | case EVIOCRMFF: | 468 | error = input_ff_upload(dev, &effect, file); |
477 | if (!dev->erase_effect) | ||
478 | return -ENOSYS; | ||
479 | 469 | ||
480 | return dev->erase_effect(dev, (int)(unsigned long) p); | 470 | if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) |
471 | return -EFAULT; | ||
472 | |||
473 | return error; | ||
474 | |||
475 | case EVIOCRMFF: | ||
476 | return input_ff_erase(dev, (int)(unsigned long) p, file); | ||
481 | 477 | ||
482 | case EVIOCGEFFECTS: | 478 | case EVIOCGEFFECTS: |
483 | if (put_user(dev->ff_effects_max, ip)) | 479 | i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0; |
480 | if (put_user(i, ip)) | ||
484 | return -EFAULT; | 481 | return -EFAULT; |
485 | return 0; | 482 | return 0; |
486 | 483 | ||
@@ -669,6 +666,7 @@ static void evdev_disconnect(struct input_handle *handle) | |||
669 | evdev->exist = 0; | 666 | evdev->exist = 0; |
670 | 667 | ||
671 | if (evdev->open) { | 668 | if (evdev->open) { |
669 | input_flush_device(handle, NULL); | ||
672 | input_close_device(handle); | 670 | input_close_device(handle); |
673 | wake_up_interruptible(&evdev->wait); | 671 | wake_up_interruptible(&evdev->wait); |
674 | list_for_each_entry(list, &evdev->list, node) | 672 | list_for_each_entry(list, &evdev->list, node) |
diff --git a/drivers/input/ff-core.c b/drivers/input/ff-core.c new file mode 100644 index 000000000000..35656cadc914 --- /dev/null +++ b/drivers/input/ff-core.c | |||
@@ -0,0 +1,367 @@ | |||
1 | /* | ||
2 | * Force feedback support for Linux input subsystem | ||
3 | * | ||
4 | * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com> | ||
5 | * Copyright (c) 2006 Dmitry Torokhov <dtor@mail.ru> | ||
6 | */ | ||
7 | |||
8 | /* | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | * | ||
14 | * This program is distributed in the hope that it will be useful, | ||
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
17 | * GNU General Public License for more details. | ||
18 | * | ||
19 | * You should have received a copy of the GNU General Public License | ||
20 | * along with this program; if not, write to the Free Software | ||
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
22 | */ | ||
23 | |||
24 | /* #define DEBUG */ | ||
25 | |||
26 | #define debug(format, arg...) pr_debug("ff-core: " format "\n", ## arg) | ||
27 | |||
28 | #include <linux/input.h> | ||
29 | #include <linux/module.h> | ||
30 | #include <linux/mutex.h> | ||
31 | |||
32 | /* | ||
33 | * Check that the effect_id is a valid effect and whether the user | ||
34 | * is the owner | ||
35 | */ | ||
36 | static int check_effect_access(struct ff_device *ff, int effect_id, | ||
37 | struct file *file) | ||
38 | { | ||
39 | if (effect_id < 0 || effect_id >= ff->max_effects || | ||
40 | !ff->effect_owners[effect_id]) | ||
41 | return -EINVAL; | ||
42 | |||
43 | if (file && ff->effect_owners[effect_id] != file) | ||
44 | return -EACCES; | ||
45 | |||
46 | return 0; | ||
47 | } | ||
48 | |||
49 | /* | ||
50 | * Checks whether 2 effects can be combined together | ||
51 | */ | ||
52 | static inline int check_effects_compatible(struct ff_effect *e1, | ||
53 | struct ff_effect *e2) | ||
54 | { | ||
55 | return e1->type == e2->type && | ||
56 | (e1->type != FF_PERIODIC || | ||
57 | e1->u.periodic.waveform == e2->u.periodic.waveform); | ||
58 | } | ||
59 | |||
60 | /* | ||
61 | * Convert an effect into compatible one | ||
62 | */ | ||
63 | static int compat_effect(struct ff_device *ff, struct ff_effect *effect) | ||
64 | { | ||
65 | int magnitude; | ||
66 | |||
67 | switch (effect->type) { | ||
68 | case FF_RUMBLE: | ||
69 | if (!test_bit(FF_PERIODIC, ff->ffbit)) | ||
70 | return -EINVAL; | ||
71 | |||
72 | /* | ||
73 | * calculate manginude of sine wave as average of rumble's | ||
74 | * 2/3 of strong magnitude and 1/3 of weak magnitude | ||
75 | */ | ||
76 | magnitude = effect->u.rumble.strong_magnitude / 3 + | ||
77 | effect->u.rumble.weak_magnitude / 6; | ||
78 | |||
79 | effect->type = FF_PERIODIC; | ||
80 | effect->u.periodic.waveform = FF_SINE; | ||
81 | effect->u.periodic.period = 50; | ||
82 | effect->u.periodic.magnitude = max(magnitude, 0x7fff); | ||
83 | effect->u.periodic.offset = 0; | ||
84 | effect->u.periodic.phase = 0; | ||
85 | effect->u.periodic.envelope.attack_length = 0; | ||
86 | effect->u.periodic.envelope.attack_level = 0; | ||
87 | effect->u.periodic.envelope.fade_length = 0; | ||
88 | effect->u.periodic.envelope.fade_level = 0; | ||
89 | |||
90 | return 0; | ||
91 | |||
92 | default: | ||
93 | /* Let driver handle conversion */ | ||
94 | return 0; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * input_ff_upload() - upload effect into force-feedback device | ||
100 | * @dev: input device | ||
101 | * @effect: effect to be uploaded | ||
102 | * @file: owner of the effect | ||
103 | */ | ||
104 | int input_ff_upload(struct input_dev *dev, struct ff_effect *effect, | ||
105 | struct file *file) | ||
106 | { | ||
107 | struct ff_device *ff = dev->ff; | ||
108 | struct ff_effect *old; | ||
109 | int ret = 0; | ||
110 | int id; | ||
111 | |||
112 | if (!test_bit(EV_FF, dev->evbit)) | ||
113 | return -ENOSYS; | ||
114 | |||
115 | if (effect->type < FF_EFFECT_MIN || effect->type > FF_EFFECT_MAX || | ||
116 | !test_bit(effect->type, dev->ffbit)) { | ||
117 | debug("invalid or not supported effect type in upload"); | ||
118 | return -EINVAL; | ||
119 | } | ||
120 | |||
121 | if (effect->type == FF_PERIODIC && | ||
122 | (effect->u.periodic.waveform < FF_WAVEFORM_MIN || | ||
123 | effect->u.periodic.waveform > FF_WAVEFORM_MAX || | ||
124 | !test_bit(effect->u.periodic.waveform, dev->ffbit))) { | ||
125 | debug("invalid or not supported wave form in upload"); | ||
126 | return -EINVAL; | ||
127 | } | ||
128 | |||
129 | if (!test_bit(effect->type, ff->ffbit)) { | ||
130 | ret = compat_effect(ff, effect); | ||
131 | if (ret) | ||
132 | return ret; | ||
133 | } | ||
134 | |||
135 | mutex_lock(&ff->mutex); | ||
136 | |||
137 | if (effect->id == -1) { | ||
138 | for (id = 0; id < ff->max_effects; id++) | ||
139 | if (!ff->effect_owners[id]) | ||
140 | break; | ||
141 | |||
142 | if (id >= ff->max_effects) { | ||
143 | ret = -ENOSPC; | ||
144 | goto out; | ||
145 | } | ||
146 | |||
147 | effect->id = id; | ||
148 | old = NULL; | ||
149 | |||
150 | } else { | ||
151 | id = effect->id; | ||
152 | |||
153 | ret = check_effect_access(ff, id, file); | ||
154 | if (ret) | ||
155 | goto out; | ||
156 | |||
157 | old = &ff->effects[id]; | ||
158 | |||
159 | if (!check_effects_compatible(effect, old)) { | ||
160 | ret = -EINVAL; | ||
161 | goto out; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | ret = ff->upload(dev, effect, old); | ||
166 | if (ret) | ||
167 | goto out; | ||
168 | |||
169 | ff->effects[id] = *effect; | ||
170 | ff->effect_owners[id] = file; | ||
171 | |||
172 | out: | ||
173 | mutex_unlock(&ff->mutex); | ||
174 | return ret; | ||
175 | } | ||
176 | EXPORT_SYMBOL_GPL(input_ff_upload); | ||
177 | |||
178 | /* | ||
179 | * Erases the effect if the requester is also the effect owner. The mutex | ||
180 | * should already be locked before calling this function. | ||
181 | */ | ||
182 | static int erase_effect(struct input_dev *dev, int effect_id, | ||
183 | struct file *file) | ||
184 | { | ||
185 | struct ff_device *ff = dev->ff; | ||
186 | int error; | ||
187 | |||
188 | error = check_effect_access(ff, effect_id, file); | ||
189 | if (error) | ||
190 | return error; | ||
191 | |||
192 | ff->playback(dev, effect_id, 0); | ||
193 | |||
194 | if (ff->erase) { | ||
195 | error = ff->erase(dev, effect_id); | ||
196 | if (error) | ||
197 | return error; | ||
198 | } | ||
199 | |||
200 | ff->effect_owners[effect_id] = NULL; | ||
201 | |||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * input_ff_erase - erase an effect from device | ||
207 | * @dev: input device to erase effect from | ||
208 | * @effect_id: id of the ffect to be erased | ||
209 | * @file: purported owner of the request | ||
210 | * | ||
211 | * This function erases a force-feedback effect from specified device. | ||
212 | * The effect will only be erased if it was uploaded through the same | ||
213 | * file handle that is requesting erase. | ||
214 | */ | ||
215 | int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file) | ||
216 | { | ||
217 | struct ff_device *ff = dev->ff; | ||
218 | int ret; | ||
219 | |||
220 | if (!test_bit(EV_FF, dev->evbit)) | ||
221 | return -ENOSYS; | ||
222 | |||
223 | mutex_lock(&ff->mutex); | ||
224 | ret = erase_effect(dev, effect_id, file); | ||
225 | mutex_unlock(&ff->mutex); | ||
226 | |||
227 | return ret; | ||
228 | } | ||
229 | EXPORT_SYMBOL_GPL(input_ff_erase); | ||
230 | |||
231 | /* | ||
232 | * flush_effects - erase all effects owned by a file handle | ||
233 | */ | ||
234 | static int flush_effects(struct input_dev *dev, struct file *file) | ||
235 | { | ||
236 | struct ff_device *ff = dev->ff; | ||
237 | int i; | ||
238 | |||
239 | debug("flushing now"); | ||
240 | |||
241 | mutex_lock(&ff->mutex); | ||
242 | |||
243 | for (i = 0; i < ff->max_effects; i++) | ||
244 | erase_effect(dev, i, file); | ||
245 | |||
246 | mutex_unlock(&ff->mutex); | ||
247 | |||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | /** | ||
252 | * input_ff_event() - generic handler for force-feedback events | ||
253 | * @dev: input device to send the effect to | ||
254 | * @type: event type (anything but EV_FF is ignored) | ||
255 | * @code: event code | ||
256 | * @value: event value | ||
257 | */ | ||
258 | int input_ff_event(struct input_dev *dev, unsigned int type, | ||
259 | unsigned int code, int value) | ||
260 | { | ||
261 | struct ff_device *ff = dev->ff; | ||
262 | |||
263 | if (type != EV_FF) | ||
264 | return 0; | ||
265 | |||
266 | mutex_lock(&ff->mutex); | ||
267 | |||
268 | switch (code) { | ||
269 | case FF_GAIN: | ||
270 | if (!test_bit(FF_GAIN, dev->ffbit) || value > 0xffff) | ||
271 | break; | ||
272 | |||
273 | ff->set_gain(dev, value); | ||
274 | break; | ||
275 | |||
276 | case FF_AUTOCENTER: | ||
277 | if (!test_bit(FF_AUTOCENTER, dev->ffbit) || value > 0xffff) | ||
278 | break; | ||
279 | |||
280 | ff->set_autocenter(dev, value); | ||
281 | break; | ||
282 | |||
283 | default: | ||
284 | ff->playback(dev, code, value); | ||
285 | break; | ||
286 | } | ||
287 | |||
288 | mutex_unlock(&ff->mutex); | ||
289 | return 0; | ||
290 | } | ||
291 | EXPORT_SYMBOL_GPL(input_ff_event); | ||
292 | |||
293 | /** | ||
294 | * input_ff_create() - create force-feedback device | ||
295 | * @dev: input device supporting force-feedback | ||
296 | * @max_effects: maximum number of effects supported by the device | ||
297 | * | ||
298 | * This function allocates all necessary memory for a force feedback | ||
299 | * portion of an input device and installs all default handlers. | ||
300 | * @dev->ffbit should be already set up before calling this function. | ||
301 | * Once ff device is created you need to setup its upload, erase, | ||
302 | * playback and other handlers before registering input device | ||
303 | */ | ||
304 | int input_ff_create(struct input_dev *dev, int max_effects) | ||
305 | { | ||
306 | struct ff_device *ff; | ||
307 | int i; | ||
308 | |||
309 | if (!max_effects) { | ||
310 | printk(KERN_ERR | ||
311 | "ff-core: cannot allocate device without any effects\n"); | ||
312 | return -EINVAL; | ||
313 | } | ||
314 | |||
315 | ff = kzalloc(sizeof(struct ff_device) + | ||
316 | max_effects * sizeof(struct file *), GFP_KERNEL); | ||
317 | if (!ff) | ||
318 | return -ENOMEM; | ||
319 | |||
320 | ff->effects = kcalloc(max_effects, sizeof(struct ff_effect), | ||
321 | GFP_KERNEL); | ||
322 | if (!ff->effects) { | ||
323 | kfree(ff); | ||
324 | return -ENOMEM; | ||
325 | } | ||
326 | |||
327 | ff->max_effects = max_effects; | ||
328 | mutex_init(&ff->mutex); | ||
329 | |||
330 | dev->ff = ff; | ||
331 | dev->flush = flush_effects; | ||
332 | dev->event = input_ff_event; | ||
333 | set_bit(EV_FF, dev->evbit); | ||
334 | |||
335 | /* Copy "true" bits into ff device bitmap */ | ||
336 | for (i = 0; i <= FF_MAX; i++) | ||
337 | if (test_bit(i, dev->ffbit)) | ||
338 | set_bit(i, ff->ffbit); | ||
339 | |||
340 | /* we can emulate RUMBLE with periodic effects */ | ||
341 | if (test_bit(FF_PERIODIC, ff->ffbit)) | ||
342 | set_bit(FF_RUMBLE, dev->ffbit); | ||
343 | |||
344 | return 0; | ||
345 | } | ||
346 | EXPORT_SYMBOL_GPL(input_ff_create); | ||
347 | |||
348 | /** | ||
349 | * input_ff_free() - frees force feedback portion of input device | ||
350 | * @dev: input device supporintg force feedback | ||
351 | * | ||
352 | * This function is only needed in error path as input core will | ||
353 | * automatically free force feedback structures when device is | ||
354 | * destroyed. | ||
355 | */ | ||
356 | void input_ff_destroy(struct input_dev *dev) | ||
357 | { | ||
358 | clear_bit(EV_FF, dev->evbit); | ||
359 | if (dev->ff) { | ||
360 | if (dev->ff->destroy) | ||
361 | dev->ff->destroy(dev->ff); | ||
362 | kfree(dev->ff->private); | ||
363 | kfree(dev->ff); | ||
364 | dev->ff = NULL; | ||
365 | } | ||
366 | } | ||
367 | EXPORT_SYMBOL_GPL(input_ff_destroy); | ||
diff --git a/drivers/input/input.c b/drivers/input/input.c index 9cb4b9a54f01..1fc0517e9428 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c | |||
@@ -176,6 +176,10 @@ void input_event(struct input_dev *dev, unsigned int type, unsigned int code, in | |||
176 | break; | 176 | break; |
177 | 177 | ||
178 | case EV_FF: | 178 | case EV_FF: |
179 | |||
180 | if (value < 0) | ||
181 | return; | ||
182 | |||
179 | if (dev->event) | 183 | if (dev->event) |
180 | dev->event(dev, type, code, value); | 184 | dev->event(dev, type, code, value); |
181 | break; | 185 | break; |
@@ -762,7 +766,9 @@ static void input_dev_release(struct class_device *class_dev) | |||
762 | { | 766 | { |
763 | struct input_dev *dev = to_input_dev(class_dev); | 767 | struct input_dev *dev = to_input_dev(class_dev); |
764 | 768 | ||
769 | input_ff_destroy(dev); | ||
765 | kfree(dev); | 770 | kfree(dev); |
771 | |||
766 | module_put(THIS_MODULE); | 772 | module_put(THIS_MODULE); |
767 | } | 773 | } |
768 | 774 | ||