diff mbox series

[02/18] HID: bpf: add first in-tree HID-BPF fix for the XPPen Artist 24

Message ID 20240410-bpf_sources-v1-2-a8bf16033ef8@kernel.org (mailing list archive)
State Mainlined
Commit 04b3e5ab055553e074ea54ef316982b55cdde96b
Delegated to: Jiri Kosina
Headers show
Series HID: Include current HID-BPF fixes in tree | expand

Commit Message

Benjamin Tissoires April 10, 2024, 5:19 p.m. UTC
This commit adds a fix for XPPen Artist 24 where the second button on
the pen is used as an eraser.

It's a "feature" from Microsoft, but it turns out that it's actually
painful for artists. So we ship here a HID-BPF program that turns this
second button into an actual button.

Note that the HID-BPF program is not directly loaded by the kernel itself
but by udev-hid-bpf[0]. But having the sources here allows us to also
integrate tests into tools/testing/selftests/hid to ensure the HID-BPF
program are actually tested.

[0] https://gitlab.freedesktop.org/libevdev/udev-hid-bpf

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
---
 drivers/hid/bpf/progs/Makefile              |  91 +++++++++++
 drivers/hid/bpf/progs/README                | 102 +++++++++++++
 drivers/hid/bpf/progs/XPPen__Artist24.bpf.c | 229 ++++++++++++++++++++++++++++
 drivers/hid/bpf/progs/hid_bpf.h             |  15 ++
 drivers/hid/bpf/progs/hid_bpf_helpers.h     | 170 +++++++++++++++++++++
 5 files changed, 607 insertions(+)

Comments

