Merge branch 'irq-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[cascardo/linux.git] / drivers / input / evdev.c
index f0f8928..d2b34fb 100644 (file)
@@ -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);