HID: bpf: add in-tree HID-BPF fix for the Wacom ArtPen
[linux-block.git] / drivers / hid / bpf / progs / Wacom__ArtPen.bpf.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 Benjamin Tissoires
3  */
4
5 #include "vmlinux.h"
6 #include "hid_bpf.h"
7 #include "hid_bpf_helpers.h"
8 #include <bpf/bpf_tracing.h>
9
10 #define VID_WACOM               0x056a
11 #define ART_PEN_ID              0x0804
12 #define PID_INTUOS_PRO_2_M      0x0357
13
14 HID_BPF_CONFIG(
15         HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
16 );
17
18 /*
19  * This filter is here for the Art Pen stylus only:
20  * - when used on some Wacom devices (see the list of attached PIDs), this pen
21  *   reports pressure every other events.
22  * - to solve that, given that we know that the next event will be the same as
23  *   the current one, we can emulate a smoother pressure reporting by reporting
24  *   the mean of the previous value and the current one.
25  *
26  * We are effectively delaying the pressure by one event every other event, but
27  * that's less of an annoyance compared to the chunkiness of the reported data.
28  *
29  * For example, let's assume the following set of events:
30  * <Tip switch 0> <X 0> <Y 0> <Pressure    0 > <Tooltype 0x0804>
31  * <Tip switch 1> <X 1> <Y 1> <Pressure  100 > <Tooltype 0x0804>
32  * <Tip switch 1> <X 2> <Y 2> <Pressure  100 > <Tooltype 0x0804>
33  * <Tip switch 1> <X 3> <Y 3> <Pressure  200 > <Tooltype 0x0804>
34  * <Tip switch 1> <X 4> <Y 4> <Pressure  200 > <Tooltype 0x0804>
35  * <Tip switch 0> <X 5> <Y 5> <Pressure    0 > <Tooltype 0x0804>
36  *
37  * The filter will report:
38  * <Tip switch 0> <X 0> <Y 0> <Pressure    0 > <Tooltype 0x0804>
39  * <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
40  * <Tip switch 1> <X 2> <Y 2> <Pressure  100 > <Tooltype 0x0804>
41  * <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
42  * <Tip switch 1> <X 4> <Y 4> <Pressure  200 > <Tooltype 0x0804>
43  * <Tip switch 0> <X 5> <Y 5> <Pressure    0 > <Tooltype 0x0804>
44  *
45  */
46
47 struct wacom_params {
48         __u16 pid;
49         __u16 rdesc_len;
50         __u8 report_id;
51         __u8 report_len;
52         struct {
53                 __u8 tip_switch;
54                 __u8 pressure;
55                 __u8 tool_type;
56         } offsets;
57 };
58
59 /*
60  * Multiple device can support the same stylus, so
61  * we need to know which device has which offsets
62  */
63 static const struct wacom_params devices[] = {
64         {
65                 .pid = PID_INTUOS_PRO_2_M,
66                 .rdesc_len = 949,
67                 .report_id = 16,
68                 .report_len = 27,
69                 .offsets = {
70                         .tip_switch = 1,
71                         .pressure = 8,
72                         .tool_type = 25,
73                 },
74         },
75 };
76
77 static struct wacom_params params = { 0 };
78
79 /* HID-BPF reports a 64 bytes chunk anyway, so this ensures
80  * the verifier to know we are addressing the memory correctly
81  */
82 #define PEN_REPORT_LEN          64
83
84 /* only odd frames are modified */
85 static bool odd;
86
87 static __u16 prev_pressure;
88
89 static inline void *get_bits(__u8 *data, unsigned int byte_offset)
90 {
91         return data + byte_offset;
92 }
93
94 static inline __u16 *get_u16(__u8 *data, unsigned int offset)
95 {
96         return (__u16 *)get_bits(data, offset);
97 }
98
99 static inline __u8 *get_u8(__u8 *data, unsigned int offset)
100 {
101         return (__u8 *)get_bits(data, offset);
102 }
103
104 SEC("fmod_ret/hid_bpf_device_event")
105 int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
106 {
107         __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
108         __u16 *pressure, *tool_type;
109         __u8 *tip_switch;
110
111         if (!data)
112                 return 0; /* EPERM check */
113
114         if (data[0] != params.report_id ||
115             params.offsets.tip_switch >= PEN_REPORT_LEN ||
116             params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
117             params.offsets.tool_type >= PEN_REPORT_LEN - 1)
118                 return 0; /* invalid report or parameters */
119
120         tool_type = get_u16(data, params.offsets.tool_type);
121         if (*tool_type != ART_PEN_ID)
122                 return 0;
123
124         tip_switch = get_u8(data, params.offsets.tip_switch);
125         if ((*tip_switch & 0x01) == 0) {
126                 prev_pressure = 0;
127                 odd = true;
128                 return 0;
129         }
130
131         pressure = get_u16(data, params.offsets.pressure);
132
133         if (odd)
134                 *pressure = (*pressure + prev_pressure) / 2;
135
136         prev_pressure = *pressure;
137         odd = !odd;
138
139         return 0;
140 }
141
142 SEC("syscall")
143 int probe(struct hid_bpf_probe_args *ctx)
144 {
145         struct hid_bpf_ctx *hid_ctx;
146         __u16 pid;
147         int i;
148
149         /* get a struct hid_device to access the actual pid of the device */
150         hid_ctx = hid_bpf_allocate_context(ctx->hid);
151         if (!hid_ctx) {
152                 ctx->retval = -ENODEV;
153                 return -1; /* EPERM check */
154         }
155         pid = hid_ctx->hid->product;
156
157         ctx->retval = -EINVAL;
158
159         /* Match the given device with the list of known devices */
160         for (i = 0; i < ARRAY_SIZE(devices); i++) {
161                 const struct wacom_params *device = &devices[i];
162
163                 if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
164                         params = *device;
165                         ctx->retval = 0;
166                 }
167         }
168
169         hid_bpf_release_context(hid_ctx);
170         return 0;
171 }
172
173 char _license[] SEC("license") = "GPL";