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