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"; 322