Extract and reset the abs fuzz value for the x/y axes

The kernel fuzz handling is buggy, especially when we want to rely on the fuzz
value for our hysteresis. But since this is a hw property and (at least
sometimes) set by the driver, we can't make this a pure libinput hwdb set
either.

So our workaround is:
* extract the (non-zero) fuzz into a udev property so we don't lose it
* set the fuzz to 0 to disable the in-kernel hysteresis
* overwrite our internal absinfo with the property fuzz

This way we get to use the hw-specified fuzz without having the kernel muck
around with it. We also get to use the EVDEV_ABS_ values in 60-evdev.hwdb to
override a driver-set fuzz.

Two drawbacks:
- we're resetting the kernel fuzz to 0, this affects any other users of the
  device node. That's probably a minor impact only.
- we can only save this in a udev property there's a risk of this information
  getting lost when playing around with udev rules. That too should be a minor
  issue.

https://bugs.freedesktop.org/show_bug.cgi?id=105303

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
Peter Hutterer 2018-03-05 13:17:43 +10:00
parent 1b64888a22
commit 1523d8bb2e
5 changed files with 101 additions and 2 deletions

View file

@ -87,7 +87,7 @@ executable('libinput-device-group',
install_dir : udev_dir)
executable('libinput-model-quirks',
'udev/libinput-model-quirks.c',
dependencies : dep_udev,
dependencies : [dep_udev, dep_libevdev],
include_directories : [includes_src, includes_include],
install : true,
install_dir : udev_dir)

View file

@ -1505,6 +1505,7 @@ static void
evdev_extract_abs_axes(struct evdev_device *device)
{
struct libevdev *evdev = device->evdev;
int fuzz;
if (!libevdev_has_event_code(evdev, EV_ABS, ABS_X) ||
!libevdev_has_event_code(evdev, EV_ABS, ABS_Y))
@ -1512,6 +1513,12 @@ evdev_extract_abs_axes(struct evdev_device *device)
if (evdev_fix_abs_resolution(device, ABS_X, ABS_Y))
device->abs.is_fake_resolution = true;
if ((fuzz = evdev_read_fuzz_prop(device, ABS_X)))
libevdev_set_abs_fuzz(evdev, ABS_X, fuzz);
if ((fuzz = evdev_read_fuzz_prop(device, ABS_Y)))
libevdev_set_abs_fuzz(evdev, ABS_Y, fuzz);
device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_X);
device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_Y);
device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum -
@ -1529,6 +1536,11 @@ evdev_extract_abs_axes(struct evdev_device *device)
ABS_MT_POSITION_Y))
device->abs.is_fake_resolution = true;
if ((fuzz = evdev_read_fuzz_prop(device, ABS_MT_POSITION_X)))
libevdev_set_abs_fuzz(evdev, ABS_MT_POSITION_X, fuzz);
if ((fuzz = evdev_read_fuzz_prop(device, ABS_MT_POSITION_Y)))
libevdev_set_abs_fuzz(evdev, ABS_MT_POSITION_Y, fuzz);
device->abs.absinfo_x = libevdev_get_abs_info(evdev, ABS_MT_POSITION_X);
device->abs.absinfo_y = libevdev_get_abs_info(evdev, ABS_MT_POSITION_Y);
device->abs.dimensions.x = abs(device->abs.absinfo_x->maximum -
@ -2162,6 +2174,33 @@ evdev_read_calibration_prop(struct evdev_device *device)
calibration[5]);
}
int
evdev_read_fuzz_prop(struct evdev_device *device, unsigned int code)
{
const char *prop;
char name[32];
int rc;
int fuzz = 0;
rc = snprintf(name, sizeof(name), "LIBINPUT_FUZZ_%02x", code);
if (rc == -1)
return 0;
prop = udev_device_get_property_value(device->udev_device, name);
if (prop == NULL)
return 0;
rc = safe_atoi(prop, &fuzz);
if (rc == -1 || fuzz < 0) {
evdev_log_bug_libinput(device,
"invalid LIBINPUT_FUZZ property value: %s\n",
prop);
return 0;
}
return fuzz;
}
bool
evdev_device_has_capability(struct evdev_device *device,
enum libinput_device_capability capability)

View file

@ -378,6 +378,9 @@ evdev_init_calibration(struct evdev_device *device,
void
evdev_read_calibration_prop(struct evdev_device *device);
int
evdev_read_fuzz_prop(struct evdev_device *device, unsigned int code);
enum switch_reliability
evdev_read_switch_reliability_prop(struct evdev_device *device);

View file

@ -15,7 +15,6 @@ KERNEL!="event*", GOTO="libinput_model_quirks_end"
# First, run the program and import the LIBINPUT_MODEL_FIRMWARE_VERSION
# environment (if any)
KERNELS=="*input*", \
ENV{ID_INPUT_TOUCHPAD}=="1", \
IMPORT{program}="@UDEV_TEST_PATH@libinput-model-quirks %S%p"
# Second, match on anything with that env set and import from the hwdb

View file

@ -31,6 +31,7 @@
#include <unistd.h>
#include <libudev.h>
#include <linux/input.h>
#include <libevdev/libevdev.h>
#include "libinput-util.h"
@ -107,6 +108,61 @@ handle_touchpad(struct udev_device *device)
handle_touchpad_synaptics(device);
}
/**
* 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_RDWR);
if (fd == -1 && errno == EACCES)
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) {
struct input_absinfo abs;
int fuzz;
fuzz = libevdev_get_abs_fuzz(evdev, *code);
if (!fuzz)
continue;
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:
close(fd);
libevdev_free(evdev);
}
int main(int argc, char **argv)
{
int rc = 1;
@ -127,6 +183,8 @@ int main(int argc, char **argv)
if (!device)
goto out;
handle_absfuzz(device);
if (prop_value(device, "ID_INPUT_TOUCHPAD"))
handle_touchpad(device);