Benjamin Tissoires April 11, 2024, 7:09 a.m. UTC | #1
On Apr 10 2024, Benjamin Tissoires wrote:
> This commit adds a fix for XPPen Artist 24 where the second button on
> the pen is used as an eraser.
> 
> It's a "feature" from Microsoft, but it turns out that it's actually
> painful for artists. So we ship here a HID-BPF program that turns this
> second button into an actual button.
> 
> Note that the HID-BPF program is not directly loaded by the kernel itself
> but by udev-hid-bpf[0]. But having the sources here allows us to also
> integrate tests into tools/testing/selftests/hid to ensure the HID-BPF
> program are actually tested.
> 
> [0] https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
> 
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> ---
>  drivers/hid/bpf/progs/Makefile              |  91 +++++++++++
>  drivers/hid/bpf/progs/README                | 102 +++++++++++++
>  drivers/hid/bpf/progs/XPPen__Artist24.bpf.c | 229 ++++++++++++++++++++++++++++
>  drivers/hid/bpf/progs/hid_bpf.h             |  15 ++
>  drivers/hid/bpf/progs/hid_bpf_helpers.h     | 170 +++++++++++++++++++++
>  5 files changed, 607 insertions(+)
> 
> diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile
> new file mode 100644
> index 000000000000..63ed7e02adf1
> --- /dev/null
> +++ b/drivers/hid/bpf/progs/Makefile
> @@ -0,0 +1,91 @@
> +# SPDX-License-Identifier: GPL-2.0
> +OUTPUT := .output
> +abs_out := $(abspath $(OUTPUT))
> +
> +CLANG ?= clang
> +LLC ?= llc
> +LLVM_STRIP ?= llvm-strip
> +
> +TOOLS_PATH := $(abspath ../../../../tools)
> +BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
> +BPFTOOL_OUTPUT := $(abs_out)/bpftool
> +DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
> +BPFTOOL ?= $(DEFAULT_BPFTOOL)
> +
> +LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
> +LIBBPF_OUTPUT := $(abs_out)/libbpf
> +LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
> +LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
> +BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
> +
> +INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
> +CFLAGS := -g -Wall
> +
> +VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux)				\
> +		     $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)	\
> +		     ../../../../vmlinux				\
> +		     /sys/kernel/btf/vmlinux				\
> +		     /boot/vmlinux-$(shell uname -r)
> +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
> +ifeq ($(VMLINUX_BTF),)
> +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
> +endif
> +
> +ifeq ($(V),1)
> +Q =
> +msg =
> +else
> +Q = @
> +msg = @printf '  %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
> +MAKEFLAGS += --no-print-directory
> +submake_extras := feature_display=0
> +endif
> +
> +.DELETE_ON_ERROR:
> +
> +.PHONY: all clean
> +
> +SOURCES = $(wildcard *.bpf.c)
> +TARGETS = $(SOURCES:.bpf.c=.bpf.o)
> +
> +all: $(TARGETS)
> +
> +clean:
> +	$(call msg,CLEAN)
> +	$(Q)rm -rf $(OUTPUT) $(TARGETS)
> +
> +%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
> +	$(call msg,BPF,$@)
> +	$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES)			      \
> +		 -c $(filter %.c,$^) -o $@ &&				      \
> +	$(LLVM_STRIP) -g $@
> +
> +vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
> +ifeq ($(VMLINUX_H),)
> +	$(call msg,GEN,,$@)
> +	$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
> +else
> +	$(call msg,CP,,$@)
> +	$(Q)cp "$(VMLINUX_H)" $@
> +endif
> +
> +$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
> +	$(call msg,MKDIR,$@)
> +	$(Q)mkdir -p $@
> +
> +$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
> +	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)			       \
> +		    OUTPUT=$(abspath $(dir $@))/ prefix=		       \
> +		    DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
> +
> +ifeq ($(CROSS_COMPILE),)
> +$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
> +	$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC)			       \
> +		    OUTPUT=$(BPFTOOL_OUTPUT)/				       \
> +		    LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/		       \
> +		    LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
> +else
> +$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
> +	$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC)			       \
> +		    OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
> +endif
> diff --git a/drivers/hid/bpf/progs/README b/drivers/hid/bpf/progs/README
> new file mode 100644
> index 000000000000..20b0928f385b
> --- /dev/null
> +++ b/drivers/hid/bpf/progs/README
> @@ -0,0 +1,102 @@
> +# HID-BPF programs
> +
> +This directory contains various fixes for devices. They add new features or
> +fix some behaviors without being entirely mandatory. It is better to load them
> +when you have such a device, but they should not be a requirement for a device
> +to be working during the boot stage.
> +
> +The .bpf.c files provided here are not automatically compiled in the kernel.
> +They should be loaded in the kernel by `udev-hid-bpf`:
> +
> +https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
> +
> +The main reasons for these fixes to be here is to have a central place to
> +"upstream" them, but also this way we can test them thanks to the HID
> +selftests.
> +
> +Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
> +in the `src/bpf/stable` directory, and distributions are encouraged to
> +only ship those bpf objects. So adding a file here should eventually
> +land in distributions when they update `udev-hid-bpf`
> +
> +## Compilation
> +
> +Just run `make`
> +
> +## Installation
> +
> +### Automated way
> +
> +Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
> +
> +### Manual way
> +
> +- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
> +- create a new udev rule to automatically load it
> +
> +The following should do the trick (assuming udev-hid-bpf is available in
> +/usr/bin):
> +
> +```
> +$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
> +$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
> +[
> +  {
> +    "name": "xppen-ArtistPro16Gen2.bpf.o",
> +    "devices": [
> +      {
> +        "bus": "0x0003",
> +        "group": "0x0001",
> +        "vid": "0x28BD",
> +        "pid": "0x095A"
> +      },
> +      {
> +        "bus": "0x0003",
> +        "group": "0x0001",
> +        "vid": "0x28BD",
> +        "pid": "0x095B"
> +      }
> +    ],
> +...
> +$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
> +ACTION!="add|remove", GOTO="hid_bpf_end"
> +SUBSYSTEM!="hid", GOTO="hid_bpf_end"
> +
> +# xppen-ArtistPro16Gen2.bpf.o
> +ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
> +ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
> +# xppen-ArtistPro16Gen2.bpf.o
> +ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
> +ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
> +
> +LABEL="hid_bpf_end"
> +EOF
> +$> udevadm control --reload
> +```
> +
> +Then unplug and replug the device.
> +
> +## Checks
> +
> +### udev rule
> +
> +You can check that the udev rule is correctly working by issuing
> +
> +```
> +$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
> +...
> +run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
> +```
> +
> +### program loaded
> +
> +You can check that the program has been properly loaded with `bpftool`
> +
> +```
> +$> bpftool prog
> +...
> +247: tracing  name xppen_16_fix_eraser tag 18d389353ed2ef07  gpl
> +	loaded_at 2024-03-28T16:02:28+0100  uid 0
> +	xlated 120B  jited 77B  memlock 4096B
> +	btf_id 487
> +```
> diff --git a/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
> new file mode 100644
> index 000000000000..e1be6a12bb75
> --- /dev/null
> +++ b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
> @@ -0,0 +1,229 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/* Copyright (c) 2023 Benjamin Tissoires
> + */
> +
> +#include "vmlinux.h"
> +#include "hid_bpf.h"
> +#include "hid_bpf_helpers.h"
> +#include <bpf/bpf_tracing.h>
> +
> +#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
> +#define PID_ARTIST_24 0x093A
> +#define PID_ARTIST_24_PRO 0x092D
> +
> +HID_BPF_CONFIG(
> +	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
> +	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
> +);
> +
> +/*
> + * We need to amend the report descriptor for the following:
> + * - the device reports Eraser instead of using Secondary Barrel Switch
> + * - the pen doesn't have a rubber tail, so basically we are removing any
> + *   eraser/invert bits
> + */
> +static const __u8 fixed_rdesc[] = {
> +	0x05, 0x0d,                    // Usage Page (Digitizers)             0
> +	0x09, 0x02,                    // Usage (Pen)                         2
> +	0xa1, 0x01,                    // Collection (Application)            4
> +	0x85, 0x07,                    //  Report ID (7)                      6
> +	0x09, 0x20,                    //  Usage (Stylus)                     8
> +	0xa1, 0x00,                    //  Collection (Physical)              10
> +	0x09, 0x42,                    //   Usage (Tip Switch)                12
> +	0x09, 0x44,                    //   Usage (Barrel Switch)             14
> +	0x09, 0x5a,                    //   Usage (Secondary Barrel Switch)   16  /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
> +	0x15, 0x00,                    //   Logical Minimum (0)               18
> +	0x25, 0x01,                    //   Logical Maximum (1)               20
> +	0x75, 0x01,                    //   Report Size (1)                   22
> +	0x95, 0x03,                    //   Report Count (3)                  24
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              26
> +	0x95, 0x02,                    //   Report Count (2)                  28
> +	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              30
> +	0x09, 0x32,                    //   Usage (In Range)                  32
> +	0x95, 0x01,                    //   Report Count (1)                  34
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              36
> +	0x95, 0x02,                    //   Report Count (2)                  38
> +	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              40
> +	0x75, 0x10,                    //   Report Size (16)                  42
> +	0x95, 0x01,                    //   Report Count (1)                  44
> +	0x35, 0x00,                    //   Physical Minimum (0)              46
> +	0xa4,                          //   Push                              48
> +	0x05, 0x01,                    //   Usage Page (Generic Desktop)      49
> +	0x09, 0x30,                    //   Usage (X)                         51
> +	0x65, 0x13,                    //   Unit (EnglishLinear: in)          53
> +	0x55, 0x0d,                    //   Unit Exponent (-3)                55
> +	0x46, 0xf0, 0x50,              //   Physical Maximum (20720)          57
> +	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           60
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              63
> +	0x09, 0x31,                    //   Usage (Y)                         65
> +	0x46, 0x91, 0x2d,              //   Physical Maximum (11665)          67
> +	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           70
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              73
> +	0xb4,                          //   Pop                               75
> +	0x09, 0x30,                    //   Usage (Tip Pressure)              76
> +	0x45, 0x00,                    //   Physical Maximum (0)              78
> +	0x26, 0xff, 0x1f,              //   Logical Maximum (8191)            80
> +	0x81, 0x42,                    //   Input (Data,Var,Abs,Null)         83
> +	0x09, 0x3d,                    //   Usage (X Tilt)                    85
> +	0x15, 0x81,                    //   Logical Minimum (-127)            87
> +	0x25, 0x7f,                    //   Logical Maximum (127)             89
> +	0x75, 0x08,                    //   Report Size (8)                   91
> +	0x95, 0x01,                    //   Report Count (1)                  93
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              95
> +	0x09, 0x3e,                    //   Usage (Y Tilt)                    97
> +	0x15, 0x81,                    //   Logical Minimum (-127)            99
> +	0x25, 0x7f,                    //   Logical Maximum (127)             101
> +	0x81, 0x02,                    //   Input (Data,Var,Abs)              103
> +	0xc0,                          //  End Collection                     105
> +	0xc0,                          // End Collection                      106
> +};
> +
> +#define BIT(n) (1UL << n)
> +
> +#define TIP_SWITCH		BIT(0)
> +#define BARREL_SWITCH		BIT(1)
> +#define ERASER			BIT(2)
> +/* padding			BIT(3) */
> +/* padding			BIT(4) */
> +#define IN_RANGE		BIT(5)
> +/* padding			BIT(6) */
> +/* padding			BIT(7) */
> +
> +#define U16(index) (data[index] | (data[index + 1] << 8))
> +
> +SEC("fmod_ret/hid_bpf_rdesc_fixup")
> +int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
> +{
> +	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
> +
> +	if (!data)
> +		return 0; /* EPERM check */
> +
> +	__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
> +
> +	return sizeof(fixed_rdesc);
> +}
> +
> +static __u8 prev_state = 0;
> +
> +/*
> + * There are a few cases where the device is sending wrong event
> + * sequences, all related to the second button (the pen doesn't
> + * have an eraser switch on the tail end):
> + *
> + *   whenever the second button gets pressed or released, an
> + *   out-of-proximity event is generated and then the firmware
> + *   compensate for the missing state (and the firmware uses
> + *   eraser for that button):
> + *
> + *   - if the pen is in range, an extra out-of-range is sent
> + *     when the second button is pressed/released:
> + *     // Pen is in range
> + *     E:                               InRange
> + *
> + *     // Second button is pressed
> + *     E:
> + *     E:                        Eraser InRange
> + *
> + *     // Second button is released
> + *     E:
> + *     E:                               InRange
> + *
> + *     This case is ignored by this filter, it's "valid"
> + *     and userspace knows how to deal with it, there are just
> + *     a few out-of-prox events generated, but the user doesn´t
> + *     see them.
> + *
> + *   - if the pen is in contact, 2 extra events are added when
> + *     the second button is pressed/released: an out of range
> + *     and an in range:
> + *
> + *     // Pen is in contact
> + *     E: TipSwitch                     InRange
> + *
> + *     // Second button is pressed
> + *     E:                                         <- false release, needs to be filtered out
> + *     E:                        Eraser InRange   <- false release, needs to be filtered out
> + *     E: TipSwitch              Eraser InRange
> + *
> + *     // Second button is released
> + *     E:                                         <- false release, needs to be filtered out
> + *     E:                               InRange   <- false release, needs to be filtered out
> + *     E: TipSwitch                     InRange
> + *
> + */
> +SEC("fmod_ret/hid_bpf_device_event")
> +int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
> +{
> +	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
> +	__u8 current_state, changed_state;
> +	bool prev_tip;
> +	__u16 tilt;
> +
> +	if (!data)
> +		return 0; /* EPERM check */
> +
> +	current_state = data[1];
> +
> +	/* if the state is identical to previously, early return */
> +	if (current_state == prev_state)
> +		return 0;
> +
> +	prev_tip = !!(prev_state & TIP_SWITCH);
> +
> +	/*
> +	 * Illegal transition: pen is in range with the tip pressed, and
> +	 * it goes into out of proximity.
> +	 *
> +	 * Ideally we should hold the event, start a timer and deliver it
> +	 * only if the timer ends, but we are not capable of that now.
> +	 *
> +	 * And it doesn't matter because when we are in such cases, this
> +	 * means we are detecting a false release.
> +	 */
> +	if ((current_state & IN_RANGE) == 0) {
> +		if (prev_tip)
> +			return HID_IGNORE_EVENT;
> +		return 0;
> +	}
> +
> +	/*
> +	 * XOR to only set the bits that have changed between
> +	 * previous and current state
> +	 */
> +	changed_state = prev_state ^ current_state;
> +
> +	/* Store the new state for future processing */
> +	prev_state = current_state;
> +
> +	/*
> +	 * We get both a tipswitch and eraser change in the same HID report:
> +	 * this is not an authorized transition and is unlikely to happen
> +	 * in real life.
> +	 * This is likely to be added by the firmware to emulate the
> +	 * eraser mode so we can skip the event.
> +	 */
> +	if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
> +		return HID_IGNORE_EVENT;
> +
> +	return 0;
> +}
> +
> +SEC("syscall")
> +int probe(struct hid_bpf_probe_args *ctx)
> +{
> +	/*
> +	 * The device exports 3 interfaces.
> +	 */
> +	ctx->retval = ctx->rdesc_size != 107;
> +	if (ctx->retval)
> +		ctx->retval = -EINVAL;
> +
> +	/* ensure the kernel isn't fixed already */
> +	if (ctx->rdesc[17] != 0x45) /* Eraser */
> +		ctx->retval = -EINVAL;
> +
> +	return 0;
> +}
> +
> +char _license[] SEC("license") = "GPL";
> diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h
> new file mode 100644
> index 000000000000..7ee371cac2e1
> --- /dev/null
> +++ b/drivers/hid/bpf/progs/hid_bpf.h
> @@ -0,0 +1,15 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (c) 2022 Benjamin Tissoires
> + */
> +
> +#ifndef ____HID_BPF__H
> +#define ____HID_BPF__H
> +
> +struct hid_bpf_probe_args {
> +	unsigned int hid;
> +	unsigned int rdesc_size;
> +	unsigned char rdesc[4096];
> +	int retval;
> +};
> +
> +#endif /* ____HID_BPF__H */
> diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
> new file mode 100644
> index 000000000000..1d53b10aaa2e
> --- /dev/null
> +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
> @@ -0,0 +1,170 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/* Copyright (c) 2022 Benjamin Tissoires
> + */
> +
> +#ifndef __HID_BPF_HELPERS_H
> +#define __HID_BPF_HELPERS_H
> +
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include <linux/errno.h>
> +
> +extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
> +			      unsigned int offset,
> +			      const size_t __sz) __ksym;
> +extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
> +extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
> +extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
> +			      __u8 *data,
> +			      size_t buf__sz,
> +			      enum hid_report_type type,
> +			      enum hid_class_request reqtype) __ksym;
> +
> +#define HID_MAX_DESCRIPTOR_SIZE	4096
> +#define HID_IGNORE_EVENT	-1
> +
> +/* extracted from <linux/input.h> */
> +#define BUS_ANY			0x00
> +#define BUS_PCI			0x01
> +#define BUS_ISAPNP		0x02
> +#define BUS_USB			0x03
> +#define BUS_HIL			0x04
> +#define BUS_BLUETOOTH		0x05
> +#define BUS_VIRTUAL		0x06
> +#define BUS_ISA			0x10
> +#define BUS_I8042		0x11
> +#define BUS_XTKBD		0x12
> +#define BUS_RS232		0x13
> +#define BUS_GAMEPORT		0x14
> +#define BUS_PARPORT		0x15
> +#define BUS_AMIGA		0x16
> +#define BUS_ADB			0x17
> +#define BUS_I2C			0x18
> +#define BUS_HOST		0x19
> +#define BUS_GSC			0x1A
> +#define BUS_ATARI		0x1B
> +#define BUS_SPI			0x1C
> +#define BUS_RMI			0x1D
> +#define BUS_CEC			0x1E
> +#define BUS_INTEL_ISHTP		0x1F
> +#define BUS_AMD_SFH		0x20
> +
> +/* extracted from <linux/hid.h> */
> +#define HID_GROUP_ANY				0x0000
> +#define HID_GROUP_GENERIC			0x0001
> +#define HID_GROUP_MULTITOUCH			0x0002
> +#define HID_GROUP_SENSOR_HUB			0x0003
> +#define HID_GROUP_MULTITOUCH_WIN_8		0x0004
> +#define HID_GROUP_RMI				0x0100
> +#define HID_GROUP_WACOM				0x0101
> +#define HID_GROUP_LOGITECH_DJ_DEVICE		0x0102
> +#define HID_GROUP_STEAM				0x0103
> +#define HID_GROUP_LOGITECH_27MHZ_DEVICE		0x0104
> +#define HID_GROUP_VIVALDI			0x0105
> +
> +/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
> +#define HID_VID_ANY				0x0000
> +#define HID_PID_ANY				0x0000
> +
> +/* duplicated from incluse/linux/array_size.h
> + */

