1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
|
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_HUION 0x256C
#define PID_KAMVAS_PRO_19 0x006B
#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
#define TEST_PREFIX "uhid test "
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
);
bool prev_was_out_of_range;
bool in_eraser_mode;
/*
* We need to amend the report descriptor for the following:
* - the second button is reported through Secondary Tip Switch instead of Secondary Barrel Switch
* - the third button is reported through Invert, and we need some room to report it.
*
*/
static const __u8 fixed_rdesc[] = {
0x05, 0x0d, // Usage Page (Digitizers) 0
0x09, 0x02, // Usage (Pen) 2
0xa1, 0x01, // Collection (Application) 4
0x85, 0x0a, // Report ID (10) 6
0x09, 0x20, // Usage (Stylus) 8
0xa1, 0x01, // Collection (Application) 10
0x09, 0x42, // Usage (Tip Switch) 12
0x09, 0x44, // Usage (Barrel Switch) 14
0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from Secondary Tip Switch */
0x09, 0x3c, // Usage (Invert) 18
0x09, 0x45, // Usage (Eraser) 20
0x15, 0x00, // Logical Minimum (0) 22
0x25, 0x01, // Logical Maximum (1) 24
0x75, 0x01, // Report Size (1) 26
0x95, 0x05, // Report Count (5) 28 /* changed (was 5) */
0x81, 0x02, // Input (Data,Var,Abs) 30
0x05, 0x09, // Usage Page (Button) /* inserted */
0x09, 0x4a, // Usage (0x4a) /* inserted to be translated as input usage 0x149: BTN_STYLUS3 */
0x95, 0x01, // Report Count (1) /* inserted */
0x81, 0x02, // Input (Data,Var,Abs) /* inserted */
0x05, 0x0d, // Usage Page (Digitizers) /* inserted */
0x09, 0x32, // Usage (In Range) 32
0x75, 0x01, // Report Size (1) 34
0x95, 0x01, // Report Count (1) 36
0x81, 0x02, // Input (Data,Var,Abs) 38
0x81, 0x03, // Input (Cnst,Var,Abs) 40
0x05, 0x01, // Usage Page (Generic Desktop) 42
0x09, 0x30, // Usage (X) 44
0x09, 0x31, // Usage (Y) 46
0x55, 0x0d, // Unit Exponent (-3) 48
0x65, 0x33, // Unit (EnglishLinear: in³) 50
0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
0x35, 0x00, // Physical Minimum (0) 55
0x46, 0x00, 0x08, // Physical Maximum (2048) 57
0x75, 0x10, // Report Size (16) 60
0x95, 0x02, // Report Count (2) 62
0x81, 0x02, // Input (Data,Var,Abs) 64
0x05, 0x0d, // Usage Page (Digitizers) 66
0x09, 0x30, // Usage (Tip Pressure) 68
0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
0x75, 0x10, // Report Size (16) 73
0x95, 0x01, // Report Count (1) 75
0x81, 0x02, // Input (Data,Var,Abs) 77
0x09, 0x3d, // Usage (X Tilt) 79
0x09, 0x3e, // Usage (Y Tilt) 81
0x15, 0xa6, // Logical Minimum (-90) 83
0x25, 0x5a, // Logical Maximum (90) 85
0x75, 0x08, // Report Size (8) 87
0x95, 0x02, // Report Count (2) 89
0x81, 0x02, // Input (Data,Var,Abs) 91
0xc0, // End Collection 93
0xc0, // End Collection 94
0x05, 0x0d, // Usage Page (Digitizers) 95
0x09, 0x04, // Usage (Touch Screen) 97
0xa1, 0x01, // Collection (Application) 99
0x85, 0x04, // Report ID (4) 101
0x09, 0x22, // Usage (Finger) 103
0xa1, 0x02, // Collection (Logical) 105
0x05, 0x0d, // Usage Page (Digitizers) 107
0x95, 0x01, // Report Count (1) 109
0x75, 0x06, // Report Size (6) 111
0x09, 0x51, // Usage (Contact Id) 113
0x15, 0x00, // Logical Minimum (0) 115
0x25, 0x3f, // Logical Maximum (63) 117
0x81, 0x02, // Input (Data,Var,Abs) 119
0x09, 0x42, // Usage (Tip Switch) 121
0x25, 0x01, // Logical Maximum (1) 123
0x75, 0x01, // Report Size (1) 125
0x95, 0x01, // Report Count (1) 127
0x81, 0x02, // Input (Data,Var,Abs) 129
0x75, 0x01, // Report Size (1) 131
0x95, 0x01, // Report Count (1) 133
0x81, 0x03, // Input (Cnst,Var,Abs) 135
0x05, 0x01, // Usage Page (Generic Desktop) 137
0x75, 0x10, // Report Size (16) 139
0x55, 0x0e, // Unit Exponent (-2) 141
0x65, 0x11, // Unit (SILinear: cm) 143
0x09, 0x30, // Usage (X) 145
0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
0x35, 0x00, // Physical Minimum (0) 150
0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
0x81, 0x42, // Input (Data,Var,Abs,Null) 155
0x09, 0x31, // Usage (Y) 157
0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
0x81, 0x42, // Input (Data,Var,Abs,Null) 165
0x05, 0x0d, // Usage Page (Digitizers) 167
0x09, 0x30, // Usage (Tip Pressure) 169
0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
0x75, 0x10, // Report Size (16) 174
0x95, 0x01, // Report Count (1) 176
0x81, 0x02, // Input (Data,Var,Abs) 178
0xc0, // End Collection 180
0x05, 0x0d, // Usage Page (Digitizers) 181
0x09, 0x22, // Usage (Finger) 183
0xa1, 0x02, // Collection (Logical) 185
0x05, 0x0d, // Usage Page (Digitizers) 187
0x95, 0x01, // Report Count (1) 189
0x75, 0x06, // Report Size (6) 191
0x09, 0x51, // Usage (Contact Id) 193
0x15, 0x00, // Logical Minimum (0) 195
0x25, 0x3f, // Logical Maximum (63) 197
0x81, 0x02, // Input (Data,Var,Abs) 199
0x09, 0x42, // Usage (Tip Switch) 201
0x25, 0x01, // Logical Maximum (1) 203
0x75, 0x01, // Report Size (1) 205
0x95, 0x01, // Report Count (1) 207
0x81, 0x02, // Input (Data,Var,Abs) 209
0x75, 0x01, // Report Size (1) 211
0x95, 0x01, // Report Count (1) 213
0x81, 0x03, // Input (Cnst,Var,Abs) 215
0x05, 0x01, // Usage Page (Generic Desktop) 217
0x75, 0x10, // Report Size (16) 219
0x55, 0x0e, // Unit Exponent (-2) 221
0x65, 0x11, // Unit (SILinear: cm) 223
0x09, 0x30, // Usage (X) 225
0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
0x35, 0x00, // Physical Minimum (0) 230
0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
0x81, 0x42, // Input (Data,Var,Abs,Null) 235
0x09, 0x31, // Usage (Y) 237
0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
0x81, 0x42, // Input (Data,Var,Abs,Null) 245
0x05, 0x0d, // Usage Page (Digitizers) 247
0x09, 0x30, // Usage (Tip Pressure) 249
0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
0x75, 0x10, // Report Size (16) 254
0x95, 0x01, // Report Count (1) 256
0x81, 0x02, // Input (Data,Var,Abs) 258
0xc0, // End Collection 260
0x05, 0x0d, // Usage Page (Digitizers) 261
0x09, 0x56, // Usage (Scan Time) 263
0x55, 0x00, // Unit Exponent (0) 265
0x65, 0x00, // Unit (None) 267
0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
0x95, 0x01, // Report Count (1) 274
0x75, 0x20, // Report Size (32) 276
0x81, 0x02, // Input (Data,Var,Abs) 278
0x09, 0x54, // Usage (Contact Count) 280
0x25, 0x7f, // Logical Maximum (127) 282
0x95, 0x01, // Report Count (1) 284
0x75, 0x08, // Report Size (8) 286
0x81, 0x02, // Input (Data,Var,Abs) 288
0x75, 0x08, // Report Size (8) 290
0x95, 0x08, // Report Count (8) 292
0x81, 0x03, // Input (Cnst,Var,Abs) 294
0x85, 0x05, // Report ID (5) 296
0x09, 0x55, // Usage (Contact Max) 298
0x25, 0x0a, // Logical Maximum (10) 300
0x75, 0x08, // Report Size (8) 302
0x95, 0x01, // Report Count (1) 304
0xb1, 0x02, // Feature (Data,Var,Abs) 306
0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
0x85, 0x06, // Report ID (6) 313
0x15, 0x00, // Logical Minimum (0) 315
0x26, 0xff, 0x00, // Logical Maximum (255) 317
0x75, 0x08, // Report Size (8) 320
0x96, 0x00, 0x01, // Report Count (256) 322
0xb1, 0x02, // Feature (Data,Var,Abs) 325
0xc0, // End Collection 327
};
SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
if (!data)
return 0; /* EPERM check */
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
return sizeof(fixed_rdesc);
}
/*
* This tablet reports the 3rd button through invert, but this conflict
* with the normal eraser mode.
* Fortunately, before entering eraser mode, (so Invert = 1),
* the tablet always sends an out-of-proximity event.
* So we can detect that single event and:
* - if there was none but the invert bit was toggled: this is the
* third button
* - if there was this out-of-proximity event, we are entering
* eraser mode, and we will until the next out-of-proximity.
*/
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
if (!data)
return 0; /* EPERM check */
if (data[0] != 0x0a) /* not the pen report ID */
return 0;
/* stylus is out of range */
if (!(data[1] & 0x40)) {
prev_was_out_of_range = true;
in_eraser_mode = false;
return 0;
}
/* going into eraser mode (Invert = 1) only happens after an
* out of range event
*/
if (prev_was_out_of_range && (data[1] & 0x18))
in_eraser_mode = true;
/* eraser mode works fine */
if (in_eraser_mode)
return 0;
/* copy the Invert bit reported for the 3rd button in bit 7 */
if (data[1] & 0x08)
data[1] |= 0x20;
/* clear Invert bit now that it was copied */
data[1] &= 0xf7;
prev_was_out_of_range = false;
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
ctx->retval = ctx->rdesc_size != 328;
if (ctx->retval)
ctx->retval = -EINVAL;
/* ensure the kernel isn't fixed already */
if (ctx->rdesc[17] != 0x43) /* Secondary Tip Switch */
ctx->retval = -EINVAL;
struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
if (!hctx) {
return ctx->retval = -EINVAL;
return 0;
}
const char *name = hctx->hid->name;
/* strip out TEST_PREFIX */
if (!__builtin_memcmp(name, TEST_PREFIX, sizeof(TEST_PREFIX) - 1))
name += sizeof(TEST_PREFIX) - 1;
if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)))
ctx->retval = -EINVAL;
hid_bpf_release_context(hctx);
return 0;
}
char _license[] SEC("license") = "GPL";
|