Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

samples/hid: add new hid BPF example

Everything should be available in the selftest part of the tree, but
providing an example without uhid and hidraw will be more easy to
follow for users.

This example will probably ever only work on the Etekcity Scroll 6E
because we need to adapt the various raw values to the actual device.

On that device, the X and Y axis will be swapped and inverted, and on
any other device, chances are high that the device will not work until
Ctrl-C is hit.

The Makefiles are taken from samples/bpf to not reinvent the wheel and
to force using in-kernel libbpf and bpftool.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

authored by

Benjamin Tissoires and committed by
Jiri Kosina
6008105b 80e189f2

+647
+1
MAINTAINERS
··· 9100 9100 F: drivers/hid/ 9101 9101 F: include/linux/hid* 9102 9102 F: include/uapi/linux/hid* 9103 + F: samples/hid/ 9103 9104 F: tools/testing/selftests/hid/ 9104 9105 9105 9106 HID LOGITECH DRIVERS
+7
samples/hid/.gitignore
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + hid_mouse 3 + *.out 4 + *.skel.h 5 + /vmlinux.h 6 + /bpftool/ 7 + /libbpf/
+246
samples/hid/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + 3 + HID_SAMPLES_PATH ?= $(abspath $(srctree)/$(src)) 4 + TOOLS_PATH := $(HID_SAMPLES_PATH)/../../tools 5 + 6 + pound := \# 7 + 8 + # List of programs to build 9 + tprogs-y += hid_mouse 10 + 11 + # Libbpf dependencies 12 + LIBBPF_SRC = $(TOOLS_PATH)/lib/bpf 13 + LIBBPF_OUTPUT = $(abspath $(HID_SAMPLES_PATH))/libbpf 14 + LIBBPF_DESTDIR = $(LIBBPF_OUTPUT) 15 + LIBBPF_INCLUDE = $(LIBBPF_DESTDIR)/include 16 + LIBBPF = $(LIBBPF_OUTPUT)/libbpf.a 17 + 18 + EXTRA_HEADERS := hid_bpf_attach.h 19 + EXTRA_BPF_HEADERS := hid_bpf_helpers.h 20 + 21 + hid_mouse-objs := hid_mouse.o 22 + 23 + # Tell kbuild to always build the programs 24 + always-y := $(tprogs-y) 25 + 26 + ifeq ($(ARCH), arm) 27 + # Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux 28 + # headers when arm instruction set identification is requested. 29 + ARM_ARCH_SELECTOR := $(filter -D__LINUX_ARM_ARCH__%, $(KBUILD_CFLAGS)) 30 + BPF_EXTRA_CFLAGS := $(ARM_ARCH_SELECTOR) 31 + TPROGS_CFLAGS += $(ARM_ARCH_SELECTOR) 32 + endif 33 + 34 + ifeq ($(ARCH), mips) 35 + TPROGS_CFLAGS += -D__SANE_USERSPACE_TYPES__ 36 + ifdef CONFIG_MACH_LOONGSON64 37 + BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-loongson64 38 + BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-generic 39 + endif 40 + endif 41 + 42 + TPROGS_CFLAGS += -Wall -O2 43 + TPROGS_CFLAGS += -Wmissing-prototypes 44 + TPROGS_CFLAGS += -Wstrict-prototypes 45 + 46 + TPROGS_CFLAGS += -I$(objtree)/usr/include 47 + TPROGS_CFLAGS += -I$(LIBBPF_INCLUDE) 48 + TPROGS_CFLAGS += -I$(srctree)/tools/include 49 + 50 + ifdef SYSROOT 51 + TPROGS_CFLAGS += --sysroot=$(SYSROOT) 52 + TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib 53 + endif 54 + 55 + TPROGS_LDLIBS += $(LIBBPF) -lelf -lz 56 + 57 + # Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline: 58 + # make M=samples/bpf LLC=~/git/llvm-project/llvm/build/bin/llc CLANG=~/git/llvm-project/llvm/build/bin/clang 59 + LLC ?= llc 60 + CLANG ?= clang 61 + OPT ?= opt 62 + LLVM_DIS ?= llvm-dis 63 + LLVM_OBJCOPY ?= llvm-objcopy 64 + LLVM_READELF ?= llvm-readelf 65 + BTF_PAHOLE ?= pahole 66 + 67 + # Detect that we're cross compiling and use the cross compiler 68 + ifdef CROSS_COMPILE 69 + CLANG_ARCH_ARGS = --target=$(notdir $(CROSS_COMPILE:%-=%)) 70 + endif 71 + 72 + # Don't evaluate probes and warnings if we need to run make recursively 73 + ifneq ($(src),) 74 + HDR_PROBE := $(shell printf "$(pound)include <linux/types.h>\n struct list_head { int a; }; int main() { return 0; }" | \ 75 + $(CC) $(TPROGS_CFLAGS) $(TPROGS_LDFLAGS) -x c - \ 76 + -o /dev/null 2>/dev/null && echo okay) 77 + 78 + ifeq ($(HDR_PROBE),) 79 + $(warning WARNING: Detected possible issues with include path.) 80 + $(warning WARNING: Please install kernel headers locally (make headers_install).) 81 + endif 82 + 83 + BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris) 84 + BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF) 85 + BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm') 86 + BTF_LLVM_PROBE := $(shell echo "int main() { return 0; }" | \ 87 + $(CLANG) -target bpf -O2 -g -c -x c - -o ./llvm_btf_verify.o; \ 88 + $(LLVM_READELF) -S ./llvm_btf_verify.o | grep BTF; \ 89 + /bin/rm -f ./llvm_btf_verify.o) 90 + 91 + BPF_EXTRA_CFLAGS += -fno-stack-protector 92 + ifneq ($(BTF_LLVM_PROBE),) 93 + BPF_EXTRA_CFLAGS += -g 94 + else 95 + ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),) 96 + BPF_EXTRA_CFLAGS += -g 97 + LLC_FLAGS += -mattr=dwarfris 98 + DWARF2BTF = y 99 + endif 100 + endif 101 + endif 102 + 103 + # Trick to allow make to be run from this directory 104 + all: 105 + $(MAKE) -C ../../ M=$(CURDIR) HID_SAMPLES_PATH=$(CURDIR) 106 + 107 + clean: 108 + $(MAKE) -C ../../ M=$(CURDIR) clean 109 + @find $(CURDIR) -type f -name '*~' -delete 110 + @$(RM) -r $(CURDIR)/libbpf $(CURDIR)/bpftool 111 + 112 + $(LIBBPF): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT) 113 + # Fix up variables inherited from Kbuild that tools/ build system won't like 114 + $(MAKE) -C $(LIBBPF_SRC) RM='rm -rf' EXTRA_CFLAGS="$(TPROGS_CFLAGS)" \ 115 + LDFLAGS=$(TPROGS_LDFLAGS) srctree=$(HID_SAMPLES_PATH)/../../ \ 116 + O= OUTPUT=$(LIBBPF_OUTPUT)/ DESTDIR=$(LIBBPF_DESTDIR) prefix= \ 117 + $@ install_headers 118 + 119 + BPFTOOLDIR := $(TOOLS_PATH)/bpf/bpftool 120 + BPFTOOL_OUTPUT := $(abspath $(HID_SAMPLES_PATH))/bpftool 121 + BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool 122 + $(BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) | $(BPFTOOL_OUTPUT) 123 + $(MAKE) -C $(BPFTOOLDIR) srctree=$(HID_SAMPLES_PATH)/../../ \ 124 + OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap 125 + 126 + $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT): 127 + $(call msg,MKDIR,$@) 128 + $(Q)mkdir -p $@ 129 + 130 + FORCE: 131 + 132 + 133 + # Verify LLVM compiler tools are available and bpf target is supported by llc 134 + .PHONY: verify_cmds verify_target_bpf $(CLANG) $(LLC) 135 + 136 + verify_cmds: $(CLANG) $(LLC) 137 + @for TOOL in $^ ; do \ 138 + if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \ 139 + echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\ 140 + exit 1; \ 141 + else true; fi; \ 142 + done 143 + 144 + verify_target_bpf: verify_cmds 145 + @if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \ 146 + echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\ 147 + echo " NOTICE: LLVM version >= 3.7.1 required" ;\ 148 + exit 2; \ 149 + else true; fi 150 + 151 + $(HID_SAMPLES_PATH)/*.c: verify_target_bpf $(LIBBPF) 152 + $(src)/*.c: verify_target_bpf $(LIBBPF) 153 + 154 + libbpf_hdrs: $(LIBBPF) 155 + 156 + .PHONY: libbpf_hdrs 157 + 158 + $(obj)/hid_mouse.o: $(obj)/hid_mouse.skel.h 159 + 160 + -include $(HID_SAMPLES_PATH)/Makefile.target 161 + 162 + VMLINUX_BTF_PATHS ?= $(abspath $(if $(O),$(O)/vmlinux)) \ 163 + $(abspath $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)) \ 164 + $(abspath ./vmlinux) 165 + VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) 166 + 167 + $(obj)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) 168 + ifeq ($(VMLINUX_H),) 169 + ifeq ($(VMLINUX_BTF),) 170 + $(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)",\ 171 + build the kernel or set VMLINUX_BTF or VMLINUX_H variable) 172 + endif 173 + $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ 174 + else 175 + $(Q)cp "$(VMLINUX_H)" $@ 176 + endif 177 + 178 + clean-files += vmlinux.h 179 + 180 + # Get Clang's default includes on this system, as opposed to those seen by 181 + # '-target bpf'. This fixes "missing" files on some architectures/distros, 182 + # such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. 183 + # 184 + # Use '-idirafter': Don't interfere with include mechanics except where the 185 + # build would have failed anyways. 186 + define get_sys_includes 187 + $(shell $(1) -v -E - </dev/null 2>&1 \ 188 + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ 189 + $(shell $(1) -dM -E - </dev/null | grep '#define __riscv_xlen ' | sed 's/#define /-D/' | sed 's/ /=/') 190 + endef 191 + 192 + CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG)) 193 + 194 + EXTRA_BPF_HEADERS_SRC := $(addprefix $(src)/,$(EXTRA_BPF_HEADERS)) 195 + 196 + $(obj)/%.bpf.o: $(src)/%.bpf.c $(EXTRA_BPF_HEADERS_SRC) $(obj)/vmlinux.h 197 + @echo " CLANG-BPF " $@ 198 + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(SRCARCH) \ 199 + -Wno-compare-distinct-pointer-types -I$(srctree)/include \ 200 + -I$(srctree)/samples/bpf -I$(srctree)/tools/include \ 201 + -I$(LIBBPF_INCLUDE) $(CLANG_SYS_INCLUDES) \ 202 + -c $(filter %.bpf.c,$^) -o $@ 203 + 204 + LINKED_SKELS := hid_mouse.skel.h 205 + clean-files += $(LINKED_SKELS) 206 + 207 + hid_mouse.skel.h-deps := hid_mouse.bpf.o hid_bpf_attach.bpf.o 208 + 209 + LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.bpf.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps))) 210 + 211 + BPF_SRCS_LINKED := $(notdir $(wildcard $(src)/*.bpf.c)) 212 + BPF_OBJS_LINKED := $(patsubst %.bpf.c,$(obj)/%.bpf.o, $(BPF_SRCS_LINKED)) 213 + BPF_SKELS_LINKED := $(addprefix $(obj)/,$(LINKED_SKELS)) 214 + 215 + $(BPF_SKELS_LINKED): $(BPF_OBJS_LINKED) $(BPFTOOL) 216 + @echo " BPF GEN-OBJ " $(@:.skel.h=) 217 + $(Q)$(BPFTOOL) gen object $(@:.skel.h=.lbpf.o) $(addprefix $(obj)/,$($(@F)-deps)) 218 + @echo " BPF GEN-SKEL" $(@:.skel.h=) 219 + $(Q)$(BPFTOOL) gen skeleton $(@:.skel.h=.lbpf.o) name $(notdir $(@:.skel.h=)) > $@ 220 + 221 + # asm/sysreg.h - inline assembly used by it is incompatible with llvm. 222 + # But, there is no easy way to fix it, so just exclude it since it is 223 + # useless for BPF samples. 224 + # below we use long chain of commands, clang | opt | llvm-dis | llc, 225 + # to generate final object file. 'clang' compiles the source into IR 226 + # with native target, e.g., x64, arm64, etc. 'opt' does bpf CORE IR builtin 227 + # processing (llvm12) and IR optimizations. 'llvm-dis' converts 228 + # 'opt' output to IR, and finally 'llc' generates bpf byte code. 229 + $(obj)/%.o: $(src)/%.c 230 + @echo " CLANG-bpf " $@ 231 + $(Q)$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(BPF_EXTRA_CFLAGS) \ 232 + -I$(obj) -I$(srctree)/tools/testing/selftests/bpf/ \ 233 + -I$(LIBBPF_INCLUDE) \ 234 + -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ 235 + -D__TARGET_ARCH_$(SRCARCH) -Wno-compare-distinct-pointer-types \ 236 + -Wno-gnu-variable-sized-type-not-at-end \ 237 + -Wno-address-of-packed-member -Wno-tautological-compare \ 238 + -Wno-unknown-warning-option $(CLANG_ARCH_ARGS) \ 239 + -fno-asynchronous-unwind-tables \ 240 + -I$(srctree)/samples/hid/ \ 241 + -O2 -emit-llvm -Xclang -disable-llvm-passes -c $< -o - | \ 242 + $(OPT) -O2 -mtriple=bpf-pc-linux | $(LLVM_DIS) | \ 243 + $(LLC) -march=bpf $(LLC_FLAGS) -filetype=obj -o $@ 244 + ifeq ($(DWARF2BTF),y) 245 + $(BTF_PAHOLE) -J $@ 246 + endif
+75
samples/hid/Makefile.target
··· 1 + # SPDX-License-Identifier: GPL-2.0 2 + # ========================================================================== 3 + # Building binaries on the host system 4 + # Binaries are not used during the compilation of the kernel, and intended 5 + # to be build for target board, target board can be host of course. Added to 6 + # build binaries to run not on host system. 7 + # 8 + # Sample syntax 9 + # tprogs-y := xsk_example 10 + # Will compile xsk_example.c and create an executable named xsk_example 11 + # 12 + # tprogs-y := xdpsock 13 + # xdpsock-objs := xdpsock_1.o xdpsock_2.o 14 + # Will compile xdpsock_1.c and xdpsock_2.c, and then link the executable 15 + # xdpsock, based on xdpsock_1.o and xdpsock_2.o 16 + # 17 + # Derived from scripts/Makefile.host 18 + # 19 + __tprogs := $(sort $(tprogs-y)) 20 + 21 + # C code 22 + # Executables compiled from a single .c file 23 + tprog-csingle := $(foreach m,$(__tprogs), \ 24 + $(if $($(m)-objs),,$(m))) 25 + 26 + # C executables linked based on several .o files 27 + tprog-cmulti := $(foreach m,$(__tprogs),\ 28 + $(if $($(m)-objs),$(m))) 29 + 30 + # Object (.o) files compiled from .c files 31 + tprog-cobjs := $(sort $(foreach m,$(__tprogs),$($(m)-objs))) 32 + 33 + tprog-csingle := $(addprefix $(obj)/,$(tprog-csingle)) 34 + tprog-cmulti := $(addprefix $(obj)/,$(tprog-cmulti)) 35 + tprog-cobjs := $(addprefix $(obj)/,$(tprog-cobjs)) 36 + 37 + ##### 38 + # Handle options to gcc. Support building with separate output directory 39 + 40 + _tprogc_flags = $(TPROGS_CFLAGS) \ 41 + $(TPROGCFLAGS_$(basetarget).o) 42 + 43 + # $(objtree)/$(obj) for including generated headers from checkin source files 44 + ifeq ($(KBUILD_EXTMOD),) 45 + ifdef building_out_of_srctree 46 + _tprogc_flags += -I $(objtree)/$(obj) 47 + endif 48 + endif 49 + 50 + tprogc_flags = -Wp,-MD,$(depfile) $(_tprogc_flags) 51 + 52 + # Create executable from a single .c file 53 + # tprog-csingle -> Executable 54 + quiet_cmd_tprog-csingle = CC $@ 55 + cmd_tprog-csingle = $(CC) $(tprogc_flags) $(TPROGS_LDFLAGS) -o $@ $< \ 56 + $(TPROGS_LDLIBS) $(TPROGLDLIBS_$(@F)) 57 + $(tprog-csingle): $(obj)/%: $(src)/%.c FORCE 58 + $(call if_changed_dep,tprog-csingle) 59 + 60 + # Link an executable based on list of .o files, all plain c 61 + # tprog-cmulti -> executable 62 + quiet_cmd_tprog-cmulti = LD $@ 63 + cmd_tprog-cmulti = $(CC) $(tprogc_flags) $(TPROGS_LDFLAGS) -o $@ \ 64 + $(addprefix $(obj)/,$($(@F)-objs)) \ 65 + $(TPROGS_LDLIBS) $(TPROGLDLIBS_$(@F)) 66 + $(tprog-cmulti): $(tprog-cobjs) FORCE 67 + $(call if_changed,tprog-cmulti) 68 + $(call multi_depend, $(tprog-cmulti), , -objs) 69 + 70 + # Create .o file from a single .c file 71 + # tprog-cobjs -> .o 72 + quiet_cmd_tprog-cobjs = CC $@ 73 + cmd_tprog-cobjs = $(CC) $(tprogc_flags) -c -o $@ $< 74 + $(tprog-cobjs): $(obj)/%.o: $(src)/%.c FORCE 75 + $(call if_changed_dep,tprog-cobjs)
+18
samples/hid/hid_bpf_attach.bpf.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* Copyright (c) 2022 Benjamin Tissoires 3 + */ 4 + 5 + #include "vmlinux.h" 6 + #include <bpf/bpf_helpers.h> 7 + #include <bpf/bpf_tracing.h> 8 + #include "hid_bpf_attach.h" 9 + #include "hid_bpf_helpers.h" 10 + 11 + SEC("syscall") 12 + int attach_prog(struct attach_prog_args *ctx) 13 + { 14 + ctx->retval = hid_bpf_attach_prog(ctx->hid, 15 + ctx->prog_fd, 16 + 0); 17 + return 0; 18 + }
+14
samples/hid/hid_bpf_attach.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* Copyright (c) 2022 Benjamin Tissoires 3 + */ 4 + 5 + #ifndef __HID_BPF_ATTACH_H 6 + #define __HID_BPF_ATTACH_H 7 + 8 + struct attach_prog_args { 9 + int prog_fd; 10 + unsigned int hid; 11 + int retval; 12 + }; 13 + 14 + #endif /* __HID_BPF_ATTACH_H */
+19
samples/hid/hid_bpf_helpers.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only */ 2 + /* Copyright (c) 2022 Benjamin Tissoires 3 + */ 4 + 5 + #ifndef __HID_BPF_HELPERS_H 6 + #define __HID_BPF_HELPERS_H 7 + 8 + /* following are kfuncs exported by HID for HID-BPF */ 9 + extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, 10 + unsigned int offset, 11 + const size_t __sz) __ksym; 12 + extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym; 13 + extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, 14 + __u8 *data, 15 + size_t buf__sz, 16 + enum hid_report_type type, 17 + enum hid_class_request reqtype) __ksym; 18 + 19 + #endif /* __HID_BPF_HELPERS_H */
+112
samples/hid/hid_mouse.bpf.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #include "vmlinux.h" 4 + #include <bpf/bpf_helpers.h> 5 + #include <bpf/bpf_tracing.h> 6 + #include "hid_bpf_helpers.h" 7 + 8 + SEC("fmod_ret/hid_bpf_device_event") 9 + int BPF_PROG(hid_y_event, struct hid_bpf_ctx *hctx) 10 + { 11 + s16 y; 12 + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */); 13 + 14 + if (!data) 15 + return 0; /* EPERM check */ 16 + 17 + bpf_printk("event: size: %d", hctx->size); 18 + bpf_printk("incoming event: %02x %02x %02x", 19 + data[0], 20 + data[1], 21 + data[2]); 22 + bpf_printk(" %02x %02x %02x", 23 + data[3], 24 + data[4], 25 + data[5]); 26 + bpf_printk(" %02x %02x %02x", 27 + data[6], 28 + data[7], 29 + data[8]); 30 + 31 + y = data[3] | (data[4] << 8); 32 + 33 + y = -y; 34 + 35 + data[3] = y & 0xFF; 36 + data[4] = (y >> 8) & 0xFF; 37 + 38 + bpf_printk("modified event: %02x %02x %02x", 39 + data[0], 40 + data[1], 41 + data[2]); 42 + bpf_printk(" %02x %02x %02x", 43 + data[3], 44 + data[4], 45 + data[5]); 46 + bpf_printk(" %02x %02x %02x", 47 + data[6], 48 + data[7], 49 + data[8]); 50 + 51 + return 0; 52 + } 53 + 54 + SEC("fmod_ret/hid_bpf_device_event") 55 + int BPF_PROG(hid_x_event, struct hid_bpf_ctx *hctx) 56 + { 57 + s16 x; 58 + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */); 59 + 60 + if (!data) 61 + return 0; /* EPERM check */ 62 + 63 + x = data[1] | (data[2] << 8); 64 + 65 + x = -x; 66 + 67 + data[1] = x & 0xFF; 68 + data[2] = (x >> 8) & 0xFF; 69 + return 0; 70 + } 71 + 72 + SEC("fmod_ret/hid_bpf_rdesc_fixup") 73 + int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx) 74 + { 75 + __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */); 76 + 77 + if (!data) 78 + return 0; /* EPERM check */ 79 + 80 + bpf_printk("rdesc: %02x %02x %02x", 81 + data[0], 82 + data[1], 83 + data[2]); 84 + bpf_printk(" %02x %02x %02x", 85 + data[3], 86 + data[4], 87 + data[5]); 88 + bpf_printk(" %02x %02x %02x ...", 89 + data[6], 90 + data[7], 91 + data[8]); 92 + 93 + /* 94 + * The original report descriptor contains: 95 + * 96 + * 0x05, 0x01, // Usage Page (Generic Desktop) 30 97 + * 0x16, 0x01, 0x80, // Logical Minimum (-32767) 32 98 + * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 35 99 + * 0x09, 0x30, // Usage (X) 38 100 + * 0x09, 0x31, // Usage (Y) 40 101 + * 102 + * So byte 39 contains Usage X and byte 41 Usage Y. 103 + * 104 + * We simply swap the axes here. 105 + */ 106 + data[39] = 0x31; 107 + data[41] = 0x30; 108 + 109 + return 0; 110 + } 111 + 112 + char _license[] SEC("license") = "GPL";
+155
samples/hid/hid_mouse.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* Copyright (c) 2022 Benjamin Tissoires 3 + * 4 + * This is a pure HID-BPF example, and should be considered as such: 5 + * on the Etekcity Scroll 6E, the X and Y axes will be swapped and 6 + * inverted. On any other device... Not sure what this will do. 7 + * 8 + * This C main file is generic though. To adapt the code and test, users 9 + * must amend only the .bpf.c file, which this program will load any 10 + * eBPF program it finds. 11 + */ 12 + 13 + #include <assert.h> 14 + #include <errno.h> 15 + #include <fcntl.h> 16 + #include <libgen.h> 17 + #include <signal.h> 18 + #include <stdbool.h> 19 + #include <stdio.h> 20 + #include <stdlib.h> 21 + #include <string.h> 22 + #include <sys/resource.h> 23 + #include <unistd.h> 24 + 25 + #include <linux/bpf.h> 26 + #include <linux/errno.h> 27 + 28 + #include <bpf/bpf.h> 29 + #include <bpf/libbpf.h> 30 + 31 + #include "hid_mouse.skel.h" 32 + #include "hid_bpf_attach.h" 33 + 34 + static bool running = true; 35 + 36 + static void int_exit(int sig) 37 + { 38 + running = false; 39 + exit(0); 40 + } 41 + 42 + static void usage(const char *prog) 43 + { 44 + fprintf(stderr, 45 + "%s: %s /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n", 46 + __func__, prog); 47 + fprintf(stderr, 48 + "This program will upload and attach a HID-BPF program to the given device.\n" 49 + "On the Etekcity Scroll 6E, the X and Y axis will be inverted, but on any other\n" 50 + "device, chances are high that the device will not be working anymore\n\n" 51 + "consider this as a demo and adapt the eBPF program to your needs\n" 52 + "Hit Ctrl-C to unbind the program and reset the device\n"); 53 + } 54 + 55 + static int get_hid_id(const char *path) 56 + { 57 + const char *str_id, *dir; 58 + char uevent[1024]; 59 + int fd; 60 + 61 + memset(uevent, 0, sizeof(uevent)); 62 + snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path); 63 + 64 + fd = open(uevent, O_RDONLY | O_NONBLOCK); 65 + if (fd < 0) 66 + return -ENOENT; 67 + 68 + close(fd); 69 + 70 + dir = basename((char *)path); 71 + 72 + str_id = dir + sizeof("0003:0001:0A37."); 73 + return (int)strtol(str_id, NULL, 16); 74 + } 75 + 76 + int main(int argc, char **argv) 77 + { 78 + struct hid_mouse *skel; 79 + struct bpf_program *prog; 80 + int err; 81 + const char *optstr = ""; 82 + const char *sysfs_path; 83 + int opt, hid_id, attach_fd; 84 + struct attach_prog_args args = { 85 + .retval = -1, 86 + }; 87 + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, 88 + .ctx_in = &args, 89 + .ctx_size_in = sizeof(args), 90 + ); 91 + 92 + while ((opt = getopt(argc, argv, optstr)) != -1) { 93 + switch (opt) { 94 + default: 95 + usage(basename(argv[0])); 96 + return 1; 97 + } 98 + } 99 + 100 + if (optind == argc) { 101 + usage(basename(argv[0])); 102 + return 1; 103 + } 104 + 105 + sysfs_path = argv[optind]; 106 + if (!sysfs_path) { 107 + perror("sysfs"); 108 + return 1; 109 + } 110 + 111 + skel = hid_mouse__open_and_load(); 112 + if (!skel) { 113 + fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__); 114 + return -1; 115 + } 116 + 117 + hid_id = get_hid_id(sysfs_path); 118 + 119 + if (hid_id < 0) { 120 + fprintf(stderr, "can not open HID device: %m\n"); 121 + return 1; 122 + } 123 + args.hid = hid_id; 124 + 125 + attach_fd = bpf_program__fd(skel->progs.attach_prog); 126 + if (attach_fd < 0) { 127 + fprintf(stderr, "can't locate attach prog: %m\n"); 128 + return 1; 129 + } 130 + 131 + bpf_object__for_each_program(prog, *skel->skeleton->obj) { 132 + /* ignore syscalls */ 133 + if (bpf_program__get_type(prog) != BPF_PROG_TYPE_TRACING) 134 + continue; 135 + 136 + args.retval = -1; 137 + args.prog_fd = bpf_program__fd(prog); 138 + err = bpf_prog_test_run_opts(attach_fd, &tattr); 139 + if (err) { 140 + fprintf(stderr, "can't attach prog to hid device %d: %m (err: %d)\n", 141 + hid_id, err); 142 + return 1; 143 + } 144 + } 145 + 146 + signal(SIGINT, int_exit); 147 + signal(SIGTERM, int_exit); 148 + 149 + while (running) 150 + sleep(1); 151 + 152 + hid_mouse__destroy(skel); 153 + 154 + return 0; 155 + }