FWIW, Peter mentioned on the matching MR on udev-hid-bpf that this
comment was likely superflous:
https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/66#note_2365932

Cheers,
Benjamin

> +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
> +
> +/* Helper macro to convert (foo, __LINE__)  into foo134 so we can use __LINE__ for
> + * field/variable names
> + */
> +#define COMBINE1(X, Y) X ## Y
> +#define COMBINE(X, Y) COMBINE1(X, Y)
> +
> +/* Macro magic:
> + * __uint(foo, 123) creates a int (*foo)[1234]
> + *
> + * We use that macro to declare an anonymous struct with several
> + * fields, each is the declaration of an pointer to an array of size
> + * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
> + * would be sizeof(pointer) rather than sizeof(array). Not that we ever
> + * instantiate it anyway).
> + *
> + * This is only used for BTF introspection, we can later check "what size
> + * is the bus array" in the introspection data and thus extract the bus ID
> + * again.
> + *
> + * And we use the __LINE__ to give each of our structs a unique name so the
> + * BPF program writer doesn't have to.
> + *
> + * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
> + * shows the inspection data, start by searching for .hid_bpf_config
> + * and working backwards from that (each entry references the type_id of the
> + * content).
> + */
> +
> +#define HID_DEVICE(b, g, ven, prod)	\
> +	struct {			\
> +		__uint(name, 0);	\
> +		__uint(bus, (b));	\
> +		__uint(group, (g));	\
> +		__uint(vid, (ven));	\
> +		__uint(pid, (prod));	\
> +	} COMBINE(_entry, __LINE__)
> +
> +/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
> + * we can pass multiple HID_DEVICE() invocations in.
> + *
> + * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
> + *
> + * union {
> + *    HID_DEVICE(...);
> + *    HID_DEVICE(...);
> + * } _device_ids SEC(".hid_bpf_config")
> + *
> + */
> +
> +/* Returns the number of macro arguments, this expands
> + * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
> + * NTH_ARG always returns the 16th argument which in our case is 3.
> + *
> + * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
> + * updated.
> + */
> +#define _NARGS(...)  _NARGS1(__VA_ARGS__, _COUNTDOWN)
> +#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
> +
> +/* Add to this if we need more than 16 args */
> +#define _COUNTDOWN \
> +	15, 14, 13, 12, 11, 10, 9, 8,  \
> +	 7,  6,  5,  4,  3,  2, 1, 0
> +
> +/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
> + * 1-indexed.
> + */
> +#define _NTH_ARG( \
> +	_1,  _2,  _3,  _4,  _5,  _6,  _7, _8, \
> +	_9, _10, _11, _12, _13, _14, _15,\
> +	 N, ...) N
> +
> +/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
> +#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
> +
> +/* And now define all the ARG macros for each number of args we want to accept */
> +#define _ARG1(_1)                                                         _1;
> +#define _ARG2(_1, _2)                                                     _1; _2;
> +#define _ARG3(_1, _2, _3)                                                 _1; _2; _3;
> +#define _ARG4(_1, _2, _3, _4)                                             _1; _2; _3; _4;
> +#define _ARG5(_1, _2, _3, _4, _5)                                         _1; _2; _3; _4; _5;
> +#define _ARG6(_1, _2, _3, _4, _5, _6)                                     _1; _2; _3; _4; _5; _6;
> +#define _ARG7(_1, _2, _3, _4, _5, _6, _7)                                 _1; _2; _3; _4; _5; _6; _7;
> +#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8)                             _1; _2; _3; _4; _5; _6; _7; _8;
> +#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9)                         _1; _2; _3; _4; _5; _6; _7; _8; _9;
> +#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a)                     _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
> +#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b)                 _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
> +#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c)             _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
> +#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d)         _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
> +#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e)     _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
> +#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
> +
> +
> +#define HID_BPF_CONFIG(...)  union { \
> +	_EXPAND(_ARG, __VA_ARGS__) \
> +} _device_ids SEC(".hid_bpf_config")
> +
> +#endif /* __HID_BPF_HELPERS_H */
> 
> -- 
> 2.44.0
>
diff mbox series

