Skip to content

Commit

Permalink
udev: parse the EVDEV_ABS properties for a potential fuzz setting
Browse files Browse the repository at this point in the history
Where a fuzz is defined in the 60-evdev.hwdb, we rely on a udev builtin to
set the kernel device to that fuzz value. Unfortunately that happens after our
program is called with this order of events:
1. 60-evdev.rules calls IMPORT(builtin) for the hwdb which sets the EVDEV_ABS_*
  properties. It also sets RUN{builtin}=keyboard but that's not invoked yet.
2. 90-libinput-fuzz-override.rules calls IMPORT{program} for our fuzz override
  bits. That sets the kernel fuzz value to 0 and sets the LIBINPUT_FUZZ_*
  propertie
3. The keyboard builtin is run once all the rules have been processed.

Our problem is that where the fuzz is set in a hwdb entry, the kernel fuzz is
still unset when we get to look at it, so we always end up with a fuzz of zero
for us and a nonzero kernel fuzz.

Work around this by checking the EVDEV_ABS property, extracting the fuzz from
there and re-printing that property without the fuzz. This way we ensure the
kernel remains at zero fuzz and we use the one from the hwdb instead.

Fixes #346

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
  • Loading branch information
whot committed Sep 11, 2019
1 parent 1e6802b commit e7a9c07
Show file tree
Hide file tree
Showing 8 changed files with 459 additions and 18 deletions.
12 changes: 10 additions & 2 deletions meson.build
Expand Up @@ -149,8 +149,16 @@ executable('libinput-device-group',
include_directories : [includes_src, includes_include],
install : true,
install_dir : dir_udev_callouts)
executable('libinput-fuzz-override',
'udev/libinput-fuzz-override.c',
executable('libinput-fuzz-extract',
'udev/libinput-fuzz-extract.c',
'src/util-strings.c',
'src/util-prop-parsers.c',
dependencies : [dep_udev, dep_libevdev, dep_lm],
include_directories : [includes_src, includes_include],
install : true,
install_dir : dir_udev_callouts)
executable('libinput-fuzz-to-zero',
'udev/libinput-fuzz-to-zero.c',
dependencies : [dep_udev, dep_libevdev],
include_directories : [includes_src, includes_include],
install : true,
Expand Down
67 changes: 67 additions & 0 deletions src/util-prop-parsers.c
Expand Up @@ -401,3 +401,70 @@ parse_evcode_property(const char *prop, struct input_event *events, size_t *neve
strv_free(strv);
return rc;
}

/**
* Parse the property value for the EVDEV_ABS_00 properties. Spec is
* EVDEV_ABS_00=min:max:res:fuzz:flat
* where any element may be empty and subsequent elements may not be
* present. So we have to parse
* EVDEV_ABS_00=min:max:res
* EVDEV_ABS_00=::res
* EVDEV_ABS_00=::res:fuzz:
*
* Returns a mask of the bits set and the absinfo struct with the values.
* The abs value for an unset bit is undefined.
*/
uint32_t
parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs)
{
char *str = strdup(prop);
char *current, *next;
uint32_t mask = 0;
int bit = ABS_MASK_MIN;
int *val;
int values[5];

/* basic sanity check: 5 digits for min/max, 3 for resolution, fuzz,
* flat and the colons. That's plenty, anything over is garbage */
if (strlen(prop) > 24)
goto out;

current = str;
val = values;
while (current && *current != '\0' && bit <= ABS_MASK_FLAT) {
if (*current != ':') {
int v;
next = index(current, ':');
if (next)
*next = '\0';

if (!safe_atoi(current, &v)) {
mask = 0;
goto out;
}
*val = v;
mask |= bit;
current = next ? ++next : NULL;
} else {
current++;
}
bit <<= 1;
val++;
}

if (mask & ABS_MASK_MIN)
abs->minimum = values[0];
if (mask & ABS_MASK_MAX)
abs->maximum = values[1];
if (mask & ABS_MASK_RES)
abs->resolution = values[2];
if (mask & ABS_MASK_FUZZ)
abs->fuzz = values[3];
if (mask & ABS_MASK_FLAT)
abs->flat = values[4];

out:
free(str);

return mask;
}
11 changes: 11 additions & 0 deletions src/util-prop-parsers.h
Expand Up @@ -28,6 +28,7 @@
#include <linux/input.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

int parse_mouse_dpi_property(const char *prop);
int parse_mouse_wheel_click_angle_property(const char *prop);
Expand All @@ -54,3 +55,13 @@ enum switch_reliability {
bool
parse_switch_reliability_property(const char *prop,
enum switch_reliability *reliability);

enum {
ABS_MASK_MIN = 0x1,
ABS_MASK_MAX = 0x2,
ABS_MASK_RES = 0x4,
ABS_MASK_FUZZ = 0x8,
ABS_MASK_FLAT = 0x10,
};

uint32_t parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs);
78 changes: 78 additions & 0 deletions test/test-utils.c
Expand Up @@ -546,6 +546,83 @@ START_TEST(evcode_prop_parser)
}
END_TEST

