dd65c9
From e6682abae36dcfe8a093686a3bd67b3568157c35 Mon Sep 17 00:00:00 2001
dd65c9
From: Peter Hutterer <peter.hutterer@who-t.net>
dd65c9
Date: Thu, 19 Mar 2015 14:19:58 +1000
dd65c9
Subject: [PATCH] udev: builtin-keyboard: add support for EVDEV_ABS_*
dd65c9
dd65c9
Parse properties in the form
dd65c9
EVDEV_ABS_00="<min>:<max>:<res>:<fuzz>:<flat>"
dd65c9
dd65c9
and apply them to the kernel device. Future processes that open that device
dd65c9
will see the updated EV_ABS range.
dd65c9
dd65c9
This is particularly useful for touchpads that don't provide a resolution in
dd65c9
the kernel driver but can be fixed up through hwdb entries (e.g. bcm5974).
dd65c9
dd65c9
All values in the property are optional, e.g. a string of "::45" is valid to
dd65c9
set the resolution to 45.
dd65c9
dd65c9
The order intentionally orders resolution before fuzz and flat despite it
dd65c9
being the last element in the absinfo struct. The use-case for setting
dd65c9
fuzz/flat is almost non-existent, resolution is probably the most common case
dd65c9
we'll need.
dd65c9
dd65c9
To avoid multiple hwdb invocations for the same device, replace the
dd65c9
hwdb "keyboard:" prefix with "evdev:" and drop the separate 60-keyboard.rules
dd65c9
file. The new 60-evdev.rules is called for all event nodes
dd65c9
anyway, we don't need a separate rules file and second callout to the hwdb
dd65c9
builtin.
dd65c9
dd65c9
(cherry picked from commit 51c0c2869845a058268d54c3111d55d0dd485704)
dd65c9
dd65c9
Changes to the upstream commit:
dd65c9
- 60-keyboard.rules is left in place, the 60-keyboard.hwdb is left
dd65c9
  unmodified. This avoids any potential breakage from user-installed hwdb
dd65c9
  files that are triggered by that rule.
dd65c9
dd65c9
Conflicts:
dd65c9
	hwdb/60-keyboard.hwdb
dd65c9
	rules/60-keyboard.rules
dd65c9
dd65c9
Resolves: #1500119
dd65c9
---
dd65c9
 Makefile.am                      |  2 +
dd65c9
 hwdb/60-evdev.hwdb               | 37 +++++++++++++++++
dd65c9
 rules/60-evdev.rules             | 14 +++++++
dd65c9
 src/udev/udev-builtin-keyboard.c | 86 ++++++++++++++++++++++++++++++++++++++--
dd65c9
 4 files changed, 135 insertions(+), 4 deletions(-)
dd65c9
 create mode 100644 hwdb/60-evdev.hwdb
dd65c9
 create mode 100644 rules/60-evdev.rules
dd65c9
dd65c9
diff --git a/Makefile.am b/Makefile.am
dd65c9
index a1ebf5cb0..13c93f485 100644
dd65c9
--- a/Makefile.am
dd65c9
+++ b/Makefile.am
dd65c9
@@ -3499,6 +3499,7 @@ dist_udevrules_DATA += \
dd65c9
 	rules/42-usb-hid-pm.rules \
dd65c9
 	rules/50-udev-default.rules \
dd65c9
 	rules/60-drm.rules \
dd65c9
+	rules/60-evdev.rules \
dd65c9
 	rules/60-keyboard.rules \
dd65c9
 	rules/60-persistent-storage-tape.rules \
dd65c9
 	rules/60-persistent-serial.rules \
dd65c9
@@ -3702,6 +3703,7 @@ dist_udevhwdb_DATA = \
dd65c9
 	hwdb/20-acpi-vendor.hwdb \
dd65c9
 	hwdb/20-OUI.hwdb \
dd65c9
 	hwdb/20-net-ifname.hwdb \
dd65c9
+	hwdb/60-evdev.hwdb \
dd65c9
 	hwdb/60-keyboard.hwdb \
dd65c9
 	hwdb/70-mouse.hwdb \
dd65c9
 	hwdb/70-touchpad.hwdb
