summaryrefslogtreecommitdiff
path: root/drivers/input/evdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/evdev.c')
-rw-r--r--drivers/input/evdev.c133
1 files changed, 129 insertions, 4 deletions
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index f0f8928b3c8a..d2b34fbbc42e 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -52,6 +52,82 @@ struct evdev_client {
struct input_event buffer[];
};
+/* flush queued events of type @type, caller must hold client->buffer_lock */
+static void __evdev_flush_queue(struct evdev_client *client, unsigned int type)
+{
+ unsigned int i, head, num;
+ unsigned int mask = client->bufsize - 1;
+ bool is_report;
+ struct input_event *ev;
+
+ BUG_ON(type == EV_SYN);
+
+ head = client->tail;
+ client->packet_head = client->tail;
+
+ /* init to 1 so a leading SYN_REPORT will not be dropped */
+ num = 1;
+
+ for (i = client->tail; i != client->head; i = (i + 1) & mask) {
+ ev = &client->buffer[i];
+ is_report = ev->type == EV_SYN && ev->code == SYN_REPORT;
+
+ if (ev->type == type) {
+ /* drop matched entry */
+ continue;
+ } else if (is_report && !num) {
+ /* drop empty SYN_REPORT groups */
+ continue;
+ } else if (head != i) {
+ /* move entry to fill the gap */
+ client->buffer[head].time = ev->time;
+ client->buffer[head].type = ev->type;
+ client->buffer[head].code = ev->code;
+ client->buffer[head].value = ev->value;
+ }
+
+ num++;
+ head = (head + 1) & mask;
+
+ if (is_report) {
+ num = 0;
+ client->packet_head = head;
+ }
+ }
+
+ client->head = head;
+}
+
+/* queue SYN_DROPPED event */
+static void evdev_queue_syn_dropped(struct evdev_client *client)
+{
+ unsigned long flags;
+ struct input_event ev;
+ ktime_t time;
+
+ time = ktime_get();
+ if (client->clkid != CLOCK_MONOTONIC)
+ time = ktime_sub(time, ktime_get_monotonic_offset());
+
+ ev.time = ktime_to_timeval(time);
+ ev.type = EV_SYN;
+ ev.code = SYN_DROPPED;
+ ev.value = 0;
+
+ spin_lock_irqsave(&client->buffer_lock, flags);
+
+ client->buffer[client->head++] = ev;
+ client->head &= client->bufsize - 1;
+
+ if (unlikely(client->head == client->tail)) {
+ /* drop queue but keep our SYN_DROPPED event */
+ client->tail = (client->head - 1) & (client->bufsize - 1);
+ client->packet_head = client->tail;
+ }
+
+ spin_unlock_irqrestore(&client->buffer_lock, flags);
+}
+
static void __pass_event(struct evdev_client *client,
const struct input_event *event)
{
@@ -650,6 +726,51 @@ static int evdev_handle_set_keycode_v2(struct input_dev *dev, void __user *p)
return input_set_keycode(dev, &ke);
}
+/*
+ * If we transfer state to the user, we should flush all pending events
+ * of the same type from the client's queue. Otherwise, they might end up
+ * with duplicate events, which can screw up client's state tracking.
+ * If bits_to_user fails after flushing the queue, we queue a SYN_DROPPED
+ * event so user-space will notice missing events.
+ *
+ * LOCKING:
+ * We need to take event_lock before buffer_lock to avoid dead-locks. But we
+ * need the even_lock only to guarantee consistent state. We can safely release
+ * it while flushing the queue. This allows input-core to handle filters while
+ * we flush the queue.
+ */
+static int evdev_handle_get_val(struct evdev_client *client,
+ struct input_dev *dev, unsigned int type,
+ unsigned long *bits, unsigned int max,
+ unsigned int size, void __user *p, int compat)
+{
+ int ret;
+ unsigned long *mem;
+
+ mem = kmalloc(sizeof(unsigned long) * max, GFP_KERNEL);
+ if (!mem)
+ return -ENOMEM;
+
+ spin_lock_irq(&dev->event_lock);
+ spin_lock(&client->buffer_lock);
+
+ memcpy(mem, bits, sizeof(unsigned long) * max);
+
+ spin_unlock(&dev->event_lock);
+
+ __evdev_flush_queue(client, type);
+
+ spin_unlock_irq(&client->buffer_lock);
+
+ ret = bits_to_user(mem, max, size, p, compat);
+ if (ret < 0)
+ evdev_queue_syn_dropped(client);
+
+ kfree(mem);
+
+ return ret;
+}
+
static int evdev_handle_mt_request(struct input_dev *dev,
unsigned int size,
int __user *ip)
@@ -771,16 +892,20 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
return evdev_handle_mt_request(dev, size, ip);
case EVIOCGKEY(0):
- return bits_to_user(dev->key, KEY_MAX, size, p, compat_mode);
+ return evdev_handle_get_val(client, dev, EV_KEY, dev->key,
+ KEY_MAX, size, p, compat_mode);
case EVIOCGLED(0):
- return bits_to_user(dev->led, LED_MAX, size, p, compat_mode);
+ return evdev_handle_get_val(client, dev, EV_LED, dev->led,
+ LED_MAX, size, p, compat_mode);
case EVIOCGSND(0):
- return bits_to_user(dev->snd, SND_MAX, size, p, compat_mode);
+ return evdev_handle_get_val(client, dev, EV_SND, dev->snd,
+ SND_MAX, size, p, compat_mode);
case EVIOCGSW(0):
- return bits_to_user(dev->sw, SW_MAX, size, p, compat_mode);
+ return evdev_handle_get_val(client, dev, EV_SW, dev->sw,
+ SW_MAX, size, p, compat_mode);
case EVIOCGNAME(0):
return str_to_user(dev->name, size, p);