START_TEST(evdev_abs_parser)
{
struct test {
uint32_t which;
const char *prop;
int min, max, res, fuzz, flat;

} tests[] = {
{ .which = (ABS_MASK_MIN|ABS_MASK_MAX),
.prop = "1:2",
.min = 1, .max = 2 },
{ .which = (ABS_MASK_MIN|ABS_MASK_MAX),
.prop = "1:2:",
.min = 1, .max = 2 },
{ .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES),
.prop = "10:20:30",
.min = 10, .max = 20, .res = 30 },
{ .which = (ABS_MASK_RES),
.prop = "::100",
.res = 100 },
{ .which = (ABS_MASK_MIN),
.prop = "10:",
.min = 10 },
{ .which = (ABS_MASK_MAX|ABS_MASK_RES),
.prop = ":10:1001",
.max = 10, .res = 1001 },
{ .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ),
.prop = "1:2:3:4",
.min = 1, .max = 2, .res = 3, .fuzz = 4},
{ .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT),
.prop = "1:2:3:4:5",
.min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5},
{ .which = (ABS_MASK_MIN|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT),
.prop = "1::3:4:50",
.min = 1, .res = 3, .fuzz = 4, .flat = 50},
{ .which = ABS_MASK_FUZZ|ABS_MASK_FLAT,
.prop = ":::5:60",
.fuzz = 5, .flat = 60},
{ .which = ABS_MASK_FUZZ,
.prop = ":::5:",
.fuzz = 5 },
{ .which = ABS_MASK_RES, .prop = "::12::",
.res = 12 },
/* Malformed property but parsing this one makes us more
* future proof */
{ .which = (ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT),
.prop = "::12:1:2:3:4:5:6",
.res = 12, .fuzz = 1, .flat = 2 },
{ .which = 0, .prop = ":::::" },
{ .which = 0, .prop = ":" },
{ .which = 0, .prop = "" },
{ .which = 0, .prop = ":asb::::" },
{ .which = 0, .prop = "foo" },
};
struct test *t;

ARRAY_FOR_EACH(tests, t) {
struct input_absinfo abs;
uint32_t mask;

mask = parse_evdev_abs_prop(t->prop, &abs);
ck_assert_int_eq(mask, t->which);

if (t->which & ABS_MASK_MIN)
ck_assert_int_eq(abs.minimum, t->min);
if (t->which & ABS_MASK_MAX)
ck_assert_int_eq(abs.maximum, t->max);
if (t->which & ABS_MASK_RES)
ck_assert_int_eq(abs.resolution, t->res);
if (t->which & ABS_MASK_FUZZ)
ck_assert_int_eq(abs.fuzz, t->fuzz);
if (t->which & ABS_MASK_FLAT)
ck_assert_int_eq(abs.flat, t->flat);
}
}
END_TEST

