udev: parse the EVDEV_ABS properties for a potential fuzz setting

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>
This commit is contained in:
Peter Hutterer 2019-09-04 15:09:13 +10:00
parent 1e6802b91b
commit e7a9c07ffe
8 changed files with 459 additions and 18 deletions

View file

@ -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,

View file

@ -401,3 +401,70 @@ out:
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;
}

View file

@ -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);
@ -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);

View file

@ -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);
@ -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);

View file

@ -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"

View file

@ -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;
}

View file

@ -27,21 +27,19 @@
#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 "libinput-util.h"
#include "util-strings.h"
#include "util-macros.h"
#include "util-bits.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)
reset_absfuzz_to_zero(struct udev_device *device)
{
const char *devnode;
struct libevdev *evdev = NULL;
@ -58,8 +56,6 @@ handle_absfuzz(struct udev_device *device)
goto out;
fd = open(devnode, O_RDWR);
if (fd == -1 && errno == EACCES)
fd = open(devnode, O_RDONLY);
if (fd < 0)
goto out;
@ -81,8 +77,6 @@ handle_absfuzz(struct udev_device *device)
abs = *libevdev_get_abs_info(evdev, *code);
abs.fuzz = 0;
libevdev_kernel_set_abs_info(evdev, *code, &abs);
printf("LIBINPUT_FUZZ_%02x=%d\n", *code, fuzz);
}
out:
@ -110,7 +104,7 @@ int main(int argc, char **argv)
if (!device)
goto out;
handle_absfuzz(device);
reset_absfuzz_to_zero(device);
rc = 0;

View file

@ -0,0 +1,126 @@
/*
* Copyright © 2019 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 <check.h>
/* remove the main() from the included program so we can define our own */
#define main __disabled
int main(int argc, char **argv);
#include "libinput-fuzz-extract.c"
#undef main
START_TEST(test_parse_ev_abs)
{
struct test {
uint32_t which;
const char *prop;
int min, max, res, fuzz, flat;
} tests[] = {
{ .which = (MIN|MAX),
.prop = "1:2",
.min = 1, .max = 2 },
{ .which = (MIN|MAX),
.prop = "1:2:",
.min = 1, .max = 2 },
{ .which = (MIN|MAX|RES),
.prop = "10:20:30",
.min = 10, .max = 20, .res = 30 },
{ .which = (RES),
.prop = "::100",
.res = 100 },
{ .which = (MIN),
.prop = "10:",
.min = 10 },
{ .which = (MAX|RES),
.prop = ":10:1001",
.max = 10, .res = 1001 },
{ .which = (MIN|MAX|RES|FUZZ),
.prop = "1:2:3:4",
.min = 1, .max = 2, .res = 3, .fuzz = 4},
{ .which = (MIN|MAX|RES|FUZZ|FLAT),
.prop = "1:2:3:4:5",
.min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5},
{ .which = (MIN|RES|FUZZ|FLAT),
.prop = "1::3:4:50",
.min = 1, .res = 3, .fuzz = 4, .flat = 50},
{ .which = FUZZ|FLAT,
.prop = ":::5:60",
.fuzz = 5, .flat = 60},
{ .which = FUZZ,
.prop = ":::5:",
.fuzz = 5 },
{ .which = RES, .prop = "::12::",
.res = 12 },
/* Malformed property but parsing this one makes us more
* future proof */
{ .which = (RES|FUZZ|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_ev_abs_prop(t->prop, &abs);
ck_assert_int_eq(mask, t->which);
if (t->which & MIN)
ck_assert_int_eq(abs.minimum, t->min);
if (t->which & MAX)
ck_assert_int_eq(abs.maximum, t->max);
if (t->which & RES)
ck_assert_int_eq(abs.resolution, t->res);
if (t->which & FUZZ)
ck_assert_int_eq(abs.fuzz, t->fuzz);
if (t->which & FLAT)
ck_assert_int_eq(abs.flat, t->flat);
}
}
END_TEST
int main(int argc, char **argv) {
SRunner *sr = srunner_create(NULL);
Suite *s = suite_create("fuzz-override");
TCase *tc = tcase_create("parser");
int nfailed;
tcase_add_test(tc, test_parse_ev_abs);
suite_add_tcase(s, tc);
srunner_add_suite(sr, s);
srunner_run_all(sr, CK_NORMAL);
nfailed = srunner_ntests_failed(sr);
srunner_free(sr);
return nfailed;
}