Browse Source

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>
Dmitry Torokhov 17 years ago
parent
commit
f4f37c8ec7
4 changed files with 80 additions and 11 deletions
  1. 2 2
      drivers/char/keyboard.c
  2. 3 3
      drivers/input/evdev.c
  3. 72 6
      drivers/input/input.c
  4. 3 0
      include/linux/input.h

+ 2 - 2
drivers/char/keyboard.c

@@ -194,7 +194,7 @@ int getkeycode(unsigned int scancode)
 	int error = -ENODEV;
 	int error = -ENODEV;
 
 
 	list_for_each_entry(handle, &kbd_handler.h_list, h_node) {
 	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)
 		if (!error)
 			return keycode;
 			return keycode;
 	}
 	}
@@ -208,7 +208,7 @@ int setkeycode(unsigned int scancode, unsigned int keycode)
 	int error = -ENODEV;
 	int error = -ENODEV;
 
 
 	list_for_each_entry(handle, &kbd_handler.h_list, h_node) {
 	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)
 		if (!error)
 			break;
 			break;
 	}
 	}

+ 3 - 3
drivers/input/evdev.c

@@ -617,7 +617,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
 		if (get_user(t, ip))
 		if (get_user(t, ip))
 			return -EFAULT;
 			return -EFAULT;
 
 
-		error = dev->getkeycode(dev, t, &v);
+		error = input_get_keycode(dev, t, &v);
 		if (error)
 		if (error)
 			return 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))
 		if (get_user(t, ip) || get_user(v, ip + 1))
 			return -EFAULT;
 			return -EFAULT;
 
 
-		return dev->setkeycode(dev, t, v);
+		return input_set_keycode(dev, t, v);
 
 
 	case EVIOCSFF:
 	case EVIOCSFF:
 		if (copy_from_user(&effect, p, sizeof(effect)))
 		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_FF:  bits = dev->ffbit;  len = FF_MAX;  break;
 				case EV_SW:  bits = dev->swbit;  len = SW_MAX;  break;
 				case EV_SW:  bits = dev->swbit;  len = SW_MAX;  break;
 				default: return -EINVAL;
 				default: return -EINVAL;
-			}
+				}
 				return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode);
 				return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode);
 			}
 			}
 
 

+ 72 - 6
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)) {
 	if (is_event_supported(EV_KEY, dev->evbit, EV_MAX)) {
 		for (code = 0; code <= KEY_MAX; code++) {
 		for (code = 0; code <= KEY_MAX; code++) {
 			if (is_event_supported(code, dev->keybit, KEY_MAX) &&
 			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);
 				input_pass_event(dev, EV_KEY, code, 0);
 			}
 			}
 		}
 		}
@@ -526,7 +526,7 @@ static int input_default_getkeycode(struct input_dev *dev,
 	if (!dev->keycodesize)
 	if (!dev->keycodesize)
 		return -EINVAL;
 		return -EINVAL;
 
 
-	if (scancode < 0 || scancode >= dev->keycodemax)
+	if (scancode >= dev->keycodemax)
 		return -EINVAL;
 		return -EINVAL;
 
 
 	*keycode = input_fetch_keycode(dev, scancode);
 	*keycode = input_fetch_keycode(dev, scancode);
@@ -540,10 +540,7 @@ static int input_default_setkeycode(struct input_dev *dev,
 	int old_keycode;
 	int old_keycode;
 	int i;
 	int i;
 
 
-	if (scancode < 0 || scancode >= dev->keycodemax)
-		return -EINVAL;
-
-	if (keycode < 0 || keycode > KEY_MAX)
+	if (scancode >= dev->keycodemax)
 		return -EINVAL;
 		return -EINVAL;
 
 
 	if (!dev->keycodesize)
 	if (!dev->keycodesize)
@@ -586,6 +583,75 @@ static int input_default_setkeycode(struct input_dev *dev,
 	return 0;
 	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) \
 #define MATCH_BIT(bit, max) \
 		for (i = 0; i < BITS_TO_LONGS(max); i++) \
 		for (i = 0; i < BITS_TO_LONGS(max); i++) \

+ 3 - 0
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);
 	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;
 extern struct class input_class;
 
 
 /**
 /**