summaryrefslogtreecommitdiffstats
path: root/drivers/input/evdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/input/evdev.c')
-rw-r--r--drivers/input/evdev.c198
1 files changed, 111 insertions, 87 deletions
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 2ee6c7a68bd..c908c5f8364 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -10,7 +10,8 @@
#define EVDEV_MINOR_BASE 64
#define EVDEV_MINORS 32
-#define EVDEV_BUFFER_SIZE 64
+#define EVDEV_MIN_BUFFER_SIZE 64U
+#define EVDEV_BUF_PACKETS 8
#include <linux/poll.h>
#include <linux/sched.h>
@@ -23,7 +24,6 @@
#include "input-compat.h"
struct evdev {
- int exist;
int open;
int minor;
struct input_handle handle;
@@ -33,16 +33,18 @@ struct evdev {
spinlock_t client_lock; /* protects client_list */
struct mutex mutex;
struct device dev;
+ bool exist;
};
struct evdev_client {
- struct input_event buffer[EVDEV_BUFFER_SIZE];
int head;
int tail;
spinlock_t buffer_lock; /* protects access to buffer, head and tail */
struct fasync_struct *fasync;
struct evdev *evdev;
struct list_head node;
+ int bufsize;
+ struct input_event buffer[];
};
static struct evdev *evdev_table[EVDEV_MINORS];
@@ -52,11 +54,15 @@ static void evdev_pass_event(struct evdev_client *client,
struct input_event *event)
{
/*
- * Interrupts are disabled, just acquire the lock
+ * Interrupts are disabled, just acquire the lock.
+ * Make sure we don't leave with the client buffer
+ * "empty" by having client->head == client->tail.
*/
spin_lock(&client->buffer_lock);
- client->buffer[client->head++] = *event;
- client->head &= EVDEV_BUFFER_SIZE - 1;
+ do {
+ client->buffer[client->head++] = *event;
+ client->head &= client->bufsize - 1;
+ } while (client->head == client->tail);
spin_unlock(&client->buffer_lock);
if (event->type == EV_SYN)
@@ -242,11 +248,21 @@ static int evdev_release(struct inode *inode, struct file *file)
return 0;
}
+static unsigned int evdev_compute_buffer_size(struct input_dev *dev)
+{
+ unsigned int n_events =
+ max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,
+ EVDEV_MIN_BUFFER_SIZE);
+
+ return roundup_pow_of_two(n_events);
+}
+
static int evdev_open(struct inode *inode, struct file *file)
{
struct evdev *evdev;
struct evdev_client *client;
int i = iminor(inode) - EVDEV_MINOR_BASE;
+ unsigned int bufsize;
int error;
if (i >= EVDEV_MINORS)
@@ -263,12 +279,17 @@ static int evdev_open(struct inode *inode, struct file *file)
if (!evdev)
return -ENODEV;
- client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);
+ bufsize = evdev_compute_buffer_size(evdev->handle.dev);
+
+ client = kzalloc(sizeof(struct evdev_client) +
+ bufsize * sizeof(struct input_event),
+ GFP_KERNEL);
if (!client) {
error = -ENOMEM;
goto err_put_evdev;
}
+ client->bufsize = bufsize;
spin_lock_init(&client->buffer_lock);
client->evdev = evdev;
evdev_attach_client(evdev, client);
@@ -334,7 +355,7 @@ static int evdev_fetch_next_event(struct evdev_client *client,
have_event = client->head != client->tail;
if (have_event) {
*event = client->buffer[client->tail++];
- client->tail &= EVDEV_BUFFER_SIZE - 1;
+ client->tail &= client->bufsize - 1;
}
spin_unlock_irq(&client->buffer_lock);
@@ -382,10 +403,15 @@ static unsigned int evdev_poll(struct file *file, poll_table *wait)
{
struct evdev_client *client = file->private_data;
struct evdev *evdev = client->evdev;
+ unsigned int mask;
poll_wait(file, &evdev->wait, wait);
- return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) |
- (evdev->exist ? 0 : (POLLHUP | POLLERR));
+
+ mask = evdev->exist ? POLLOUT | POLLWRNORM : POLLHUP | POLLERR;
+ if (client->head != client->tail)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
}
#ifdef CONFIG_COMPAT
@@ -466,13 +492,15 @@ static int str_to_user(const char *str, unsigned int maxlen, void __user *p)
}
#define OLD_KEY_MAX 0x1ff
-static int handle_eviocgbit(struct input_dev *dev, unsigned int cmd, void __user *p, int compat_mode)
+static int handle_eviocgbit(struct input_dev *dev,
+ unsigned int type, unsigned int size,
+ void __user *p, int compat_mode)
{
static unsigned long keymax_warn_time;
unsigned long *bits;
int len;
- switch (_IOC_NR(cmd) & EV_MAX) {
+ switch (type) {
case 0: bits = dev->evbit; len = EV_MAX; break;
case EV_KEY: bits = dev->keybit; len = KEY_MAX; break;
@@ -491,7 +519,7 @@ static int handle_eviocgbit(struct input_dev *dev, unsigned int cmd, void __user
* EVIOCGBIT(EV_KEY, KEY_MAX) and not realize that 'len'
* should be in bytes, not in bits.
*/
- if ((_IOC_NR(cmd) & EV_MAX) == EV_KEY && _IOC_SIZE(cmd) == OLD_KEY_MAX) {
+ if (type == EV_KEY && size == OLD_KEY_MAX) {
len = OLD_KEY_MAX;
if (printk_timed_ratelimit(&keymax_warn_time, 10 * 1000))
printk(KERN_WARNING
@@ -502,7 +530,7 @@ static int handle_eviocgbit(struct input_dev *dev, unsigned int cmd, void __user
BITS_TO_LONGS(OLD_KEY_MAX) * sizeof(long));
}
- return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode);
+ return bits_to_user(bits, len, size, p, compat_mode);
}
#undef OLD_KEY_MAX
@@ -516,8 +544,10 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
struct ff_effect effect;
int __user *ip = (int __user *)p;
unsigned int i, t, u, v;
+ unsigned int size;
int error;
+ /* First we check for fixed-length commands */
switch (cmd) {
case EVIOCGVERSION:
@@ -584,108 +614,102 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd,
return evdev_grab(evdev, client);
else
return evdev_ungrab(evdev, client);
+ }
- default:
-
- if (_IOC_TYPE(cmd) != 'E')
- return -EINVAL;
+ size = _IOC_SIZE(cmd);
- if (_IOC_DIR(cmd) == _IOC_READ) {
+ /* Now check variable-length commands */
+#define EVIOC_MASK_SIZE(nr) ((nr) & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT))
- if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))
- return handle_eviocgbit(dev, cmd, p, compat_mode);
+ switch (EVIOC_MASK_SIZE(cmd)) {
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0)))
- return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd),
- p, compat_mode);
+ case EVIOCGKEY(0):
+ return bits_to_user(dev->key, KEY_MAX, size, p, compat_mode);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0)))
- return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd),
- p, compat_mode);
+ case EVIOCGLED(0):
+ return bits_to_user(dev->led, LED_MAX, size, p, compat_mode);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0)))
- return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd),
- p, compat_mode);
+ case EVIOCGSND(0):
+ return bits_to_user(dev->snd, SND_MAX, size, p, compat_mode);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0)))
- return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd),
- p, compat_mode);
+ case EVIOCGSW(0):
+ return bits_to_user(dev->sw, SW_MAX, size, p, compat_mode);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0)))
- return str_to_user(dev->name, _IOC_SIZE(cmd), p);
+ case EVIOCGNAME(0):
+ return str_to_user(dev->name, size, p);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0)))
- return str_to_user(dev->phys, _IOC_SIZE(cmd), p);
+ case EVIOCGPHYS(0):
+ return str_to_user(dev->phys, size, p);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0)))
- return str_to_user(dev->uniq, _IOC_SIZE(cmd), p);
+ case EVIOCGUNIQ(0):
+ return str_to_user(dev->uniq, size, p);
- if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
+ case EVIOC_MASK_SIZE(EVIOCSFF):
+ if (input_ff_effect_from_user(p, size, &effect))
+ return -EFAULT;
- t = _IOC_NR(cmd) & ABS_MAX;
+ error = input_ff_upload(dev, &effect, file);
- abs.value = dev->abs[t];
- abs.minimum = dev->absmin[t];
- abs.maximum = dev->absmax[t];
- abs.fuzz = dev->absfuzz[t];
- abs.flat = dev->absflat[t];
- abs.resolution = dev->absres[t];
+ if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
+ return -EFAULT;
- if (copy_to_user(p, &abs, min_t(size_t,
- _IOC_SIZE(cmd),
- sizeof(struct input_absinfo))))
- return -EFAULT;
+ return error;
+ }
- return 0;
- }
+ /* Multi-number variable-length handlers */
+ if (_IOC_TYPE(cmd) != 'E')
+ return -EINVAL;
- }
+ if (_IOC_DIR(cmd) == _IOC_READ) {
- if (_IOC_DIR(cmd) == _IOC_WRITE) {
+ if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0)))
+ return handle_eviocgbit(dev,
+ _IOC_NR(cmd) & EV_MAX, size,
+ p, compat_mode);
- if (_IOC_NR(cmd) == _IOC_NR(EVIOCSFF)) {
+ if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) {
- if (input_ff_effect_from_user(p, _IOC_SIZE(cmd), &effect))
- return -EFAULT;
+ t = _IOC_NR(cmd) & ABS_MAX;
+ abs = dev->absinfo[t];
- error = input_ff_upload(dev, &effect, file);
+ if (copy_to_user(p, &abs, min_t(size_t,
+ size, sizeof(struct input_absinfo))))
+ return -EFAULT;
- if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
- return -EFAULT;
+ return 0;
+ }
+ }
- return error;
- }
+ if (_IOC_DIR(cmd) == _IOC_READ) {
- if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
+ if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) {
- t = _IOC_NR(cmd) & ABS_MAX;
+ t = _IOC_NR(cmd) & ABS_MAX;
- if (copy_from_user(&abs, p, min_t(size_t,
- _IOC_SIZE(cmd),
- sizeof(struct input_absinfo))))
- return -EFAULT;
+ if (copy_from_user(&abs, p, min_t(size_t,
+ size, sizeof(struct input_absinfo))))
+ return -EFAULT;
- /*
- * Take event lock to ensure that we are not
- * changing device parameters in the middle
- * of event.
- */
- spin_lock_irq(&dev->event_lock);
+ if (size < sizeof(struct input_absinfo))
+ abs.resolution = 0;
- dev->abs[t] = abs.value;
- dev->absmin[t] = abs.minimum;
- dev->absmax[t] = abs.maximum;
- dev->absfuzz[t] = abs.fuzz;
- dev->absflat[t] = abs.flat;
- dev->absres[t] = _IOC_SIZE(cmd) < sizeof(struct input_absinfo) ?
- 0 : abs.resolution;
+ /* We can't change number of reserved MT slots */
+ if (t == ABS_MT_SLOT)
+ return -EINVAL;
- spin_unlock_irq(&dev->event_lock);
+ /*
+ * Take event lock to ensure that we are not
+ * changing device parameters in the middle
+ * of event.
+ */
+ spin_lock_irq(&dev->event_lock);
+ dev->absinfo[t] = abs;
+ spin_unlock_irq(&dev->event_lock);
- return 0;
- }
+ return 0;
}
}
+
return -EINVAL;
}
@@ -768,7 +792,7 @@ static void evdev_remove_chrdev(struct evdev *evdev)
static void evdev_mark_dead(struct evdev *evdev)
{
mutex_lock(&evdev->mutex);
- evdev->exist = 0;
+ evdev->exist = false;
mutex_unlock(&evdev->mutex);
}
@@ -817,7 +841,7 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
init_waitqueue_head(&evdev->wait);
dev_set_name(&evdev->dev, "event%d", minor);
- evdev->exist = 1;
+ evdev->exist = true;
evdev->minor = minor;
evdev->handle.dev = input_get_device(dev);