dd65c9
diff --git a/hwdb/60-evdev.hwdb b/hwdb/60-evdev.hwdb
dd65c9
new file mode 100644
dd65c9
index 000000000..ad2d09e72
dd65c9
--- /dev/null
dd65c9
+++ b/hwdb/60-evdev.hwdb
dd65c9
@@ -0,0 +1,37 @@
dd65c9
+# This file is part of systemd.
dd65c9
+#
dd65c9
+# The lookup keys are composed in:
dd65c9
+#   60-evdev.rules
dd65c9
+#
dd65c9
+# Note: The format of the "evdev:" prefix match key is a
dd65c9
+# contract between the rules file and the hardware data, it might
dd65c9
+# change in later revisions to support more or better matches, it
dd65c9
+# is not necessarily expected to be a stable ABI.
dd65c9
+#
dd65c9
+# Match string formats:
dd65c9
+# evdev:<modalias>
dd65c9
+# evdev:name:<device name>:dmi:<dmi string>
dd65c9
+#
dd65c9
+# To add local entries, create a new file
dd65c9
+#   /etc/udev/hwdb.d/61-evdev-local.hwdb
dd65c9
+# and add your rules there. To load the new rules execute (as root):
dd65c9
+#   udevadm hwdb --update
dd65c9
+#   udevadm trigger /dev/input/eventXX
dd65c9
+# where /dev/input/eventXX is the device in question. If in
dd65c9
+# doubt, simply use /dev/input/event* to reload all input rules.
dd65c9
+#
dd65c9
+# If your changes are generally applicable, open a bug report on
dd65c9
+#   http://bugs.freedesktop.org/enter_bug.cgi?product=systemd
dd65c9
+# and include your new rules, a description of the device, and the
dd65c9
+# output of
dd65c9
+#   udevadm info /dev/input/eventXX
dd65c9
+# (or /dev/input/event*).
dd65c9
+#
dd65c9
+# Allowed properties are:
dd65c9
+#    EVDEV_ABS_<axis>=<min>:<max>:<res>:<fuzz>:<flat>
dd65c9
+#
dd65c9
+# where <axis> is the hexadecimal EV_ABS code as listed in linux/input.h
dd65c9
+# and min, max, res, fuzz, flat are the decimal values to the respective
dd65c9
+# fields of the struct input_absinfo as listed in linux/input.h.
dd65c9
+# If a field is missing the field will be left as-is. Not all fields need to
dd65c9
+# be present. e.g. ::45 sets the resolution to 45 units/mm.
dd65c9
diff --git a/rules/60-evdev.rules b/rules/60-evdev.rules
dd65c9
new file mode 100644
dd65c9
index 000000000..67308ad23
dd65c9
--- /dev/null
dd65c9
+++ b/rules/60-evdev.rules
dd65c9
@@ -0,0 +1,14 @@
dd65c9
+# do not edit this file, it will be overwritten on update
dd65c9
+
dd65c9
+ACTION=="remove", GOTO="evdev_end"
dd65c9
+KERNEL!="event*", GOTO="evdev_end"
dd65c9
+
dd65c9
+# skip later rules when we find something for this input device
dd65c9
+IMPORT{builtin}="hwdb --subsystem=input --lookup-prefix=evdev:", \
dd65c9
+  RUN{builtin}+="keyboard", GOTO="evdev_end"
dd65c9
+
dd65c9
+# device matching the input device name and the machine's DMI data
dd65c9
+KERNELS=="input*", IMPORT{builtin}="hwdb 'evdev:name:$attr{name}:$attr{[dmi/id]modalias}'", \
dd65c9
+  RUN{builtin}+="keyboard", GOTO="evdev_end"
dd65c9
+
dd65c9
+LABEL="evdev_end"
dd65c9
diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c
dd65c9
index 86f4018ef..eaa21abf6 100644
dd65c9
--- a/src/udev/udev-builtin-keyboard.c
dd65c9
+++ b/src/udev/udev-builtin-keyboard.c
dd65c9
@@ -99,6 +99,69 @@ static void map_keycode(int fd, const char *devnode, int scancode, const char *k
dd65c9
                 log_error_errno(errno, "Error calling EVIOCSKEYCODE on device node '%s' (scan code 0x%x, key code %d): %m", devnode, map.scan, map.key);
dd65c9
 }
dd65c9
 
