diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2007-11-04 00:41:12 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2008-01-21 01:11:06 -0500 |
commit | f4f37c8ec7d2491c8885c890ba74254b9adfbeee (patch) | |
tree | ecd4cb10af7fd7aa4fb02fe2a106608e9d64c3fe | |
parent | 554101e3e5f396b987c846332863a3fcdc87b1d6 (diff) |
Input: Add proper locking when changing device's keymap
Take dev->event_lock to make sure that we don't race with input_event() and
also force key up event when removing a key from keymap table.
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
-rw-r--r-- | drivers/char/keyboard.c | 4 | ||||
-rw-r--r-- | drivers/input/evdev.c | 6 | ||||
-rw-r--r-- | drivers/input/input.c | 78 | ||||
-rw-r--r-- | include/linux/input.h | 3 |
4 files changed, 80 insertions, 11 deletions
diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c index fc54d234507a..5218d0d0511e 100644 --- a/drivers/char/keyboard.c +++ b/drivers/char/keyboard.c @@ -194,7 +194,7 @@ int getkeycode(unsigned int scancode) int error = -ENODEV; list_for_each_entry(handle, &kbd_handler.h_list, h_node) { - error = handle->dev->getkeycode(handle->dev, scancode, &keycode); + error = input_get_keycode(handle->dev, scancode, &keycode); if (!error) return keycode; } @@ -208,7 +208,7 @@ int setkeycode(unsigned int scancode, unsigned int keycode) int error = -ENODEV; list_for_each_entry(handle, &kbd_handler.h_list, h_node) { - error = handle->dev->setkeycode(handle->dev, scancode, keycode); + error = input_set_keycode(handle->dev, scancode, keycode); if (!error) break; } diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index e5b4e9bfbdc5..0727b0a12557 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -617,7 +617,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, if (get_user(t, ip)) return -EFAULT; - error = dev->getkeycode(dev, t, &v); + error = input_get_keycode(dev, t, &v); if (error) return error; @@ -630,7 +630,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, if (get_user(t, ip) || get_user(v, ip + 1)) return -EFAULT; - return dev->setkeycode(dev, t, v); + return input_set_keycode(dev, t, v); case EVIOCSFF: if (copy_from_user(&effect, p, sizeof(effect))) @@ -683,7 +683,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, case EV_FF: bits = dev->ffbit; len = FF_MAX; break; case EV_SW: bits = dev->swbit; len = SW_MAX; break; default: return -EINVAL; - } + } return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode); } diff --git a/drivers/input/input.c b/drivers/input/input.c index a0be978501ff..e1729e1dd9b2 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -493,7 +493,7 @@ static void input_disconnect_device(struct input_dev *dev) if (is_event_supported(EV_KEY, dev->evbit, EV_MAX)) { for (code = 0; code <= KEY_MAX; code++) { if (is_event_supported(code, dev->keybit, KEY_MAX) && - test_bit(code, dev->key)) { + __test_and_clear_bit(code, dev->key)) { input_pass_event(dev, EV_KEY, code, 0); } } @@ -526,7 +526,7 @@ static int input_default_getkeycode(struct input_dev *dev, if (!dev->keycodesize) return -EINVAL; - if (scancode < 0 || scancode >= dev->keycodemax) + if (scancode >= dev->keycodemax) return -EINVAL; *keycode = input_fetch_keycode(dev, scancode); @@ -540,10 +540,7 @@ static int input_default_setkeycode(struct input_dev *dev, int old_keycode; int i; - if (scancode < 0 || scancode >= dev->keycodemax) - return -EINVAL; - - if (keycode < 0 || keycode > KEY_MAX) + if (scancode >= dev->keycodemax) return -EINVAL; if (!dev->keycodesize) @@ -586,6 +583,75 @@ static int input_default_setkeycode(struct input_dev *dev, return 0; } +/** + * input_get_keycode - retrieve keycode currently mapped to a given scancode + * @dev: input device which keymap is being queried + * @scancode: scancode (or its equivalent for device in question) for which + * keycode is needed + * @keycode: result + * + * This function should be called by anyone interested in retrieving current + * keymap. Presently keyboard and evdev handlers use it. + */ +int input_get_keycode(struct input_dev *dev, int scancode, int *keycode) +{ + if (scancode < 0) + return -EINVAL; + + return dev->getkeycode(dev, scancode, keycode); +} +EXPORT_SYMBOL(input_get_keycode); + +/** + * input_get_keycode - assign new keycode to a given scancode + * @dev: input device which keymap is being updated + * @scancode: scancode (or its equivalent for device in question) + * @keycode: new keycode to be assigned to the scancode + * + * This function should be called by anyone needing to update current + * keymap. Presently keyboard and evdev handlers use it. + */ +int input_set_keycode(struct input_dev *dev, int scancode, int keycode) +{ + unsigned long flags; + int old_keycode; + int retval; + + if (scancode < 0) + return -EINVAL; + + if (keycode < 0 || keycode > KEY_MAX) + return -EINVAL; + + spin_lock_irqsave(&dev->event_lock, flags); + + retval = dev->getkeycode(dev, scancode, &old_keycode); + if (retval) + goto out; + + retval = dev->setkeycode(dev, scancode, keycode); + if (retval) + goto out; + + /* + * Simulate keyup event if keycode is not present + * in the keymap anymore + */ + if (test_bit(EV_KEY, dev->evbit) && + !is_event_supported(old_keycode, dev->keybit, KEY_MAX) && + __test_and_clear_bit(old_keycode, dev->key)) { + + input_pass_event(dev, EV_KEY, old_keycode, 0); + if (dev->sync) + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } + + out: + spin_unlock_irqrestore(&dev->event_lock, flags); + + return retval; +} +EXPORT_SYMBOL(input_set_keycode); #define MATCH_BIT(bit, max) \ for (i = 0; i < BITS_TO_LONGS(max); i++) \ diff --git a/include/linux/input.h b/include/linux/input.h index 2075d6da2a31..9a963fe97300 100644 --- a/include/linux/input.h +++ b/include/linux/input.h @@ -1309,6 +1309,9 @@ static inline void input_set_abs_params(struct input_dev *dev, int axis, int min dev->absbit[BIT_WORD(axis)] |= BIT_MASK(axis); } +int input_get_keycode(struct input_dev *dev, int scancode, int *keycode); +int input_set_keycode(struct input_dev *dev, int scancode, int keycode); + extern struct class input_class; /** |