START_TEST(time_conversion)
{
ck_assert_int_eq(us(10), 10);
Expand Down Expand Up @@ -1050,6 +1127,7 @@ litest_utils_suite(void)
tcase_add_test(tc, calibration_prop_parser);
tcase_add_test(tc, range_prop_parser);
tcase_add_test(tc, evcode_prop_parser);
tcase_add_test(tc, evdev_abs_parser);
tcase_add_test(tc, safe_atoi_test);
tcase_add_test(tc, safe_atoi_base_16_test);
tcase_add_test(tc, safe_atoi_base_8_test);
Expand Down
15 changes: 11 additions & 4 deletions udev/90-libinput-fuzz-override.rules.in
Expand Up @@ -6,15 +6,22 @@
ACTION!="add|change", GOTO="libinput_fuzz_override_end"
KERNEL!="event*", GOTO="libinput_fuzz_override_end"

# libinput-fuzz-override must only be called once per device, otherwise
# we'll lose the fuzz information
# Two-step process: fuzz-extract sets the LIBINPUT_FUZZ property and
# fuzz-to-zero sets the kernel fuzz to zero. They must be in IMPORT and RUN,
# respectively, to correctly interact with the 60-evdev.hwdb
#
# Drawback: if this rule is triggered more than once, we'll lose the fuzz
# information (because the kernel fuzz will then be zero). Nothing we can do
# about that.
ATTRS{capabilities/abs}!="0", \
ENV{ID_INPUT_TOUCHPAD}=="1", \
IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \
IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \
RUN{program}="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \
GOTO="libinput_fuzz_override_end"
ATTRS{capabilities/abs}!="0", \
ENV{ID_INPUT_TOUCHSCREEN}=="1", \
IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \
IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \
RUN{program}="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \
GOTO="libinput_fuzz_override_end"

LABEL="libinput_fuzz_override_end"
150 changes: 150 additions & 0 deletions udev/libinput-fuzz-extract.c
@@ -0,0 +1,150 @@
/*
* Copyright © 2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <libudev.h>
#include <linux/input.h>
#include <libevdev/libevdev.h>

#include "util-prop-parsers.h"
#include "util-macros.h"

/**
* For a non-zero fuzz on the x/y axes, print that fuzz as property and
* reset the kernel's fuzz to 0.
* https://bugs.freedesktop.org/show_bug.cgi?id=105202
*/
static void
handle_absfuzz(struct udev_device *device)
{
const char *devnode;
struct libevdev *evdev = NULL;
int fd = -1;
int rc;
unsigned int *code;
unsigned int axes[] = {ABS_X,
ABS_Y,
ABS_MT_POSITION_X,
ABS_MT_POSITION_Y};

devnode = udev_device_get_devnode(device);
if (!devnode)
goto out;

fd = open(devnode, O_RDONLY);
if (fd < 0)
goto out;

rc = libevdev_new_from_fd(fd, &evdev);
if (rc != 0)
goto out;

if (!libevdev_has_event_type(evdev, EV_ABS))
goto out;

ARRAY_FOR_EACH(axes, code) {
int fuzz;

fuzz = libevdev_get_abs_fuzz(evdev, *code);
if (fuzz)
printf("LIBINPUT_FUZZ_%02x=%d\n", *code, fuzz);
}

out:
close(fd);
libevdev_free(evdev);
}

/**
* Where a device has EVDEV_ABS_... set with a fuzz, that fuzz hasn't been
* applied to the kernel yet. So we need to extract it ourselves **and**
* update the property so the kernel won't actually set it later.
*/
static void
handle_evdev_abs(struct udev_device *device)
{
unsigned int *code;
unsigned int axes[] = {ABS_X,
ABS_Y,
ABS_MT_POSITION_X,
ABS_MT_POSITION_Y};

ARRAY_FOR_EACH(axes, code) {
const char *prop;
char name[64];
uint32_t mask;
struct input_absinfo abs;

snprintf(name, sizeof(name), "EVDEV_ABS_%02X", *code);
prop = udev_device_get_property_value(device, name);
if (!prop)
continue;

mask = parse_evdev_abs_prop(prop, &abs);
if (mask & ABS_MASK_FUZZ)
printf("LIBINPUT_FUZZ_%02x=%d\n", *code, abs.fuzz);
}
}

int main(int argc, char **argv)
{
int rc = 1;
struct udev *udev = NULL;
struct udev_device *device = NULL;
const char *syspath;

if (argc != 2)
return 1;

syspath = argv[1];

udev = udev_new();
if (!udev)
goto out;

device = udev_device_new_from_syspath(udev, syspath);
if (!device)
goto out;

handle_absfuzz(device);
handle_evdev_abs(device);

rc = 0;

out:
if (device)
udev_device_unref(device);
if (udev)
udev_unref(udev);

return rc;
}

0 comments on commit e7a9c07

Please sign in to comment.