dd65c9
+static inline char* parse_token(const char *current, int32_t *val_out) {
dd65c9
+        char *next;
dd65c9
+        int32_t val;
dd65c9
+
dd65c9
+        if (!current)
dd65c9
+                return NULL;
dd65c9
+
dd65c9
+        val = strtol(current, &next, 0);
dd65c9
+        if (*next && *next != ':')
dd65c9
+                return NULL;
dd65c9
+
dd65c9
+        if (next != current)
dd65c9
+                *val_out = val;
dd65c9
+
dd65c9
+        if (*next)
dd65c9
+                next++;
dd65c9
+
dd65c9
+        return next;
dd65c9
+}
dd65c9
+
dd65c9
+static void override_abs(int fd, const char *devnode,
dd65c9
+                         unsigned evcode, const char *value) {
dd65c9
+        struct input_absinfo absinfo;
dd65c9
+        int rc;
dd65c9
+        char *next;
dd65c9
+
dd65c9
+        rc = ioctl(fd, EVIOCGABS(evcode), &absinfo);
dd65c9
+        if (rc < 0) {
dd65c9
+                log_error_errno(errno, "Error, unable to EVIOCGABS device '%s'",
dd65c9
+                                devnode);
dd65c9
+                return;
dd65c9
+        }
dd65c9
+
dd65c9
+        next = parse_token(value, &absinfo.minimum);
dd65c9
+        next = parse_token(next, &absinfo.maximum);
dd65c9
+        next = parse_token(next, &absinfo.resolution);
dd65c9
+        next = parse_token(next, &absinfo.fuzz);
dd65c9
+        next = parse_token(next, &absinfo.flat);
dd65c9
+        if (!next) {
dd65c9
+                log_error("Error, unable to parse EV_ABS override '%s' for '%s'\n",
dd65c9
+                          value, devnode);
dd65c9
+                return;
dd65c9
+        }
dd65c9
+
dd65c9
+        log_debug("keyboard: override %x with %d/%d/%d/%d/%d", evcode,
dd65c9
+                  absinfo.minimum, absinfo.maximum, absinfo.resolution,
dd65c9
+                  absinfo.fuzz, absinfo.flat);
dd65c9
+        rc = ioctl(fd, EVIOCSABS(evcode), &absinfo);
dd65c9
+        if (rc < 0)
dd65c9
+                log_error_errno(errno, "Error, unable to update device '%s'",
dd65c9
+                                devnode);
dd65c9
+}
dd65c9
+
dd65c9
+static int open_device(const char *devnode) {
dd65c9
+        int fd;
dd65c9
+
dd65c9
+        fd = open(devnode, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
dd65c9
+        if (fd < 0)
dd65c9
+                log_error_errno(errno, "Error, opening device '%s': %m", devnode);
dd65c9
+
dd65c9
+        return fd < 0 ? -errno : fd;
dd65c9
+}
dd65c9
+
dd65c9
 static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], bool test) {
dd65c9
         struct udev_list_entry *entry;
dd65c9
         unsigned release[1024];
dd65c9
@@ -143,14 +206,29 @@ static int builtin_keyboard(struct udev_device *dev, int argc, char *argv[], boo
dd65c9
                         }
dd65c9
 
dd65c9
                         if (fd == -1) {
dd65c9
-                                fd = open(node, O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
dd65c9
-                                if (fd < 0) {
dd65c9
-                                        log_error_errno(errno, "Error, opening device '%s': %m", node);
dd65c9
+                                fd = open_device(node);
dd65c9
+                                if (fd < 0)
dd65c9
                                         return EXIT_FAILURE;
dd65c9
-                                }
dd65c9
                         }
dd65c9
 
dd65c9
                         map_keycode(fd, node, scancode, keycode);
dd65c9
+                } else if (startswith(key, "EVDEV_ABS_")) {
dd65c9
+                        unsigned evcode;
dd65c9
+
dd65c9
+                        /* EVDEV_ABS_<EV_ABS code>=<min>:<max>:<res>:<fuzz>:<flat> */
dd65c9
+                        evcode = strtoul(key + 10, &endptr, 16);
dd65c9
+                        if (endptr[0] != '\0') {
dd65c9
+                                log_error("Error, unable to parse EV_ABS code from '%s'", key);
dd65c9
+                                continue;
dd65c9
+                        }
dd65c9
+
dd65c9
+                        if (fd == -1) {
dd65c9
+                                fd = open_device(node);
dd65c9
+                                if (fd < 0)
dd65c9
+                                        return EXIT_FAILURE;
dd65c9
+                        }
dd65c9
+
dd65c9
+                        override_abs(fd, node, evcode, udev_list_entry_get_value(entry));
dd65c9
                 }
dd65c9
         }
dd65c9