diff mbox

[v2] Input: evdev - add event-mask API

Message ID 1402913358-682-1-git-send-email-dh.herrmann@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

David Herrmann June 16, 2014, 10:09 a.m. UTC
Hardware manufacturers group keys in the weirdest way possible. This may
cause a power-key to be grouped together with normal keyboard keys and
thus be reported on the same kernel interface.

However, user-space is often only interested in specific sets of events.
For instance, daemons dealing with system-reboot (like systemd-logind)
listen for KEY_POWER, but are not interested in any main keyboard keys.
Usually, power keys are reported via separate interfaces, however,
some i8042 boards report it in the AT matrix. To avoid waking up those
system daemons on each key-press, we had two ideas:
 - split off KEY_POWER into a separate interface unconditionally
 - allow masking a specific set of events on evdev FDs

Splitting of KEY_POWER is a rather weird way to deal with this and may
break backwards-compatibility. It is also specific to KEY_POWER and might
be required for other stuff, too. Moreover, we might end up with a huge
set of input-devices just to have them properly split.

Hence, this patchset implements the second idea: An event-mask to specify
which events you're interested in. Two ioctls allow setting this mask for
each event-type. If not set, all events are reported. The type==0 entry is
used same as in EVIOCGBIT to set the actual EV_* mask of masked events.
This way, you have a two-level filter.

We are heavily forward-compatible to new event-types and event-codes. So
new user-space will be able to run on an old kernel which doesn't know the
given event-codes or event-types.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
---
v2:
 - Drop empty SYN_REPORT
 - turn evdev_get_mask_cnt() into an array-lookup
 - add documentation
 - fix coding-style

 drivers/input/evdev.c      | 156 ++++++++++++++++++++++++++++++++++++++++++++-
 include/uapi/linux/input.h |  53 +++++++++++++++
 2 files changed, 207 insertions(+), 2 deletions(-)

Comments

Peter Hutterer June 18, 2014, 4:47 a.m. UTC | #1
On Mon, Jun 16, 2014 at 12:09:18PM +0200, David Herrmann wrote:
> Hardware manufacturers group keys in the weirdest way possible. This may
> cause a power-key to be grouped together with normal keyboard keys and
> thus be reported on the same kernel interface.
> 
> However, user-space is often only interested in specific sets of events.
> For instance, daemons dealing with system-reboot (like systemd-logind)
> listen for KEY_POWER, but are not interested in any main keyboard keys.
> Usually, power keys are reported via separate interfaces, however,
> some i8042 boards report it in the AT matrix. To avoid waking up those
> system daemons on each key-press, we had two ideas:
>  - split off KEY_POWER into a separate interface unconditionally
>  - allow masking a specific set of events on evdev FDs
> 
> Splitting of KEY_POWER is a rather weird way to deal with this and may
> break backwards-compatibility. It is also specific to KEY_POWER and might
> be required for other stuff, too. Moreover, we might end up with a huge
> set of input-devices just to have them properly split.
> 
> Hence, this patchset implements the second idea: An event-mask to specify
> which events you're interested in. Two ioctls allow setting this mask for
> each event-type. If not set, all events are reported. The type==0 entry is
> used same as in EVIOCGBIT to set the actual EV_* mask of masked events.
> This way, you have a two-level filter.

one comment regarding the wording: I'd prefer to use "filtered" or
"discarded" instead of "masked". Take for example the comment below:

 "Each event-code is represented by a single bit in the event-mask. If set,
 the event is not-masked."

So if the mask is set, the event is not masked, which is rather confusing.
Rewrite this as "if the mask is set, the event is not filtered" and it's a
lot easier to understand.

[...]

> + * EVIOCGMASK - Retrieve current event-mask
> + *
> + * This retrieves the current event-mask for a specific event-type. The
> + * argument must be of type "struct input_mask" and specifies the event-type to
> + * query, the receive buffer and the size of the receive buffer.
> + *
> + * The event-mask is a per-client mask that specifies which events are forwarded
> + * to the client. Each event-code is represented by a single bit in the
> + * event-mask. If set, the event is not-masked. If unset, the event is masked
> + * and will never be queued on the client's receive buffer.

see comment above, funny thing is that until the "If set" it was quite easy
to understand, the rest made it more confusing :)

> + *
> + * This ioctl provides full forward-compatibility. That means, if a kernel is
> + * queried for an unknown event-type or if the receive buffer is larger than the
> + * number of event-codes known to the kernel, the kernel will return all zeroes
> + * for those codes (which means, those codes are masked). This effectively
> + * means, codes unknown to the kernel are always considered hard-masked.

