This tablets gets a lot of things wrong:
- the secondary button is reported through Secondary Tip Switch
- the third button is reported through Invert

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.

Signed-off-by: Benjamin Tissoires <[email protected]>
---
 drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c | 290 +++++++++++++++++++++++
 1 file changed, 290 insertions(+)

diff --git a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c 
b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
new file mode 100644
index 000000000000..ff759f2276f9
--- /dev/null
+++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c
@@ -0,0 +1,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";

-- 
2.44.0


Reply via email to