xref: /linux/drivers/hid/bpf/progs/XPPen__DecoMini4.bpf.c (revision 170aafe35cb98e0f3fbacb446ea86389fbce22ea)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 José Expósito
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_UGEE	0x28BD
11 #define PID_DECO_MINI_4	0x0929
12 #define RDESC_SIZE_PAD	177
13 #define RDESC_SIZE_PEN	109
14 #define PAD_REPORT_ID	0x06
15 
16 /*
17  * XP-Pen devices return a descriptor with the values the driver should use when
18  * one of its interfaces is queried. For this device the descriptor is:
19  *
20  * 0E 03 60 4F 88 3B 06 00 FF 1F D8 13
21  *       ----- -----       ----- -----
22  *         |     |           |     |
23  *         |     |           |     `- Resolution: 5080 (13d8)
24  *         |     |           `- Maximum pressure: 8191 (1FFF)
25  *         |     `- Logical maximum Y: 15240 (3B88)
26  *         `- Logical maximum X: 20320 (4F60)
27  *
28  * The physical maximum is calculated as (logical_max * 1000) / resolution.
29  */
30 #define LOGICAL_MAX_X	0x60, 0x4F
31 #define LOGICAL_MAX_Y	0x88, 0x3B
32 #define PHYSICAL_MAX_X	0xA0, 0x0F
33 #define PHYSICAL_MAX_Y	0xB8, 0x0B
34 #define PRESSURE_MAX	0xFF, 0x1F
35 
36 HID_BPF_CONFIG(
37 	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_MINI_4)
38 );
39 
40 /*
41  * The tablet send these values when the pad buttons are pressed individually:
42  *
43  *   Buttons released: 06 00 00 00 00 00 00 00
44  *   Button 1:         06 00 05 00 00 00 00 00 -> b
45  *   Button 2:         06 00 08 00 00 00 00 00 -> e
46  *   Button 3:         06 04 00 00 00 00 00 00 -> LAlt
47  *   Button 4:         06 00 2c 00 00 00 00 00 -> Space
48  *   Button 5:         06 01 16 00 00 00 00 00 -> LControl + s
49  *   Button 6:         06 01 1d 00 00 00 00 00 -> LControl + z
50  *
51  * When multiple buttons are pressed at the same time, the values used to
52  * identify the buttons are identical, but they appear in different bytes of the
53  * record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
54  * this is the report:
55  *
56  *   Buttons 2 and 1:  06 00 08 05 00 00 00 00 -> e + b
57  *
58  * Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
59  * report.
60  *
61  * Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
62  * and 5 generates this report:
63  *
64  *   Buttons 3 and 5:  06 05 16 00 00 00 00 00 -> LControl + LAlt + s
65  *                        -- --
66  *                         |  |
67  *                         |  `- Button 5 (0x16)
68  *                         `- 0x05 = 0101. Button 3 is pressed
69  *                                    ^
70  *
71  * pad_buttons contains a list of buttons that can be matched in
72  * HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
73  */
74 static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2C, 0x16, 0x1D };
75 
76 static const __u8 fixed_pad_rdesc[] = {
77 	0x05, 0x01,           /*  Usage Page (Desktop),                   */
78 	0x09, 0x07,           /*  Usage (Keypad),                         */
79 	0xA1, 0x01,           /*  Collection (Application),               */
80 	0x85, 0x06,           /*      Report ID (6),                      */
81 	0x05, 0x0D,           /*      Usage Page (Digitizer),             */
82 	0x09, 0x39,           /*      Usage (Tablet Function Keys),       */
83 	0xA0,                 /*      Collection (Physical),              */
84 	0x05, 0x09,           /*          Usage Page (Button),            */
85 	0x75, 0x01,           /*          Report Size (1),                */
86 	0x95, 0x06,           /*          Report Count (6),               */
87 	0x19, 0x01,           /*          Usage Minimum (01h),            */
88 	0x29, 0x06,           /*          Usage Maximum (06h),            */
89 	0x14,                 /*          Logical Minimum (0),            */
90 	0x25, 0x01,           /*          Logical Maximum (1),            */
91 	0x81, 0x02,           /*          Input (Variable),               */
92 	0x95, 0x32,           /*          Report Count (50),              */
93 	0x81, 0x01,           /*          Input (Constant),               */
94 	0xC0,                 /*      End Collection,                     */
95 	0xC0                  /*  End Collection                          */
96 };
97 
98 static const __u8 fixed_pen_rdesc[] = {
99 	0x05, 0x0d,           /*  Usage Page (Digitizers),                */
100 	0x09, 0x01,           /*  Usage (Digitizer),                      */
101 	0xa1, 0x01,           /*  Collection (Application),               */
102 	0x85, 0x07,           /*      Report ID (7),                      */
103 	0x09, 0x20,           /*      Usage (Stylus),                     */
104 	0xa1, 0x00,           /*      Collection (Physical),              */
105 	0x09, 0x42,           /*          Usage (Tip Switch),             */
106 	0x09, 0x44,           /*          Usage (Barrel Switch),          */
107 	0x09, 0x46,           /*          Usage (Tablet Pick),            */
108 	0x75, 0x01,           /*          Report Size (1),                */
109 	0x95, 0x03,           /*          Report Count (3),               */
110 	0x14,                 /*          Logical Minimum (0),            */
111 	0x25, 0x01,           /*          Logical Maximum (1),            */
112 	0x81, 0x02,           /*          Input (Variable),               */
113 	0x95, 0x02,           /*          Report Count (2),               */
114 	0x81, 0x03,           /*          Input (Constant, Variable),     */
115 	0x09, 0x32,           /*          Usage (In Range),               */
116 	0x95, 0x01,           /*          Report Count (1),               */
117 	0x81, 0x02,           /*          Input (Variable),               */
118 	0x95, 0x02,           /*          Report Count (2),               */
119 	0x81, 0x03,           /*          Input (Constant, Variable),     */
120 	0x75, 0x10,           /*          Report Size (16),               */
121 	0x95, 0x01,           /*          Report Count (1),               */
122 	0x35, 0x00,           /*          Physical Minimum (0),           */
123 	0xa4,                 /*          Push,                           */
124 	0x05, 0x01,           /*          Usage Page (Desktop),           */
125 	0x09, 0x30,           /*          Usage (X),                      */
126 	0x65, 0x13,           /*          Unit (Inch),                    */
127 	0x55, 0x0d,           /*          Unit Exponent (-3),             */
128 	0x26, LOGICAL_MAX_X,  /*          Logical Maximum,                */
129 	0x46, PHYSICAL_MAX_X, /*          Physical Maximum,               */
130 	0x81, 0x02,           /*          Input (Variable),               */
131 	0x09, 0x31,           /*          Usage (Y),                      */
132 	0x26, LOGICAL_MAX_Y,  /*          Logical Maximum,                */
133 	0x46, PHYSICAL_MAX_Y, /*          Physical Maximum,               */
134 	0x81, 0x02,           /*          Input (Variable),               */
135 	0xb4,                 /*          Pop,                            */
136 	0x09, 0x30,           /*          Usage (Tip Pressure),           */
137 	0x45, 0x00,           /*          Physical Maximum (0),           */
138 	0x26, PRESSURE_MAX,   /*          Logical Maximum,                */
139 	0x75, 0x0D,           /*          Report Size (13),               */
140 	0x95, 0x01,           /*          Report Count (1),               */
141 	0x81, 0x02,           /*          Input (Variable),               */
142 	0x75, 0x01,           /*          Report Size (1),                */
143 	0x95, 0x13,           /*          Report Count (19),              */
144 	0x81, 0x01,           /*          Input (Constant),               */
145 	0xc0,                 /*      End Collection,                     */
146 	0xc0,                 /*  End Collection                          */
147 };
148 
149 static const size_t fixed_pad_rdesc_size = sizeof(fixed_pad_rdesc);
150 static const size_t fixed_pen_rdesc_size = sizeof(fixed_pen_rdesc);
151 
152 SEC(HID_BPF_RDESC_FIXUP)
153 int BPF_PROG(hid_rdesc_fixup_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
154 {
155 	__u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
156 
157 	if (!data)
158 		return 0; /* EPERM check */
159 
160 	if (hctx->size == RDESC_SIZE_PAD) {
161 		__builtin_memcpy(data, fixed_pad_rdesc, fixed_pad_rdesc_size);
162 		return fixed_pad_rdesc_size;
163 	} else if (hctx->size == RDESC_SIZE_PEN) {
164 		__builtin_memcpy(data, fixed_pen_rdesc, fixed_pen_rdesc_size);
165 		return fixed_pen_rdesc_size;
166 	}
167 
168 	return 0;
169 }
170 
171 SEC(HID_BPF_DEVICE_EVENT)
172 int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
173 {
174 	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
175 	__u8 button_mask = 0;
176 	int d, b;
177 
178 	if (!data)
179 		return 0; /* EPERM check */
180 
181 	if (data[0] != PAD_REPORT_ID)
182 		return 0;
183 
184 	/* data[1] stores the status of BTN_2 in the 3rd bit*/
185 	if (data[1] & BIT(2))
186 		button_mask |= BIT(2);
187 
188 	/* The rest of the descriptor stores the buttons as in pad_buttons */
189 	for (d = 2; d < 8; d++) {
190 		for (b = 0; b < sizeof(pad_buttons); b++) {
191 			if (data[d] != 0 && data[d] == pad_buttons[b])
192 				button_mask |= BIT(b);
193 		}
194 	}
195 
196 	__u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
197 
198 	__builtin_memcpy(data, report, sizeof(report));
199 
200 	return 0;
201 }
202 
203 HID_BPF_OPS(deco_mini_4) = {
204 	.hid_device_event = (void *)hid_device_event_xppen_deco_mini_4,
205 	.hid_rdesc_fixup = (void *)hid_rdesc_fixup_xppen_deco_mini_4,
206 };
207 
208 SEC("syscall")
209 int probe(struct hid_bpf_probe_args *ctx)
210 {
211 	/*
212 	 * The device has 2 modes: The compatibility mode, enabled by default,
213 	 * and the raw mode, that can be activated by sending a buffer of magic
214 	 * data to a certain USB endpoint.
215 	 *
216 	 * Depending on the mode, different interfaces of the device are used:
217 	 * - First interface:  Pad in compatibility mode
218 	 * - Second interface: Pen in compatibility mode
219 	 * - Third interface:  Only used in raw mode
220 	 *
221 	 * We'll use the device in compatibility mode.
222 	 */
223 	ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD &&
224 		      ctx->rdesc_size != RDESC_SIZE_PEN;
225 	if (ctx->retval)
226 		ctx->retval = -EINVAL;
227 
228 	return 0;
229 }
230 
231 char _license[] SEC("license") = "GPL";
232