We first use bpftool to generate a json representation of the program, and then use a python script to convert it into a C array element that will be able to be generically loaded by the kernel. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@xxxxxxxxxx> --- MAINTAINERS | 1 + drivers/hid/bpf/progs/Makefile | 105 +++++++++++ drivers/hid/bpf/progs/hid_bpf.h | 15 ++ drivers/hid/bpf/progs/hid_bpf_helpers.h | 22 +++ drivers/hid/bpf/progs/hid_bpf_progs.h | 49 +++++ tools/hid/build_progs_list.py | 231 ++++++++++++++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 drivers/hid/bpf/progs/Makefile create mode 100644 drivers/hid/bpf/progs/hid_bpf.h create mode 100644 drivers/hid/bpf/progs/hid_bpf_helpers.h create mode 100644 drivers/hid/bpf/progs/hid_bpf_progs.h create mode 100755 tools/hid/build_progs_list.py diff --git a/MAINTAINERS b/MAINTAINERS index 752126fba795..8580895e280f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9102,6 +9102,7 @@ F: drivers/hid/ F: include/linux/hid* F: include/uapi/linux/hid* F: samples/hid/ +F: tools/hid F: tools/testing/selftests/hid/ HID LOGITECH DRIVERS diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile new file mode 100644 index 000000000000..ee0203d3349a --- /dev/null +++ b/drivers/hid/bpf/progs/Makefile @@ -0,0 +1,105 @@ +# 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 := $(BPFTOOL_OUTPUT)/bootstrap/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 + +HID_BPF_CONVERTER := $(TOOLS_PATH)/hid/build_progs_list.py + +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 + +TARGETS = $(patsubst %.bpf.c,%.hidbpf.h, $(wildcard *.bpf.c)) +TARGETS += hid_bpf_progs.h + +all: $(TARGETS) + +clean: + $(call msg,CLEAN) + $(Q)rm -rf $(OUTPUT) $(TARGETS) + +$(OUTPUT)/%.json: $(OUTPUT)/%.bpf.o | $(BPFTOOL) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton -L -j $< > $@ + +%.hidbpf.h: $(OUTPUT)/%.json | $(HID_BPF_CONVERTER) + $(call msg,GEN-HIDBPF,$@) + $(Q)$(HID_BPF_CONVERTER) build_prog $< -o $@ + +hid_bpf_progs.h: $(addprefix $(OUTPUT)/,$(patsubst %.bpf.c,%.json, $(wildcard *.bpf.c))) | $(HID_BPF_CONVERTER) + $(call msg,GEN-HIDBPF-LIST,$@) + $(Q)$(HID_BPF_CONVERTER) build_list $< -o $@ + +$(OUTPUT)/%.bpf.o: %.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 + +ifeq ($(CROSS_COMPILE),) +$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT) + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \ + OUTPUT=$(BPFTOOL_OUTPUT)/ \ + LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \ + LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap +else +$(DEFAULT_BPFTOOL): $(BPFTOOL_OUTPUT) + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \ + OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap +endif diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h new file mode 100644 index 000000000000..7ee371cac2e1 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_bpf.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef ____HID_BPF__H +#define ____HID_BPF__H + +struct hid_bpf_probe_args { + unsigned int hid; + unsigned int rdesc_size; + unsigned char rdesc[4096]; + int retval; +}; + +#endif /* ____HID_BPF__H */ diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h new file mode 100644 index 000000000000..4c4e63a516b3 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef __HID_BPF_HELPERS_H +#define __HID_BPF_HELPERS_H + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> + +extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, + unsigned int offset, + const size_t __sz) __ksym; +extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym; +extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym; +extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, + __u8 *data, + size_t buf__sz, + enum hid_report_type type, + enum hid_class_request reqtype) __ksym; + +#endif /* __HID_BPF_HELPERS_H */ diff --git a/drivers/hid/bpf/progs/hid_bpf_progs.h b/drivers/hid/bpf/progs/hid_bpf_progs.h new file mode 100644 index 000000000000..430e0fb47484 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_bpf_progs.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* THIS FILE IS AUTOGENERATED BY build_progs_list.py ! */ + +#ifndef __HID_BPF_PROGS_H__ +#define __HID_BPF_PROGS_H__ + +/** + * struct hid_bpf_map: custom HID representation of a map + */ +struct hid_bpf_map { + unsigned int size; + unsigned int mmap_sz; + char *data; +}; + +/** + * struct hid_bpf_prog: custom HID representation of a program + */ +struct hid_bpf_prog { +}; + +/** + * struct hid_bpf_object: custom HID representation of a BPF object + * @map_cnt: number of maps available in the BPF object + * @prog_cnt: number of programs available + * @probe: index of the probe program if any (greater than prog_cnt if none) + */ +struct hid_bpf_object { + struct hid_device_id id; + unsigned int map_cnt; + struct hid_bpf_map maps[0]; + unsigned int prog_cnt; + struct hid_bpf_prog progs[0]; + unsigned int probe; + unsigned int rodata; + unsigned int data_sz; + char *data; + unsigned int insns_sz; + char *insns; +}; + +static struct hid_bpf_object hid_objects[] = { + + + { }, +}; + +#endif /* __HID_BPF_PROGS_H__ */ + diff --git a/tools/hid/build_progs_list.py b/tools/hid/build_progs_list.py new file mode 100755 index 000000000000..8103bc2f433f --- /dev/null +++ b/tools/hid/build_progs_list.py @@ -0,0 +1,231 @@ +#!/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2022 Benjamin Tissoires + +import argparse +import json +import re +import sys + +from pathlib import Path + + +def create_header(fp, max_maps_cnt, max_progs_cnt): + print( + f"""/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* THIS FILE IS AUTOGENERATED BY build_progs_list.py ! */ + +#ifndef __HID_BPF_PROGS_H__ +#define __HID_BPF_PROGS_H__ + +/** + * struct hid_bpf_map: custom HID representation of a map + */ +struct hid_bpf_map {{ + unsigned int size; + unsigned int mmap_sz; + char *data; +}}; + +/** + * struct hid_bpf_prog: custom HID representation of a program + */ +struct hid_bpf_prog {{ +}}; + +/** + * struct hid_bpf_object: custom HID representation of a BPF object + * @map_cnt: number of maps available in the BPF object + * @prog_cnt: number of programs available + * @probe: index of the probe program if any (greater than prog_cnt if none) + */ +struct hid_bpf_object {{ + struct hid_device_id id; + unsigned int map_cnt; + struct hid_bpf_map maps[{max_maps_cnt}]; + unsigned int prog_cnt; + struct hid_bpf_prog progs[{max_progs_cnt}]; + unsigned int probe; + unsigned int rodata; + unsigned int data_sz; + char *data; + unsigned int insns_sz; + char *insns; +}}; + +static struct hid_bpf_object hid_objects[] = {{ +""", + file=fp, + ) + + +def create_footer(fp): + print( + """ + { }, +}; + +#endif /* __HID_BPF_PROGS_H__ */ +""", + file=fp, + ) + + +def get_bus_group_vid_pid(json_data): + path_regex = re.compile( + r"b([0-9a-fA-F]{4})g([0-9a-fA-F]{4})v([0-9a-fA-F]{4})p([0-9a-fA-F]{4}).*" + ) + return path_regex.match(json_data["name"]) + + +def is_valid_bpf_json(json_data): + return get_bus_group_vid_pid(json_data) is not None and json_data["use_loader"] + + +def build_prog_list(args): + input_files = [Path(p) for p in args.path] + output = args.output + max_maps_cnt = 0 + max_progs_cnt = 0 + valid_files = [] + + for prog in input_files: + with open(prog) as f: + data = json.load(f) + if not is_valid_bpf_json(data): + print(f"ignoring {prog.relative_to('.')}", file=sys.stderr) + continue + generated_name = prog.name.replace(".json", ".hidbpf.h") + max_maps_cnt = max(max_maps_cnt, len(data["maps"])) + max_progs_cnt = max(max_progs_cnt, len(data["progs"])) + + valid_files.append(generated_name) + + create_header(output, max_maps_cnt, max_progs_cnt) + + valid_files.sort() + + for file in valid_files: + print(f'#include "{file}"', file=output) + + create_footer(output) + + +def write_c_data(bytes): + outputs = ['"', ''] + for b in bytes: + w = 2 if b == '0x00' else 4 + if len(outputs[-1]) + w > 78: + outputs.append('') + if b != "0x00": + outputs[-1] += f"\\x{b[2:]}" + else: + outputs[-1] += "\\0" + outputs[-1] += '"' + return '\\\n'.join(outputs) + + +def build_c_object(args): + input_file = Path(args.path[0]) + output = args.output + + with open(input_file) as f: + data = json.load(f) + + if not is_valid_bpf_json(data): + print(f"ignoring {input_file.relative_to('.')}", file=sys.stderr) + return + + try: + probe_idx = [p["name"] for p in data["progs"]].index("probe") + except ValueError: + probe_idx = len(data["progs"]) + 1 + + try: + rodata_idx = [m["ident"] for m in data["maps"]].index("rodata") + except ValueError: + rodata_idx = len(data["maps"]) + 1 + + header_marker = ( + f"__{input_file.name}__".replace(".", "_") + .replace("-", "_") + .replace("json", "hidbpf_h") + .upper() + ) + m = get_bus_group_vid_pid(data) + output.write( + f"""/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ +/* THIS FILE IS AUTOGENERATED BY build_progs_list.py ! */ + +#ifndef {header_marker} +#define {header_marker} + +{{ + .id = {{ + .bus = 0x{m.group(1)}, + .group = 0x{m.group(2)}, + .vendor = 0x{m.group(3)}, + .product = 0x{m.group(4)}, + }}, + .map_cnt = {len(data['maps'])}, + .prog_cnt = {len(data['progs'])}, + .probe = {probe_idx}, + .rodata = {rodata_idx}, + .maps = {{""") + + for map in data['maps']: + output.write(f""" + {{ + .size = {map['size']}, + .mmap_sz = {map['mmap_sz']}, + .data = {write_c_data(map['data'])}, + }},""") + + output.write(f""" + }}, + .data_sz = {data['data_sz']}, + .data = {write_c_data(data['data'])}, + .insns_sz = {data['insns_sz']}, + .insns = {write_c_data(data['insns'])}, +}}, + +#endif /* {header_marker} */ +""" + ) + + +def main(): + ap = argparse.ArgumentParser(description="custom HID BPF json converter") + + def add_output(parser): + parser.add_argument( + "-o", + "--output", + nargs="?", + type=argparse.FileType("w"), + default=sys.stdout, + help="Output file, if not given, use stdout", + ) + + sp = ap.add_subparsers(help="sub-command help") + + obj_parser = sp.add_parser( + "build_prog", help="Convert a JSON into a generic HID BPF header" + ) + obj_parser.add_argument("path", nargs=1, help="Input file.", metavar="PATH") + add_output(obj_parser) + obj_parser.set_defaults(func=build_c_object) + + list_parser = sp.add_parser( + "build_list", help="Convert a list of JSON into a generic HID BPF header list" + ) + list_parser.add_argument("path", nargs="*", help="Input file(s).", metavar="PATH") + add_output(list_parser) + list_parser.set_defaults(func=build_prog_list) + + args = ap.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() -- 2.38.1