diff options
author | Patrik Fimml <patrikf@chromium.org> | 2020-12-03 01:42:04 +0300 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2020-12-03 09:10:35 +0300 |
commit | a181616487dbdbc953e476d1da15365f887859ed (patch) | |
tree | 221caf2e6a9d5b2ca3ee4ce0a8189a7dbb921198 /drivers/input/input.c | |
parent | d69f0a43c677e8afc67a222e1e7b51b9acc69cd3 (diff) | |
download | linux-a181616487dbdbc953e476d1da15365f887859ed.tar.xz |
Input: Add "inhibited" property
Userspace might want to implement a policy to temporarily disregard input
from certain devices, including not treating them as wakeup sources.
An example use case is a laptop, whose keyboard can be folded under the
screen to create tablet-like experience. The user then must hold the laptop
in such a way that it is difficult to avoid pressing the keyboard keys. It
is therefore desirable to temporarily disregard input from the keyboard,
until it is folded back. This obviously is a policy which should be kept
out of the kernel, but the kernel must provide suitable means to implement
such a policy.
This patch adds a sysfs interface for exactly this purpose.
To implement the said interface it adds an "inhibited" property to struct
input_dev, and effectively creates four states a device can be in: closed
uninhibited, closed inhibited, open uninhibited, open inhibited. It also
defers calling driver's ->open() and ->close() to until they are actually
needed, e.g. it makes no sense to prepare the underlying device for
generating events (->open()) if the device is inhibited.
uninhibit
closed <------------ closed
uninhibited ------------> inhibited
| ^ inhibit | ^
1st | | 1st | |
open | | open | |
| | | |
| | last | | last
| | close | | close
v | uninhibit v |
open <------------ open
uninhibited ------------> inhibited
The top inhibit/uninhibit transition happens when users == 0.
The bottom inhibit/uninhibit transition happens when users > 0.
The left open/close transition happens when !inhibited.
The right open/close transition happens when inhibited.
Due to all transitions being serialized with dev->mutex, it is impossible
to have "diagonal" transitions between closed uninhibited and open
inhibited or between open uninhibited and closed inhibited.
No new callbacks are added to drivers, because their open() and close()
serve exactly the purpose to tell the driver to start/stop providing
events to the input core. Consequently, open() and close() - if provided
- are called in both inhibit and uninhibit paths.
Signed-off-by: Patrik Fimml <patrikf@chromium.org>
Co-developed-by: Andrzej Pietrasiewicz <andrzej.p@collabora.com>
Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@collabora.com>
Link: https://lore.kernel.org/r/20200608112211.12125-8-andrzej.p@collabora.com
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Diffstat (limited to 'drivers/input/input.c')
-rw-r--r-- | drivers/input/input.c | 112 |
1 files changed, 105 insertions, 7 deletions
diff --git a/drivers/input/input.c b/drivers/input/input.c index 41377bfa142d..ccaeb2426385 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -367,8 +367,13 @@ static int input_get_disposition(struct input_dev *dev, static void input_handle_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { - int disposition = input_get_disposition(dev, type, code, &value); + int disposition; + /* filter-out events from inhibited devices */ + if (dev->inhibited) + return; + + disposition = input_get_disposition(dev, type, code, &value); if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN) add_input_randomness(type, code, value); @@ -612,10 +617,10 @@ int input_open_device(struct input_handle *handle) handle->open++; - if (dev->users++) { + if (dev->users++ || dev->inhibited) { /* - * Device is already opened, so we can exit immediately and - * report success. + * Device is already opened and/or inhibited, + * so we can exit immediately and report success. */ goto out; } @@ -675,10 +680,9 @@ void input_close_device(struct input_handle *handle) __input_release_device(handle); - if (!--dev->users) { + if (!dev->inhibited && !--dev->users) { if (dev->poller) input_dev_poller_stop(dev->poller); - if (dev->close) dev->close(dev); } @@ -1416,12 +1420,49 @@ static ssize_t input_dev_show_properties(struct device *dev, } static DEVICE_ATTR(properties, S_IRUGO, input_dev_show_properties, NULL); +static int input_inhibit_device(struct input_dev *dev); +static int input_uninhibit_device(struct input_dev *dev); + +static ssize_t inhibited_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct input_dev *input_dev = to_input_dev(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", input_dev->inhibited); +} + +static ssize_t inhibited_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct input_dev *input_dev = to_input_dev(dev); + ssize_t rv; + bool inhibited; + + if (strtobool(buf, &inhibited)) + return -EINVAL; + + if (inhibited) + rv = input_inhibit_device(input_dev); + else + rv = input_uninhibit_device(input_dev); + + if (rv != 0) + return rv; + + return len; +} + +static DEVICE_ATTR_RW(inhibited); + static struct attribute *input_dev_attrs[] = { &dev_attr_name.attr, &dev_attr_phys.attr, &dev_attr_uniq.attr, &dev_attr_modalias.attr, &dev_attr_properties.attr, + &dev_attr_inhibited.attr, NULL }; @@ -1703,6 +1744,63 @@ void input_reset_device(struct input_dev *dev) } EXPORT_SYMBOL(input_reset_device); +static int input_inhibit_device(struct input_dev *dev) +{ + int ret = 0; + + mutex_lock(&dev->mutex); + + if (dev->inhibited) + goto out; + + if (dev->users) { + if (dev->close) + dev->close(dev); + if (dev->poller) + input_dev_poller_stop(dev->poller); + } + + spin_lock_irq(&dev->event_lock); + input_dev_release_keys(dev); + input_dev_toggle(dev, false); + spin_unlock_irq(&dev->event_lock); + + dev->inhibited = true; + +out: + mutex_unlock(&dev->mutex); + return ret; +} + +static int input_uninhibit_device(struct input_dev *dev) +{ + int ret = 0; + + mutex_lock(&dev->mutex); + + if (!dev->inhibited) + goto out; + + if (dev->users) { + if (dev->open) { + ret = dev->open(dev); + if (ret) + goto out; + } + if (dev->poller) + input_dev_poller_start(dev->poller); + } + + dev->inhibited = false; + spin_lock_irq(&dev->event_lock); + input_dev_toggle(dev, true); + spin_unlock_irq(&dev->event_lock); + +out: + mutex_unlock(&dev->mutex); + return ret; +} + #ifdef CONFIG_PM_SLEEP static int input_dev_suspend(struct device *dev) { @@ -2131,7 +2229,7 @@ bool input_device_enabled(struct input_dev *dev) { lockdep_assert_held(&dev->mutex); - return dev->users > 0; + return !dev->inhibited && dev->users > 0; } EXPORT_SYMBOL_GPL(input_device_enabled); |