Message ID | 1397601105-17771-1-git-send-email-dh.herrmann@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi David, On Wed, Apr 16, 2014 at 12:31:45AM +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. That is something I had in mind for a long time, great job! > > 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 event-types. > > Signed-off-by: David Herrmann <dh.herrmann@gmail.com> > --- > drivers/input/evdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ > include/uapi/linux/input.h | 8 +++ > 2 files changed, 163 insertions(+) > > diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c > index 398648b..86778c3 100644 > --- a/drivers/input/evdev.c > +++ b/drivers/input/evdev.c > @@ -51,10 +51,139 @@ 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) > +{ > + switch (type) { > + case 0: > + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */ > + return EV_CNT; > + case EV_KEY: > + return KEY_CNT; > + case EV_REL: > + return REL_CNT; > + case EV_ABS: > + return ABS_CNT; > + case EV_MSC: > + return MSC_CNT; > + case EV_SW: > + return SW_CNT; > + case EV_LED: > + return LED_CNT; > + case EV_SND: > + return SND_CNT; > + case EV_FF: > + return FF_CNT; > + } Maybe we need a static array of code->count mapping instead of a switch? > + > + 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) > +{ > + 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 __user*)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 */ So won't this mean that client is still woken up by "empty" packet if we filter out everything but EV_SYN? Thanks.
On Wed, Apr 16, 2014 at 12:31:45AM +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. > > 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 event-types. Looks good in principle, a couple of nitpicks below but on the whole Acked-by: Peter Hutterer <peter.hutterer@who-t.net> > Signed-off-by: David Herrmann <dh.herrmann@gmail.com> > --- > drivers/input/evdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ > include/uapi/linux/input.h | 8 +++ > 2 files changed, 163 insertions(+) > > diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c > index 398648b..86778c3 100644 > --- a/drivers/input/evdev.c > +++ b/drivers/input/evdev.c > @@ -51,10 +51,139 @@ 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) > +{ > + switch (type) { > + case 0: > + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */ > + return EV_CNT; > + case EV_KEY: > + return KEY_CNT; > + case EV_REL: > + return REL_CNT; > + case EV_ABS: > + return ABS_CNT; > + case EV_MSC: > + return MSC_CNT; > + case EV_SW: > + return SW_CNT; > + case EV_LED: > + return LED_CNT; > + case EV_SND: > + return SND_CNT; > + case EV_FF: > + return FF_CNT; > + } > + > + 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) > +{ > + 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 __user*)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 || type >= EV_CNT) why not use type == EV_SYN? > + return false; > + > + /* first test whether the type is masked */ > + mask = client->evmasks[0]; if mask is NULL, you already know it's not mask, you can return early. > + 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 given type @type and code @code. A negative code > * is interpreted as catch-all. Caller must hold client->buffer_lock. */ > static void __evdev_flush_queue(struct evdev_client *client, > @@ -137,6 +266,9 @@ static void evdev_queue_syn_dropped(struct evdev_client *client) > static void __pass_event(struct evdev_client *client, > const struct input_event *event) > { > + if (__evdev_is_masked(client, event->type, event->code)) > + return; > + > client->buffer[client->head++] = *event; > client->head &= client->bufsize - 1; > > @@ -368,6 +500,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); > @@ -375,6 +508,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 > @@ -866,6 +1002,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; > @@ -927,6 +1064,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 bd24470..5b73712 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 */ > @@ -153,6 +159,8 @@ struct input_keymap_entry { > > #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */ > #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */ > +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */ > +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */ This is missing from all other ioctls but while you're adding a new one anyway: please add documentation on what the ioctl does, the input and return value/output expected, side-effects etc. right now, understanding the evdev ioctls requires either reading the kernel code or existing user-space code, with the usual risk of getting it wrong. Cheers, Peter > > #define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */ > > -- > 1.9.2 > -- 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
Hi On Sat, Apr 19, 2014 at 11:16 PM, Dmitry Torokhov <dmitry.torokhov@gmail.com> wrote: >> +static size_t evdev_get_mask_cnt(unsigned int type) >> +{ >> + switch (type) { >> + case 0: >> + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */ >> + return EV_CNT; >> + case EV_KEY: >> + return KEY_CNT; >> + case EV_REL: >> + return REL_CNT; >> + case EV_ABS: >> + return ABS_CNT; >> + case EV_MSC: >> + return MSC_CNT; >> + case EV_SW: >> + return SW_CNT; >> + case EV_LED: >> + return LED_CNT; >> + case EV_SND: >> + return SND_CNT; >> + case EV_FF: >> + return FF_CNT; >> + } > > Maybe we need a static array of code->count mapping instead of a switch? Sure, I can change that. >> + >> + 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) >> +{ >> + 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 __user*)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 */ > > So won't this mean that client is still woken up by "empty" packet if we > filter out everything but EV_SYN? Whoops, indeed. I will skip SYN_REPORT events if the queue is empty or if the previous event was already a SYN_REPORT. 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
Hi On Tue, Apr 22, 2014 at 6:29 AM, Peter Hutterer <peter.hutterer@who-t.net> wrote: >> +/* 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 || type >= EV_CNT) > > why not use type == EV_SYN? You mean instead of "!type"? Yeah, probably easier to read. >> + return false; >> + >> + /* first test whether the type is masked */ >> + mask = client->evmasks[0]; > > if mask is NULL, you already know it's not mask, you can return early. Nope. If evmasks[0] is NULL, then no type-mask has been set. That doesn't mean the given code-mask is NULL. For instance: evmasks[0] == NULL evmasks[EV_KEY][KEY_A] == 0 In that case, the user wants all events but KEY_A (even though evmasks[0] is NULL). >> #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */ >> #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */ >> +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */ >> +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */ > > This is missing from all other ioctls but while you're adding a new one > anyway: please add documentation on what the ioctl does, the input and > return value/output expected, side-effects etc. right now, understanding the > evdev ioctls requires either reading the kernel code or existing user-space > code, with the usual risk of getting it wrong. Meh.. I thought no-one will notice.. Clearly, I was wrong. I will add proper docs in v2. Thanks a lot! 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 --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 398648b..86778c3 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -51,10 +51,139 @@ 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) +{ + switch (type) { + case 0: + /* 0 is special (EV-bits instead of EV_SYN) like EVIOCGBIT */ + return EV_CNT; + case EV_KEY: + return KEY_CNT; + case EV_REL: + return REL_CNT; + case EV_ABS: + return ABS_CNT; + case EV_MSC: + return MSC_CNT; + case EV_SW: + return SW_CNT; + case EV_LED: + return LED_CNT; + case EV_SND: + return SND_CNT; + case EV_FF: + return FF_CNT; + } + + 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) +{ + 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 __user*)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 || 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 given type @type and code @code. A negative code * is interpreted as catch-all. Caller must hold client->buffer_lock. */ static void __evdev_flush_queue(struct evdev_client *client, @@ -137,6 +266,9 @@ static void evdev_queue_syn_dropped(struct evdev_client *client) static void __pass_event(struct evdev_client *client, const struct input_event *event) { + if (__evdev_is_masked(client, event->type, event->code)) + return; + client->buffer[client->head++] = *event; client->head &= client->bufsize - 1; @@ -368,6 +500,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); @@ -375,6 +508,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 @@ -866,6 +1002,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; @@ -927,6 +1064,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 bd24470..5b73712 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 */ @@ -153,6 +159,8 @@ struct input_keymap_entry { #define EVIOCGRAB _IOW('E', 0x90, int) /* Grab/Release device */ #define EVIOCREVOKE _IOW('E', 0x91, int) /* Revoke device access */ +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) /* Get event-masks */ +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */ #define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */
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 event-types. Signed-off-by: David Herrmann <dh.herrmann@gmail.com> --- drivers/input/evdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/input.h | 8 +++ 2 files changed, 163 insertions(+)