Patch

diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile
new file mode 100644
index 000000000000..63ed7e02adf1
--- /dev/null
+++ b/drivers/hid/bpf/progs/Makefile
@@ -0,0 +1,91 @@ 
+# SPDX-License-Identifier: GPL-2.0
+OUTPUT := .output
+abs_out := $(abspath $(OUTPUT))
+
+CLANG ?= clang
+LLC ?= llc
+LLVM_STRIP ?= llvm-strip
+
+TOOLS_PATH := $(abspath ../../../../tools)
+BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
+BPFTOOL_OUTPUT := $(abs_out)/bpftool
+DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
+BPFTOOL ?= $(DEFAULT_BPFTOOL)
+
+LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
+LIBBPF_OUTPUT := $(abs_out)/libbpf
+LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
+LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
+BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
+
+INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
+CFLAGS := -g -Wall
+
+VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux)				\
+		     $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)	\
+		     ../../../../vmlinux				\
+		     /sys/kernel/btf/vmlinux				\
+		     /boot/vmlinux-$(shell uname -r)
+VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
+ifeq ($(VMLINUX_BTF),)
+$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
+endif
+
+ifeq ($(V),1)
+Q =
+msg =
+else
+Q = @
+msg = @printf '  %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
+MAKEFLAGS += --no-print-directory
+submake_extras := feature_display=0
+endif
+
+.DELETE_ON_ERROR:
+
+.PHONY: all clean
+
+SOURCES = $(wildcard *.bpf.c)
+TARGETS = $(SOURCES:.bpf.c=.bpf.o)
+
+all: $(TARGETS)
+
+clean:
+	$(call msg,CLEAN)
+	$(Q)rm -rf $(OUTPUT) $(TARGETS)
+
+%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
+	$(call msg,BPF,$@)
+	$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES)			      \
+		 -c $(filter %.c,$^) -o $@ &&				      \
+	$(LLVM_STRIP) -g $@
+
+vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
+ifeq ($(VMLINUX_H),)
+	$(call msg,GEN,,$@)
+	$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
+else
+	$(call msg,CP,,$@)
+	$(Q)cp "$(VMLINUX_H)" $@
+endif
+
+$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
+	$(call msg,MKDIR,$@)
+	$(Q)mkdir -p $@
+
+$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
+	$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC)			       \
+		    OUTPUT=$(abspath $(dir $@))/ prefix=		       \
+		    DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
+
+ifeq ($(CROSS_COMPILE),)
+$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
+	$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC)			       \
+		    OUTPUT=$(BPFTOOL_OUTPUT)/				       \
+		    LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/		       \
+		    LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
+else
+$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
+	$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC)			       \
+		    OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
+endif
diff --git a/drivers/hid/bpf/progs/README b/drivers/hid/bpf/progs/README
new file mode 100644
index 000000000000..20b0928f385b
--- /dev/null
+++ b/drivers/hid/bpf/progs/README
@@ -0,0 +1,102 @@ 
+# HID-BPF programs
+
+This directory contains various fixes for devices. They add new features or
+fix some behaviors without being entirely mandatory. It is better to load them
+when you have such a device, but they should not be a requirement for a device
+to be working during the boot stage.
+
+The .bpf.c files provided here are not automatically compiled in the kernel.
+They should be loaded in the kernel by `udev-hid-bpf`:
+
+https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
+
+The main reasons for these fixes to be here is to have a central place to
+"upstream" them, but also this way we can test them thanks to the HID
+selftests.
+
+Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
+in the `src/bpf/stable` directory, and distributions are encouraged to
+only ship those bpf objects. So adding a file here should eventually
+land in distributions when they update `udev-hid-bpf`
+
+## Compilation
+
+Just run `make`
+
+## Installation
+
+### Automated way
+
+Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
+
+### Manual way
+
+- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
+- create a new udev rule to automatically load it
+
+The following should do the trick (assuming udev-hid-bpf is available in
+/usr/bin):
+
+```
+$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
+$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
+[
+  {
+    "name": "xppen-ArtistPro16Gen2.bpf.o",
+    "devices": [
+      {
+        "bus": "0x0003",
+        "group": "0x0001",
+        "vid": "0x28BD",
+        "pid": "0x095A"
+      },
+      {
+        "bus": "0x0003",
+        "group": "0x0001",
+        "vid": "0x28BD",
+        "pid": "0x095B"
+      }
+    ],
+...
+$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
+ACTION!="add|remove", GOTO="hid_bpf_end"
+SUBSYSTEM!="hid", GOTO="hid_bpf_end"
+
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+# xppen-ArtistPro16Gen2.bpf.o
+ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
+ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
+
+LABEL="hid_bpf_end"
+EOF
+$> udevadm control --reload
+```
+
+Then unplug and replug the device.
+
+## Checks
+
+### udev rule
+
+You can check that the udev rule is correctly working by issuing
+
+```
+$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
+...
+run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
+```
+
+### program loaded
+
+You can check that the program has been properly loaded with `bpftool`
+
+```
+$> bpftool prog
+...
+247: tracing  name xppen_16_fix_eraser tag 18d389353ed2ef07  gpl
+	loaded_at 2024-03-28T16:02:28+0100  uid 0
+	xlated 120B  jited 77B  memlock 4096B
+	btf_id 487
+```
diff --git a/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
new file mode 100644
index 000000000000..e1be6a12bb75
--- /dev/null
+++ b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c
@@ -0,0 +1,229 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2023 Benjamin Tissoires
+ */
+
+#include "vmlinux.h"
+#include "hid_bpf.h"
+#include "hid_bpf_helpers.h"
+#include <bpf/bpf_tracing.h>
+
+#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
+#define PID_ARTIST_24 0x093A
+#define PID_ARTIST_24_PRO 0x092D
+
+HID_BPF_CONFIG(
+	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
+	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
+);
+
+/*
+ * We need to amend the report descriptor for the following:
+ * - the device reports Eraser instead of using Secondary Barrel Switch
+ * - the pen doesn't have a rubber tail, so basically we are removing any
+ *   eraser/invert bits
+ */
+static const __u8 fixed_rdesc[] = {
+	0x05, 0x0d,                    // Usage Page (Digitizers)             0
+	0x09, 0x02,                    // Usage (Pen)                         2
+	0xa1, 0x01,                    // Collection (Application)            4
+	0x85, 0x07,                    //  Report ID (7)                      6
+	0x09, 0x20,                    //  Usage (Stylus)                     8
+	0xa1, 0x00,                    //  Collection (Physical)              10
+	0x09, 0x42,                    //   Usage (Tip Switch)                12
+	0x09, 0x44,                    //   Usage (Barrel Switch)             14
+	0x09, 0x5a,                    //   Usage (Secondary Barrel Switch)   16  /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
+	0x15, 0x00,                    //   Logical Minimum (0)               18
+	0x25, 0x01,                    //   Logical Maximum (1)               20
+	0x75, 0x01,                    //   Report Size (1)                   22
+	0x95, 0x03,                    //   Report Count (3)                  24
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              26
+	0x95, 0x02,                    //   Report Count (2)                  28
+	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              30
+	0x09, 0x32,                    //   Usage (In Range)                  32
+	0x95, 0x01,                    //   Report Count (1)                  34
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              36
+	0x95, 0x02,                    //   Report Count (2)                  38
+	0x81, 0x03,                    //   Input (Cnst,Var,Abs)              40
+	0x75, 0x10,                    //   Report Size (16)                  42
+	0x95, 0x01,                    //   Report Count (1)                  44
+	0x35, 0x00,                    //   Physical Minimum (0)              46
+	0xa4,                          //   Push                              48
+	0x05, 0x01,                    //   Usage Page (Generic Desktop)      49
+	0x09, 0x30,                    //   Usage (X)                         51
+	0x65, 0x13,                    //   Unit (EnglishLinear: in)          53
+	0x55, 0x0d,                    //   Unit Exponent (-3)                55
+	0x46, 0xf0, 0x50,              //   Physical Maximum (20720)          57
+	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           60
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              63
+	0x09, 0x31,                    //   Usage (Y)                         65
+	0x46, 0x91, 0x2d,              //   Physical Maximum (11665)          67
+	0x26, 0xff, 0x7f,              //   Logical Maximum (32767)           70
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              73
+	0xb4,                          //   Pop                               75
+	0x09, 0x30,                    //   Usage (Tip Pressure)              76
+	0x45, 0x00,                    //   Physical Maximum (0)              78
+	0x26, 0xff, 0x1f,              //   Logical Maximum (8191)            80
+	0x81, 0x42,                    //   Input (Data,Var,Abs,Null)         83
+	0x09, 0x3d,                    //   Usage (X Tilt)                    85
+	0x15, 0x81,                    //   Logical Minimum (-127)            87
+	0x25, 0x7f,                    //   Logical Maximum (127)             89
+	0x75, 0x08,                    //   Report Size (8)                   91
+	0x95, 0x01,                    //   Report Count (1)                  93
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              95
+	0x09, 0x3e,                    //   Usage (Y Tilt)                    97
+	0x15, 0x81,                    //   Logical Minimum (-127)            99
+	0x25, 0x7f,                    //   Logical Maximum (127)             101
+	0x81, 0x02,                    //   Input (Data,Var,Abs)              103
+	0xc0,                          //  End Collection                     105
+	0xc0,                          // End Collection                      106
+};
+
+#define BIT(n) (1UL << n)
+
+#define TIP_SWITCH		BIT(0)
+#define BARREL_SWITCH		BIT(1)
+#define ERASER			BIT(2)
+/* padding			BIT(3) */
+/* padding			BIT(4) */
+#define IN_RANGE		BIT(5)
+/* padding			BIT(6) */
+/* padding			BIT(7) */
+
+#define U16(index) (data[index] | (data[index + 1] << 8))
+
+SEC("fmod_ret/hid_bpf_rdesc_fixup")
+int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
+{
+	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
+
+	if (!data)
+		return 0; /* EPERM check */
+
+	__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
+
+	return sizeof(fixed_rdesc);
+}
+
+static __u8 prev_state = 0;
+
+/*
+ * There are a few cases where the device is sending wrong event
+ * sequences, all related to the second button (the pen doesn't
+ * have an eraser switch on the tail end):
+ *
+ *   whenever the second button gets pressed or released, an
+ *   out-of-proximity event is generated and then the firmware
+ *   compensate for the missing state (and the firmware uses
+ *   eraser for that button):
+ *
+ *   - if the pen is in range, an extra out-of-range is sent
+ *     when the second button is pressed/released:
+ *     // Pen is in range
+ *     E:                               InRange
+ *
+ *     // Second button is pressed
+ *     E:
+ *     E:                        Eraser InRange
+ *
+ *     // Second button is released
+ *     E:
+ *     E:                               InRange
+ *
+ *     This case is ignored by this filter, it's "valid"
+ *     and userspace knows how to deal with it, there are just
+ *     a few out-of-prox events generated, but the user doesn´t
+ *     see them.
+ *
+ *   - if the pen is in contact, 2 extra events are added when
+ *     the second button is pressed/released: an out of range
+ *     and an in range:
+ *
+ *     // Pen is in contact
+ *     E: TipSwitch                     InRange
+ *
+ *     // Second button is pressed
+ *     E:                                         <- false release, needs to be filtered out
+ *     E:                        Eraser InRange   <- false release, needs to be filtered out
+ *     E: TipSwitch              Eraser InRange
+ *
+ *     // Second button is released
+ *     E:                                         <- false release, needs to be filtered out
+ *     E:                               InRange   <- false release, needs to be filtered out
+ *     E: TipSwitch                     InRange
+ *
+ */
+SEC("fmod_ret/hid_bpf_device_event")
+int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
+{
+	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
+	__u8 current_state, changed_state;
+	bool prev_tip;
+	__u16 tilt;
+
+	if (!data)
+		return 0; /* EPERM check */
+
+	current_state = data[1];
+
+	/* if the state is identical to previously, early return */
+	if (current_state == prev_state)
+		return 0;
+
+	prev_tip = !!(prev_state & TIP_SWITCH);
+
+	/*
+	 * Illegal transition: pen is in range with the tip pressed, and
+	 * it goes into out of proximity.
+	 *
+	 * Ideally we should hold the event, start a timer and deliver it
+	 * only if the timer ends, but we are not capable of that now.
+	 *
+	 * And it doesn't matter because when we are in such cases, this
+	 * means we are detecting a false release.
+	 */
+	if ((current_state & IN_RANGE) == 0) {
+		if (prev_tip)
+			return HID_IGNORE_EVENT;
+		return 0;
+	}
+
+	/*
+	 * XOR to only set the bits that have changed between
+	 * previous and current state
+	 */
+	changed_state = prev_state ^ current_state;
+
+	/* Store the new state for future processing */
+	prev_state = current_state;
+
+	/*
+	 * We get both a tipswitch and eraser change in the same HID report:
+	 * this is not an authorized transition and is unlikely to happen
+	 * in real life.
+	 * This is likely to be added by the firmware to emulate the
+	 * eraser mode so we can skip the event.
+	 */
+	if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
+		return HID_IGNORE_EVENT;
+
+	return 0;
+}
+
+SEC("syscall")
+int probe(struct hid_bpf_probe_args *ctx)
+{
+	/*
+	 * The device exports 3 interfaces.
+	 */
+	ctx->retval = ctx->rdesc_size != 107;
+	if (ctx->retval)
+		ctx->retval = -EINVAL;
+
+	/* ensure the kernel isn't fixed already */
+	if (ctx->rdesc[17] != 0x45) /* Eraser */
+		ctx->retval = -EINVAL;
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h
new file mode 100644
index 000000000000..7ee371cac2e1
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf.h
@@ -0,0 +1,15 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef ____HID_BPF__H
+#define ____HID_BPF__H
+
+struct hid_bpf_probe_args {
+	unsigned int hid;
+	unsigned int rdesc_size;
+	unsigned char rdesc[4096];
+	int retval;
+};
+
+#endif /* ____HID_BPF__H */
diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h
new file mode 100644
index 000000000000..1d53b10aaa2e
--- /dev/null
+++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h
@@ -0,0 +1,170 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#ifndef __HID_BPF_HELPERS_H
+#define __HID_BPF_HELPERS_H
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <linux/errno.h>
+
+extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
+			      unsigned int offset,
+			      const size_t __sz) __ksym;
+extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
+extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
+extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
+			      __u8 *data,
+			      size_t buf__sz,
+			      enum hid_report_type type,
+			      enum hid_class_request reqtype) __ksym;
+
+#define HID_MAX_DESCRIPTOR_SIZE	4096
+#define HID_IGNORE_EVENT	-1
+
+/* extracted from <linux/input.h> */
+#define BUS_ANY			0x00
+#define BUS_PCI			0x01
+#define BUS_ISAPNP		0x02
+#define BUS_USB			0x03
+#define BUS_HIL			0x04
+#define BUS_BLUETOOTH		0x05
+#define BUS_VIRTUAL		0x06
+#define BUS_ISA			0x10
+#define BUS_I8042		0x11
+#define BUS_XTKBD		0x12
+#define BUS_RS232		0x13
+#define BUS_GAMEPORT		0x14
+#define BUS_PARPORT		0x15
+#define BUS_AMIGA		0x16
+#define BUS_ADB			0x17
+#define BUS_I2C			0x18
+#define BUS_HOST		0x19
+#define BUS_GSC			0x1A
+#define BUS_ATARI		0x1B
+#define BUS_SPI			0x1C
+#define BUS_RMI			0x1D
+#define BUS_CEC			0x1E
+#define BUS_INTEL_ISHTP		0x1F
+#define BUS_AMD_SFH		0x20
+
+/* extracted from <linux/hid.h> */
+#define HID_GROUP_ANY				0x0000
+#define HID_GROUP_GENERIC			0x0001
+#define HID_GROUP_MULTITOUCH			0x0002
+#define HID_GROUP_SENSOR_HUB			0x0003
+#define HID_GROUP_MULTITOUCH_WIN_8		0x0004
+#define HID_GROUP_RMI				0x0100
+#define HID_GROUP_WACOM				0x0101
+#define HID_GROUP_LOGITECH_DJ_DEVICE		0x0102
+#define HID_GROUP_STEAM				0x0103
+#define HID_GROUP_LOGITECH_27MHZ_DEVICE		0x0104
+#define HID_GROUP_VIVALDI			0x0105
+
+/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
+#define HID_VID_ANY				0x0000
+#define HID_PID_ANY				0x0000
+
+/* duplicated from incluse/linux/array_size.h
+ */
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* Helper macro to convert (foo, __LINE__)  into foo134 so we can use __LINE__ for
+ * field/variable names
+ */
+#define COMBINE1(X, Y) X ## Y
+#define COMBINE(X, Y) COMBINE1(X, Y)
+
+/* Macro magic:
+ * __uint(foo, 123) creates a int (*foo)[1234]
+ *
+ * We use that macro to declare an anonymous struct with several
+ * fields, each is the declaration of an pointer to an array of size
+ * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
+ * would be sizeof(pointer) rather than sizeof(array). Not that we ever
+ * instantiate it anyway).
+ *
+ * This is only used for BTF introspection, we can later check "what size
+ * is the bus array" in the introspection data and thus extract the bus ID
+ * again.
+ *
+ * And we use the __LINE__ to give each of our structs a unique name so the
+ * BPF program writer doesn't have to.
+ *
+ * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
+ * shows the inspection data, start by searching for .hid_bpf_config
+ * and working backwards from that (each entry references the type_id of the
+ * content).
+ */
+
+#define HID_DEVICE(b, g, ven, prod)	\
+	struct {			\
+		__uint(name, 0);	\
+		__uint(bus, (b));	\
+		__uint(group, (g));	\
+		__uint(vid, (ven));	\
+		__uint(pid, (prod));	\
+	} COMBINE(_entry, __LINE__)
+
+/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
+ * we can pass multiple HID_DEVICE() invocations in.
+ *
+ * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
+ *
+ * union {
+ *    HID_DEVICE(...);
+ *    HID_DEVICE(...);
+ * } _device_ids SEC(".hid_bpf_config")
+ *
+ */
+
+/* Returns the number of macro arguments, this expands
+ * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
+ * NTH_ARG always returns the 16th argument which in our case is 3.
+ *
+ * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
+ * updated.
+ */
+#define _NARGS(...)  _NARGS1(__VA_ARGS__, _COUNTDOWN)
+#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
+
+/* Add to this if we need more than 16 args */
+#define _COUNTDOWN \
+	15, 14, 13, 12, 11, 10, 9, 8,  \
+	 7,  6,  5,  4,  3,  2, 1, 0
+
+/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
+ * 1-indexed.
+ */
+#define _NTH_ARG( \
+	_1,  _2,  _3,  _4,  _5,  _6,  _7, _8, \
+	_9, _10, _11, _12, _13, _14, _15,\
+	 N, ...) N
+
+/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
+#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
+
+/* And now define all the ARG macros for each number of args we want to accept */
+#define _ARG1(_1)                                                         _1;
+#define _ARG2(_1, _2)                                                     _1; _2;
+#define _ARG3(_1, _2, _3)                                                 _1; _2; _3;
+#define _ARG4(_1, _2, _3, _4)                                             _1; _2; _3; _4;
+#define _ARG5(_1, _2, _3, _4, _5)                                         _1; _2; _3; _4; _5;
+#define _ARG6(_1, _2, _3, _4, _5, _6)                                     _1; _2; _3; _4; _5; _6;
+#define _ARG7(_1, _2, _3, _4, _5, _6, _7)                                 _1; _2; _3; _4; _5; _6; _7;
+#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8)                             _1; _2; _3; _4; _5; _6; _7; _8;
+#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9)                         _1; _2; _3; _4; _5; _6; _7; _8; _9;
+#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a)                     _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
+#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b)                 _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
+#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c)             _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
+#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d)         _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
+#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e)     _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
+#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
+
+
+#define HID_BPF_CONFIG(...)  union { \
+	_EXPAND(_ARG, __VA_ARGS__) \
+} _device_ids SEC(".hid_bpf_config")
+
+#endif /* __HID_BPF_HELPERS_H */