Skip to content

Commit 040adbe

Browse files
Benjamin TissoiresJiri Kosina
authored andcommitted
HID: bpf: Add support for the Waltop Batteryless Tablet
Several bugs as outlined in udev-hid-bpf#66 and udev-hid-bpf!198: - pressure curve is far from linear - tilt range is ±60, not ±127 - pressing the second button sets both tip down and barrel switch Fix the second button by adding a Secondary Barrel Switch in the existing padding and check for the tip down/barrel switch down combo. When both values become true at the same time, set the Secondary Barrel Switch instead. Implement a custom pressure curve that maps the hardware range 0-102 linearly to the logical range 0-1224, and maps the hardware range 103-2047 logarithmically to the logical range 1232-2047. This mapping isn’t perfect, but it’s way more natural than the stock configuration. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net> Signed-off-by: Jan Felix Langenbach <JanFelix.Langenbach@protonmail.com> Link: https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/merge_requests/200 Signed-off-by: Benjamin Tissoires <bentiss@kernel.org> Signed-off-by: Jiri Kosina <jkosina@suse.com>
1 parent 029dff1 commit 040adbe

1 file changed

Lines changed: 321 additions & 0 deletions

File tree

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/* Copyright (c) 2025 Red Hat
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_WALTOP 0x172F
11+
#define PID_BATTERYLESS_TABLET 0x0505
12+
13+
HID_BPF_CONFIG(
14+
HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_WALTOP, PID_BATTERYLESS_TABLET)
15+
);
16+
17+
#define EXPECTED_RDESC_SIZE 335
18+
#define PEN_REPORT_ID 16
19+
20+
#define TIP_SWITCH BIT(0)
21+
#define BARREL_SWITCH BIT(1)
22+
#define SECONDARY_BARREL_SWITCH BIT(5)
23+
24+
static __u8 last_button_state;
25+
26+
static const __u8 fixed_rdesc[] = {
27+
0x05, 0x01, // Usage Page (Generic Desktop)
28+
0x09, 0x02, // Usage (Mouse)
29+
0xa1, 0x01, // Collection (Application)
30+
0x85, 0x01, // Report ID (1)
31+
0x09, 0x01, // Usage (Pointer)
32+
0xa1, 0x00, // Collection (Physical)
33+
0x05, 0x09, // Usage Page (Button)
34+
0x19, 0x01, // Usage Minimum (1)
35+
0x29, 0x05, // Usage Maximum (5)
36+
0x15, 0x00, // Logical Minimum (0)
37+
0x25, 0x01, // Logical Maximum (1)
38+
0x75, 0x01, // Report Size (1)
39+
0x95, 0x05, // Report Count (5)
40+
0x81, 0x02, // Input (Data,Var,Abs)
41+
0x75, 0x03, // Report Size (3)
42+
0x95, 0x01, // Report Count (1)
43+
0x81, 0x03, // Input (Cnst,Var,Abs)
44+
0x05, 0x01, // Usage Page (Generic Desktop)
45+
0x09, 0x30, // Usage (X)
46+
0x09, 0x31, // Usage (Y)
47+
0x09, 0x38, // Usage (Wheel)
48+
0x15, 0x81, // Logical Minimum (-127)
49+
0x25, 0x7f, // Logical Maximum (127)
50+
0x75, 0x08, // Report Size (8)
51+
0x95, 0x03, // Report Count (3)
52+
0x81, 0x06, // Input (Data,Var,Rel)
53+
0x05, 0x0c, // Usage Page (Consumer)
54+
0x15, 0x81, // Logical Minimum (-127)
55+
0x25, 0x7f, // Logical Maximum (127)
56+
0x75, 0x08, // Report Size (8)
57+
0x95, 0x01, // Report Count (1)
58+
0x0a, 0x38, 0x02, // Usage (AC Pan)
59+
0x81, 0x06, // Input (Data,Var,Rel)
60+
0xc0, // End Collection
61+
0xc0, // End Collection
62+
0x05, 0x0d, // Usage Page (Digitizers)
63+
0x09, 0x02, // Usage (Pen)
64+
0xa1, 0x01, // Collection (Application)
65+
0x85, 0x02, // Report ID (2)
66+
0x09, 0x20, // Usage (Stylus)
67+
0xa1, 0x00, // Collection (Physical)
68+
0x09, 0x00, // Usage (0x0000)
69+
0x15, 0x00, // Logical Minimum (0)
70+
0x26, 0xff, 0x00, // Logical Maximum (255)
71+
0x75, 0x08, // Report Size (8)
72+
0x95, 0x09, // Report Count (9)
73+
0x81, 0x02, // Input (Data,Var,Abs)
74+
0x09, 0x3f, // Usage (Azimuth)
75+
0x09, 0x40, // Usage (Altitude)
76+
0x15, 0x00, // Logical Minimum (0)
77+
0x26, 0xff, 0x00, // Logical Maximum (255)
78+
0x75, 0x08, // Report Size (8)
79+
0x95, 0x02, // Report Count (2)
80+
0xb1, 0x02, // Feature (Data,Var,Abs)
81+
0xc0, // End Collection
82+
0x85, 0x05, // Report ID (5)
83+
0x05, 0x0d, // Usage Page (Digitizers)
84+
0x09, 0x20, // Usage (Stylus)
85+
0xa1, 0x00, // Collection (Physical)
86+
0x09, 0x00, // Usage (0x0000)
87+
0x15, 0x00, // Logical Minimum (0)
88+
0x26, 0xff, 0x00, // Logical Maximum (255)
89+
0x75, 0x08, // Report Size (8)
90+
0x95, 0x07, // Report Count (7)
91+
0x81, 0x02, // Input (Data,Var,Abs)
92+
0xc0, // End Collection
93+
0x85, 0x0a, // Report ID (10)
94+
0x05, 0x0d, // Usage Page (Digitizers)
95+
0x09, 0x20, // Usage (Stylus)
96+
0xa1, 0x00, // Collection (Physical)
97+
0x09, 0x00, // Usage (0x0000)
98+
0x15, 0x00, // Logical Minimum (0)
99+
0x26, 0xff, 0x00, // Logical Maximum (255)
100+
0x75, 0x08, // Report Size (8)
101+
0x95, 0x07, // Report Count (7)
102+
0x81, 0x02, // Input (Data,Var,Abs)
103+
0xc0, // End Collection
104+
0x85, 0x10, // Report ID (16)
105+
0x09, 0x20, // Usage (Stylus)
106+
0xa1, 0x00, // Collection (Physical)
107+
0x09, 0x42, // Usage (Tip Switch)
108+
0x09, 0x44, // Usage (Barrel Switch)
109+
0x09, 0x3c, // Usage (Invert)
110+
0x09, 0x45, // Usage (Eraser)
111+
0x09, 0x32, // Usage (In Range)
112+
0x09, 0x5a, // Usage (Secondary Barrel Switch) <-- added
113+
0x15, 0x00, // Logical Minimum (0)
114+
0x25, 0x01, // Logical Maximum (1)
115+
0x75, 0x01, // Report Size (1)
116+
0x95, 0x06, // Report Count (6) <--- changed from 5
117+
0x81, 0x02, // Input (Data,Var,Abs)
118+
0x95, 0x02, // Report Count (2) <--- changed from 3
119+
0x81, 0x03, // Input (Cnst,Var,Abs)
120+
0x05, 0x01, // Usage Page (Generic Desktop)
121+
0x09, 0x30, // Usage (X)
122+
0x75, 0x10, // Report Size (16)
123+
0x95, 0x01, // Report Count (1)
124+
0xa4, // Push
125+
0x55, 0x0d, // Unit Exponent (-3)
126+
0x65, 0x33, // Unit (EnglishLinear: in³)
127+
0x15, 0x00, // Logical Minimum (0)
128+
0x26, 0x00, 0x7d, // Logical Maximum (32000)
129+
0x35, 0x00, // Physical Minimum (0)
130+
0x46, 0x00, 0x7d, // Physical Maximum (32000)
131+
0x81, 0x02, // Input (Data,Var,Abs)
132+
0x09, 0x31, // Usage (Y)
133+
0x15, 0x00, // Logical Minimum (0)
134+
0x26, 0x20, 0x4e, // Logical Maximum (20000)
135+
0x35, 0x00, // Physical Minimum (0)
136+
0x46, 0x20, 0x4e, // Physical Maximum (20000)
137+
0x81, 0x02, // Input (Data,Var,Abs)
138+
0x05, 0x0d, // Usage Page (Digitizers)
139+
0x09, 0x30, // Usage (Tip Pressure)
140+
0x15, 0x00, // Logical Minimum (0)
141+
0x26, 0xff, 0x07, // Logical Maximum (2047)
142+
0x35, 0x00, // Physical Minimum (0)
143+
0x46, 0xff, 0x07, // Physical Maximum (2047)
144+
0x81, 0x02, // Input (Data,Var,Abs)
145+
0x05, 0x0d, // Usage Page (Digitizers)
146+
0x09, 0x3d, // Usage (X Tilt)
147+
0x09, 0x3e, // Usage (Y Tilt)
148+
0x15, 0xc4, // Logical Minimum (-60) <- changed from -127
149+
0x25, 0x3c, // Logical Maximum (60) <- changed from 127
150+
0x75, 0x08, // Report Size (8)
151+
0x95, 0x02, // Report Count (2)
152+
0x81, 0x02, // Input (Data,Var,Abs)
153+
0xc0, // End Collection
154+
0xc0, // End Collection
155+
0x05, 0x01, // Usage Page (Generic Desktop)
156+
0x09, 0x06, // Usage (Keyboard)
157+
0xa1, 0x01, // Collection (Application)
158+
0x85, 0x0d, // Report ID (13)
159+
0x05, 0x07, // Usage Page (Keyboard/Keypad)
160+
0x19, 0xe0, // Usage Minimum (224)
161+
0x29, 0xe7, // Usage Maximum (231)
162+
0x15, 0x00, // Logical Minimum (0)
163+
0x25, 0x01, // Logical Maximum (1)
164+
0x75, 0x01, // Report Size (1)
165+
0x95, 0x08, // Report Count (8)
166+
0x81, 0x02, // Input (Data,Var,Abs)
167+
0x75, 0x08, // Report Size (8)
168+
0x95, 0x01, // Report Count (1)
169+
0x81, 0x01, // Input (Cnst,Arr,Abs)
170+
0x05, 0x07, // Usage Page (Keyboard/Keypad)
171+
0x19, 0x00, // Usage Minimum (0)
172+
0x29, 0x65, // Usage Maximum (101)
173+
0x15, 0x00, // Logical Minimum (0)
174+
0x25, 0x65, // Logical Maximum (101)
175+
0x75, 0x08, // Report Size (8)
176+
0x95, 0x05, // Report Count (5)
177+
0x81, 0x00, // Input (Data,Arr,Abs)
178+
0xc0, // End Collection
179+
0x05, 0x0c, // Usage Page (Consumer)
180+
0x09, 0x01, // Usage (Consumer Control)
181+
0xa1, 0x01, // Collection (Application)
182+
0x85, 0x0c, // Report ID (12)
183+
0x09, 0xe9, // Usage (Volume Increment)
184+
0x09, 0xea, // Usage (Volume Decrement)
185+
0x09, 0xe2, // Usage (Mute)
186+
0x15, 0x00, // Logical Minimum (0)
187+
0x25, 0x01, // Logical Maximum (1)
188+
0x75, 0x01, // Report Size (1)
189+
0x95, 0x03, // Report Count (3)
190+
0x81, 0x06, // Input (Data,Var,Rel)
191+
0x75, 0x05, // Report Size (5)
192+
0x95, 0x01, // Report Count (1)
193+
0x81, 0x07, // Input (Cnst,Var,Rel)
194+
0xc0, // End Collection
195+
};
196+
197+
static inline unsigned int bitwidth32(__u32 x)
198+
{
199+
return 32 - __builtin_clzg(x, 32);
200+
}
201+
202+
static inline unsigned int floor_log2_32(__u32 x)
203+
{
204+
return bitwidth32(x) - 1;
205+
}
206+
207+
/* Maps the interval [0, 2047] to itself using a scaled
208+
* approximation of the function log2(x+1).
209+
*/
210+
static unsigned int scaled_log2(__u16 v)
211+
{
212+
const unsigned int XMAX = 2047;
213+
const unsigned int YMAX = 11; /* log2(2048) = 11 */
214+
215+
unsigned int x = v + 1;
216+
unsigned int n = floor_log2_32(x);
217+
unsigned int b = 1 << n;
218+
219+
/* Fixed-point fraction in [0, 1), linearly
220+
* interpolated using delta-y = 1 and
221+
* delta-x = (2b - b) = b.
222+
*/
223+
unsigned int frac = (x - b) << YMAX;
224+
unsigned int lerp = frac / b;
225+
unsigned int log2 = (n << YMAX) + lerp;
226+
227+
return ((log2 * XMAX) / YMAX) >> YMAX;
228+
}
229+
230+
SEC(HID_BPF_RDESC_FIXUP)
231+
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
232+
{
233+
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
234+
235+
if (!data)
236+
return 0; /* EPERM check */
237+
238+
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
239+
240+
return sizeof(fixed_rdesc);
241+
}
242+
243+
SEC(HID_BPF_DEVICE_EVENT)
244+
int BPF_PROG(waltop_fix_events, struct hid_bpf_ctx *hctx)
245+
{
246+
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
247+
248+
if (!data)
249+
return 0; /* EPERM check */
250+
251+
__u8 report_id = data[0];
252+
253+
if (report_id != PEN_REPORT_ID)
254+
return 0;
255+
256+
/* On this tablet if the secondary barrel switch is pressed,
257+
* the tablet sends tip down and barrel down. Change this to
258+
* just secondary barrel down when there is no ambiguity.
259+
*
260+
* It's possible that there is a bug in the firmware and the
261+
* device intends to set invert + eraser instead (i.e. the
262+
* pysical button is an eraser button) but since
263+
* the pressure is always zero, said eraser button
264+
* would be useless anyway.
265+
*
266+
* So let's just change the button to secondary barrel down.
267+
*/
268+
269+
__u8 tip_switch = data[1] & TIP_SWITCH;
270+
__u8 barrel_switch = data[1] & BARREL_SWITCH;
271+
272+
__u8 tip_held = last_button_state & TIP_SWITCH;
273+
__u8 barrel_held = last_button_state & BARREL_SWITCH;
274+
275+
if (tip_switch && barrel_switch && !tip_held && !barrel_held) {
276+
data[1] &= ~(TIP_SWITCH | BARREL_SWITCH); /* release tip and barrel */
277+
data[1] |= SECONDARY_BARREL_SWITCH; /* set secondary barrel switch */
278+
}
279+
280+
last_button_state = data[1];
281+
282+
/* The pressure sensor on this tablet maps around half of the
283+
* logical pressure range into the interval [0-100]. Further
284+
* pressure causes the sensor value to increase exponentially
285+
* up to a maximum value of 2047.
286+
*
287+
* The values 12 and 102 were chosen to have an integer slope
288+
* with smooth transition between the two curves around the
289+
* value 100.
290+
*/
291+
292+
__u16 pressure = (((__u16)data[6]) << 0) | (((__u16)data[7]) << 8);
293+
294+
if (pressure <= 102)
295+
pressure *= 12;
296+
else
297+
pressure = scaled_log2(pressure);
298+
299+
data[6] = pressure >> 0;
300+
data[7] = pressure >> 8;
301+
302+
return 0;
303+
}
304+
305+
HID_BPF_OPS(waltop_batteryless) = {
306+
.hid_device_event = (void *)waltop_fix_events,
307+
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
308+
};
309+
310+
SEC("syscall")
311+
int probe(struct hid_bpf_probe_args *ctx)
312+
{
313+
if (ctx->rdesc_size == EXPECTED_RDESC_SIZE)
314+
ctx->retval = 0;
315+
else
316+
ctx->retval = -EINVAL;
317+
318+
return 0;
319+
}
320+
321+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)