diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-11-06 11:57:41 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-11-06 11:57:41 -0800 |
| commit | 3e069adabc9487b5e28065a17e6a228da3412dfd (patch) | |
| tree | 93b4db74b3d293bc30e69a7ffb4a2ea1c4069682 /drivers/input/evdev.c | |
| parent | 02f0d3f758ab456c50199b723a53f2443fa4f684 (diff) | |
| parent | e60e063c14b13d0f66ffc708b8aa5d1a8208606e (diff) | |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov:
"Items of note:
- evdev users can now limit or mask the kind of events they will
receive. This will allow applications such as power manager or
network manager to only be woken when user presses special keys
such as KEY_POWER or KEY_WIFI and not be bothered with ordinary
key presses coming from keyboard
- support for FocalTech FT6236 touchscreen controller
- support for ROHM BU21023/24 touchscreen controller
- edt-ft5x06 touchscreen driver got a face lift and can now be used
with FT5506
- support for Google Fiber TV Box remote controls
- improvements in xpad driver (with more to come)
- several parport-based drivers have been switched to the new device
model
- other miscellaneous driver improvements"
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: (70 commits)
HID: hid-gfrm: avoid warning for input_configured API change
HID: hid-input: allow input_configured callback return errors
Input: evdev - fix bug in checking duplicate clock change request
Input: add userio module
Input: evdev - add event-mask API
Input: snvs_pwrkey - remove duplicated semicolon
HID: hid-gfrm: Google Fiber TV Box remote controls
Input: e3x0-button - update Kconfig description
Input: tegra-kbc - drop use of IRQF_NO_SUSPEND flag
Input: tegra-kbc - enable support for the standard "wakeup-source" property
Input: xen - check return value of xenbus_printf
Input: hp_sdc_rtc - fix y2038 problem in proc_show
Input: nomadik-ske-keypad - fix a trivial typo
Input: xpad - fix clash of presence handling with LED setting
Input: edt-ft5x06 - work around FT5506 firmware bug
Input: edt-ft5x06 - add support for FT5506
Input: edt-ft5x06 - add support for different max support points
Input: edt-ft5x06 - use max support points to determine how much to read
Input: rotary-encoder - add support for quarter-period mode
Input: rotary-encoder - use of_property_read_bool
...
Diffstat (limited to 'drivers/input/evdev.c')
| -rw-r--r-- | drivers/input/evdev.c | 270 |
1 files changed, 251 insertions, 19 deletions
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 08d496411f75..e9ae3d500a55 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -56,12 +56,57 @@ struct evdev_client { struct fasync_struct *fasync; struct evdev *evdev; struct list_head node; - int clk_type; + unsigned int clk_type; bool revoked; + unsigned long *evmasks[EV_CNT]; unsigned int bufsize; struct input_event buffer[]; }; +static size_t evdev_get_mask_cnt(unsigned int type) +{ + static const size_t counts[EV_CNT] = { + /* EV_SYN==0 is EV_CNT, _not_ SYN_CNT, see EVIOCGBIT */ + [EV_SYN] = EV_CNT, + [EV_KEY] = KEY_CNT, + [EV_REL] = REL_CNT, + [EV_ABS] = ABS_CNT, + [EV_MSC] = MSC_CNT, + [EV_SW] = SW_CNT, + [EV_LED] = LED_CNT, + [EV_SND] = SND_CNT, + [EV_FF] = FF_CNT, + }; + + return (type < EV_CNT) ? counts[type] : 0; +} + +/* requires the buffer lock to be held */ +static bool __evdev_is_filtered(struct evdev_client *client, + unsigned int type, + unsigned int code) +{ + unsigned long *mask; + size_t cnt; + + /* EV_SYN and unknown codes are never filtered */ + if (type == EV_SYN || type >= EV_CNT) + return false; + + /* first test whether the type is filtered */ + mask = client->evmasks[0]; + if (mask && !test_bit(type, mask)) + return true; + + /* unknown values are never filtered */ + cnt = evdev_get_mask_cnt(type); + if (!cnt || code >= cnt) + return false; + + mask = client->evmasks[type]; + return mask && !test_bit(code, mask); +} + /* flush queued events of type @type, caller must hold client->buffer_lock */ static void __evdev_flush_queue(struct evdev_client *client, unsigned int type) { @@ -146,37 +191,39 @@ static void evdev_queue_syn_dropped(struct evdev_client *client) static int evdev_set_clk_type(struct evdev_client *client, unsigned int clkid) { unsigned long flags; - - if (client->clk_type == clkid) - return 0; + unsigned int clk_type; switch (clkid) { case CLOCK_REALTIME: - client->clk_type = EV_CLK_REAL; + clk_type = EV_CLK_REAL; break; case CLOCK_MONOTONIC: - client->clk_type = EV_CLK_MONO; + clk_type = EV_CLK_MONO; break; case CLOCK_BOOTTIME: - client->clk_type = EV_CLK_BOOT; + clk_type = EV_CLK_BOOT; break; default: return -EINVAL; } - /* - * Flush pending events and queue SYN_DROPPED event, - * but only if the queue is not empty. - */ - spin_lock_irqsave(&client->buffer_lock, flags); + if (client->clk_type != clk_type) { + client->clk_type = clk_type; - if (client->head != client->tail) { - client->packet_head = client->head = client->tail; - __evdev_queue_syn_dropped(client); - } + /* + * Flush pending events and queue SYN_DROPPED event, + * but only if the queue is not empty. + */ + spin_lock_irqsave(&client->buffer_lock, flags); - spin_unlock_irqrestore(&client->buffer_lock, flags); + if (client->head != client->tail) { + client->packet_head = client->head = client->tail; + __evdev_queue_syn_dropped(client); + } + + spin_unlock_irqrestore(&client->buffer_lock, flags); + } return 0; } @@ -226,12 +273,21 @@ static void evdev_pass_values(struct evdev_client *client, spin_lock(&client->buffer_lock); for (v = vals; v != vals + count; v++) { + if (__evdev_is_filtered(client, v->type, v->code)) + continue; + + if (v->type == EV_SYN && v->code == SYN_REPORT) { + /* drop empty SYN_REPORT */ + if (client->packet_head == client->head) + continue; + + wakeup = true; + } + event.type = v->type; event.code = v->code; event.value = v->value; __pass_event(client, &event); - if (v->type == EV_SYN && v->code == SYN_REPORT) - wakeup = true; } spin_unlock(&client->buffer_lock); @@ -410,6 +466,7 @@ static int evdev_release(struct inode *inode, struct file *file) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; + unsigned int i; mutex_lock(&evdev->mutex); evdev_ungrab(evdev, client); @@ -417,6 +474,9 @@ static int evdev_release(struct inode *inode, struct file *file) evdev_detach_client(evdev, client); + for (i = 0; i < EV_CNT; ++i) + kfree(client->evmasks[i]); + kvfree(client); evdev_close_device(evdev); @@ -627,7 +687,46 @@ static int bits_to_user(unsigned long *bits, unsigned int maxbit, return len; } + +static int bits_from_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, const void __user *p, int compat) +{ + int len, i; + + if (compat) { + if (maxlen % sizeof(compat_long_t)) + return -EINVAL; + + len = BITS_TO_LONGS_COMPAT(maxbit) * sizeof(compat_long_t); + if (len > maxlen) + len = maxlen; + + for (i = 0; i < len / sizeof(compat_long_t); i++) + if (copy_from_user((compat_long_t *) bits + + i + 1 - ((i % 2) << 1), + (compat_long_t __user *) p + i, + sizeof(compat_long_t))) + return -EFAULT; + if (i % 2) + *((compat_long_t *) bits + i - 1) = 0; + + } else { + if (maxlen % sizeof(long)) + return -EINVAL; + + len = BITS_TO_LONGS(maxbit) * sizeof(long); + if (len > maxlen) + len = maxlen; + + if (copy_from_user(bits, p, len)) + return -EFAULT; + } + + return len; +} + #else + static int bits_to_user(unsigned long *bits, unsigned int maxbit, unsigned int maxlen, void __user *p, int compat) { @@ -640,6 +739,24 @@ static int bits_to_user(unsigned long *bits, unsigned int maxbit, return copy_to_user(p, bits, len) ? -EFAULT : len; } + +static int bits_from_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, const void __user *p, int compat) +{ + size_t chunk_size = compat ? sizeof(compat_long_t) : sizeof(long); + int len; + + if (maxlen % chunk_size) + return -EINVAL; + + len = compat ? BITS_TO_LONGS_COMPAT(maxbit) : BITS_TO_LONGS(maxbit); + len *= chunk_size; + if (len > maxlen) + len = maxlen; + + return copy_from_user(bits, p, len) ? -EFAULT : len; +} + #endif /* __BIG_ENDIAN */ #else @@ -655,6 +772,21 @@ static int bits_to_user(unsigned long *bits, unsigned int maxbit, return copy_to_user(p, bits, len) ? -EFAULT : len; } +static int bits_from_user(unsigned long *bits, unsigned int maxbit, + unsigned int maxlen, const void __user *p, int compat) +{ + int len; + + if (maxlen % sizeof(long)) + return -EINVAL; + + len = BITS_TO_LONGS(maxbit) * sizeof(long); + if (len > maxlen) + len = maxlen; + + return copy_from_user(bits, p, len) ? -EFAULT : len; +} + #endif /* CONFIG_COMPAT */ static int str_to_user(const char *str, unsigned int maxlen, void __user *p) @@ -849,6 +981,81 @@ static int evdev_revoke(struct evdev *evdev, struct evdev_client *client, return 0; } +/* must be called with evdev-mutex held */ +static int evdev_set_mask(struct evdev_client *client, + unsigned int type, + const void __user *codes, + u32 codes_size, + int compat) +{ + unsigned long flags, *mask, *oldmask; + size_t cnt; + int error; + + /* we allow unknown types and 'codes_size > size' for forward-compat */ + cnt = evdev_get_mask_cnt(type); + if (!cnt) + return 0; + + mask = kcalloc(sizeof(unsigned long), BITS_TO_LONGS(cnt), GFP_KERNEL); + if (!mask) + return -ENOMEM; + + error = bits_from_user(mask, cnt - 1, codes_size, codes, compat); + if (error < 0) { + kfree(mask); + return error; + } + + spin_lock_irqsave(&client->buffer_lock, flags); + oldmask = client->evmasks[type]; + client->evmasks[type] = mask; + spin_unlock_irqrestore(&client->buffer_lock, flags); + + kfree(oldmask); + + return 0; +} + +/* must be called with evdev-mutex held */ +static int evdev_get_mask(struct evdev_client *client, + unsigned int type, + void __user *codes, + u32 codes_size, + int compat) +{ + unsigned long *mask; + size_t cnt, size, xfer_size; + int i; + int error; + + /* we allow unknown types and 'codes_size > size' for forward-compat */ + cnt = evdev_get_mask_cnt(type); + size = sizeof(unsigned long) * BITS_TO_LONGS(cnt); + xfer_size = min_t(size_t, codes_size, size); + + if (cnt > 0) { + mask = client->evmasks[type]; + if (mask) { + error = bits_to_user(mask, cnt - 1, + xfer_size, codes, compat); + if (error < 0) + return error; + } else { + /* fake mask with all bits set */ + for (i = 0; i < xfer_size; i++) + if (put_user(0xffU, (u8 __user *)codes + i)) + return -EFAULT; + } + } + + if (xfer_size < codes_size) + if (clear_user(codes + xfer_size, codes_size - xfer_size)) + return -EFAULT; + + return 0; +} + static long evdev_do_ioctl(struct file *file, unsigned int cmd, void __user *p, int compat_mode) { @@ -856,6 +1063,7 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, struct evdev *evdev = client->evdev; struct input_dev *dev = evdev->handle.dev; struct input_absinfo abs; + struct input_mask mask; struct ff_effect effect; int __user *ip = (int __user *)p; unsigned int i, t, u, v; @@ -917,6 +1125,30 @@ static long evdev_do_ioctl(struct file *file, unsigned int cmd, else return evdev_revoke(evdev, client, file); + case EVIOCGMASK: { + void __user *codes_ptr; + + if (copy_from_user(&mask, p, sizeof(mask))) + return -EFAULT; + + codes_ptr = (void __user *)(unsigned long)mask.codes_ptr; + return evdev_get_mask(client, + mask.type, codes_ptr, mask.codes_size, + compat_mode); + } + + case EVIOCSMASK: { + const void __user *codes_ptr; + + if (copy_from_user(&mask, p, sizeof(mask))) + return -EFAULT; + + codes_ptr = (const void __user *)(unsigned long)mask.codes_ptr; + return evdev_set_mask(client, + mask.type, codes_ptr, mask.codes_size, + compat_mode); + } + case EVIOCSCLOCKID: if (copy_from_user(&i, p, sizeof(unsigned int))) return -EFAULT; |
