xref: /linux/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c (revision 6e7fd890f1d6ac83805409e9c346240de2705584)
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 
89 static inline void *get_bits(__u8 *data, unsigned int byte_offset)
90 {
91 	return data + byte_offset;
92 }
93 
94 static inline __u16 *get_u16(__u8 *data, unsigned int offset)
95 {
96 	return (__u16 *)get_bits(data, offset);
97 }
98 
99 static inline __u8 *get_u8(__u8 *data, unsigned int offset)
100 {
101 	return (__u8 *)get_bits(data, offset);
102 }
103 
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")
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