diff options
Diffstat (limited to 'drivers/input/evdev.c')
| -rw-r--r-- | drivers/input/evdev.c | 133 | 
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);  | 
