// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2022 Benjamin Tissoires * * This program will morph the Microsoft Surface Dial into a mouse, * and depending on the chosen resolution enable or not the haptic feedback: * - a resolution (-r) of 3600 will report 3600 "ticks" in one full rotation * without haptic feedback * - any other resolution will report N "ticks" in a full rotation with haptic * feedback * * A good default for low resolution haptic scrolling is 72 (1 "tick" every 5 * degrees), and set to 3600 for smooth scrolling. */ #include <assert.h> #include <errno.h> #include <fcntl.h> #include <libgen.h> #include <signal.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> #include <unistd.h> #include <linux/bpf.h> #include <linux/errno.h> #include <bpf/bpf.h> #include <bpf/libbpf.h> #include "hid_surface_dial.skel.h" #include "hid_bpf_attach.h" static bool running = true; struct haptic_syscall_args { unsigned int hid; int retval; }; static void int_exit(int sig) { running = false; exit(0); } static void usage(const char *prog) { fprintf(stderr, "%s: %s [OPTIONS] /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n" " OPTIONS:\n" " -r N\t set the given resolution to the device (number of ticks per 360°)\n\n", __func__, prog); fprintf(stderr, "This program will morph the Microsoft Surface Dial into a mouse,\n" "and depending on the chosen resolution enable or not the haptic feedback:\n" "- a resolution (-r) of 3600 will report 3600 'ticks' in one full rotation\n" " without haptic feedback\n" "- any other resolution will report N 'ticks' in a full rotation with haptic\n" " feedback\n" "\n" "A good default for low resolution haptic scrolling is 72 (1 'tick' every 5\n" "degrees), and set to 3600 for smooth scrolling.\n"); } static int get_hid_id(const char *path) { const char *str_id, *dir; char uevent[1024]; int fd; memset(uevent, 0, sizeof(uevent)); snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path); fd = open(uevent, O_RDONLY | O_NONBLOCK); if (fd < 0) return -ENOENT; close(fd); dir = basename((char *)path); str_id = dir + sizeof("0003:0001:0A37."); return (int)strtol(str_id, NULL, 16); } static int attach_prog(struct hid_surface_dial *skel, struct bpf_program *prog, int hid_id) { struct attach_prog_args args = { .hid = hid_id, .retval = -1, }; int attach_fd, err; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, .ctx_in = &args, .ctx_size_in = sizeof(args), ); attach_fd = bpf_program__fd(skel->progs.attach_prog); if (attach_fd < 0) { fprintf(stderr, "can't locate attach prog: %m\n"); return 1; } args.prog_fd = bpf_program__fd(prog); err = bpf_prog_test_run_opts(attach_fd, &tattr); if (err) { fprintf(stderr, "can't attach prog to hid device %d: %m (err: %d)\n", hid_id, err); return 1; } return 0; } static int set_haptic(struct hid_surface_dial *skel, int hid_id) { struct haptic_syscall_args args = { .hid = hid_id, .retval = -1, }; int haptic_fd, err; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, .ctx_in = &args, .ctx_size_in = sizeof(args), ); haptic_fd = bpf_program__fd(skel->progs.set_haptic); if (haptic_fd < 0) { fprintf(stderr, "can't locate haptic prog: %m\n"); return 1; } err = bpf_prog_test_run_opts(haptic_fd, &tattr); if (err) { fprintf(stderr, "can't set haptic configuration to hid device %d: %m (err: %d)\n", hid_id, err); return 1; } return 0; } int main(int argc, char **argv) { struct hid_surface_dial *skel; struct bpf_program *prog; const char *optstr = "r:"; const char *sysfs_path; int opt, hid_id, resolution = 72; while ((opt = getopt(argc, argv, optstr)) != -1) { switch (opt) { case 'r': { char *endp = NULL; long l = -1; if (optarg) { l = strtol(optarg, &endp, 10); if (endp && *endp) l = -1; } if (l < 0) { fprintf(stderr, "invalid r option %s - expecting a number\n", optarg ? optarg : ""); exit(EXIT_FAILURE); }; resolution = (int) l; break; } default: usage(basename(argv[0])); return 1; } } if (optind == argc) { usage(basename(argv[0])); return 1; } sysfs_path = argv[optind]; if (!sysfs_path) { perror("sysfs"); return 1; } skel = hid_surface_dial__open_and_load(); if (!skel) { fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__); return -1; } hid_id = get_hid_id(sysfs_path); if (hid_id < 0) { fprintf(stderr, "can not open HID device: %m\n"); return 1; } skel->data->resolution = resolution; skel->data->physical = (int)(resolution / 72); bpf_object__for_each_program(prog, *skel->skeleton->obj) { /* ignore syscalls */ if (bpf_program__get_type(prog) != BPF_PROG_TYPE_TRACING) continue; attach_prog(skel, prog, hid_id); } signal(SIGINT, int_exit); signal(SIGTERM, int_exit); set_haptic(skel, hid_id); while (running) sleep(1); hid_surface_dial__destroy(skel); return 0; }