Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.9.
  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
 35static bool running = true;
 36
 37struct haptic_syscall_args {
 38	unsigned int hid;
 39	int retval;
 40};
 41
 42static void int_exit(int sig)
 43{
 44	running = false;
 45	exit(0);
 46}
 47
 48static void usage(const char *prog)
 49{
 50	fprintf(stderr,
 51		"%s: %s [OPTIONS] /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n"
 52		"  OPTIONS:\n"
 53		"    -r N\t set the given resolution to the device (number of ticks per 360°)\n\n",
 54		__func__, prog);
 55	fprintf(stderr,
 56		"This program will morph the Microsoft Surface Dial into a mouse,\n"
 57		"and depending on the chosen resolution enable or not the haptic feedback:\n"
 58		"- a resolution (-r) of 3600 will report 3600 'ticks' in one full rotation\n"
 59		"  without haptic feedback\n"
 60		"- any other resolution will report N 'ticks' in a full rotation with haptic\n"
 61		"  feedback\n"
 62		"\n"
 63		"A good default for low resolution haptic scrolling is 72 (1 'tick' every 5\n"
 64		"degrees), and set to 3600 for smooth scrolling.\n");
 65}
 66
 67static int get_hid_id(const char *path)
 68{
 69	const char *str_id, *dir;
 70	char uevent[1024];
 71	int fd;
 72
 73	memset(uevent, 0, sizeof(uevent));
 74	snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path);
 75
 76	fd = open(uevent, O_RDONLY | O_NONBLOCK);
 77	if (fd < 0)
 78		return -ENOENT;
 79
 80	close(fd);
 81
 82	dir = basename((char *)path);
 83
 84	str_id = dir + sizeof("0003:0001:0A37.");
 85	return (int)strtol(str_id, NULL, 16);
 86}
 87
 88static int set_haptic(struct hid_surface_dial *skel, int hid_id)
 89{
 90	struct haptic_syscall_args args = {
 91		.hid = hid_id,
 92		.retval = -1,
 93	};
 94	int haptic_fd, err;
 95	DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
 96			    .ctx_in = &args,
 97			    .ctx_size_in = sizeof(args),
 98	);
 99
100	haptic_fd = bpf_program__fd(skel->progs.set_haptic);
101	if (haptic_fd < 0) {
102		fprintf(stderr, "can't locate haptic prog: %m\n");
103		return 1;
104	}
105
106	err = bpf_prog_test_run_opts(haptic_fd, &tattr);
107	if (err) {
108		fprintf(stderr, "can't set haptic configuration to hid device %d: %m (err: %d)\n",
109			hid_id, err);
110		return 1;
111	}
112	return 0;
113}
114
115int main(int argc, char **argv)
116{
117	struct hid_surface_dial *skel;
118	const char *optstr = "r:";
119	struct bpf_link *link;
120	const char *sysfs_path;
121	int err, opt, hid_id, resolution = 72;
122
123	while ((opt = getopt(argc, argv, optstr)) != -1) {
124		switch (opt) {
125		case 'r':
126			{
127				char *endp = NULL;
128				long l = -1;
129
130				if (optarg) {
131					l = strtol(optarg, &endp, 10);
132					if (endp && *endp)
133						l = -1;
134				}
135
136				if (l < 0) {
137					fprintf(stderr,
138						"invalid r option %s - expecting a number\n",
139						optarg ? optarg : "");
140					exit(EXIT_FAILURE);
141				};
142
143				resolution = (int) l;
144				break;
145			}
146		default:
147			usage(basename(argv[0]));
148			return 1;
149		}
150	}
151
152	if (optind == argc) {
153		usage(basename(argv[0]));
154		return 1;
155	}
156
157	sysfs_path = argv[optind];
158	if (!sysfs_path) {
159		perror("sysfs");
160		return 1;
161	}
162
163	skel = hid_surface_dial__open();
164	if (!skel) {
165		fprintf(stderr, "%s  %s:%d", __func__, __FILE__, __LINE__);
166		return -1;
167	}
168
169	hid_id = get_hid_id(sysfs_path);
170	if (hid_id < 0) {
171		fprintf(stderr, "can not open HID device: %m\n");
172		return 1;
173	}
174
175	skel->struct_ops.surface_dial->hid_id = hid_id;
176
177	err = hid_surface_dial__load(skel);
178	if (err < 0) {
179		fprintf(stderr, "can not load HID-BPF program: %m\n");
180		return 1;
181	}
182
183	skel->data->resolution = resolution;
184	skel->data->physical = (int)(resolution / 72);
185
186	link = bpf_map__attach_struct_ops(skel->maps.surface_dial);
187	if (!link) {
188		fprintf(stderr, "can not attach HID-BPF program: %m\n");
189		return 1;
190	}
191
192	signal(SIGINT, int_exit);
193	signal(SIGTERM, int_exit);
194
195	set_haptic(skel, hid_id);
196
197	while (running)
198		sleep(1);
199
200	hid_surface_dial__destroy(skel);
201
202	return 0;
203}