From d5dd44b01c225f20166f2e004e41f72c3ea95b5a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 26 Jun 2017 21:35:12 +0200 Subject: [PATCH 1/3] logind: make use of EVIOCSMASK input ioctl to mask out events we aren't interested in This way logind will get woken up only when an actual event took place, and not for every key press on the system. The ioctl EVIOCSMASK was added by @dvdhrm already in October 2015, for the use in logind, among others, hence let's actually make use of it now. While we are at it, also fix usage of the EVIOCGSW ioctl, where we assumed a byte array, even though a unsigned long native endian array is returned. --- src/basic/missing.h | 12 +++++++ src/login/logind-button.c | 73 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/basic/missing.h b/src/basic/missing.h index 55028754cd..7c323c61cf 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -569,6 +570,17 @@ struct btrfs_ioctl_quota_ctl_args { # define EVIOCREVOKE _IOW('E', 0x91, int) #endif +#ifndef EVIOCSMASK + +struct input_mask { + uint32_t type; + uint32_t codes_size; + uint64_t codes_ptr; +}; + +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) +#endif + #ifndef DRM_IOCTL_SET_MASTER # define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) #endif diff --git a/src/login/logind-button.c b/src/login/logind-button.c index d739af8ea2..c47c415e60 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -32,6 +32,18 @@ #include "string-util.h" #include "util.h" +#define CONST_MAX4(a, b, c, d) CONST_MAX(CONST_MAX(a, b), CONST_MAX(c, d)) + +#define ULONG_BITS (sizeof(unsigned long)*8) + +static bool bitset_get(const unsigned long *bits, unsigned i) { + return (bits[i / ULONG_BITS] >> (i % ULONG_BITS)) & 1UL; +} + +static void bitset_put(unsigned long *bits, unsigned i) { + bits[i / ULONG_BITS] |= (unsigned long) 1 << (i % ULONG_BITS); +} + Button* button_new(Manager *m, const char *name) { Button *b; @@ -231,6 +243,59 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } +static int button_set_mask(Button *b) { + unsigned long + types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {}, + keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1] = {}, + switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {}; + struct input_mask mask; + + assert(b); + assert(b->fd >= 0); + + bitset_put(types, EV_KEY); + bitset_put(types, EV_SW); + + mask = (struct input_mask) { + .type = EV_SYN, + .codes_size = sizeof(types), + .codes_ptr = PTR_TO_UINT64(types), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + /* Log only at debug level if the kernel doesn't do EVIOCSMASK yet */ + return log_full_errno(IN_SET(errno, ENOTTY, EOPNOTSUPP, EINVAL) ? LOG_DEBUG : LOG_WARNING, + errno, "Failed to set EV_SYN event mask on /dev/input/%s: %m", b->name); + + bitset_put(keys, KEY_POWER); + bitset_put(keys, KEY_POWER2); + bitset_put(keys, KEY_SLEEP); + bitset_put(keys, KEY_SUSPEND); + + mask = (struct input_mask) { + .type = EV_KEY, + .codes_size = sizeof(keys), + .codes_ptr = PTR_TO_UINT64(keys), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + return log_warning_errno(errno, "Failed to set EV_KEY event mask on /dev/input/%s: %m", b->name); + + bitset_put(switches, SW_LID); + bitset_put(switches, SW_DOCK); + + mask = (struct input_mask) { + .type = EV_SW, + .codes_size = sizeof(switches), + .codes_ptr = PTR_TO_UINT64(switches), + }; + + if (ioctl(b->fd, EVIOCSMASK, &mask) < 0) + return log_warning_errno(errno, "Failed to set EV_SW event mask on /dev/input/%s: %m", b->name); + + return 0; +} + int button_open(Button *b) { char *p, name[256]; int r; @@ -250,6 +315,8 @@ int button_open(Button *b) { goto fail; } + (void) button_set_mask(b); + r = sd_event_add_io(b->manager->event, &b->io_event_source, b->fd, EPOLLIN, button_dispatch, b); if (r < 0) { log_error_errno(r, "Failed to add button event: %m"); @@ -266,7 +333,7 @@ fail: } int button_check_switches(Button *b) { - uint8_t switches[SW_MAX/8+1] = {}; + unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {}; assert(b); if (b->fd < 0) @@ -275,8 +342,8 @@ int button_check_switches(Button *b) { if (ioctl(b->fd, EVIOCGSW(sizeof(switches)), switches) < 0) return -errno; - b->lid_closed = (switches[SW_LID/8] >> (SW_LID % 8)) & 1; - b->docked = (switches[SW_DOCK/8] >> (SW_DOCK % 8)) & 1; + b->lid_closed = bitset_get(switches, SW_LID); + b->docked = bitset_get(switches, SW_DOCK); if (b->lid_closed) button_install_check_event_source(b); From 2546b70a5e507bd1e00fc7ea0e2ed24cd77349af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Jun 2017 17:46:28 +0200 Subject: [PATCH 2/3] logind: filter out input devices that have none of the keys/switche we care about Let's check what keys are there, before we actually hang on to the opened devices. --- src/login/logind-button.c | 46 ++++++++++++++++++++++++++++++++++++++- src/login/logind-core.c | 6 ++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/login/logind-button.c b/src/login/logind-button.c index c47c415e60..e53dd63c29 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -243,6 +243,42 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u return 0; } +static int button_suitable(Button *b) { + unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1]; + + assert(b); + assert(b->fd); + + if (ioctl(b->fd, EVIOCGBIT(EV_SYN, sizeof(types)), types) < 0) + return -errno; + + if (bitset_get(types, EV_KEY)) { + unsigned long keys[CONST_MAX4(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND)/ULONG_BITS+1]; + + if (ioctl(b->fd, EVIOCGBIT(EV_KEY, sizeof(keys)), keys) < 0) + return -errno; + + if (bitset_get(keys, KEY_POWER) || + bitset_get(keys, KEY_POWER2) || + bitset_get(keys, KEY_SLEEP) || + bitset_get(keys, KEY_SUSPEND)) + return true; + } + + if (bitset_get(types, EV_SW)) { + unsigned long switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1]; + + if (ioctl(b->fd, EVIOCGBIT(EV_SW, sizeof(switches)), switches) < 0) + return -errno; + + if (bitset_get(switches, SW_LID) || + bitset_get(switches, SW_DOCK)) + return true; + } + + return false; +} + static int button_set_mask(Button *b) { unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {}, @@ -308,7 +344,15 @@ int button_open(Button *b) { b->fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (b->fd < 0) - return log_warning_errno(errno, "Failed to open %s: %m", b->name); + return log_warning_errno(errno, "Failed to open %s: %m", p); + + r = button_suitable(b); + if (r < 0) + return log_warning_errno(r, "Failed to determine whether input device is relevant to us: %m"); + if (r == 0) { + log_debug("Device %s does not expose keys or switches relevant to us, ignoring.", p); + return -EADDRNOTAVAIL; + } if (ioctl(b->fd, EVIOCGNAME(sizeof(name)), name) < 0) { r = log_error_errno(errno, "Failed to get input name: %m"); diff --git a/src/login/logind-core.c b/src/login/logind-core.c index eff5a4a36f..ebe1d68634 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -269,7 +269,11 @@ int manager_process_button_device(Manager *m, struct udev_device *d) { sn = "seat0"; button_set_seat(b, sn); - button_open(b); + + r = button_open(b); + if (r < 0) /* event device doesn't have any keys or switches relevant to us? (or any other error + * opening the device?) let's close the button again. */ + button_free(b); } return 0; From 5b987a4e3e98f30b324ce75e07116ea8908bdf44 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 27 Jun 2017 16:14:30 +0200 Subject: [PATCH 3/3] logind: relax udev rules matching devices logind watches for Now that we have support for key/switch masking in logind, we can relax the rules by which logind picks the devices to watch a bit, after all we won't wake up anymore for every single event, but instead only the events we actually care about. This should make power/suspend keys on normal usb/atkbd keyboards just work. --- src/login/70-power-switch.rules | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/login/70-power-switch.rules b/src/login/70-power-switch.rules index e2855b50f7..394a80f1f8 100644 --- a/src/login/70-power-switch.rules +++ b/src/login/70-power-switch.rules @@ -7,12 +7,7 @@ ACTION=="remove", GOTO="power_switch_end" -SUBSYSTEM=="input", KERNEL=="event*", SUBSYSTEMS=="acpi", TAG+="power-switch" -SUBSYSTEM=="input", KERNEL=="event*", KERNELS=="thinkpad_acpi", TAG+="power-switch" -SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="twl4030_pwrbutton", TAG+="power-switch" -SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="tps65217_pwr_but", TAG+="power-switch" -SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="* WMI hotkeys", TAG+="power-switch" -SUBSYSTEM=="input", KERNEL=="event*", \ - SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", ATTRS{keys}=="*,116|116,*|116|*,116,*", TAG+="power-switch" +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_SWITCH}=="1", TAG+="power-switch" +SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_INPUT_KEY}=="1", TAG+="power-switch" LABEL="power_switch_end"