hard-masked is an odd word, using it here with the masked vs filtered
vs zeroes makes the whole thing quite confusing.

> + * If the receive buffer is too small to contain the whole event-mask, a
> + * truncated mask is copied to user-space.

"At maximum, codes_size bytes are copied" says the same and doesn't leave
one wondering where/when the truncation happens.

> + *
> + * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
> + * returned if the receive-buffer points to invalid memory. EINVAL is returned
> + * if the kernel does not implement the ioctl.

this is nitpicking, but given that ioctls return -1 and set errno, the
documentation should consistently use "may fail with..." instead
of "returns"


So, here's the comments folded in:

 EVIOCGMASK - Retrieve current event-mask

 This retrieves the current event-mask for a specific event-type. The
 argument must be of type "struct input_mask" and specifies the event-type to
 query, the receive buffer and the size of the receive buffer.

 The event-mask is a per-client mask that specifies which events are forwarded
 to the client. Each event-code is represented by a single bit in the
 event-mask. If the bit is set, the event is passed to the client normally.
 Otherwise, the event is filtered and and will never be queued on the
 client's receive buffer.

 The default event-mask for a client has all bits set, i.e. all events are
 forwarded to the client. If a kernel is queried for an unknown event-type
 or if the receive buffer is larger than the number of event-codes known to
 the kernel, the kernel returns all zeroes for those codes.

 At maximum, codes_size bytes are copied.

 This ioctl may fail with ENODEV in case the file is revoked, EFAULT
 if the receive-buffer points to invalid memory, or EINVAL if the kernel
 does not implement the ioctl.


Otherwise:
Acked-by: Peter Hutterer <peter.hutterer@who-t.net>

Cheers,
   Peter

