Re: [PATCH bpf-next v6 12/23] HID: initial BPF implementation

From: Quentin Monnet
Date: Wed Jul 13 2022 - 04:49:04 EST


On 12/07/2022 15:58, Benjamin Tissoires wrote:
> Declare an entry point that can use fmod_ret BPF programs, and
> also an API to access and change the incoming data.
>
> A simpler implementation would consist in just calling
> hid_bpf_device_event() for any incoming event and let users deal
> with the fact that they will be called for any event of any device.
>
> The goal of HID-BPF is to partially replace drivers, so this situation
> can be problematic because we might have programs which will step on
> each other toes.
>
> For that, we add a new API hid_bpf_attach_prog() that can be called
> from a syscall and we manually deal with a jump table in hid-bpf.
>
> Whenever we add a program to the jump table (in other words, when we
> attach a program to a HID device), we keep the number of time we added
> this program in the jump table so we can release it whenever there are
> no other users.
>
> HID devices have an RCU protected list of available programs in the
> jump table, and those programs are called one after the other thanks
> to bpf_tail_call().
>
> To achieve the detection of users losing their fds on the programs we
> attached, we add 2 tracing facilities on bpf_prog_release() (for when
> a fd is closed) and bpf_free_inode() (for when a pinned program gets
> unpinned).
>
> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@xxxxxxxxxx>
>
> ---
>
> changes in v6:
> - use BTF_ID to get the btf_id of hid_bpf_device_event instead of
> loading/unloading a dummy eBPF program.
>
> changes in v5:
> - all the HID bpf operations are in their dedicated module
> - a bpf program is preloaded on startup so we can call subsequent
> calls with bpf_tail_call
> - make hid_bpf_ctx more compact
> - add a dedicated hid_bpf_attach_prog() API
> - store the list of progs in each hdev
> - monitor the calls to bpf_prog_release to automatically release
> attached progs when there are no other users
> - add kernel docs directly when functions are defined
>
> new-ish in v4:
> - far from complete, but gives an overview of what we can do now.
>
> fix initial HID-bpf
> ---
> drivers/hid/Kconfig | 2 +
> drivers/hid/Makefile | 2 +
> drivers/hid/bpf/Kconfig | 19 +
> drivers/hid/bpf/Makefile | 11 +
> drivers/hid/bpf/entrypoints/Makefile | 88 +++
> drivers/hid/bpf/entrypoints/README | 4 +
> drivers/hid/bpf/entrypoints/entrypoints.bpf.c | 66 ++
> .../hid/bpf/entrypoints/entrypoints.lskel.h | 682 ++++++++++++++++++
> drivers/hid/bpf/hid_bpf_dispatch.c | 235 ++++++
> drivers/hid/bpf/hid_bpf_dispatch.h | 27 +
> drivers/hid/bpf/hid_bpf_jmp_table.c | 568 +++++++++++++++
> drivers/hid/hid-core.c | 15 +
> include/linux/hid.h | 5 +
> include/linux/hid_bpf.h | 99 +++
> include/uapi/linux/hid_bpf.h | 25 +
> tools/include/uapi/linux/hid.h | 62 ++
> tools/include/uapi/linux/hid_bpf.h | 25 +
> 17 files changed, 1935 insertions(+)
> create mode 100644 drivers/hid/bpf/Kconfig
> create mode 100644 drivers/hid/bpf/Makefile
> create mode 100644 drivers/hid/bpf/entrypoints/Makefile
> create mode 100644 drivers/hid/bpf/entrypoints/README
> create mode 100644 drivers/hid/bpf/entrypoints/entrypoints.bpf.c
> create mode 100644 drivers/hid/bpf/entrypoints/entrypoints.lskel.h
> create mode 100644 drivers/hid/bpf/hid_bpf_dispatch.c
> create mode 100644 drivers/hid/bpf/hid_bpf_dispatch.h
> create mode 100644 drivers/hid/bpf/hid_bpf_jmp_table.c
> create mode 100644 include/linux/hid_bpf.h
> create mode 100644 include/uapi/linux/hid_bpf.h
> create mode 100644 tools/include/uapi/linux/hid.h
> create mode 100644 tools/include/uapi/linux/hid_bpf.h
>
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index 70da5931082f..4bedced90545 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -1310,6 +1310,8 @@ endmenu
>
> endif # HID
>
> +source "drivers/hid/bpf/Kconfig"
> +
> source "drivers/hid/usbhid/Kconfig"
>
> source "drivers/hid/i2c-hid/Kconfig"
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index cac2cbe26d11..94672a2d4fbb 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -5,6 +5,8 @@
> hid-y := hid-core.o hid-input.o hid-quirks.o
> hid-$(CONFIG_DEBUG_FS) += hid-debug.o
>
> +obj-$(CONFIG_HID_BPF) += bpf/
> +
> obj-$(CONFIG_HID) += hid.o
> obj-$(CONFIG_UHID) += uhid.o
>
> diff --git a/drivers/hid/bpf/Kconfig b/drivers/hid/bpf/Kconfig
> new file mode 100644
> index 000000000000..c54a2f07b8d7
> --- /dev/null
> +++ b/drivers/hid/bpf/Kconfig
> @@ -0,0 +1,19 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +menu "HID-BPF support"
> + #depends on x86_64
> +
> +config HID_BPF
> + bool "HID-BPF support"
> + default y
> + depends on BPF && BPF_SYSCALL
> + select HID
> + help
> + This option allows to support eBPF programs on the HID subsystem.
> + eBPF programs can fix HID devices in a lighter way than a full
> + kernel patch and allow a lot more flexibility.
> +
> + For documentation, see Documentation/hid/hid-bpf.rst
> +
> + If unsure, say Y.
> +
> +endmenu
> diff --git a/drivers/hid/bpf/Makefile b/drivers/hid/bpf/Makefile
> new file mode 100644
> index 000000000000..cf55120cf7d6
> --- /dev/null
> +++ b/drivers/hid/bpf/Makefile
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0
> +#
> +# Makefile for HID-BPF
> +#
> +
> +LIBBPF_INCLUDE = $(srctree)/tools/lib
> +
> +obj-$(CONFIG_HID_BPF) += hid_bpf.o
> +CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
> +CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
> +hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
> diff --git a/drivers/hid/bpf/entrypoints/Makefile b/drivers/hid/bpf/entrypoints/Makefile
> new file mode 100644
> index 000000000000..dd60a460c6c4
> --- /dev/null
> +++ b/drivers/hid/bpf/entrypoints/Makefile
> @@ -0,0 +1,88 @@
> +# SPDX-License-Identifier: GPL-2.0
> +OUTPUT := .output
> +abs_out := $(abspath $(OUTPUT))
> +
> +CLANG ?= clang
> +LLC ?= llc
> +LLVM_STRIP ?= llvm-strip
> +
> +TOOLS_PATH := $(abspath ../../../../tools)
> +BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
> +BPFTOOL_OUTPUT := $(abs_out)/bpftool
> +DEFAULT_BPFTOOL := $(OUTPUT)/sbin/bpftool
> +BPFTOOL ?= $(DEFAULT_BPFTOOL)
> +
> +LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
> +LIBBPF_OUTPUT := $(abs_out)/libbpf
> +LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
> +LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
> +BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
> +
> +INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
> +CFLAGS := -g -Wall
> +
> +VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
> + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
> + ../../../../vmlinux \
> + /sys/kernel/btf/vmlinux \
> + /boot/vmlinux-$(shell uname -r)
> +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
> +ifeq ($(VMLINUX_BTF),)
> +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
> +endif
> +
> +ifeq ($(V),1)
> +Q =
> +msg =
> +else
> +Q = @
> +msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
> +MAKEFLAGS += --no-print-directory
> +submake_extras := feature_display=0
> +endif
> +
> +.DELETE_ON_ERROR:
> +
> +.PHONY: all clean
> +
> +all: entrypoints.lskel.h
> +
> +clean:
> + $(call msg,CLEAN)
> + $(Q)rm -rf $(OUTPUT) entrypoints
> +
> +entrypoints.lskel.h: $(OUTPUT)/entrypoints.bpf.o | $(BPFTOOL)
> + $(call msg,GEN-SKEL,$@)
> + $(Q)$(BPFTOOL) gen skeleton -L $< > $@
> +
> +
> +$(OUTPUT)/entrypoints.bpf.o: entrypoints.bpf.c $(OUTPUT)/vmlinux.h $(BPFOBJ) | $(OUTPUT)
> + $(call msg,BPF,$@)
> + $(Q)$(CLANG) -g -O2 -target bpf $(INCLUDES) \
> + -c $(filter %.c,$^) -o $@ && \
> + $(LLVM_STRIP) -g $@
> +
> +$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
> +ifeq ($(VMLINUX_H),)
> + $(call msg,GEN,,$@)
> + $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
> +else
> + $(call msg,CP,,$@)
> + $(Q)cp "$(VMLINUX_H)" $@
> +endif
> +
> +$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
> + $(call msg,MKDIR,$@)
> + $(Q)mkdir -p $@
> +
> +$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
> + $(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
> + OUTPUT=$(abspath $(dir $@))/ prefix= \
> + DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
> +
> +$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
> + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
> + OUTPUT=$(BPFTOOL_OUTPUT)/ \
> + LIBBPF_OUTPUT=$(LIBBPF_OUTPUT)/ \
> + LIBBPF_DESTDIR=$(LIBBPF_DESTDIR)/ \
> + prefix= DESTDIR=$(abs_out)/ install-bin

Hi Benjamin,

Note that, at other locations where bpftool is needed to generate the
vmlinux.h or the skeletons, there is some work in progress to use only
the "bootstrap" version of bpftool (the intermediary bpftool binary used
to generate skeletons required for the final bpftool binary) [0]. This
is enough to generate these objects, it makes compiling the bpftool
binary faster, and solves some issues related to cross-compilation. It's
probably worth exploring in your case (or as a follow-up) as well.

Quentin

[0]
https://lore.kernel.org/all/20220712030813.865410-1-pulehui@xxxxxxxxxx/t/#u