1 // SPDX-License-Identifier: GPL-2.0-only
2 /* Copyright (c) 2024 Benjamin Tissoires
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_WACOM 0x056a
11 #define ART_PEN_ID 0x0804
12 #define PID_INTUOS_PRO_2_M 0x0357
13
14 HID_BPF_CONFIG(
15 HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
16 );
17
18 /*
19 * This filter is here for the Art Pen stylus only:
20 * - when used on some Wacom devices (see the list of attached PIDs), this pen
21 * reports pressure every other events.
22 * - to solve that, given that we know that the next event will be the same as
23 * the current one, we can emulate a smoother pressure reporting by reporting
24 * the mean of the previous value and the current one.
25 *
26 * We are effectively delaying the pressure by one event every other event, but
27 * that's less of an annoyance compared to the chunkiness of the reported data.
28 *
29 * For example, let's assume the following set of events:
30 * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
31 * <Tip switch 1> <X 1> <Y 1> <Pressure 100 > <Tooltype 0x0804>
32 * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
33 * <Tip switch 1> <X 3> <Y 3> <Pressure 200 > <Tooltype 0x0804>
34 * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
35 * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
36 *
37 * The filter will report:
38 * <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
39 * <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
40 * <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
41 * <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
42 * <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
43 * <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
44 *
45 */
46
47 struct wacom_params {
48 __u16 pid;
49 __u16 rdesc_len;
50 __u8 report_id;
51 __u8 report_len;
52 struct {
53 __u8 tip_switch;
54 __u8 pressure;
55 __u8 tool_type;
56 } offsets;
57 };
58
59 /*
60 * Multiple device can support the same stylus, so
61 * we need to know which device has which offsets
62 */
63 static const struct wacom_params devices[] = {
64 {
65 .pid = PID_INTUOS_PRO_2_M,
66 .rdesc_len = 949,
67 .report_id = 16,
68 .report_len = 27,
69 .offsets = {
70 .tip_switch = 1,
71 .pressure = 8,
72 .tool_type = 25,
73 },
74 },
75 };
76
77 static struct wacom_params params = { 0 };
78
79 /* HID-BPF reports a 64 bytes chunk anyway, so this ensures
80 * the verifier to know we are addressing the memory correctly
81 */
82 #define PEN_REPORT_LEN 64
83
84 /* only odd frames are modified */
85 static bool odd;
86
87 static __u16 prev_pressure;
88
get_bits(__u8 * data,unsigned int byte_offset)89 static inline void *get_bits(__u8 *data, unsigned int byte_offset)
90 {
91 return data + byte_offset;
92 }
93
get_u16(__u8 * data,unsigned int offset)94 static inline __u16 *get_u16(__u8 *data, unsigned int offset)
95 {
96 return (__u16 *)get_bits(data, offset);
97 }
98
get_u8(__u8 * data,unsigned int offset)99 static inline __u8 *get_u8(__u8 *data, unsigned int offset)
100 {
101 return (__u8 *)get_bits(data, offset);
102 }
103
SEC(HID_BPF_DEVICE_EVENT)104 SEC(HID_BPF_DEVICE_EVENT)
105 int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
106 {
107 __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
108 __u16 *pressure, *tool_type;
109 __u8 *tip_switch;
110
111 if (!data)
112 return 0; /* EPERM check */
113
114 if (data[0] != params.report_id ||
115 params.offsets.tip_switch >= PEN_REPORT_LEN ||
116 params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
117 params.offsets.tool_type >= PEN_REPORT_LEN - 1)
118 return 0; /* invalid report or parameters */
119
120 tool_type = get_u16(data, params.offsets.tool_type);
121 if (*tool_type != ART_PEN_ID)
122 return 0;
123
124 tip_switch = get_u8(data, params.offsets.tip_switch);
125 if ((*tip_switch & 0x01) == 0) {
126 prev_pressure = 0;
127 odd = true;
128 return 0;
129 }
130
131 pressure = get_u16(data, params.offsets.pressure);
132
133 if (odd)
134 *pressure = (*pressure + prev_pressure) / 2;
135
136 prev_pressure = *pressure;
137 odd = !odd;
138
139 return 0;
140 }
141
142 HID_BPF_OPS(wacom_artpen) = {
143 .hid_device_event = (void *)artpen_pressure_interpolate,
144 };
145
146 SEC("syscall")
probe(struct hid_bpf_probe_args * ctx)147 int probe(struct hid_bpf_probe_args *ctx)
148 {
149 struct hid_bpf_ctx *hid_ctx;
150 __u16 pid;
151 int i;
152
153 /* get a struct hid_device to access the actual pid of the device */
154 hid_ctx = hid_bpf_allocate_context(ctx->hid);
155 if (!hid_ctx) {
156 ctx->retval = -ENODEV;
157 return -1; /* EPERM check */
158 }
159 pid = hid_ctx->hid->product;
160
161 ctx->retval = -EINVAL;
162
163 /* Match the given device with the list of known devices */
164 for (i = 0; i < ARRAY_SIZE(devices); i++) {
165 const struct wacom_params *device = &devices[i];
166
167 if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
168 params = *device;
169 ctx->retval = 0;
170 }
171 }
172
173 hid_bpf_release_context(hid_ctx);
174 return 0;
175 }
176
177 char _license[] SEC("license") = "GPL";
178