> + */
> +#define EVIOCGMASK		_IOR('E', 0x92, struct input_mask)	/* Get event-masks */
> +
> +/**
> + * EVIOCSMASK - Set event-mask
> + *
> + * This is the counterpart to EVIOCGMASK. Instead of receiving the current
> + * event-mask, this changes the client's event-mask for a specific type. See
> + * EVIOCGMASK for a description of event-masks and the argument-type.
> + *
> + * This ioctl provides full forward-compatibility. If the passed event-type is
> + * unknown to the kernel, or if the number of codes is bigger than known to the
> + * kernel, the ioctl is still accepted and applied. However, any unknown codes
> + * are left untouched and stay masked. That means, the kernel hard-masks unknown
> + * codes regardless of what the client requests.
> + * If the new mask doesn't cover all known event-codes, all remaining codes are
> + * automatically cleared and thus masked.
> + *
> + * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
> + * returned if the receive-buffer points to invalid memory. EINVAL is returned
> + * if the kernel does not implement the ioctl.
> + */
> +#define EVIOCSMASK		_IOW('E', 0x93, struct input_mask)	/* Set event-masks */
> +
>  #define EVIOCSCLOCKID		_IOW('E', 0xa0, int)			/* Set clockid to be used for timestamps */
>  
>  /*
> -- 
> 2.0.0
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Herrmann June 18, 2014, 5:39 a.m. UTC | #2
Hi

On Wed, Jun 18, 2014 at 6:47 AM, Peter Hutterer
<peter.hutterer@who-t.net> wrote:
> On Mon, Jun 16, 2014 at 12:09:18PM +0200, David Herrmann wrote:
>> Hardware manufacturers group keys in the weirdest way possible. This may
>> cause a power-key to be grouped together with normal keyboard keys and
>> thus be reported on the same kernel interface.
>>
>> However, user-space is often only interested in specific sets of events.
>> For instance, daemons dealing with system-reboot (like systemd-logind)
>> listen for KEY_POWER, but are not interested in any main keyboard keys.
>> Usually, power keys are reported via separate interfaces, however,
>> some i8042 boards report it in the AT matrix. To avoid waking up those
>> system daemons on each key-press, we had two ideas:
>>  - split off KEY_POWER into a separate interface unconditionally
>>  - allow masking a specific set of events on evdev FDs
>>
>> Splitting of KEY_POWER is a rather weird way to deal with this and may
>> break backwards-compatibility. It is also specific to KEY_POWER and might
>> be required for other stuff, too. Moreover, we might end up with a huge
>> set of input-devices just to have them properly split.
>>
>> Hence, this patchset implements the second idea: An event-mask to specify
>> which events you're interested in. Two ioctls allow setting this mask for
>> each event-type. If not set, all events are reported. The type==0 entry is
>> used same as in EVIOCGBIT to set the actual EV_* mask of masked events.
>> This way, you have a two-level filter.
>
> one comment regarding the wording: I'd prefer to use "filtered" or
> "discarded" instead of "masked". Take for example the comment below:
>
>  "Each event-code is represented by a single bit in the event-mask. If set,
>  the event is not-masked."
>
> So if the mask is set, the event is not masked, which is rather confusing.
> Rewrite this as "if the mask is set, the event is not filtered" and it's a
> lot easier to understand.
>
> [...]
>
>> + * EVIOCGMASK - Retrieve current event-mask
>> + *
>> + * This retrieves the current event-mask for a specific event-type. The
>> + * argument must be of type "struct input_mask" and specifies the event-type to
>> + * query, the receive buffer and the size of the receive buffer.
>> + *
>> + * The event-mask is a per-client mask that specifies which events are forwarded
>> + * to the client. Each event-code is represented by a single bit in the
>> + * event-mask. If set, the event is not-masked. If unset, the event is masked
>> + * and will never be queued on the client's receive buffer.
>
> see comment above, funny thing is that until the "If set" it was quite easy
> to understand, the rest made it more confusing :)
>
>> + *
>> + * This ioctl provides full forward-compatibility. That means, if a kernel is
>> + * queried for an unknown event-type or if the receive buffer is larger than the
>> + * number of event-codes known to the kernel, the kernel will return all zeroes
>> + * for those codes (which means, those codes are masked). This effectively
>> + * means, codes unknown to the kernel are always considered hard-masked.
>
> hard-masked is an odd word, using it here with the masked vs filtered
> vs zeroes makes the whole thing quite confusing.
>
>> + * If the receive buffer is too small to contain the whole event-mask, a
>> + * truncated mask is copied to user-space.
>
> "At maximum, codes_size bytes are copied" says the same and doesn't leave
> one wondering where/when the truncation happens.
>
>> + *
>> + * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
>> + * returned if the receive-buffer points to invalid memory. EINVAL is returned
>> + * if the kernel does not implement the ioctl.
>
> this is nitpicking, but given that ioctls return -1 and set errno, the
> documentation should consistently use "may fail with..." instead
> of "returns"

I disagree on that one. I document kernel-API and we return negative
error-codes on failure. It's POSIX/glibc that turns this into -1 and
sets errno (and only god knows why that brainfu**ed idea got
standardized..).
However, turning this into "may fail with..." suits both of us, so I'm
fine with it.

Thanks for the re-wording. I amended the changes and also turned
__evdev_is_masked() into __evdev_is_filtered() to make it more
readable.

I will send v3 shortly, thanks!
David
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index fd325ec..f8367190 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -51,10 +51,130 @@  struct evdev_client {
 	struct list_head node;
 	int clkid;
 	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 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;
+}
+
+/* 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)
+{
+	unsigned long flags, *mask, *oldmask;
+	size_t cnt, size;
+
+	/* unknown masks are simply ignored for forward-compat */
+	cnt = evdev_get_mask_cnt(type);
+	if (!cnt)
+		return 0;
+
+	/* we allow 'codes_size > size' for forward-compat */
+	size = sizeof(unsigned long) * BITS_TO_LONGS(cnt);
+
+	mask = kzalloc(size, GFP_KERNEL);
+	if (!mask)
+		return -ENOMEM;
+
+	if (copy_from_user(mask, codes, min_t(size_t, codes_size, size))) {
+		kfree(mask);
+		return -EFAULT;
+	}
+
+	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)
+{
+	unsigned long *mask;
+	size_t cnt, size, min, i;
+	u8 __user *out;
+
+	/* 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);
+	min = min_t(size_t, codes_size, size);
+
+	if (cnt > 0) {
+		mask = client->evmasks[type];
+		if (mask) {
+			if (copy_to_user(codes, mask, min))
+				return -EFAULT;
+		} else {
+			/* fake mask with all bits set */
+			out = (u8 __user*)codes;
+			for (i = 0; i < min; ++i) {
+				if (put_user((u8)0xff,  out + i))
+					return -EFAULT;
+			}
+		}
+	}
+
+	codes = (u8*)codes + min;
+	codes_size -= min;
+
+	if (codes_size > 0 && clear_user(codes, codes_size))
+		return -EFAULT;
+
+	return 0;
+}
+
+/* requires the buffer lock to be held */
+static bool __evdev_is_masked(struct evdev_client *client,
+			      unsigned int type,
+			      unsigned int code)
+{
+	unsigned long *mask;
+	size_t cnt;
+
+	/* EV_SYN and unknown codes are never masked */
+	if (type == EV_SYN || type >= EV_CNT)
+		return false;
+
+	/* first test whether the type is masked */
+	mask = client->evmasks[0];
+	if (mask && !test_bit(type, mask))
+		return true;
+
+	/* unknown values are never masked */
+	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)
 {
@@ -177,12 +297,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_masked(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);
@@ -365,6 +494,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);
@@ -372,6 +502,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]);
+
 	if (is_vmalloc_addr(client))
 		vfree(client);
 	else
