summaryrefslogtreecommitdiff
path: root/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c
blob: dc05aa48faa7504b8691c7547eef73d207682ef2 (plain)
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
// 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_WACOM		0x056a
#define ART_PEN_ID		0x0804
#define PID_INTUOS_PRO_2_M	0x0357

HID_BPF_CONFIG(
	HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
);

/*
 * This filter is here for the Art Pen stylus only:
 * - when used on some Wacom devices (see the list of attached PIDs), this pen
 *   reports pressure every other events.
 * - to solve that, given that we know that the next event will be the same as
 *   the current one, we can emulate a smoother pressure reporting by reporting
 *   the mean of the previous value and the current one.
 *
 * We are effectively delaying the pressure by one event every other event, but
 * that's less of an annoyance compared to the chunkiness of the reported data.
 *
 * For example, let's assume the following set of events:
 * <Tip switch 0> <X 0> <Y 0> <Pressure    0 > <Tooltype 0x0804>
 * <Tip switch 1> <X 1> <Y 1> <Pressure  100 > <Tooltype 0x0804>
 * <Tip switch 1> <X 2> <Y 2> <Pressure  100 > <Tooltype 0x0804>
 * <Tip switch 1> <X 3> <Y 3> <Pressure  200 > <Tooltype 0x0804>
 * <Tip switch 1> <X 4> <Y 4> <Pressure  200 > <Tooltype 0x0804>
 * <Tip switch 0> <X 5> <Y 5> <Pressure    0 > <Tooltype 0x0804>
 *
 * The filter will report:
 * <Tip switch 0> <X 0> <Y 0> <Pressure    0 > <Tooltype 0x0804>
 * <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
 * <Tip switch 1> <X 2> <Y 2> <Pressure  100 > <Tooltype 0x0804>
 * <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
 * <Tip switch 1> <X 4> <Y 4> <Pressure  200 > <Tooltype 0x0804>
 * <Tip switch 0> <X 5> <Y 5> <Pressure    0 > <Tooltype 0x0804>
 *
 */

struct wacom_params {
	__u16 pid;
	__u16 rdesc_len;
	__u8 report_id;
	__u8 report_len;
	struct {
		__u8 tip_switch;
		__u8 pressure;
		__u8 tool_type;
	} offsets;
};

/*
 * Multiple device can support the same stylus, so
 * we need to know which device has which offsets
 */
static const struct wacom_params devices[] = {
	{
		.pid = PID_INTUOS_PRO_2_M,
		.rdesc_len = 949,
		.report_id = 16,
		.report_len = 27,
		.offsets = {
			.tip_switch = 1,
			.pressure = 8,
			.tool_type = 25,
		},
	},
};

static struct wacom_params params = { 0 };

/* HID-BPF reports a 64 bytes chunk anyway, so this ensures
 * the verifier to know we are addressing the memory correctly
 */
#define PEN_REPORT_LEN		64

/* only odd frames are modified */
static bool odd;

static __u16 prev_pressure;

static inline void *get_bits(__u8 *data, unsigned int byte_offset)
{
	return data + byte_offset;
}

static inline __u16 *get_u16(__u8 *data, unsigned int offset)
{
	return (__u16 *)get_bits(data, offset);
}

static inline __u8 *get_u8(__u8 *data, unsigned int offset)
{
	return (__u8 *)get_bits(data, offset);
}

SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
{
	__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
	__u16 *pressure, *tool_type;
	__u8 *tip_switch;

	if (!data)
		return 0; /* EPERM check */

	if (data[0] != params.report_id ||
	    params.offsets.tip_switch >= PEN_REPORT_LEN ||
	    params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
	    params.offsets.tool_type >= PEN_REPORT_LEN - 1)
		return 0; /* invalid report or parameters */

	tool_type = get_u16(data, params.offsets.tool_type);
	if (*tool_type != ART_PEN_ID)
		return 0;

	tip_switch = get_u8(data, params.offsets.tip_switch);
	if ((*tip_switch & 0x01) == 0) {
		prev_pressure = 0;
		odd = true;
		return 0;
	}

	pressure = get_u16(data, params.offsets.pressure);

	if (odd)
		*pressure = (*pressure + prev_pressure) / 2;

	prev_pressure = *pressure;
	odd = !odd;

	return 0;
}

SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
	struct hid_bpf_ctx *hid_ctx;
	__u16 pid;
	int i;

	/* get a struct hid_device to access the actual pid of the device */
	hid_ctx = hid_bpf_allocate_context(ctx->hid);
	if (!hid_ctx) {
		ctx->retval = -ENODEV;
		return -1; /* EPERM check */
	}
	pid = hid_ctx->hid->product;

	ctx->retval = -EINVAL;

	/* Match the given device with the list of known devices */
	for (i = 0; i < ARRAY_SIZE(devices); i++) {
		const struct wacom_params *device = &devices[i];

		if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
			params = *device;
			ctx->retval = 0;
		}
	}

	hid_bpf_release_context(hid_ctx);
	return 0;
}

char _license[] SEC("license") = "GPL";