From patchwork Thu Feb 24 11:08:23 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758385 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id EE971C433EF for ; Thu, 24 Feb 2022 11:09:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233801AbiBXLJw (ORCPT ); Thu, 24 Feb 2022 06:09:52 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:45652 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233793AbiBXLJk (ORCPT ); Thu, 24 Feb 2022 06:09:40 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 03F6114A21F for ; Thu, 24 Feb 2022 03:09:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700946; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=pdCtIwX6TvapsyaeSBYuI4Jikta/5NiEYvF5VaQm/IM=; b=IEoKpstaOrpbNzGoSaWFAXLFLxg7hYLcEDLKRuBYjfhWRW3Zw4ULK9GXx6TerI2irpQFcq iNOOg4T66ezY0S9lhQ9aNxTQlH/xWsIMM/9ZNdNKHzM8fD39EeBUZnlR6pZcbIOWBNDBv2 OhWvcBoelGNK/YsYASd6BBdgdf+zBLA= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-176-3LfxZROOPUWCd5Cv48yiqg-1; Thu, 24 Feb 2022 06:09:00 -0500 X-MC-Unique: 3LfxZROOPUWCd5Cv48yiqg-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 4C01B824FA7; Thu, 24 Feb 2022 11:08:58 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id A342079A22; Thu, 24 Feb 2022 11:08:47 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 1/6] HID: initial BPF implementation Date: Thu, 24 Feb 2022 12:08:23 +0100 Message-Id: <20220224110828.2168231-2-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org HID is a protocol that could benefit from using BPF too. This patch implements a net-like use of BPF capability for HID. Any incoming report coming from the device gets injected into a series of BPF programs that can modify it or even discard it by setting the size in the context to 0. The kernel/bpf implementation is based on net-namespace.c, with only the bpf_link part kept, there is no real points in keeping the bpf_prog_{attach|detach} API. The implementation is split into 2 parts: - the kernel/bpf part which isn't aware of the HID usage, but takes care of handling the BPF links - the drivers/hid/hid-bpf.c part which knows about HID Note that HID can be compiled in as a module, and so the functions that kernel/bpf/hid.c needs to call in hid.ko are exported in struct hid_hooks. Signed-off-by: Benjamin Tissoires --- drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 176 ++++++++ drivers/hid/hid-core.c | 21 +- include/linux/bpf-hid.h | 87 ++++ include/linux/bpf_types.h | 4 + include/linux/hid.h | 16 + include/uapi/linux/bpf.h | 7 + include/uapi/linux/bpf_hid.h | 39 ++ kernel/bpf/Makefile | 3 + kernel/bpf/hid.c | 437 +++++++++++++++++++ kernel/bpf/syscall.c | 8 + samples/bpf/.gitignore | 1 + samples/bpf/Makefile | 4 + samples/bpf/hid_mouse_kern.c | 66 +++ samples/bpf/hid_mouse_user.c | 129 ++++++ tools/include/uapi/linux/bpf.h | 7 + tools/lib/bpf/libbpf.c | 7 + tools/lib/bpf/libbpf.h | 2 + tools/lib/bpf/libbpf.map | 1 + tools/testing/selftests/bpf/prog_tests/hid.c | 318 ++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 20 + 21 files changed, 1351 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-bpf.c create mode 100644 include/linux/bpf-hid.h create mode 100644 include/uapi/linux/bpf_hid.h create mode 100644 kernel/bpf/hid.c create mode 100644 samples/bpf/hid_mouse_kern.c create mode 100644 samples/bpf/hid_mouse_user.c create mode 100644 tools/testing/selftests/bpf/prog_tests/hid.c create mode 100644 tools/testing/selftests/bpf/progs/hid.c diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..08d2d7619937 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_BPF) += hid-bpf.o obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_UHID) += uhid.o diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c new file mode 100644 index 000000000000..6c8445820944 --- /dev/null +++ b/drivers/hid/hid-bpf.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BPF in HID support for Linux + * + * Copyright (c) 2021 Benjamin Tissoires + */ + +#include +#include +#include + +#include +#include + +static int __hid_bpf_match_sysfs(struct device *dev, const void *data) +{ + struct kernfs_node *kn = dev->kobj.sd; + struct kernfs_node *uevent_kn; + + uevent_kn = kernfs_find_and_get_ns(kn, "uevent", NULL); + + return uevent_kn == data; +} + +static struct hid_device *hid_bpf_fd_to_hdev(int fd) +{ + struct device *dev; + struct hid_device *hdev; + struct fd f = fdget(fd); + struct inode *inode; + struct kernfs_node *node; + + if (!f.file) { + hdev = ERR_PTR(-EBADF); + goto out; + } + + inode = file_inode(f.file); + node = inode->i_private; + + dev = bus_find_device(&hid_bus_type, NULL, node, __hid_bpf_match_sysfs); + + if (dev) + hdev = to_hid_device(dev); + else + hdev = ERR_PTR(-EINVAL); + + out: + fdput(f); + return hdev; +} + +static struct hid_bpf_ctx *hid_bpf_allocate_ctx(struct hid_device *hdev) +{ + struct hid_bpf_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + ctx->hdev = hdev; + + return ctx; +} + +static int hid_bpf_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + int err = 0; + + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + if (!hdev->bpf.ctx) { + hdev->bpf.ctx = hid_bpf_allocate_ctx(hdev); + if (IS_ERR(hdev->bpf.ctx)) { + err = PTR_ERR(hdev->bpf.ctx); + hdev->bpf.ctx = NULL; + } + } + break; + default: + /* do nothing */ + } + + return err; +} + +static void hid_bpf_array_detached(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + kfree(hdev->bpf.ctx); + hdev->bpf.ctx = NULL; + break; + default: + /* do nothing */ + } +} + +static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type type, + struct hid_bpf_ctx *ctx, u8 *data, int size) +{ + enum hid_bpf_event event = HID_BPF_UNDEF; + + if (type < 0 || !ctx) + return -EINVAL; + + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + event = HID_BPF_DEVICE_EVENT; + if (size > sizeof(ctx->u.device.data)) + return -E2BIG; + break; + default: + return -EINVAL; + } + + if (!hdev->bpf.run_array[type]) + return 0; + + memset(ctx, 0, sizeof(*ctx)); + ctx->hdev = hdev; + ctx->type = event; + + if (size && data) { + switch (event) { + case HID_BPF_DEVICE_EVENT: + memcpy(ctx->u.device.data, data, size); + ctx->u.device.size = size; + break; + default: + /* do nothing */ + } + } + + BPF_PROG_RUN_ARRAY(hdev->bpf.run_array[type], ctx, bpf_prog_run); + + return 0; +} + +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) +{ + int ret; + + if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_DEVICE_EVENT)) + return data; + + ret = hid_bpf_run_progs(hdev, BPF_HID_ATTACH_DEVICE_EVENT, + hdev->bpf.ctx, data, *size); + if (ret) + return data; + + if (!hdev->bpf.ctx->u.device.size) + return ERR_PTR(-EINVAL); + + *size = hdev->bpf.ctx->u.device.size; + + return hdev->bpf.ctx->u.device.data; +} + +int __init hid_bpf_module_init(void) +{ + struct bpf_hid_hooks hooks = { + .hdev_from_fd = hid_bpf_fd_to_hdev, + .link_attach = hid_bpf_link_attach, + .array_detached = hid_bpf_array_detached, + }; + + bpf_hid_set_hooks(&hooks); + + return 0; +} + +void __exit hid_bpf_module_exit(void) +{ + bpf_hid_set_hooks(NULL); +} diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..a80bffe6ce4a 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1748,13 +1748,21 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_driver *hdrv; unsigned int a; u32 rsize, csize = size; - u8 *cdata = data; + u8 *cdata; int ret = 0; + data = hid_bpf_raw_event(hid, data, &size); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + goto out; + } + report = hid_get_report(report_enum, data); if (!report) goto out; + cdata = data; + if (report_enum->numbered) { cdata++; csize--; @@ -2528,10 +2536,12 @@ int hid_add_device(struct hid_device *hdev) hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev); - if (!ret) + if (!ret) { hdev->status |= HID_STAT_ADDED; - else + } else { hid_debug_unregister(hdev); + bpf_hid_exit(hdev); + } return ret; } @@ -2567,6 +2577,7 @@ struct hid_device *hid_allocate_device(void) spin_lock_init(&hdev->debug_list_lock); sema_init(&hdev->driver_input_lock, 1); mutex_init(&hdev->ll_open_lock); + bpf_hid_init(hdev); return hdev; } @@ -2574,6 +2585,7 @@ EXPORT_SYMBOL_GPL(hid_allocate_device); static void hid_remove_device(struct hid_device *hdev) { + bpf_hid_exit(hdev); if (hdev->status & HID_STAT_ADDED) { device_del(&hdev->dev); hid_debug_unregister(hdev); @@ -2700,6 +2712,8 @@ static int __init hid_init(void) hid_debug_init(); + hid_bpf_module_init(); + return 0; err_bus: bus_unregister(&hid_bus_type); @@ -2709,6 +2723,7 @@ static int __init hid_init(void) static void __exit hid_exit(void) { + hid_bpf_module_exit(); hid_debug_exit(); hidraw_exit(); bus_unregister(&hid_bus_type); diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h new file mode 100644 index 000000000000..363fb6a4923f --- /dev/null +++ b/include/linux/bpf-hid.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BPF_HID_H +#define _BPF_HID_H + +#include +#include +#include +#include + +struct bpf_prog; +struct bpf_prog_array; +struct hid_device; + +enum bpf_hid_attach_type { + BPF_HID_ATTACH_INVALID = -1, + BPF_HID_ATTACH_DEVICE_EVENT = 0, + MAX_BPF_HID_ATTACH_TYPE +}; + +struct bpf_hid { + struct hid_bpf_ctx *ctx; + + /* Array of programs to run compiled from links */ + struct bpf_prog_array __rcu *run_array[MAX_BPF_HID_ATTACH_TYPE]; + struct list_head links[MAX_BPF_HID_ATTACH_TYPE]; +}; + +static inline enum bpf_hid_attach_type +to_bpf_hid_attach_type(enum bpf_attach_type attach_type) +{ + switch (attach_type) { + case BPF_HID_DEVICE_EVENT: + return BPF_HID_ATTACH_DEVICE_EVENT; + default: + return BPF_HID_ATTACH_INVALID; + } +} + +union bpf_attr; +struct bpf_prog; + +#if IS_ENABLED(CONFIG_HID) +int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr); +int bpf_hid_link_create(const union bpf_attr *attr, + struct bpf_prog *prog); +#else +static inline int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + return -EOPNOTSUPP; +} + +static inline int bpf_hid_link_create(const union bpf_attr *attr, + struct bpf_prog *prog) +{ + return -EOPNOTSUPP; +} +#endif + +static inline bool bpf_hid_link_empty(struct bpf_hid *bpf, + enum bpf_hid_attach_type type) +{ + return list_empty(&bpf->links[type]); +} + +struct bpf_hid_hooks { + struct hid_device *(*hdev_from_fd)(int fd); + int (*link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); + void (*array_detached)(struct hid_device *hdev, enum bpf_hid_attach_type type); +}; + +#ifdef CONFIG_BPF +int bpf_hid_init(struct hid_device *hdev); +void bpf_hid_exit(struct hid_device *hdev); +void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks); +#else +static inline int bpf_hid_init(struct hid_device *hdev) +{ + return 0; +} + +static inline void bpf_hid_exit(struct hid_device *hdev) {} +static inline void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) {} +#endif + +#endif /* _BPF_HID_H */ diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index 48a91c51c015..1509862aacc4 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -76,6 +76,10 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_EXT, bpf_extension, BPF_PROG_TYPE(BPF_PROG_TYPE_LSM, lsm, void *, void *) #endif /* CONFIG_BPF_LSM */ +#if IS_ENABLED(CONFIG_HID) +BPF_PROG_TYPE(BPF_PROG_TYPE_HID, hid, + __u32, u32) +#endif #endif BPF_PROG_TYPE(BPF_PROG_TYPE_SYSCALL, bpf_syscall, void *, void *) diff --git a/include/linux/hid.h b/include/linux/hid.h index 7487b0586fe6..8fd79011f461 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -15,6 +15,7 @@ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include /* * We parse each description item into this structure. Short items data @@ -639,6 +641,10 @@ struct hid_device { /* device report descriptor */ struct list_head debug_list; spinlock_t debug_list_lock; wait_queue_head_t debug_wait; + +#ifdef CONFIG_BPF + struct bpf_hid bpf; +#endif }; #define to_hid_device(pdev) \ @@ -1205,4 +1211,14 @@ do { \ #define hid_dbg_once(hid, fmt, ...) \ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__) +#ifdef CONFIG_BPF +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +int hid_bpf_module_init(void); +void hid_bpf_module_exit(void); +#else +static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline int hid_bpf_module_init(void) { return 0; } +static inline void hid_bpf_module_exit(void) {} +#endif + #endif diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index afe3d0d7f5f2..5978b92cacd3 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */ + BPF_PROG_TYPE_HID, }; enum bpf_attach_type { @@ -997,6 +998,7 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, + BPF_HID_DEVICE_EVENT, __MAX_BPF_ATTACH_TYPE }; @@ -1011,6 +1013,7 @@ enum bpf_link_type { BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, + BPF_LINK_TYPE_HID = 8, MAX_BPF_LINK_TYPE, }; @@ -5870,6 +5873,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp; + struct { + __s32 hidraw_ino; + __u32 attach_type; + } hid; }; } __attribute__((aligned(8))); diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h new file mode 100644 index 000000000000..243ac45a253f --- /dev/null +++ b/include/uapi/linux/bpf_hid.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ + +/* + * HID BPF public headers + * + * Copyright (c) 2021 Benjamin Tissoires + */ + +#ifndef _UAPI__LINUX_BPF_HID_H__ +#define _UAPI__LINUX_BPF_HID_H__ + +#include + +#define HID_BPF_MAX_BUFFER_SIZE 16384 /* 16kb */ + +struct hid_device; + +enum hid_bpf_event { + HID_BPF_UNDEF = 0, + HID_BPF_DEVICE_EVENT, +}; + +/* type is HID_BPF_DEVICE_EVENT */ +struct hid_bpf_ctx_device_event { + __u8 data[HID_BPF_MAX_BUFFER_SIZE]; + unsigned long size; +}; + +struct hid_bpf_ctx { + enum hid_bpf_event type; + struct hid_device *hdev; + + union { + struct hid_bpf_ctx_device_event device; + } u; +}; + +#endif /* _UAPI__LINUX_BPF_HID_H__ */ + diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index c1a9be6a4b9f..8d5619d3d7e5 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -35,6 +35,9 @@ ifeq ($(CONFIG_BPF_JIT),y) obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o obj-${CONFIG_BPF_LSM} += bpf_lsm.o endif +ifneq ($(CONFIG_HID),) +obj-$(CONFIG_BPF_SYSCALL) += hid.o +endif obj-$(CONFIG_BPF_PRELOAD) += preload/ obj-$(CONFIG_BPF_SYSCALL) += relo_core.o diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c new file mode 100644 index 000000000000..d3cb952bfc26 --- /dev/null +++ b/kernel/bpf/hid.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * based on kernel/bpf/net-namespace.c + */ + +#include +#include +#include +#include +#include + +/* + * Functions to manage BPF programs attached to hid + */ + +struct bpf_hid_link { + struct bpf_link link; + enum bpf_attach_type type; + enum bpf_hid_attach_type hid_type; + + /* Must be accessed with bpf_hid_mutex held. */ + struct hid_device *hdev; + struct list_head node; /* node in list of links attached to hid */ +}; + +/* Protects updates to bpf_hid */ +DEFINE_MUTEX(bpf_hid_mutex); + +static struct bpf_hid_hooks hid_hooks = {0}; + +void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) +{ + if (hooks) + hid_hooks = *hooks; + else + memset(&hid_hooks, 0, sizeof(hid_hooks)); +} +EXPORT_SYMBOL_GPL(bpf_hid_set_hooks); + +static const struct bpf_func_proto * +hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) +{ + switch (func_id) { + default: + return bpf_base_func_proto(func_id); + } +} + +static bool hid_is_valid_access(int off, int size, + enum bpf_access_type access_type, + const struct bpf_prog *prog, + struct bpf_insn_access_aux *info) +{ + /* everything not in ctx is prohibited */ + if (off < 0 || off + size > sizeof(struct hid_bpf_ctx)) + return false; + + switch (off) { + /* type, hdev are read-only */ + case bpf_ctx_range_till(struct hid_bpf_ctx, type, hdev): + return access_type == BPF_READ; + } + + /* everything else is read/write */ + return true; +} + +const struct bpf_verifier_ops hid_verifier_ops = { + .get_func_proto = hid_func_proto, + .is_valid_access = hid_is_valid_access +}; + +/* Must be called with bpf_hid_mutex held. */ +static void bpf_hid_run_array_detach(struct hid_device *hdev, + enum bpf_hid_attach_type type) +{ + struct bpf_prog_array *run_array; + + run_array = rcu_replace_pointer(hdev->bpf.run_array[type], NULL, + lockdep_is_held(&bpf_hid_mutex)); + bpf_prog_array_free(run_array); + + if (hid_hooks.array_detached) + hid_hooks.array_detached(hdev, type); +} + +static int link_index(struct hid_device *hdev, enum bpf_hid_attach_type type, + struct bpf_hid_link *link) +{ + struct bpf_hid_link *pos; + int i = 0; + + list_for_each_entry(pos, &hdev->bpf.links[type], node) { + if (pos == link) + return i; + i++; + } + return -ENOENT; +} + +static int link_count(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + struct list_head *pos; + int i = 0; + + list_for_each(pos, &hdev->bpf.links[type]) + i++; + return i; +} + +static void fill_prog_array(struct hid_device *hdev, enum bpf_hid_attach_type type, + struct bpf_prog_array *prog_array) +{ + struct bpf_hid_link *pos; + unsigned int i = 0; + + list_for_each_entry(pos, &hdev->bpf.links[type], node) { + prog_array->items[i].prog = pos->link.prog; + i++; + } +} + +static void bpf_hid_link_release(struct bpf_link *link) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + enum bpf_hid_attach_type type = hid_link->hid_type; + struct bpf_prog_array *old_array, *new_array; + struct hid_device *hdev; + int cnt, idx; + + mutex_lock(&bpf_hid_mutex); + + hdev = hid_link->hdev; + if (!hdev) + goto out_unlock; + + /* Remember link position in case of safe delete */ + idx = link_index(hdev, type, hid_link); + list_del(&hid_link->node); + + cnt = link_count(hdev, type); + if (!cnt) { + bpf_hid_run_array_detach(hdev, type); + goto out_unlock; + } + + old_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + new_array = bpf_prog_array_alloc(cnt, GFP_KERNEL); + if (!new_array) { + WARN_ON(bpf_prog_array_delete_safe_at(old_array, idx)); + goto out_unlock; + } + fill_prog_array(hdev, type, new_array); + rcu_assign_pointer(hdev->bpf.run_array[type], new_array); + bpf_prog_array_free(old_array); + +out_unlock: + hid_link->hdev = NULL; + mutex_unlock(&bpf_hid_mutex); +} + +static int bpf_hid_link_detach(struct bpf_link *link) +{ + bpf_hid_link_release(link); + return 0; +} + +static void bpf_hid_link_dealloc(struct bpf_link *link) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + + kfree(hid_link); +} + +static int bpf_hid_link_update_prog(struct bpf_link *link, + struct bpf_prog *new_prog, + struct bpf_prog *old_prog) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + enum bpf_hid_attach_type type = hid_link->hid_type; + struct bpf_prog_array *run_array; + struct hid_device *hdev; + int idx, ret; + + if (old_prog && old_prog != link->prog) + return -EPERM; + if (new_prog->type != link->prog->type) + return -EINVAL; + + mutex_lock(&bpf_hid_mutex); + + hdev = hid_link->hdev; + if (!hdev) { + /* hid dying */ + ret = -ENOLINK; + goto out_unlock; + } + + run_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + idx = link_index(hdev, type, hid_link); + ret = bpf_prog_array_update_at(run_array, idx, new_prog); + if (ret) + goto out_unlock; + + old_prog = xchg(&link->prog, new_prog); + bpf_prog_put(old_prog); + +out_unlock: + mutex_unlock(&bpf_hid_mutex); + return ret; +} + +static int bpf_hid_link_fill_info(const struct bpf_link *link, + struct bpf_link_info *info) +{ + const struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + int hidraw_ino = -1; + struct hid_device *hdev; + struct hidraw *hidraw; + + mutex_lock(&bpf_hid_mutex); + hdev = hid_link->hdev; + if (hdev && hdev->hidraw) { + hidraw = hdev->hidraw; + hidraw_ino = hidraw->minor; + } + mutex_unlock(&bpf_hid_mutex); + + info->hid.hidraw_ino = hidraw_ino; + info->hid.attach_type = hid_link->type; + return 0; +} + +static void bpf_hid_link_show_fdinfo(const struct bpf_link *link, + struct seq_file *seq) +{ + struct bpf_link_info info = {}; + + bpf_hid_link_fill_info(link, &info); + seq_printf(seq, + "hidraw_ino:\t%u\n" + "attach_type:\t%u\n", + info.hid.hidraw_ino, + info.hid.attach_type); +} + +static const struct bpf_link_ops bpf_hid_link_ops = { + .release = bpf_hid_link_release, + .dealloc = bpf_hid_link_dealloc, + .detach = bpf_hid_link_detach, + .update_prog = bpf_hid_link_update_prog, + .fill_link_info = bpf_hid_link_fill_info, + .show_fdinfo = bpf_hid_link_show_fdinfo, +}; + +/* Must be called with bpf_hid_mutex held. */ +static int __bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr, + struct hid_device *hdev, + enum bpf_hid_attach_type type) +{ + __u32 __user *prog_ids = u64_to_user_ptr(attr->query.prog_ids); + struct bpf_prog_array *run_array; + u32 prog_cnt = 0, flags = 0; + + run_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + if (run_array) + prog_cnt = bpf_prog_array_length(run_array); + + if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags))) + return -EFAULT; + if (copy_to_user(&uattr->query.prog_cnt, &prog_cnt, sizeof(prog_cnt))) + return -EFAULT; + if (!attr->query.prog_cnt || !prog_ids || !prog_cnt) + return 0; + + return bpf_prog_array_copy_to_user(run_array, prog_ids, + attr->query.prog_cnt); +} + +int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + enum bpf_hid_attach_type type; + struct hid_device *hdev; + int ret; + + if (attr->query.query_flags || !hid_hooks.hdev_from_fd) + return -EINVAL; + + type = to_bpf_hid_attach_type(attr->query.attach_type); + if (type < 0) + return -EINVAL; + + hdev = hid_hooks.hdev_from_fd(attr->query.target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + mutex_lock(&bpf_hid_mutex); + ret = __bpf_hid_prog_query(attr, uattr, hdev, type); + mutex_unlock(&bpf_hid_mutex); + + return ret; +} + +static int bpf_hid_max_progs(enum bpf_hid_attach_type type) +{ + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + return 64; + default: + return 0; + } +} + +static int bpf_hid_link_attach(struct hid_device *hdev, struct bpf_link *link, + enum bpf_hid_attach_type type) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + struct bpf_prog_array *run_array; + int cnt, err = 0; + + mutex_lock(&bpf_hid_mutex); + + cnt = link_count(hdev, type); + if (cnt >= bpf_hid_max_progs(type)) { + err = -E2BIG; + goto out_unlock; + } + + if (hid_hooks.link_attach) { + err = hid_hooks.link_attach(hdev, type); + if (err) + goto out_unlock; + } + + run_array = bpf_prog_array_alloc(cnt + 1, GFP_KERNEL); + if (!run_array) { + err = -ENOMEM; + goto out_unlock; + } + + list_add_tail(&hid_link->node, &hdev->bpf.links[type]); + + fill_prog_array(hdev, type, run_array); + run_array = rcu_replace_pointer(hdev->bpf.run_array[type], run_array, + lockdep_is_held(&bpf_hid_mutex)); + bpf_prog_array_free(run_array); + +out_unlock: + mutex_unlock(&bpf_hid_mutex); + return err; +} + +int bpf_hid_link_create(const union bpf_attr *attr, struct bpf_prog *prog) +{ + enum bpf_hid_attach_type hid_type; + struct bpf_link_primer link_primer; + struct bpf_hid_link *hid_link; + enum bpf_attach_type type; + struct hid_device *hdev; + int err; + + if (attr->link_create.flags || !hid_hooks.hdev_from_fd) + return -EINVAL; + + type = attr->link_create.attach_type; + hid_type = to_bpf_hid_attach_type(type); + if (hid_type < 0) + return -EINVAL; + + hdev = hid_hooks.hdev_from_fd(attr->link_create.target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + hid_link = kzalloc(sizeof(*hid_link), GFP_USER); + if (!hid_link) + return -ENOMEM; + + bpf_link_init(&hid_link->link, BPF_LINK_TYPE_HID, + &bpf_hid_link_ops, prog); + hid_link->hdev = hdev; + hid_link->type = type; + hid_link->hid_type = hid_type; + + err = bpf_link_prime(&hid_link->link, &link_primer); + if (err) { + kfree(hid_link); + return err; + } + + err = bpf_hid_link_attach(hdev, &hid_link->link, hid_type); + if (err) { + bpf_link_cleanup(&link_primer); + return err; + } + + return bpf_link_settle(&link_primer); +} + +const struct bpf_prog_ops hid_prog_ops = { +}; + +int bpf_hid_init(struct hid_device *hdev) +{ + int type; + + for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) + INIT_LIST_HEAD(&hdev->bpf.links[type]); + + return 0; +} +EXPORT_SYMBOL_GPL(bpf_hid_init); + +void bpf_hid_exit(struct hid_device *hdev) +{ + enum bpf_hid_attach_type type; + struct bpf_hid_link *hid_link; + + mutex_lock(&bpf_hid_mutex); + for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) { + bpf_hid_run_array_detach(hdev, type); + list_for_each_entry(hid_link, &hdev->bpf.links[type], node) { + hid_link->hdev = NULL; /* auto-detach link */ + } + } + mutex_unlock(&bpf_hid_mutex); +} +EXPORT_SYMBOL_GPL(bpf_hid_exit); diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 9c7a72b65eee..230ca6964a7e 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3,6 +3,7 @@ */ #include #include +#include #include #include #include @@ -2174,6 +2175,7 @@ static bool is_net_admin_prog_type(enum bpf_prog_type prog_type) case BPF_PROG_TYPE_CGROUP_SYSCTL: case BPF_PROG_TYPE_SOCK_OPS: case BPF_PROG_TYPE_EXT: /* extends any prog */ + case BPF_PROG_TYPE_HID: return true; case BPF_PROG_TYPE_CGROUP_SKB: /* always unpriv */ @@ -3188,6 +3190,8 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) return BPF_PROG_TYPE_SK_LOOKUP; case BPF_XDP: return BPF_PROG_TYPE_XDP; + case BPF_HID_DEVICE_EVENT: + return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; } @@ -3331,6 +3335,8 @@ static int bpf_prog_query(const union bpf_attr *attr, case BPF_SK_MSG_VERDICT: case BPF_SK_SKB_VERDICT: return sock_map_bpf_prog_query(attr, uattr); + case BPF_HID_DEVICE_EVENT: + return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; } @@ -4325,6 +4331,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr) ret = bpf_perf_link_attach(attr, prog); break; #endif + case BPF_PROG_TYPE_HID: + return bpf_hid_link_create(attr, prog); default: ret = -EINVAL; } diff --git a/samples/bpf/.gitignore b/samples/bpf/.gitignore index 0e7bfdbff80a..65440bd618b2 100644 --- a/samples/bpf/.gitignore +++ b/samples/bpf/.gitignore @@ -2,6 +2,7 @@ cpustat fds_example hbm +hid_mouse ibumad lathist lwt_len_hist diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile index 38638845db9d..84ef458487df 100644 --- a/samples/bpf/Makefile +++ b/samples/bpf/Makefile @@ -60,6 +60,8 @@ tprogs-y += xdp_redirect_map tprogs-y += xdp_redirect tprogs-y += xdp_monitor +tprogs-y += hid_mouse + # Libbpf dependencies LIBBPF_SRC = $(TOOLS_PATH)/lib/bpf LIBBPF_OUTPUT = $(abspath $(BPF_SAMPLES_PATH))/libbpf @@ -124,6 +126,7 @@ xdp_redirect_cpu-objs := xdp_redirect_cpu_user.o $(XDP_SAMPLE) xdp_redirect_map-objs := xdp_redirect_map_user.o $(XDP_SAMPLE) xdp_redirect-objs := xdp_redirect_user.o $(XDP_SAMPLE) xdp_monitor-objs := xdp_monitor_user.o $(XDP_SAMPLE) +hid_mouse-objs := hid_mouse_user.o # Tell kbuild to always build the programs always-y := $(tprogs-y) @@ -181,6 +184,7 @@ always-y += ibumad_kern.o always-y += hbm_out_kern.o always-y += hbm_edt_kern.o always-y += xdpsock_kern.o +always-y += hid_mouse_kern.o ifeq ($(ARCH), arm) # Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux diff --git a/samples/bpf/hid_mouse_kern.c b/samples/bpf/hid_mouse_kern.c new file mode 100644 index 000000000000..83b0ab5a04d0 --- /dev/null +++ b/samples/bpf/hid_mouse_kern.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2021 Benjamin Tissoires + */ +#include +#include +#include +#include + +SEC("hid/device_event") +int hid_y_event(struct hid_bpf_ctx *ctx) +{ + s16 y; + + bpf_printk("event: %02x size: %d", ctx->type, ctx->u.device.size); + bpf_printk("incoming event: %02x %02x %02x", + ctx->u.device.data[0], + ctx->u.device.data[1], + ctx->u.device.data[2]); + bpf_printk(" %02x %02x %02x", + ctx->u.device.data[3], + ctx->u.device.data[4], + ctx->u.device.data[5]); + bpf_printk(" %02x %02x %02x", + ctx->u.device.data[6], + ctx->u.device.data[7], + ctx->u.device.data[8]); + + y = ctx->u.device.data[3] | (ctx->u.device.data[4] << 8); + + y = -y; + + ctx->u.device.data[3] = y & 0xFF; + ctx->u.device.data[4] = (y >> 8) & 0xFF; + + bpf_printk("modified event: %02x %02x %02x", + ctx->u.device.data[0], + ctx->u.device.data[1], + ctx->u.device.data[2]); + bpf_printk(" %02x %02x %02x", + ctx->u.device.data[3], + ctx->u.device.data[4], + ctx->u.device.data[5]); + bpf_printk(" %02x %02x %02x", + ctx->u.device.data[6], + ctx->u.device.data[7], + ctx->u.device.data[8]); + + return 0; +} + +SEC("hid/device_event") +int hid_x_event(struct hid_bpf_ctx *ctx) +{ + s16 x; + + x = ctx->u.device.data[1] | (ctx->u.device.data[2] << 8); + + x = -x; + + ctx->u.device.data[1] = x & 0xFF; + ctx->u.device.data[2] = (x >> 8) & 0xFF; + return 0; +} + +char _license[] SEC("license") = "GPL"; +u32 _version SEC("version") = LINUX_VERSION_CODE; diff --git a/samples/bpf/hid_mouse_user.c b/samples/bpf/hid_mouse_user.c new file mode 100644 index 000000000000..d4f37caca2fa --- /dev/null +++ b/samples/bpf/hid_mouse_user.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2021 Benjamin Tissoires + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bpf_util.h" +#include +#include + +static char *sysfs_path; +static int sysfs_fd; +static int prog_count; + +struct prog { + int fd; + struct bpf_link *link; + enum bpf_attach_type type; +}; + +static struct prog progs[10]; + +static void int_exit(int sig) +{ + for (prog_count--; prog_count >= 0; prog_count--) + bpf_link__destroy(progs[prog_count].link); + + close(sysfs_fd); + exit(0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "%s: %s /sys/bus/hid/devices/0BUS:0VID:0PID:00ID/uevent\n\n", + __func__, prog); +} + +int main(int argc, char **argv) +{ + struct bpf_prog_info info = {}; + __u32 info_len = sizeof(info); + const char *optstr = ""; + struct bpf_object *obj; + struct bpf_program *prog; + int opt; + char filename[256]; + int err; + + while ((opt = getopt(argc, argv, optstr)) != -1) { + switch (opt) { + 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; + } + + snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); + obj = bpf_object__open_file(filename, NULL); + err = libbpf_get_error(obj); + if (err) { + fprintf(stderr, "ERROR: opening BPF object file failed\n"); + obj = NULL; + err = 1; + goto cleanup; + } + + /* load BPF program */ + err = bpf_object__load(obj); + if (err) { + fprintf(stderr, "ERROR: loading BPF object file failed\n"); + goto cleanup; + } + + sysfs_fd = open(sysfs_path, O_RDONLY); + + bpf_object__for_each_program(prog, obj) { + progs[prog_count].fd = bpf_program__fd(prog); + progs[prog_count].type = bpf_program__get_expected_attach_type(prog); + progs[prog_count].link = bpf_program__attach_hid(prog, sysfs_fd); + if (libbpf_get_error(progs[prog_count].link)) { + fprintf(stderr, "bpf_prog_attach: err=%m\n"); + progs[prog_count].fd = 0; + progs[prog_count].link = NULL; + goto cleanup; + } + prog_count++; + } + + signal(SIGINT, int_exit); + signal(SIGTERM, int_exit); + + err = bpf_obj_get_info_by_fd(progs[0].fd, &info, &info_len); + if (err) { + printf("can't get prog info - %s\n", strerror(errno)); + goto cleanup; + } + + while (1) + ; + + cleanup: + for (prog_count--; prog_count >= 0; prog_count--) + bpf_link__destroy(progs[prog_count].link); + + bpf_object__close(obj); + return err; +} diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index afe3d0d7f5f2..5978b92cacd3 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */ + BPF_PROG_TYPE_HID, }; enum bpf_attach_type { @@ -997,6 +998,7 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, + BPF_HID_DEVICE_EVENT, __MAX_BPF_ATTACH_TYPE }; @@ -1011,6 +1013,7 @@ enum bpf_link_type { BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, + BPF_LINK_TYPE_HID = 8, MAX_BPF_LINK_TYPE, }; @@ -5870,6 +5873,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp; + struct { + __s32 hidraw_ino; + __u32 attach_type; + } hid; }; } __attribute__((aligned(8))); diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 7e978feaf822..bad16e85032e 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8676,6 +8676,7 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("cgroup/setsockopt", CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT, SEC_ATTACHABLE | SEC_SLOPPY_PFX), SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE), SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX), + SEC_DEF("hid/device_event", HID, BPF_HID_DEVICE_EVENT, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), }; #define MAX_TYPE_NAME_SIZE 32 @@ -10655,6 +10656,12 @@ static struct bpf_link *attach_iter(const struct bpf_program *prog, long cookie) return bpf_program__attach_iter(prog, NULL); } +struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd) +{ + return bpf_program__attach_fd(prog, hid_fd, 0, "hid"); +} + struct bpf_link *bpf_program__attach(const struct bpf_program *prog) { if (!prog->sec_def || !prog->sec_def->attach_fn) diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index c8d8daad212e..f677ac0a9ede 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -529,6 +529,8 @@ struct bpf_iter_attach_opts { LIBBPF_API struct bpf_link * bpf_program__attach_iter(const struct bpf_program *prog, const struct bpf_iter_attach_opts *opts); +LIBBPF_API struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd); /* * Libbpf allows callers to adjust BPF programs before being loaded diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index 47e70c9058d9..fdc6fa743953 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -424,6 +424,7 @@ LIBBPF_0.6.0 { LIBBPF_0.7.0 { global: bpf_btf_load; + bpf_program__attach_hid; bpf_program__expected_attach_type; bpf_program__log_buf; bpf_program__log_level; diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c new file mode 100644 index 000000000000..692d78b9dc4a --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red Hat */ +#include +#include +#include "hid.skel.h" + +#include +#include +#include +#include +#include +#include + +static unsigned char rdesc[] = { + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x21, /* Usage (Vendor Usage 0x21) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x85, 0x01, /* REPORT_ID (1) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x06, /* USAGE_MINIMUM (6) */ + 0x29, 0x08, /* USAGE_MAXIMUM (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0xc0, /* END_COLLECTION */ + 0xc0, /* END_COLLECTION */ +}; + +static int uhid_write(int fd, const struct uhid_event *ev) +{ + ssize_t ret; + + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %m\n"); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", + ret, sizeof(ev)); + return -EFAULT; + } else { + return 0; + } +} + +static int create(int fd, int rand_nb) +{ + struct uhid_event ev; + char buf[25]; + + sprintf(buf, "test-uhid-device-%d", rand_nb); + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char *)ev.u.create.name, buf); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = 0x0001; + ev.u.create.product = 0x0a37; + ev.u.create.version = 0; + ev.u.create.country = 0; + + sprintf(buf, "%d", rand_nb); + strcpy((char *)ev.u.create.phys, buf); + + return uhid_write(fd, &ev); +} + +static void destroy(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +static int send_event(int fd, u8 *buf, size_t size) +{ + struct uhid_event ev; + + if (size > sizeof(ev.u.input.data)) + return -E2BIG; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT2; + ev.u.input2.size = size; + + memcpy(ev.u.input2.data, buf, size); + + return uhid_write(fd, &ev); +} + +static int setup_uhid(int rand_nb) +{ + int fd; + const char *path = "/dev/uhid"; + int ret; + + fd = open(path, O_RDWR | O_CLOEXEC); + if (!ASSERT_GE(fd, 0, "open uhid-cdev")) + return -EPERM; + + ret = create(fd, rand_nb); + if (!ASSERT_OK(ret, "create uhid device")) { + close(fd); + return -EPERM; + } + + return fd; +} + +static int get_sysfs_fd(int rand_nb) +{ + const char *workdir = "/sys/devices/virtual/misc/uhid"; + const char *target = "0003:0001:0A37.*"; + char uevent[1024]; + char temp[512]; + char phys[512]; + DIR *d; + struct dirent *dir; + int fd, nread; + int found = -1; + + /* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */ + + sprintf(phys, "PHYS=%d", rand_nb); + + d = opendir(workdir); + if (d) { + while ((dir = readdir(d)) != NULL) { + if (fnmatch(target, dir->d_name, 0)) + continue; + + /* we found the correct VID/PID, now check for phys */ + sprintf(uevent, "%s/%s/uevent", workdir, dir->d_name); + fd = open(uevent, O_RDONLY | O_NONBLOCK); + if (fd < 0) + continue; + + nread = read(fd, temp, ARRAY_SIZE(temp)); + if (nread > 0 && (strstr(temp, phys)) != NULL) { + found = fd; + break; + } + + close(fd); + fd = 0; + } + closedir(d); + } + + return found; +} + +static int get_hidraw(struct bpf_link *link) +{ + struct bpf_link_info info = {0}; + int prog_id, i; + + /* retry 5 times in case the system is loaded */ + for (i = 5; i > 0; i--) { + usleep(10); + prog_id = link_info_prog_id(link, &info); + if (!prog_id) + continue; + if (info.hid.hidraw_ino >= 0) + break; + } + + if (!prog_id) + return -1; + + return info.hid.hidraw_ino; +} + +/* + * Attach hid_first_event to the given uhid device, + * retrieve and open the matching hidraw node, + * inject one event in the uhid device, + * check that the program sees it and can change the data + */ +static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + u8 buf[10] = {0}; + int ret = -1; + + /* check that the program is correctly loaded */ + ASSERT_EQ(hid_skel->data->callback_check, 52, "callback_check1"); + ASSERT_EQ(hid_skel->data->callback2_check, 52, "callback2_check1"); + + /* attach the first program */ + hid_skel->links.hid_first_event = + bpf_program__attach_hid(hid_skel->progs.hid_first_event, sysfs_fd); + if (!ASSERT_OK_PTR(hid_skel->links.hid_first_event, + "attach_hid(hid_first_event)")) + return PTR_ERR(hid_skel->links.hid_first_event); + + hidraw_ino = get_hidraw(hid_skel->links.hid_first_event); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + send_event(uhid_fd, buf, 4); + + /* check that hid_first_event() was executed */ + ASSERT_EQ(hid_skel->data->callback_check, 42, "callback_check1"); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 4, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 47, "hid_first_event")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + +void serial_test_hid_bpf(void) +{ + struct hid *hid_skel = NULL; + int err, uhid_fd, sysfs_fd; + time_t t; + int rand_nb; + + /* initialize random number generator */ + srand((unsigned int)time(&t)); + + rand_nb = rand() % 1024; + + uhid_fd = setup_uhid(rand_nb); + if (!ASSERT_GE(uhid_fd, 0, "setup uhid")) + return; + + /* give a little bit of time for the device to appear */ + /* TODO: check on uhid events */ + usleep(1000); + + /* locate the uevent file of the created device */ + sysfs_fd = get_sysfs_fd(rand_nb); + if (!ASSERT_GE(sysfs_fd, 0, "locate sysfs uhid device")) + goto cleanup; + + hid_skel = hid__open_and_load(); + if (!ASSERT_OK_PTR(hid_skel, "hid_skel_load")) + goto cleanup; + + /* start the tests! */ + err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid"); + +cleanup: + hid__destroy(hid_skel); + destroy(uhid_fd); +} diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c new file mode 100644 index 000000000000..f28bb6007875 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red hat */ +#include +#include +#include + +char _license[] SEC("license") = "GPL"; + +__u64 callback_check = 52; +__u64 callback2_check = 52; + +SEC("hid/device_event") +int hid_first_event(struct hid_bpf_ctx *ctx) +{ + callback_check = ctx->u.device.data[1]; + + ctx->u.device.data[2] = ctx->u.device.data[1] + 5; + + return 0; +} From patchwork Thu Feb 24 11:08:24 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758386 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1FEEDC4321E for ; Thu, 24 Feb 2022 11:09:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233800AbiBXLJy (ORCPT ); Thu, 24 Feb 2022 06:09:54 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46390 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230097AbiBXLJw (ORCPT ); Thu, 24 Feb 2022 06:09:52 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 28CF714FBDC for ; Thu, 24 Feb 2022 03:09:21 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700960; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=N3cp1+AUEw8KVSjrTX4gG0oeNe61qbEHhfSUqcT0Biw=; b=NWoLE/CLzTSz5vSCf6tNRGRMg4Jd0eOM49RIK3lBG1mTiHFHvhXjbM8SX4NOiEPeOht3Zo 2k2UFvOjVCIa9ew38VCmPzTUTeCdE04VKWna3N7a7tMOHF/A8p5Kr/fxPvbvuxjTZJWIFM GaM3e5zxvCBqtEhLzGoBo2QHVV/cP8w= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-111-aqdgt50QOw2APlfdXy7NtA-1; Thu, 24 Feb 2022 06:09:16 -0500 X-MC-Unique: aqdgt50QOw2APlfdXy7NtA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 725E41006AA5; Thu, 24 Feb 2022 11:09:14 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id 9C44C79A22; Thu, 24 Feb 2022 11:08:58 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 2/6] HID: bpf: allow to change the report descriptor from an eBPF program Date: Thu, 24 Feb 2022 12:08:24 +0100 Message-Id: <20220224110828.2168231-3-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org The report descriptor is the dictionary of the HID protocol specific to the given device. Changing it is a common habit in the HID world, and making that feature accessible from eBPF allows to fix devices without having to install a new kernel. Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bpf.c | 66 +++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/bpf-hid.h | 4 ++ include/linux/hid.h | 6 ++ include/uapi/linux/bpf.h | 1 + include/uapi/linux/bpf_hid.h | 8 +++ kernel/bpf/hid.c | 5 ++ kernel/bpf/syscall.c | 2 + samples/bpf/hid_mouse_kern.c | 25 +++++++ tools/include/uapi/linux/bpf.h | 1 + tools/lib/bpf/libbpf.c | 1 + tools/testing/selftests/bpf/prog_tests/hid.c | 69 ++++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 48 ++++++++++++++ 13 files changed, 238 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 6c8445820944..2d54c87cda1a 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -63,6 +63,14 @@ static struct hid_bpf_ctx *hid_bpf_allocate_ctx(struct hid_device *hdev) return ctx; } +static int hid_reconnect(struct hid_device *hdev) +{ + if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status)) + return device_reprobe(&hdev->dev); + + return 0; +} + static int hid_bpf_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) { int err = 0; @@ -84,6 +92,17 @@ static int hid_bpf_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type return err; } +static void hid_bpf_link_attached(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + switch (type) { + case BPF_HID_ATTACH_RDESC_FIXUP: + hid_reconnect(hdev); + break; + default: + /* do nothing */ + } +} + static void hid_bpf_array_detached(struct hid_device *hdev, enum bpf_hid_attach_type type) { switch (type) { @@ -91,6 +110,9 @@ static void hid_bpf_array_detached(struct hid_device *hdev, enum bpf_hid_attach_ kfree(hdev->bpf.ctx); hdev->bpf.ctx = NULL; break; + case BPF_HID_ATTACH_RDESC_FIXUP: + hid_reconnect(hdev); + break; default: /* do nothing */ } @@ -110,6 +132,11 @@ static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type t if (size > sizeof(ctx->u.device.data)) return -E2BIG; break; + case BPF_HID_ATTACH_RDESC_FIXUP: + event = HID_BPF_RDESC_FIXUP; + if (size > sizeof(ctx->u.rdesc.data)) + return -E2BIG; + break; default: return -EINVAL; } @@ -127,6 +154,10 @@ static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type t memcpy(ctx->u.device.data, data, size); ctx->u.device.size = size; break; + case HID_BPF_RDESC_FIXUP: + memcpy(ctx->u.rdesc.data, data, size); + ctx->u.device.size = size; + break; default: /* do nothing */ } @@ -157,11 +188,46 @@ u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) return hdev->bpf.ctx->u.device.data; } +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{ + struct hid_bpf_ctx *ctx = NULL; + int ret; + + if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP)) + goto ignore_bpf; + + ctx = hid_bpf_allocate_ctx(hdev); + if (IS_ERR(ctx)) + goto ignore_bpf; + + ret = hid_bpf_run_progs(hdev, BPF_HID_ATTACH_RDESC_FIXUP, ctx, rdesc, *size); + if (ret) + goto ignore_bpf; + + *size = ctx->u.rdesc.size; + + if (!*size) { + rdesc = NULL; + goto unlock; + } + + rdesc = kmemdup(ctx->u.rdesc.data, *size, GFP_KERNEL); + + unlock: + kfree(ctx); + return rdesc; + + ignore_bpf: + kfree(ctx); + return kmemdup(rdesc, *size, GFP_KERNEL); +} + int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .link_attach = hid_bpf_link_attach, + .link_attached = hid_bpf_link_attached, .array_detached = hid_bpf_array_detached, }; diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index a80bffe6ce4a..0eb8189faaee 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize; - buf = kmemdup(start, size, GFP_KERNEL); + /* hid_bpf_report_fixup() ensures we work on a copy of rdesc */ + buf = hid_bpf_report_fixup(device, start, &size); if (buf == NULL) return -ENOMEM; diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 363fb6a4923f..377012a019da 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -14,6 +14,7 @@ struct hid_device; enum bpf_hid_attach_type { BPF_HID_ATTACH_INVALID = -1, BPF_HID_ATTACH_DEVICE_EVENT = 0, + BPF_HID_ATTACH_RDESC_FIXUP, MAX_BPF_HID_ATTACH_TYPE }; @@ -31,6 +32,8 @@ to_bpf_hid_attach_type(enum bpf_attach_type attach_type) switch (attach_type) { case BPF_HID_DEVICE_EVENT: return BPF_HID_ATTACH_DEVICE_EVENT; + case BPF_HID_RDESC_FIXUP: + return BPF_HID_ATTACH_RDESC_FIXUP; default: return BPF_HID_ATTACH_INVALID; } @@ -67,6 +70,7 @@ static inline bool bpf_hid_link_empty(struct bpf_hid *bpf, struct bpf_hid_hooks { struct hid_device *(*hdev_from_fd)(int fd); int (*link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); + void (*link_attached)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*array_detached)(struct hid_device *hdev, enum bpf_hid_attach_type type); }; diff --git a/include/linux/hid.h b/include/linux/hid.h index 8fd79011f461..66d949d10b78 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1213,10 +1213,16 @@ do { \ #ifdef CONFIG_BPF u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size); int hid_bpf_module_init(void); void hid_bpf_module_exit(void); #else static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *size) +{ + return kmemdup(rdesc, *size, GFP_KERNEL); +} static inline int hid_bpf_module_init(void) { return 0; } static inline void hid_bpf_module_exit(void) {} #endif diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 5978b92cacd3..a7a8d9cfcf24 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -999,6 +999,7 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, BPF_HID_DEVICE_EVENT, + BPF_HID_RDESC_FIXUP, __MAX_BPF_ATTACH_TYPE }; diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h index 243ac45a253f..c0801d7174c3 100644 --- a/include/uapi/linux/bpf_hid.h +++ b/include/uapi/linux/bpf_hid.h @@ -18,6 +18,7 @@ struct hid_device; enum hid_bpf_event { HID_BPF_UNDEF = 0, HID_BPF_DEVICE_EVENT, + HID_BPF_RDESC_FIXUP, }; /* type is HID_BPF_DEVICE_EVENT */ @@ -26,12 +27,19 @@ struct hid_bpf_ctx_device_event { unsigned long size; }; +/* type is HID_BPF_RDESC_FIXUP */ +struct hid_bpf_ctx_rdesc_fixup { + __u8 data[HID_BPF_MAX_BUFFER_SIZE]; + unsigned long size; +}; + struct hid_bpf_ctx { enum hid_bpf_event type; struct hid_device *hdev; union { struct hid_bpf_ctx_device_event device; + struct hid_bpf_ctx_rdesc_fixup rdesc; } u; }; diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index d3cb952bfc26..47cb0580b14a 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -315,6 +315,8 @@ static int bpf_hid_max_progs(enum bpf_hid_attach_type type) switch (type) { case BPF_HID_ATTACH_DEVICE_EVENT: return 64; + case BPF_HID_ATTACH_RDESC_FIXUP: + return 1; default: return 0; } @@ -355,6 +357,9 @@ static int bpf_hid_link_attach(struct hid_device *hdev, struct bpf_link *link, lockdep_is_held(&bpf_hid_mutex)); bpf_prog_array_free(run_array); + if (hid_hooks.link_attached) + hid_hooks.link_attached(hdev, type); + out_unlock: mutex_unlock(&bpf_hid_mutex); return err; diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 230ca6964a7e..62889cc71a02 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3191,6 +3191,7 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) case BPF_XDP: return BPF_PROG_TYPE_XDP; case BPF_HID_DEVICE_EVENT: + case BPF_HID_RDESC_FIXUP: return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; @@ -3336,6 +3337,7 @@ static int bpf_prog_query(const union bpf_attr *attr, case BPF_SK_SKB_VERDICT: return sock_map_bpf_prog_query(attr, uattr); case BPF_HID_DEVICE_EVENT: + case BPF_HID_RDESC_FIXUP: return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; diff --git a/samples/bpf/hid_mouse_kern.c b/samples/bpf/hid_mouse_kern.c index 83b0ab5a04d0..b4fac1c8bf6e 100644 --- a/samples/bpf/hid_mouse_kern.c +++ b/samples/bpf/hid_mouse_kern.c @@ -62,5 +62,30 @@ int hid_x_event(struct hid_bpf_ctx *ctx) return 0; } +SEC("hid/rdesc_fixup") +int hid_rdesc_fixup(struct hid_bpf_ctx *ctx) +{ + if (ctx->type != HID_BPF_RDESC_FIXUP) + return 0; + + bpf_printk("rdesc: %02x %02x %02x", + ctx->u.rdesc.data[0], + ctx->u.rdesc.data[1], + ctx->u.rdesc.data[2]); + bpf_printk(" %02x %02x %02x", + ctx->u.rdesc.data[3], + ctx->u.rdesc.data[4], + ctx->u.rdesc.data[5]); + bpf_printk(" %02x %02x %02x ...", + ctx->u.rdesc.data[6], + ctx->u.rdesc.data[7], + ctx->u.rdesc.data[8]); + + ctx->u.rdesc.data[39] = 0x31; + ctx->u.rdesc.data[41] = 0x30; + + return 0; +} + char _license[] SEC("license") = "GPL"; u32 _version SEC("version") = LINUX_VERSION_CODE; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 5978b92cacd3..a7a8d9cfcf24 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -999,6 +999,7 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, BPF_HID_DEVICE_EVENT, + BPF_HID_RDESC_FIXUP, __MAX_BPF_ATTACH_TYPE }; diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index bad16e85032e..b7af873116fb 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8677,6 +8677,7 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE), SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX), SEC_DEF("hid/device_event", HID, BPF_HID_DEVICE_EVENT, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), + SEC_DEF("hid/rdesc_fixup", HID, BPF_HID_RDESC_FIXUP, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), }; #define MAX_TYPE_NAME_SIZE 32 diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index 692d78b9dc4a..dccbbcaa69e5 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -9,6 +9,7 @@ #include #include #include +#include #include static unsigned char rdesc[] = { @@ -279,6 +280,71 @@ static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; } +/* + * Attach hid_rdesc_fixup to the given uhid device, + * retrieve and open the matching hidraw node, + * check that the hidraw report descriptor has been updated. + */ +static int test_rdesc_fixup(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + struct hidraw_report_descriptor rpt_desc = {0}; + int err, desc_size, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + int ret = -1; + + /* attach the program */ + hid_skel->links.hid_rdesc_fixup = + bpf_program__attach_hid(hid_skel->progs.hid_rdesc_fixup, sysfs_fd); + if (!ASSERT_OK_PTR(hid_skel->links.hid_rdesc_fixup, + "attach_hid(hid_rdesc_fixup)")) + return PTR_ERR(hid_skel->links.hid_rdesc_fixup); + + /* give a little bit of time for the device to appear */ + /* TODO: check on uhid events */ + usleep(1000); + + hidraw_ino = get_hidraw(hid_skel->links.hid_rdesc_fixup); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* check that hid_rdesc_fixup() was executed */ + ASSERT_EQ(hid_skel->data->callback2_check, 0x21, "callback_check2"); + + /* read the exposed report descriptor from hidraw */ + err = ioctl(hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + if (!ASSERT_GE(err, 0, "HIDIOCGRDESCSIZE")) + goto cleanup; + + /* ensure the new size of the rdesc is bigger than the old one */ + if (!ASSERT_GT(desc_size, sizeof(rdesc), "new_rdesc_size")) + goto cleanup; + + rpt_desc.size = desc_size; + err = ioctl(hidraw_fd, HIDIOCGRDESC, &rpt_desc); + if (!ASSERT_GE(err, 0, "HIDIOCGRDESC")) + goto cleanup; + + if (!ASSERT_EQ(rpt_desc.value[4], 0x42, "hid_rdesc_fixup")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + void serial_test_hid_bpf(void) { struct hid *hid_skel = NULL; @@ -312,6 +378,9 @@ void serial_test_hid_bpf(void) err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid"); + err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_rdesc_fixup"); + cleanup: hid__destroy(hid_skel); destroy(uhid_fd); diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index f28bb6007875..f7a64c637782 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -18,3 +18,51 @@ int hid_first_event(struct hid_bpf_ctx *ctx) return 0; } + +static __u8 rdesc[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x32, /* USAGE (Z) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x06, /* USAGE_MINIMUM (6) */ + 0x29, 0x08, /* USAGE_MAXIMUM (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0xc0, /* END_COLLECTION */ + 0xc0, /* END_COLLECTION */ +}; + +SEC("hid/rdesc_fixup") +int hid_rdesc_fixup(struct hid_bpf_ctx *ctx) +{ + callback2_check = ctx->u.rdesc.data[4]; + + /* insert rdesc at offset 52 */ + __builtin_memcpy(&ctx->u.rdesc.data[52], rdesc, sizeof(rdesc)); + ctx->u.rdesc.size = sizeof(rdesc) + 52; + + ctx->u.rdesc.data[4] = 0x42; + + return 0; +} From patchwork Thu Feb 24 11:08:25 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758387 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B3621C433FE for ; Thu, 24 Feb 2022 11:09:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233826AbiBXLJ6 (ORCPT ); Thu, 24 Feb 2022 06:09:58 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46426 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230097AbiBXLJ4 (ORCPT ); Thu, 24 Feb 2022 06:09:56 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 0967014A21A for ; Thu, 24 Feb 2022 03:09:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700964; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=7YFf/SYfa0TO9SFiqYAnYqx3zzId6FxMxZIck8PhS+Y=; b=H6lmCu90h8ZhThEmStA4/K6Ag4HVbX8YfKt6TMobS5OWFI+/x64U8qR+2hY/diqIrYgaZP q6PjKANZBnuDEgho+Q7/VkLQPEImEuNfcKZ34+BijDGiSllvwPh1aZDFSgE1xqSxyiXoFB tZ/kni64qvGk8VHpKJ2SyuaQonfYsFs= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-271--wjlzGryM4OVMnRi6itedA-1; Thu, 24 Feb 2022 06:09:21 -0500 X-MC-Unique: -wjlzGryM4OVMnRi6itedA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 6A7A6824FA7; Thu, 24 Feb 2022 11:09:18 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id C310F79A22; Thu, 24 Feb 2022 11:09:14 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 3/6] HID: bpf: add hid_{get|set}_data helpers Date: Thu, 24 Feb 2022 12:08:25 +0100 Message-Id: <20220224110828.2168231-4-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org When we process an incoming HID report, it is common to have to account for fields that are not aligned in the report. HID is using 2 helpers hid_field_extract() and implement() to pick up any data at any offset within the report. Export those 2 helpers in BPF programs so users can also rely on them. The second net worth advantage of those helpers is that now we can fetch data anywhere in the report without knowing at compile time the location of it. The boundary checks are done in hid-bpf.c, to prevent a memory leak. Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bpf.c | 22 +++++++ drivers/hid/hid-core.c | 4 +- include/linux/bpf-hid.h | 2 + include/linux/hid.h | 2 + include/uapi/linux/bpf.h | 16 +++++ kernel/bpf/hid.c | 68 ++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 16 +++++ tools/testing/selftests/bpf/prog_tests/hid.c | 59 +++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 14 ++++ 9 files changed, 201 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 2d54c87cda1a..d775bda9d28d 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -118,6 +118,26 @@ static void hid_bpf_array_detached(struct hid_device *hdev, enum bpf_hid_attach_ } } +int hid_bpf_get_data(struct hid_device *hdev, u8 *buf, u64 offset, u8 n) +{ + if (n > 32 || + ((offset + n) >> 3) >= HID_BPF_MAX_BUFFER_SIZE) + return 0; + + return hid_field_extract(hdev, buf, offset, n); +} + +int hid_bpf_set_data(struct hid_device *hdev, u8 *buf, u64 offset, u8 n, u32 data) +{ + if (n > 32 || + ((offset + n) >> 3) >= HID_BPF_MAX_BUFFER_SIZE) + return -EINVAL; + + implement(hdev, buf, offset, n, data); + + return 0; +} + static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type type, struct hid_bpf_ctx *ctx, u8 *data, int size) { @@ -229,6 +249,8 @@ int __init hid_bpf_module_init(void) .link_attach = hid_bpf_link_attach, .link_attached = hid_bpf_link_attached, .array_detached = hid_bpf_array_detached, + .hid_get_data = hid_bpf_get_data, + .hid_set_data = hid_bpf_set_data, }; bpf_hid_set_hooks(&hooks); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 0eb8189faaee..d3f4499ee4cd 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1416,8 +1416,8 @@ static void __implement(u8 *report, unsigned offset, int n, u32 value) } } -static void implement(const struct hid_device *hid, u8 *report, - unsigned offset, unsigned n, u32 value) +void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, + u32 value) { if (unlikely(n > 32)) { hid_warn(hid, "%s() called with n (%d) > 32! (%s)\n", diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 377012a019da..07cbd5cf595c 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -72,6 +72,8 @@ struct bpf_hid_hooks { int (*link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*link_attached)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*array_detached)(struct hid_device *hdev, enum bpf_hid_attach_type type); + int (*hid_get_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size); + int (*hid_set_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size, u32 data); }; #ifdef CONFIG_BPF diff --git a/include/linux/hid.h b/include/linux/hid.h index 66d949d10b78..7454e844324c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -944,6 +944,8 @@ bool hid_compare_device_paths(struct hid_device *hdev_a, s32 hid_snto32(__u32 value, unsigned n); __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, unsigned offset, unsigned n); +void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, + u32 value); #ifdef CONFIG_PM int hid_driver_suspend(struct hid_device *hdev, pm_message_t state); diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index a7a8d9cfcf24..0571d9b954c9 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5090,6 +5090,20 @@ union bpf_attr { * Return * 0 on success, or a negative error in case of failure. On error * *dst* buffer is zeroed out. + * + * u32 bpf_hid_get_data(void *ctx, u64 offset, u8 n) + * Description + * Get the data of size n at the given offset in the + * ctx->event.data field + * Return + * The value at offset. In case of error: 0. + * + * int bpf_hid_set_data(void *ctx, u64 offset, u8 n, u32 data) + * Description + * Set the data of size n at the given offset in the + * ctx->event.data field + * Return + * 0 on success, a negative error on failure. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5284,6 +5298,8 @@ union bpf_attr { FN(xdp_load_bytes), \ FN(xdp_store_bytes), \ FN(copy_from_user_task), \ + FN(hid_get_data), \ + FN(hid_set_data), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 47cb0580b14a..9eb7bd6ac6c8 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -37,10 +37,78 @@ void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) } EXPORT_SYMBOL_GPL(bpf_hid_set_hooks); +BPF_CALL_3(bpf_hid_get_data, void*, ctx, u64, offset, u8, n) +{ + struct hid_bpf_ctx *bpf_ctx = ctx; + u8 *buf; + + if (!hid_hooks.hid_get_data) + return -EOPNOTSUPP; + + switch (bpf_ctx->type) { + case HID_BPF_DEVICE_EVENT: + buf = bpf_ctx->u.device.data; + break; + case HID_BPF_RDESC_FIXUP: + buf = bpf_ctx->u.rdesc.data; + break; + default: + return -EOPNOTSUPP; + } + + return hid_hooks.hid_get_data(bpf_ctx->hdev, buf, offset, n); +} + +static const struct bpf_func_proto bpf_hid_get_data_proto = { + .func = bpf_hid_get_data, + .gpl_only = true, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_ANYTHING, + .arg3_type = ARG_ANYTHING, +}; + +BPF_CALL_4(bpf_hid_set_data, void*, ctx, u64, offset, u8, n, u32, data) +{ + struct hid_bpf_ctx *bpf_ctx = ctx; + u8 *buf; + + if (!hid_hooks.hid_set_data) + return -EOPNOTSUPP; + + switch (bpf_ctx->type) { + case HID_BPF_DEVICE_EVENT: + buf = bpf_ctx->u.device.data; + break; + case HID_BPF_RDESC_FIXUP: + buf = bpf_ctx->u.rdesc.data; + break; + default: + return -EOPNOTSUPP; + } + + hid_hooks.hid_set_data(bpf_ctx->hdev, buf, offset, n, data); + return 0; +} + +static const struct bpf_func_proto bpf_hid_set_data_proto = { + .func = bpf_hid_set_data, + .gpl_only = true, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_ANYTHING, + .arg3_type = ARG_ANYTHING, + .arg4_type = ARG_ANYTHING, +}; + static const struct bpf_func_proto * hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { + case BPF_FUNC_hid_get_data: + return &bpf_hid_get_data_proto; + case BPF_FUNC_hid_set_data: + return &bpf_hid_set_data_proto; default: return bpf_base_func_proto(func_id); } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index a7a8d9cfcf24..0571d9b954c9 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5090,6 +5090,20 @@ union bpf_attr { * Return * 0 on success, or a negative error in case of failure. On error * *dst* buffer is zeroed out. + * + * u32 bpf_hid_get_data(void *ctx, u64 offset, u8 n) + * Description + * Get the data of size n at the given offset in the + * ctx->event.data field + * Return + * The value at offset. In case of error: 0. + * + * int bpf_hid_set_data(void *ctx, u64 offset, u8 n, u32 data) + * Description + * Set the data of size n at the given offset in the + * ctx->event.data field + * Return + * 0 on success, a negative error on failure. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5284,6 +5298,8 @@ union bpf_attr { FN(xdp_load_bytes), \ FN(xdp_store_bytes), \ FN(copy_from_user_task), \ + FN(hid_get_data), \ + FN(hid_set_data), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index dccbbcaa69e5..7d4f740a0a08 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -280,6 +280,62 @@ static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; } +/* + * Attach hid_set_get_data to the given uhid device, + * retrieve and open the matching hidraw node, + * inject one event in the uhid device, + * check that the program makes correct use of bpf_hid_{set|get}_data. + */ +static int test_hid_set_get_data(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + u8 buf[10] = {0}; + int ret = -1; + + /* attach hid_set_get_data program */ + hid_skel->links.hid_set_get_data = + bpf_program__attach_hid(hid_skel->progs.hid_set_get_data, sysfs_fd); + if (!ASSERT_OK_PTR(hid_skel->links.hid_set_get_data, + "attach_hid(hid_set_get_data)")) + return PTR_ERR(hid_skel->links.hid_set_get_data); + + hidraw_ino = get_hidraw(hid_skel->links.hid_set_get_data); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + send_event(uhid_fd, buf, 4); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 4, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], (42 >> 2), "hid_set_get_data")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -378,6 +434,9 @@ void serial_test_hid_bpf(void) err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid"); + err = test_hid_set_get_data(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_set_get_data"); + err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_rdesc_fixup"); diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index f7a64c637782..01d9c556a3a1 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -66,3 +66,17 @@ int hid_rdesc_fixup(struct hid_bpf_ctx *ctx) return 0; } + +SEC("hid/device_event") +int hid_set_get_data(struct hid_bpf_ctx *ctx) +{ + __u32 x; + + /* extract data at bit offset 10 of size 4 (half a byte) */ + x = bpf_hid_get_data(ctx, 10, 4); + + /* reinject it */ + bpf_hid_set_data(ctx, 16, 4, x); + + return 0; +} From patchwork Thu Feb 24 11:08:26 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758388 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4AE1DC433FE for ; Thu, 24 Feb 2022 11:09:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233840AbiBXLKN (ORCPT ); Thu, 24 Feb 2022 06:10:13 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46556 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233832AbiBXLKK (ORCPT ); Thu, 24 Feb 2022 06:10:10 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 0AEA914F29A for ; Thu, 24 Feb 2022 03:09:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700979; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4xV2lmIzjMYY4MLS/LARIvSo701YdKE1gxInO7k6PmQ=; b=dm2usxyeiNDimHLqXlreBnTixbnqXKdNQltDEusPcS9Et8lPyKC9CKXqJTJshRgYM/5Gn0 DkFhUIXcATbqh6GJzJ2QRNbeZ3jc1RBAffw3xjK2MtGnKJS89HeUj7MNMHOfhsct57m9sB maMCVZSNooK8N2OjlaWp1Ru872teSlk= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-507-NF33J5o-OI60gJ1uevKL0w-1; Thu, 24 Feb 2022 06:09:35 -0500 X-MC-Unique: NF33J5o-OI60gJ1uevKL0w-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 72DF3800496; Thu, 24 Feb 2022 11:09:33 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id BB50C79A22; Thu, 24 Feb 2022 11:09:18 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 4/6] HID: bpf: add new BPF type to trigger commands from userspace Date: Thu, 24 Feb 2022 12:08:26 +0100 Message-Id: <20220224110828.2168231-5-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org Given that we can not call bpf_hid_raw_request() from within an IRQ, userspace needs to have a way to communicate with the device when it needs. Implement a new type that the caller can run at will without being in an IRQ context. Signed-off-by: Benjamin Tissoires --- include/linux/bpf-hid.h | 3 + include/uapi/linux/bpf.h | 1 + include/uapi/linux/bpf_hid.h | 9 ++ kernel/bpf/hid.c | 117 +++++++++++++++++++ kernel/bpf/syscall.c | 2 + tools/include/uapi/linux/bpf.h | 1 + tools/lib/bpf/libbpf.c | 1 + tools/testing/selftests/bpf/prog_tests/hid.c | 56 +++++++++ tools/testing/selftests/bpf/progs/hid.c | 10 ++ 9 files changed, 200 insertions(+) diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 07cbd5cf595c..00ac4555aa5b 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -15,6 +15,7 @@ enum bpf_hid_attach_type { BPF_HID_ATTACH_INVALID = -1, BPF_HID_ATTACH_DEVICE_EVENT = 0, BPF_HID_ATTACH_RDESC_FIXUP, + BPF_HID_ATTACH_USER_EVENT, MAX_BPF_HID_ATTACH_TYPE }; @@ -34,6 +35,8 @@ to_bpf_hid_attach_type(enum bpf_attach_type attach_type) return BPF_HID_ATTACH_DEVICE_EVENT; case BPF_HID_RDESC_FIXUP: return BPF_HID_ATTACH_RDESC_FIXUP; + case BPF_HID_USER_EVENT: + return BPF_HID_ATTACH_USER_EVENT; default: return BPF_HID_ATTACH_INVALID; } diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 0571d9b954c9..a374cc4aade6 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1000,6 +1000,7 @@ enum bpf_attach_type { BPF_PERF_EVENT, BPF_HID_DEVICE_EVENT, BPF_HID_RDESC_FIXUP, + BPF_HID_USER_EVENT, __MAX_BPF_ATTACH_TYPE }; diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h index c0801d7174c3..7a263568e132 100644 --- a/include/uapi/linux/bpf_hid.h +++ b/include/uapi/linux/bpf_hid.h @@ -19,6 +19,7 @@ enum hid_bpf_event { HID_BPF_UNDEF = 0, HID_BPF_DEVICE_EVENT, HID_BPF_RDESC_FIXUP, + HID_BPF_USER_EVENT, }; /* type is HID_BPF_DEVICE_EVENT */ @@ -33,6 +34,13 @@ struct hid_bpf_ctx_rdesc_fixup { unsigned long size; }; +/* type is HID_BPF_USER_EVENT */ +struct hid_bpf_ctx_user_event { + __u8 data[HID_BPF_MAX_BUFFER_SIZE]; + unsigned long size; + int retval; +}; + struct hid_bpf_ctx { enum hid_bpf_event type; struct hid_device *hdev; @@ -40,6 +48,7 @@ struct hid_bpf_ctx { union { struct hid_bpf_ctx_device_event device; struct hid_bpf_ctx_rdesc_fixup rdesc; + struct hid_bpf_ctx_user_event user; } u; }; diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 9eb7bd6ac6c8..3714413e1eb6 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -52,6 +52,9 @@ BPF_CALL_3(bpf_hid_get_data, void*, ctx, u64, offset, u8, n) case HID_BPF_RDESC_FIXUP: buf = bpf_ctx->u.rdesc.data; break; + case HID_BPF_USER_EVENT: + buf = bpf_ctx->u.user.data; + break; default: return -EOPNOTSUPP; } @@ -83,6 +86,9 @@ BPF_CALL_4(bpf_hid_set_data, void*, ctx, u64, offset, u8, n, u32, data) case HID_BPF_RDESC_FIXUP: buf = bpf_ctx->u.rdesc.data; break; + case HID_BPF_USER_EVENT: + buf = bpf_ctx->u.user.data; + break; default: return -EOPNOTSUPP; } @@ -385,6 +391,8 @@ static int bpf_hid_max_progs(enum bpf_hid_attach_type type) return 64; case BPF_HID_ATTACH_RDESC_FIXUP: return 1; + case BPF_HID_ATTACH_USER_EVENT: + return 64; default: return 0; } @@ -479,7 +487,116 @@ int bpf_hid_link_create(const union bpf_attr *attr, struct bpf_prog *prog) return bpf_link_settle(&link_primer); } +static int hid_bpf_prog_test_run(struct bpf_prog *prog, + const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + struct hid_device *hdev = NULL; + struct bpf_prog_array *progs; + struct hid_bpf_ctx *ctx = NULL; + bool valid_prog = false; + int i; + int target_fd, ret; + void __user *data_out = u64_to_user_ptr(attr->test.data_out); + void __user *data_in = u64_to_user_ptr(attr->test.data_in); + u32 user_size = attr->test.data_size_in; + + if (!hid_hooks.hdev_from_fd) + return -EOPNOTSUPP; + + if (attr->test.ctx_size_in != sizeof(int)) + return -EINVAL; + + if (copy_from_user(&target_fd, (void *)attr->test.ctx_in, attr->test.ctx_size_in)) + return -EFAULT; + + hdev = hid_hooks.hdev_from_fd(target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + ret = mutex_lock_interruptible(&bpf_hid_mutex); + if (ret) + return ret; + + /* check if the given program is of correct type and registered */ + progs = rcu_dereference_protected(hdev->bpf.run_array[BPF_HID_ATTACH_USER_EVENT], + lockdep_is_held(&bpf_hid_mutex)); + if (!progs) { + ret = -EFAULT; + goto unlock; + } + + for (i = 0; i < bpf_prog_array_length(progs); i++) { + if (progs->items[i].prog == prog) { + valid_prog = true; + break; + } + } + + if (!valid_prog) { + ret = -EINVAL; + goto unlock; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + + ctx->hdev = hdev; + ctx->type = HID_BPF_USER_EVENT; + + /* copy data_in from userspace */ + if (user_size) { + if (user_size > HID_BPF_MAX_BUFFER_SIZE) + user_size = HID_BPF_MAX_BUFFER_SIZE; + + if (copy_from_user(ctx->u.user.data, data_in, user_size)) { + ret = -EFAULT; + goto unlock; + } + + ctx->u.user.size = user_size; + } + + migrate_disable(); + + ret = bpf_prog_run(prog, ctx); + + migrate_enable(); + + user_size = attr->test.data_size_out; + + if (user_size && data_out) { + if (user_size > ctx->u.user.size) + user_size = ctx->u.user.size; + + if (copy_to_user(data_out, ctx->u.user.data, user_size)) { + ret = -EFAULT; + goto unlock; + } + + if (copy_to_user(&uattr->test.data_size_out, &user_size, sizeof(user_size))) { + ret = -EFAULT; + goto unlock; + } + } + + if (copy_to_user(&uattr->test.retval, &ctx->u.user.retval, sizeof(ctx->u.user.retval))) { + ret = -EFAULT; + goto unlock; + } + +unlock: + kfree(ctx); + + mutex_unlock(&bpf_hid_mutex); + return ret; +} + const struct bpf_prog_ops hid_prog_ops = { + .test_run = hid_bpf_prog_test_run, }; int bpf_hid_init(struct hid_device *hdev) diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 62889cc71a02..0a6d08dabe59 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3192,6 +3192,7 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) return BPF_PROG_TYPE_XDP; case BPF_HID_DEVICE_EVENT: case BPF_HID_RDESC_FIXUP: + case BPF_HID_USER_EVENT: return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; @@ -3338,6 +3339,7 @@ static int bpf_prog_query(const union bpf_attr *attr, return sock_map_bpf_prog_query(attr, uattr); case BPF_HID_DEVICE_EVENT: case BPF_HID_RDESC_FIXUP: + case BPF_HID_USER_EVENT: return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 0571d9b954c9..a374cc4aade6 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1000,6 +1000,7 @@ enum bpf_attach_type { BPF_PERF_EVENT, BPF_HID_DEVICE_EVENT, BPF_HID_RDESC_FIXUP, + BPF_HID_USER_EVENT, __MAX_BPF_ATTACH_TYPE }; diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index b7af873116fb..290864d2f865 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8678,6 +8678,7 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX), SEC_DEF("hid/device_event", HID, BPF_HID_DEVICE_EVENT, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), SEC_DEF("hid/rdesc_fixup", HID, BPF_HID_RDESC_FIXUP, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), + SEC_DEF("hid/user_event", HID, BPF_HID_USER_EVENT, SEC_ATTACHABLE_OPT | SEC_SLOPPY_PFX), }; #define MAX_TYPE_NAME_SIZE 32 diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index 7d4f740a0a08..d297a571e910 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -336,6 +336,59 @@ static int test_hid_set_get_data(struct hid *hid_skel, int uhid_fd, int sysfs_fd return ret; } +/* + * Attach hid_user to the given uhid device, + * call the bpf program from userspace + * check that the program is called and does the expected. + */ +static int test_hid_user_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, prog_fd; + u8 buf[10] = {0}; + int ret = -1; + + LIBBPF_OPTS(bpf_test_run_opts, run_attrs, + .repeat = 1, + .ctx_in = &sysfs_fd, + .ctx_size_in = sizeof(sysfs_fd), + .data_in = buf, + .data_size_in = sizeof(buf), + .data_out = buf, + .data_size_out = sizeof(buf), + ); + + /* attach hid_user program */ + hid_skel->links.hid_user = bpf_program__attach_hid(hid_skel->progs.hid_user, sysfs_fd); + if (!ASSERT_OK_PTR(hid_skel->links.hid_user, + "attach_hid(hid_user)")) + return PTR_ERR(hid_skel->links.hid_user); + + buf[0] = 39; + + prog_fd = bpf_program__fd(hid_skel->progs.hid_user); + + err = bpf_prog_test_run_opts(prog_fd, &run_attrs); + if (!ASSERT_EQ(err, 0, "bpf_prog_test_run_xattr")) + goto cleanup; + + if (!ASSERT_EQ(run_attrs.retval, 72, "bpf_prog_test_run_xattr_retval")) + goto cleanup; + + if (!ASSERT_EQ(buf[1], 42, "hid_user_check_in")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 4, "hid_user_check_static_out")) + goto cleanup; + + ret = 0; + +cleanup: + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -437,6 +490,9 @@ void serial_test_hid_bpf(void) err = test_hid_set_get_data(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_set_get_data"); + err = test_hid_user_call(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_user"); + err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_rdesc_fixup"); diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index 01d9c556a3a1..b2db809b3367 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -80,3 +80,13 @@ int hid_set_get_data(struct hid_bpf_ctx *ctx) return 0; } + +SEC("hid/user_event") +int hid_user(struct hid_bpf_ctx *ctx) +{ + ctx->u.user.data[1] = ctx->u.user.data[0] + 3; + ctx->u.user.data[2] = 4; + ctx->u.user.retval = 72; + + return 0; +} From patchwork Thu Feb 24 11:08:27 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758389 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93AFAC433F5 for ; Thu, 24 Feb 2022 11:09:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233858AbiBXLKU (ORCPT ); Thu, 24 Feb 2022 06:10:20 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46684 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233835AbiBXLKQ (ORCPT ); Thu, 24 Feb 2022 06:10:16 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 80E6D14F981 for ; Thu, 24 Feb 2022 03:09:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700985; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=4A1fWlnBebac/KzDK39/KFbG0TBuCQPRorVPRC/e3BU=; b=NgLBpEtknTC1JaRXBk2zix3iyOlcAXtyJQGhJEherAMf0zud9qCZBQy0R3TQYzk4ED+S7a 5HbtMC2WdiyHtjUSaNSYCH3/lwpjN7OmQWBQidRaUGDr1q9+k8RjSVWgCJ0XSjTARdGPE3 I2jUZykqQX29icGQlB7IIzn4MMSr6Mg= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-369-OevdC6KlNraNSZBlY0Wmog-1; Thu, 24 Feb 2022 06:09:40 -0500 X-MC-Unique: OevdC6KlNraNSZBlY0Wmog-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id D6CE4824FA6; Thu, 24 Feb 2022 11:09:37 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id CDFBE79A22; Thu, 24 Feb 2022 11:09:33 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 5/6] HID: bpf: tests: rely on uhid event to know if a test device is ready Date: Thu, 24 Feb 2022 12:08:27 +0100 Message-Id: <20220224110828.2168231-6-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org We need this for 2 reasons: - first we remove the ugly sleeps - then when we try to communicate with the device, we need to have another thread that handles that communication and simulate a real device Signed-off-by: Benjamin Tissoires --- tools/testing/selftests/bpf/prog_tests/hid.c | 126 ++++++++++++++++++- 1 file changed, 120 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index d297a571e910..b0cf615b0d0f 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -67,6 +67,12 @@ static unsigned char rdesc[] = { 0xc0, /* END_COLLECTION */ }; +static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; + +/* no need to protect uhid_stopped, only one thread accesses it */ +static bool uhid_stopped; + static int uhid_write(int fd, const struct uhid_event *ev) { ssize_t ret; @@ -118,6 +124,104 @@ static void destroy(int fd) uhid_write(fd, &ev); } +static int event(int fd) +{ + struct uhid_event ev; + ssize_t ret; + + memset(&ev, 0, sizeof(ev)); + ret = read(fd, &ev, sizeof(ev)); + if (ret == 0) { + fprintf(stderr, "Read HUP on uhid-cdev\n"); + return -EFAULT; + } else if (ret < 0) { + fprintf(stderr, "Cannot read uhid-cdev: %m\n"); + return -errno; + } else if (ret != sizeof(ev)) { + fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", + ret, sizeof(ev)); + return -EFAULT; + } + + switch (ev.type) { + case UHID_START: + pthread_mutex_lock(&uhid_started_mtx); + pthread_cond_signal(&uhid_started); + pthread_mutex_unlock(&uhid_started_mtx); + + fprintf(stderr, "UHID_START from uhid-dev\n"); + break; + case UHID_STOP: + uhid_stopped = true; + + fprintf(stderr, "UHID_STOP from uhid-dev\n"); + break; + case UHID_OPEN: + fprintf(stderr, "UHID_OPEN from uhid-dev\n"); + break; + case UHID_CLOSE: + fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); + break; + case UHID_OUTPUT: + fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); + break; + case UHID_GET_REPORT: + fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + break; + case UHID_SET_REPORT: + fprintf(stderr, "UHID_SET_REPORT from uhid-dev\n"); + break; + default: + fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); + } + + return 0; +} + +static void *read_uhid_events_thread(void *arg) +{ + int fd = *(int *)arg; + struct pollfd pfds[1]; + int ret = 0; + + pfds[0].fd = fd; + pfds[0].events = POLLIN; + + uhid_stopped = false; + + while (!uhid_stopped) { + ret = poll(pfds, 1, 100); + if (ret < 0) { + fprintf(stderr, "Cannot poll for fds: %m\n"); + break; + } + if (pfds[0].revents & POLLIN) { + ret = event(fd); + if (ret) + break; + } + } + + return (void *)(long)ret; +} + +static int uhid_start_listener(pthread_t *tid, int uhid_fd) +{ + int fd = uhid_fd; + + pthread_mutex_lock(&uhid_started_mtx); + if (CHECK_FAIL(pthread_create(tid, NULL, read_uhid_events_thread, + (void *)&fd))) { + pthread_mutex_unlock(&uhid_started_mtx); + close(fd); + return -EIO; + } + pthread_cond_wait(&uhid_started, &uhid_started_mtx); + pthread_mutex_unlock(&uhid_started_mtx); + + return 0; +} + static int send_event(int fd, u8 *buf, size_t size) { struct uhid_event ev; @@ -399,7 +503,9 @@ static int test_rdesc_fixup(struct hid *hid_skel, int uhid_fd, int sysfs_fd) struct hidraw_report_descriptor rpt_desc = {0}; int err, desc_size, hidraw_ino, hidraw_fd = -1; char hidraw_path[64] = {0}; + void *uhid_err; int ret = -1; + pthread_t tid; /* attach the program */ hid_skel->links.hid_rdesc_fixup = @@ -408,9 +514,8 @@ static int test_rdesc_fixup(struct hid *hid_skel, int uhid_fd, int sysfs_fd) "attach_hid(hid_rdesc_fixup)")) return PTR_ERR(hid_skel->links.hid_rdesc_fixup); - /* give a little bit of time for the device to appear */ - /* TODO: check on uhid events */ - usleep(1000); + err = uhid_start_listener(&tid, uhid_fd); + ASSERT_OK(err, "uhid_start_listener"); hidraw_ino = get_hidraw(hid_skel->links.hid_rdesc_fixup); if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) @@ -451,6 +556,10 @@ static int test_rdesc_fixup(struct hid *hid_skel, int uhid_fd, int sysfs_fd) hid__detach(hid_skel); + pthread_join(tid, &uhid_err); + err = (int)(long)uhid_err; + CHECK_FAIL(err); + return ret; } @@ -458,7 +567,9 @@ void serial_test_hid_bpf(void) { struct hid *hid_skel = NULL; int err, uhid_fd, sysfs_fd; + void *uhid_err; time_t t; + pthread_t tid; int rand_nb; /* initialize random number generator */ @@ -470,9 +581,8 @@ void serial_test_hid_bpf(void) if (!ASSERT_GE(uhid_fd, 0, "setup uhid")) return; - /* give a little bit of time for the device to appear */ - /* TODO: check on uhid events */ - usleep(1000); + err = uhid_start_listener(&tid, uhid_fd); + ASSERT_OK(err, "uhid_start_listener"); /* locate the uevent file of the created device */ sysfs_fd = get_sysfs_fd(rand_nb); @@ -499,4 +609,8 @@ void serial_test_hid_bpf(void) cleanup: hid__destroy(hid_skel); destroy(uhid_fd); + + pthread_join(tid, &uhid_err); + err = (int)(long)uhid_err; + CHECK_FAIL(err); } From patchwork Thu Feb 24 11:08:28 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Benjamin Tissoires X-Patchwork-Id: 12758390 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id CB9BEC43219 for ; Thu, 24 Feb 2022 11:09:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233853AbiBXLKV (ORCPT ); Thu, 24 Feb 2022 06:10:21 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46812 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233849AbiBXLKV (ORCPT ); Thu, 24 Feb 2022 06:10:21 -0500 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 63C6A233E67 for ; Thu, 24 Feb 2022 03:09:50 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645700989; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=Co9gM4NcroP5T8KaacXR96ovTPx67wSvELeX0sLff8Q=; b=jS8U4wouPSVXzpZM/QrXkJ1rGySF10+wjzpXSkIL0juwJkle5nWehGicYjT3IUgShIihSg 70r/kqG9ujPOcwkK9hrAmQEH99V+dvFfXyZ2wTyGCgESAg3uQK37P540yNtgDznd5iW8Nw kdTN2jnnkZj4oqiTq8B0Wig47shoYZE= Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-648-FF2gvOBhMCqk5gb9g44ZyA-1; Thu, 24 Feb 2022 06:09:44 -0500 X-MC-Unique: FF2gvOBhMCqk5gb9g44ZyA-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id E1918824FA6; Thu, 24 Feb 2022 11:09:41 +0000 (UTC) Received: from plouf.redhat.com (unknown [10.39.194.109]) by smtp.corp.redhat.com (Postfix) with ESMTP id 33DA37A526; Thu, 24 Feb 2022 11:09:38 +0000 (UTC) From: Benjamin Tissoires To: Jiri Kosina , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Shuah Khan , Dave Marchevsky , Joe Stringer Cc: Tero Kristo , linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, netdev@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Benjamin Tissoires Subject: [PATCH bpf-next v1 6/6] HID: bpf: add bpf_hid_raw_request helper function Date: Thu, 24 Feb 2022 12:08:28 +0100 Message-Id: <20220224110828.2168231-7-benjamin.tissoires@redhat.com> In-Reply-To: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> References: <20220224110828.2168231-1-benjamin.tissoires@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org When we are in a user_event context, we can talk to the device to fetch or set features/outputs/inputs reports. Add a bpf helper to do so. This helper is thus only available to user_events, because calling this function while in IRQ context (any other BPF type) is forbidden. Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-bpf.c | 63 +++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/bpf-hid.h | 2 + include/linux/hid.h | 1 + include/uapi/linux/bpf.h | 8 +++ kernel/bpf/hid.c | 26 +++++++ tools/include/uapi/linux/bpf.h | 8 +++ tools/testing/selftests/bpf/prog_tests/hid.c | 71 +++++++++++++++++++- tools/testing/selftests/bpf/progs/hid.c | 57 ++++++++++++++++ 9 files changed, 236 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index d775bda9d28d..180941061e53 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -138,6 +138,68 @@ int hid_bpf_set_data(struct hid_device *hdev, u8 *buf, u64 offset, u8 n, u32 dat return 0; } +int hid_bpf_raw_request(struct hid_device *hdev, u8 *buf, size_t size, + u8 rtype, u8 reqtype) +{ + struct hid_report *report; + struct hid_report_enum *report_enum; + u8 *dma_data; + u32 report_len; + int ret; + + /* check arguments */ + switch (rtype) { + case HID_INPUT_REPORT: + case HID_OUTPUT_REPORT: + case HID_FEATURE_REPORT: + break; + default: + return -EINVAL; + } + + switch (reqtype) { + case HID_REQ_GET_REPORT: + case HID_REQ_GET_IDLE: + case HID_REQ_GET_PROTOCOL: + case HID_REQ_SET_REPORT: + case HID_REQ_SET_IDLE: + case HID_REQ_SET_PROTOCOL: + break; + default: + return -EINVAL; + } + + if (size < 1) + return -EINVAL; + + report_enum = hdev->report_enum + rtype; + report = hid_get_report(report_enum, buf); + if (!report) + return -EINVAL; + + report_len = hid_report_len(report); + + if (size > report_len) + size = report_len; + + dma_data = kmemdup(buf, size, GFP_KERNEL); + if (!dma_data) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, + dma_data[0], + dma_data, + size, + rtype, + reqtype); + + if (ret > 0) + memcpy(buf, dma_data, ret); + + kfree(dma_data); + return ret; +} + static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type type, struct hid_bpf_ctx *ctx, u8 *data, int size) { @@ -251,6 +313,7 @@ int __init hid_bpf_module_init(void) .array_detached = hid_bpf_array_detached, .hid_get_data = hid_bpf_get_data, .hid_set_data = hid_bpf_set_data, + .hid_raw_request = hid_bpf_raw_request, }; bpf_hid_set_hooks(&hooks); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index d3f4499ee4cd..d0e015986e17 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1686,8 +1686,7 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value) } EXPORT_SYMBOL_GPL(hid_set_field); -static struct hid_report *hid_get_report(struct hid_report_enum *report_enum, - const u8 *data) +struct hid_report *hid_get_report(struct hid_report_enum *report_enum, const u8 *data) { struct hid_report *report; unsigned int n = 0; /* Normally report number is 0 */ diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 00ac4555aa5b..05d88b48c315 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -77,6 +77,8 @@ struct bpf_hid_hooks { void (*array_detached)(struct hid_device *hdev, enum bpf_hid_attach_type type); int (*hid_get_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size); int (*hid_set_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size, u32 data); + int (*hid_raw_request)(struct hid_device *hdev, u8 *buf, size_t size, + u8 rtype, u8 reqtype); }; #ifdef CONFIG_BPF diff --git a/include/linux/hid.h b/include/linux/hid.h index 7454e844324c..b2698df31e5b 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -946,6 +946,7 @@ __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, unsigned offset, unsigned n); void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, u32 value); +struct hid_report *hid_get_report(struct hid_report_enum *report_enum, const u8 *data); #ifdef CONFIG_PM int hid_driver_suspend(struct hid_device *hdev, pm_message_t state); diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index a374cc4aade6..058095d9961d 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5105,6 +5105,13 @@ union bpf_attr { * ctx->event.data field * Return * 0 on success, a negative error on failure. + * + * int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 rtype, u8 reqtype) + * Description + * communicate with the HID device + * Return + * 0 on success. + * negative value on error. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5301,6 +5308,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(hid_get_data), \ FN(hid_set_data), \ + FN(hid_raw_request), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 3714413e1eb6..9dfb66f9b1b6 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -107,6 +107,28 @@ static const struct bpf_func_proto bpf_hid_set_data_proto = { .arg4_type = ARG_ANYTHING, }; +BPF_CALL_5(bpf_hid_raw_request, void*, ctx, void*, buf, u64, size, + u8, rtype, u8, reqtype) +{ + struct hid_bpf_ctx *bpf_ctx = ctx; + + if (!hid_hooks.hid_raw_request) + return -EOPNOTSUPP; + + return hid_hooks.hid_raw_request(bpf_ctx->hdev, buf, size, rtype, reqtype); +} + +static const struct bpf_func_proto bpf_hid_raw_request_proto = { + .func = bpf_hid_raw_request, + .gpl_only = true, /* hid_raw_request is EXPORT_SYMBOL_GPL */ + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_PTR_TO_MEM, + .arg3_type = ARG_CONST_SIZE_OR_ZERO, + .arg4_type = ARG_ANYTHING, + .arg5_type = ARG_ANYTHING, +}; + static const struct bpf_func_proto * hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { @@ -115,6 +137,10 @@ hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) return &bpf_hid_get_data_proto; case BPF_FUNC_hid_set_data: return &bpf_hid_set_data_proto; + case BPF_FUNC_hid_raw_request: + if (prog->expected_attach_type != BPF_HID_DEVICE_EVENT) + return &bpf_hid_raw_request_proto; + return NULL; default: return bpf_base_func_proto(func_id); } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index a374cc4aade6..058095d9961d 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5105,6 +5105,13 @@ union bpf_attr { * ctx->event.data field * Return * 0 on success, a negative error on failure. + * + * int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 rtype, u8 reqtype) + * Description + * communicate with the HID device + * Return + * 0 on success. + * negative value on error. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5301,6 +5308,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(hid_get_data), \ FN(hid_set_data), \ + FN(hid_raw_request), \ /* */ /* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index b0cf615b0d0f..3dbad78ec121 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -67,6 +67,8 @@ static unsigned char rdesc[] = { 0xc0, /* END_COLLECTION */ }; +static u8 feature_data[] = { 1, 2 }; + static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; @@ -126,7 +128,7 @@ static void destroy(int fd) static int event(int fd) { - struct uhid_event ev; + struct uhid_event ev, answer; ssize_t ret; memset(&ev, 0, sizeof(ev)); @@ -143,6 +145,8 @@ static int event(int fd) return -EFAULT; } + memset(&answer, 0, sizeof(answer)); + switch (ev.type) { case UHID_START: pthread_mutex_lock(&uhid_started_mtx); @@ -167,6 +171,15 @@ static int event(int fd) break; case UHID_GET_REPORT: fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + + answer.type = UHID_GET_REPORT_REPLY; + answer.u.get_report_reply.id = ev.u.get_report.id; + answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO; + answer.u.get_report_reply.size = sizeof(feature_data); + memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data)); + + uhid_write(fd, &answer); + break; case UHID_SET_REPORT: fprintf(stderr, "UHID_SET_REPORT from uhid-dev\n"); @@ -493,6 +506,59 @@ static int test_hid_user_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; } +/* + * Attach hid_user_raw_request to the given uhid device, + * call the bpf program from userspace + * check that the program is called and does the expected. + */ +static int test_hid_user_raw_request_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, prog_fd; + u8 buf[10] = {0}; + int ret = -1; + + LIBBPF_OPTS(bpf_test_run_opts, run_attrs, + .repeat = 1, + .ctx_in = &sysfs_fd, + .ctx_size_in = sizeof(sysfs_fd), + .data_in = buf, + .data_size_in = sizeof(buf), + .data_out = buf, + .data_size_out = sizeof(buf), + ); + + /* attach hid_user_raw_request program */ + hid_skel->links.hid_user_raw_request = + bpf_program__attach_hid(hid_skel->progs.hid_user_raw_request, sysfs_fd); + if (!ASSERT_OK_PTR(hid_skel->links.hid_user_raw_request, + "attach_hid(hid_user_raw_request)")) + return PTR_ERR(hid_skel->links.hid_user_raw_request); + + buf[0] = 2; /* HID_FEATURE_REPORT */ + buf[1] = 1; /* HID_REQ_GET_REPORT */ + buf[2] = 1; /* report ID */ + + prog_fd = bpf_program__fd(hid_skel->progs.hid_user_raw_request); + + err = bpf_prog_test_run_opts(prog_fd, &run_attrs); + if (!ASSERT_EQ(err, 0, "bpf_prog_test_run_xattr")) + goto cleanup; + + if (!ASSERT_EQ(run_attrs.retval, 2, "bpf_prog_test_run_xattr_retval")) + goto cleanup; + + if (!ASSERT_EQ(buf[3], 2, "hid_user_raw_request_check_in")) + goto cleanup; + + ret = 0; + +cleanup: + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -603,6 +669,9 @@ void serial_test_hid_bpf(void) err = test_hid_user_call(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_user"); + err = test_hid_user_raw_request_call(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_user_raw_request"); + err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_rdesc_fixup"); diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index b2db809b3367..d49eb9e0e745 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -9,6 +9,11 @@ char _license[] SEC("license") = "GPL"; __u64 callback_check = 52; __u64 callback2_check = 52; +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 4096 * 64); +} ringbuf SEC(".maps"); + SEC("hid/device_event") int hid_first_event(struct hid_bpf_ctx *ctx) { @@ -90,3 +95,55 @@ int hid_user(struct hid_bpf_ctx *ctx) return 0; } + +SEC("hid/user_event") +int hid_user_raw_request(struct hid_bpf_ctx *ctx) +{ + const unsigned int buflen = 256; + const unsigned int _buflen = buflen * sizeof(__u8); + __u8 *buf; + int ret; + __u32 size; + __u8 rtype, reqtype; + + buf = bpf_ringbuf_reserve(&ringbuf, _buflen, 0); + if (!buf) + return -12; /* -ENOMEM */ + + __builtin_memcpy(buf, ctx->u.user.data, _buflen); + + /* + * build up a custom API for our needs: + * offset 0, size 1: report type + * offset 1, size 1: request type + * offset 2+: data + */ + rtype = buf[0]; + reqtype = buf[1]; + size = ctx->u.user.size - 2; + + if (size < _buflen - 2) { + ret = bpf_hid_raw_request(ctx, + &buf[2], + size, + rtype, + reqtype); + if (ret < 0) + goto discard; + } else { + ret = -7; /* -E2BIG */ + goto discard; + } + + __builtin_memcpy(&ctx->u.user.data[2], &buf[2], _buflen - 2); + + ctx->u.user.size = ret + 2; + ctx->u.user.retval = ret; + + ret = 0; + + discard: + bpf_ringbuf_discard(buf, 0); + + return ret; +}