@@ -811,6 +944,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;
@@ -872,6 +1006,24 @@  static long evdev_do_ioctl(struct file *file, unsigned int cmd,
 		else
 			return evdev_revoke(evdev, client, file);
 
+	case EVIOCGMASK:
+		if (copy_from_user(&mask, p, sizeof(mask)))
+			return -EFAULT;
+
+		return evdev_get_mask(client,
+				      mask.type,
+				      (void*)(long)mask.codes_ptr,
+				      mask.codes_size);
+
+	case EVIOCSMASK:
+		if (copy_from_user(&mask, p, sizeof(mask)))
+			return -EFAULT;
+
+		return evdev_set_mask(client,
+				      mask.type,
+				      (const void*)(long)mask.codes_ptr,
+				      mask.codes_size);
+
 	case EVIOCSCLOCKID:
 		if (copy_from_user(&i, p, sizeof(unsigned int)))
 			return -EFAULT;
diff --git a/include/uapi/linux/input.h b/include/uapi/linux/input.h
index 19df18c..4753f94 100644
--- a/include/uapi/linux/input.h
+++ b/include/uapi/linux/input.h
@@ -97,6 +97,12 @@  struct input_keymap_entry {
 	__u8  scancode[32];
 };
 
+struct input_mask {
+	__u32 type;
+	__u32 codes_size;
+	__u64 codes_ptr;
+};
+
 #define EVIOCGVERSION		_IOR('E', 0x01, int)			/* get driver version */
 #define EVIOCGID		_IOR('E', 0x02, struct input_id)	/* get device ID */
 #define EVIOCGREP		_IOR('E', 0x03, unsigned int[2])	/* get repeat settings */
@@ -154,6 +160,53 @@  struct input_keymap_entry {
 #define EVIOCGRAB		_IOW('E', 0x90, int)			/* Grab/Release device */
 #define EVIOCREVOKE		_IOW('E', 0x91, int)			/* Revoke device access */
 
+/**
+ * EVIOCGMASK - Retrieve current event-mask
+ *
+ * This retrieves the current event-mask for a specific event-type. The
+ * argument must be of type "struct input_mask" and specifies the event-type to
+ * query, the receive buffer and the size of the receive buffer.
+ *
+ * The event-mask is a per-client mask that specifies which events are forwarded
+ * to the client. Each event-code is represented by a single bit in the
+ * event-mask. If set, the event is not-masked. If unset, the event is masked
+ * and will never be queued on the client's receive buffer.
+ *
+ * This ioctl provides full forward-compatibility. That means, if a kernel is
+ * queried for an unknown event-type or if the receive buffer is larger than the
+ * number of event-codes known to the kernel, the kernel will return all zeroes
+ * for those codes (which means, those codes are masked). This effectively
+ * means, codes unknown to the kernel are always considered hard-masked.
+ * If the receive buffer is too small to contain the whole event-mask, a
+ * truncated mask is copied to user-space.
+ *
+ * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
+ * returned if the receive-buffer points to invalid memory. EINVAL is returned
+ * if the kernel does not implement the ioctl.
+ */
+#define EVIOCGMASK		_IOR('E', 0x92, struct input_mask)	/* Get event-masks */
+
+/**
+ * EVIOCSMASK - Set event-mask
+ *
+ * This is the counterpart to EVIOCGMASK. Instead of receiving the current
+ * event-mask, this changes the client's event-mask for a specific type. See
+ * EVIOCGMASK for a description of event-masks and the argument-type.
+ *
+ * This ioctl provides full forward-compatibility. If the passed event-type is
+ * unknown to the kernel, or if the number of codes is bigger than known to the
+ * kernel, the ioctl is still accepted and applied. However, any unknown codes
+ * are left untouched and stay masked. That means, the kernel hard-masks unknown
+ * codes regardless of what the client requests.
+ * If the new mask doesn't cover all known event-codes, all remaining codes are
+ * automatically cleared and thus masked.
+ *
+ * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is
+ * returned if the receive-buffer points to invalid memory. EINVAL is returned
+ * if the kernel does not implement the ioctl.
+ */
+#define EVIOCSMASK		_IOW('E', 0x93, struct input_mask)	/* Set event-masks */
+
 #define EVIOCSCLOCKID		_IOW('E', 0xa0, int)			/* Set clockid to be used for timestamps */
 
 /*