1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright (c) 2022 Benjamin Tissoires |
3 | * |
4 | * This program will morph the Microsoft Surface Dial into a mouse, |
5 | * and depending on the chosen resolution enable or not the haptic feedback: |
6 | * - a resolution (-r) of 3600 will report 3600 "ticks" in one full rotation |
7 | * without haptic feedback |
8 | * - any other resolution will report N "ticks" in a full rotation with haptic |
9 | * feedback |
10 | * |
11 | * A good default for low resolution haptic scrolling is 72 (1 "tick" every 5 |
12 | * degrees), and set to 3600 for smooth scrolling. |
13 | */ |
14 | |
15 | #include <assert.h> |
16 | #include <errno.h> |
17 | #include <fcntl.h> |
18 | #include <libgen.h> |
19 | #include <signal.h> |
20 | #include <stdbool.h> |
21 | #include <stdio.h> |
22 | #include <stdlib.h> |
23 | #include <string.h> |
24 | #include <sys/resource.h> |
25 | #include <unistd.h> |
26 | |
27 | #include <linux/bpf.h> |
28 | #include <linux/errno.h> |
29 | |
30 | #include <bpf/bpf.h> |
31 | #include <bpf/libbpf.h> |
32 | |
33 | #include "hid_surface_dial.skel.h" |
34 | #include "hid_bpf_attach.h" |
35 | |
36 | static bool running = true; |
37 | |
38 | struct haptic_syscall_args { |
39 | unsigned int hid; |
40 | int retval; |
41 | }; |
42 | |
43 | static void int_exit(int sig) |
44 | { |
45 | running = false; |
46 | exit(status: 0); |
47 | } |
48 | |
49 | static void usage(const char *prog) |
50 | { |
51 | fprintf(stderr, |
52 | format: "%s: %s [OPTIONS] /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n" |
53 | " OPTIONS:\n" |
54 | " -r N\t set the given resolution to the device (number of ticks per 360°)\n\n" , |
55 | __func__, prog); |
56 | fprintf(stderr, |
57 | format: "This program will morph the Microsoft Surface Dial into a mouse,\n" |
58 | "and depending on the chosen resolution enable or not the haptic feedback:\n" |
59 | "- a resolution (-r) of 3600 will report 3600 'ticks' in one full rotation\n" |
60 | " without haptic feedback\n" |
61 | "- any other resolution will report N 'ticks' in a full rotation with haptic\n" |
62 | " feedback\n" |
63 | "\n" |
64 | "A good default for low resolution haptic scrolling is 72 (1 'tick' every 5\n" |
65 | "degrees), and set to 3600 for smooth scrolling.\n" ); |
66 | } |
67 | |
68 | static int get_hid_id(const char *path) |
69 | { |
70 | const char *str_id, *dir; |
71 | char uevent[1024]; |
72 | int fd; |
73 | |
74 | memset(s: uevent, c: 0, n: sizeof(uevent)); |
75 | snprintf(s: uevent, maxlen: sizeof(uevent) - 1, format: "%s/uevent" , path); |
76 | |
77 | fd = open(file: uevent, O_RDONLY | O_NONBLOCK); |
78 | if (fd < 0) |
79 | return -ENOENT; |
80 | |
81 | close(fd: fd); |
82 | |
83 | dir = basename(path: (char *)path); |
84 | |
85 | str_id = dir + sizeof("0003:0001:0A37." ); |
86 | return (int)strtol(nptr: str_id, NULL, base: 16); |
87 | } |
88 | |
89 | static int attach_prog(struct hid_surface_dial *skel, struct bpf_program *prog, int hid_id) |
90 | { |
91 | struct attach_prog_args args = { |
92 | .hid = hid_id, |
93 | .retval = -1, |
94 | }; |
95 | int attach_fd, err; |
96 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, |
97 | .ctx_in = &args, |
98 | .ctx_size_in = sizeof(args), |
99 | ); |
100 | |
101 | attach_fd = bpf_program__fd(skel->progs.attach_prog); |
102 | if (attach_fd < 0) { |
103 | fprintf(stderr, format: "can't locate attach prog: %m\n" ); |
104 | return 1; |
105 | } |
106 | |
107 | args.prog_fd = bpf_program__fd(prog); |
108 | err = bpf_prog_test_run_opts(attach_fd, &tattr); |
109 | if (err) { |
110 | fprintf(stderr, format: "can't attach prog to hid device %d: %m (err: %d)\n" , |
111 | hid_id, err); |
112 | return 1; |
113 | } |
114 | return 0; |
115 | } |
116 | |
117 | static int set_haptic(struct hid_surface_dial *skel, int hid_id) |
118 | { |
119 | struct haptic_syscall_args args = { |
120 | .hid = hid_id, |
121 | .retval = -1, |
122 | }; |
123 | int haptic_fd, err; |
124 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, |
125 | .ctx_in = &args, |
126 | .ctx_size_in = sizeof(args), |
127 | ); |
128 | |
129 | haptic_fd = bpf_program__fd(skel->progs.set_haptic); |
130 | if (haptic_fd < 0) { |
131 | fprintf(stderr, format: "can't locate haptic prog: %m\n" ); |
132 | return 1; |
133 | } |
134 | |
135 | err = bpf_prog_test_run_opts(haptic_fd, &tattr); |
136 | if (err) { |
137 | fprintf(stderr, format: "can't set haptic configuration to hid device %d: %m (err: %d)\n" , |
138 | hid_id, err); |
139 | return 1; |
140 | } |
141 | return 0; |
142 | } |
143 | |
144 | int main(int argc, char **argv) |
145 | { |
146 | struct hid_surface_dial *skel; |
147 | struct bpf_program *prog; |
148 | const char *optstr = "r:" ; |
149 | const char *sysfs_path; |
150 | int opt, hid_id, resolution = 72; |
151 | |
152 | while ((opt = getopt(argc: argc, argv: argv, shortopts: optstr)) != -1) { |
153 | switch (opt) { |
154 | case 'r': |
155 | { |
156 | char *endp = NULL; |
157 | long l = -1; |
158 | |
159 | if (optarg) { |
160 | l = strtol(nptr: optarg, endptr: &endp, base: 10); |
161 | if (endp && *endp) |
162 | l = -1; |
163 | } |
164 | |
165 | if (l < 0) { |
166 | fprintf(stderr, |
167 | format: "invalid r option %s - expecting a number\n" , |
168 | optarg ? optarg : "" ); |
169 | exit(EXIT_FAILURE); |
170 | }; |
171 | |
172 | resolution = (int) l; |
173 | break; |
174 | } |
175 | default: |
176 | usage(basename(path: argv[0])); |
177 | return 1; |
178 | } |
179 | } |
180 | |
181 | if (optind == argc) { |
182 | usage(basename(path: argv[0])); |
183 | return 1; |
184 | } |
185 | |
186 | sysfs_path = argv[optind]; |
187 | if (!sysfs_path) { |
188 | perror(s: "sysfs" ); |
189 | return 1; |
190 | } |
191 | |
192 | skel = hid_surface_dial__open_and_load(); |
193 | if (!skel) { |
194 | fprintf(stderr, format: "%s %s:%d" , __func__, __FILE__, __LINE__); |
195 | return -1; |
196 | } |
197 | |
198 | hid_id = get_hid_id(path: sysfs_path); |
199 | if (hid_id < 0) { |
200 | fprintf(stderr, format: "can not open HID device: %m\n" ); |
201 | return 1; |
202 | } |
203 | |
204 | skel->data->resolution = resolution; |
205 | skel->data->physical = (int)(resolution / 72); |
206 | |
207 | bpf_object__for_each_program(prog, *skel->skeleton->obj) { |
208 | /* ignore syscalls */ |
209 | if (bpf_program__get_type(prog) != BPF_PROG_TYPE_TRACING) |
210 | continue; |
211 | |
212 | attach_prog(skel, prog, hid_id); |
213 | } |
214 | |
215 | signal(SIGINT, handler: int_exit); |
216 | signal(SIGTERM, handler: int_exit); |
217 | |
218 | set_haptic(skel, hid_id); |
219 | |
220 | while (running) |
221 | sleep(seconds: 1); |
222 | |
223 | hid_surface_dial__destroy(skel); |
224 | |
225 | return 0; |
226 | } |
227 | |