Commit 30b86641 authored by Benjamin Tissoires's avatar Benjamin Tissoires

Merge branch 'for-6.11/bpf' into for-linus

- Rewrite of HID-BPF internal implementation to use bpf struct_ops
  instead of tracing (Benjamin Tissoires)
- Add new HID-BPF hooks to be able to intercept userspace calls
  targetting a HID device and filtering them (Benjamin Tissoires)
- Add support for various new devices through HID-BPF filters (Benjamin
  Tissoires)
parents e518f368 a67a1deb
......@@ -129,19 +129,37 @@ When a BPF program needs to emit input events, it needs to talk with the HID
protocol, and rely on the HID kernel processing to translate the HID data into
input events.
In-tree HID-BPF programs and ``udev-hid-bpf``
=============================================
Official device fixes are shipped in the kernel tree as source in the
``drivers/hid/bpf/progs`` directory. This allows to add selftests to them in
``tools/testing/selftests/hid``.
However, the compilation of these objects is not part of a regular kernel compilation
given that they need an external tool to be loaded. This tool is currently
`udev-hid-bpf <https://libevdev.pages.freedesktop.org/udev-hid-bpf/index.html>`_.
For convenience, that external repository duplicates the files from here in
``drivers/hid/bpf/progs`` into its own ``src/bpf/stable`` directory. This allows
distributions to not have to pull the entire kernel source tree to ship and package
those HID-BPF fixes. ``udev-hid-bpf`` also has capabilities of handling multiple
objects files depending on the kernel the user is running.
Available types of programs
===========================
HID-BPF is built "on top" of BPF, meaning that we use tracing method to
HID-BPF is built "on top" of BPF, meaning that we use bpf struct_ops method to
declare our programs.
HID-BPF has the following attachment types available:
1. event processing/filtering with ``SEC("fmod_ret/hid_bpf_device_event")`` in libbpf
1. event processing/filtering with ``SEC("struct_ops/hid_device_event")`` in libbpf
2. actions coming from userspace with ``SEC("syscall")`` in libbpf
3. change of the report descriptor with ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` in libbpf
3. change of the report descriptor with ``SEC("struct_ops/hid_rdesc_fixup")`` or
``SEC("struct_ops.s/hid_rdesc_fixup")`` in libbpf
A ``hid_bpf_device_event`` is calling a BPF program when an event is received from
A ``hid_device_event`` is calling a BPF program when an event is received from
the device. Thus we are in IRQ context and can act on the data or notify userspace.
And given that we are in IRQ context, we can not talk back to the device.
......@@ -149,37 +167,42 @@ A ``syscall`` means that userspace called the syscall ``BPF_PROG_RUN`` facility.
This time, we can do any operations allowed by HID-BPF, and talking to the device is
allowed.
Last, ``hid_bpf_rdesc_fixup`` is different from the others as there can be only one
Last, ``hid_rdesc_fixup`` is different from the others as there can be only one
BPF program of this type. This is called on ``probe`` from the driver and allows to
change the report descriptor from the BPF program. Once a ``hid_bpf_rdesc_fixup``
change the report descriptor from the BPF program. Once a ``hid_rdesc_fixup``
program has been loaded, it is not possible to overwrite it unless the program which
inserted it allows us by pinning the program and closing all of its fds pointing to it.
Note that ``hid_rdesc_fixup`` can be declared as sleepable (``SEC("struct_ops.s/hid_rdesc_fixup")``).
Developer API:
==============
User API data structures available in programs:
-----------------------------------------------
Available ``struct_ops`` for HID-BPF:
-------------------------------------
.. kernel-doc:: include/linux/hid_bpf.h
:identifiers: hid_bpf_ops
Available tracing functions to attach a HID-BPF program:
--------------------------------------------------------
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
:functions: hid_bpf_device_event hid_bpf_rdesc_fixup
User API data structures available in programs:
-----------------------------------------------
Available API that can be used in all HID-BPF programs:
-------------------------------------------------------
.. kernel-doc:: include/linux/hid_bpf.h
:identifiers: hid_bpf_ctx
Available API that can be used in all HID-BPF struct_ops programs:
------------------------------------------------------------------
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
:functions: hid_bpf_get_data
:identifiers: hid_bpf_get_data
Available API that can be used in syscall HID-BPF programs:
-----------------------------------------------------------
Available API that can be used in syscall HID-BPF programs or in sleepable HID-BPF struct_ops programs:
-------------------------------------------------------------------------------------------------------
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
:functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_allocate_context hid_bpf_release_context
:identifiers: hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_try_input_report hid_bpf_allocate_context hid_bpf_release_context
General overview of a HID-BPF program
=====================================
......@@ -222,20 +245,21 @@ This allows the following:
Effect of a HID-BPF program
---------------------------
For all HID-BPF attachment types except for :c:func:`hid_bpf_rdesc_fixup`, several eBPF
programs can be attached to the same device.
For all HID-BPF attachment types except for :c:func:`hid_rdesc_fixup`, several eBPF
programs can be attached to the same device. If a HID-BPF struct_ops has a
:c:func:`hid_rdesc_fixup` while another is already attached to the device, the
kernel will return `-EINVAL` when attaching the struct_ops.
Unless ``HID_BPF_FLAG_INSERT_HEAD`` is added to the flags while attaching the
program, the new program is appended at the end of the list.
``HID_BPF_FLAG_INSERT_HEAD`` will insert the new program at the beginning of the
list which is useful for e.g. tracing where we need to get the unprocessed events
from the device.
Unless ``BPF_F_BEFORE`` is added to the flags while attaching the program, the new
program is appended at the end of the list.
``BPF_F_BEFORE`` will insert the new program at the beginning of the list which is
useful for e.g. tracing where we need to get the unprocessed events from the device.
Note that if there are multiple programs using the ``HID_BPF_FLAG_INSERT_HEAD`` flag,
Note that if there are multiple programs using the ``BPF_F_BEFORE`` flag,
only the most recently loaded one is actually the first in the list.
``SEC("fmod_ret/hid_bpf_device_event")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``SEC("struct_ops/hid_device_event")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Whenever a matching event is raised, the eBPF programs are called one after the other
and are working on the same data buffer.
......@@ -258,17 +282,17 @@ with, userspace needs to refer to the device by its unique system id (the last 4
in the sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``).
To retrieve a context associated with the device, the program must call
:c:func:`hid_bpf_allocate_context` and must release it with :c:func:`hid_bpf_release_context`
hid_bpf_allocate_context() and must release it with hid_bpf_release_context()
before returning.
Once the context is retrieved, one can also request a pointer to kernel memory with
:c:func:`hid_bpf_get_data`. This memory is big enough to support all input/output/feature
hid_bpf_get_data(). This memory is big enough to support all input/output/feature
reports of the given device.
``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``SEC("struct_ops/hid_rdesc_fixup")``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``hid_bpf_rdesc_fixup`` program works in a similar manner to
``.report_fixup`` of ``struct hid_driver``.
The ``hid_rdesc_fixup`` program works in a similar manner to ``.report_fixup``
of ``struct hid_driver``.
When the device is probed, the kernel sets the data buffer of the context with the
content of the report descriptor. The memory associated with that buffer is
......@@ -277,33 +301,31 @@ content of the report descriptor. The memory associated with that buffer is
The eBPF program can modify the data buffer at-will and the kernel uses the
modified content and size as the report descriptor.
Whenever a ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is attached (if no
program was attached before), the kernel immediately disconnects the HID device
and does a reprobe.
Whenever a struct_ops containing a ``SEC("struct_ops/hid_rdesc_fixup")`` program
is attached (if no program was attached before), the kernel immediately disconnects
the HID device and does a reprobe.
In the same way, when the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program is
detached, the kernel issues a disconnect on the device.
In the same way, when this struct_ops is detached, the kernel issues a disconnect
on the device.
There is no ``detach`` facility in HID-BPF. Detaching a program happens when
all the user space file descriptors pointing at a program are closed.
all the user space file descriptors pointing at a HID-BPF struct_ops link are closed.
Thus, if we need to replace a report descriptor fixup, some cooperation is
required from the owner of the original report descriptor fixup.
The previous owner will likely pin the program in the bpffs, and we can then
The previous owner will likely pin the struct_ops link in the bpffs, and we can then
replace it through normal bpf operations.
Attaching a bpf program to a device
===================================
``libbpf`` does not export any helper to attach a HID-BPF program.
Users need to use a dedicated ``syscall`` program which will call
``hid_bpf_attach_prog(hid_id, program_fd, flags)``.
We now use standard struct_ops attachment through ``bpf_map__attach_struct_ops()``.
But given that we need to attach a struct_ops to a dedicated HID device, the caller
must set ``hid_id`` in the struct_ops map before loading the program in the kernel.
``hid_id`` is the unique system ID of the HID device (the last 4 numbers in the
sysfs path: ``/sys/bus/hid/devices/xxxx:yyyy:zzzz:0000``)
``progam_fd`` is the opened file descriptor of the program to attach.
``flags`` is of type ``enum hid_bpf_attach_flags``.
One can also set ``flags``, which is of type ``enum hid_bpf_attach_flags``.
We can not rely on hidraw to bind a BPF program to a HID device. hidraw is an
artefact of the processing of the HID device, and is not stable. Some drivers
......@@ -358,32 +380,15 @@ For that, we can create a basic skeleton for our BPF program::
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,
const size_t __sz) __ksym;
extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096 * 64);
} ringbuf SEC(".maps");
struct attach_prog_args {
int prog_fd;
unsigned int hid;
unsigned int flags;
int retval;
};
SEC("syscall")
int attach_prog(struct attach_prog_args *ctx)
{
ctx->retval = hid_bpf_attach_prog(ctx->hid,
ctx->prog_fd,
ctx->flags);
return 0;
}
__u8 current_value = 0;
SEC("?fmod_ret/hid_bpf_device_event")
SEC("struct_ops/hid_device_event")
int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */);
......@@ -407,37 +412,37 @@ For that, we can create a basic skeleton for our BPF program::
return 0;
}
To attach ``filter_switch``, userspace needs to call the ``attach_prog`` syscall
program first::
static int attach_filter(struct hid *hid_skel, int hid_id)
{
int err, prog_fd;
int ret = -1;
struct attach_prog_args args = {
.hid = hid_id,
SEC(".struct_ops.link")
struct hid_bpf_ops haptic_tablet = {
.hid_device_event = (void *)filter_switch,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
args.prog_fd = bpf_program__fd(hid_skel->progs.filter_switch);
prog_fd = bpf_program__fd(hid_skel->progs.attach_prog);
To attach ``haptic_tablet``, userspace needs to set ``hid_id`` first::
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
static int attach_filter(struct hid *hid_skel, int hid_id)
{
int err, link_fd;
hid_skel->struct_ops.haptic_tablet->hid_id = hid_id;
err = hid__load(skel);
if (err)
return err;
return args.retval; /* the fd of the created bpf_link */
link_fd = bpf_map__attach_struct_ops(hid_skel->maps.haptic_tablet);
if (!link_fd) {
fprintf(stderr, "can not attach HID-BPF program: %m\n");
return -1;
}
return link_fd; /* the fd of the created bpf_link */
}
Our userspace program can now listen to notifications on the ring buffer, and
is awaken only when the value changes.
When the userspace program doesn't need to listen to events anymore, it can just
close the returned fd from :c:func:`attach_filter`, which will tell the kernel to
close the returned bpf link from :c:func:`attach_filter`, which will tell the kernel to
detach the program from the HID device.
Of course, in other use cases, the userspace program can also pin the fd to the
......
......@@ -8,4 +8,4 @@ LIBBPF_INCLUDE = $(srctree)/tools/lib
obj-$(CONFIG_HID_BPF) += hid_bpf.o
CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_struct_ops.o
# SPDX-License-Identifier: GPL-2.0
OUTPUT := .output
abs_out := $(abspath $(OUTPUT))
CLANG ?= clang
LLC ?= llc
LLVM_STRIP ?= llvm-strip
TOOLS_PATH := $(abspath ../../../../tools)
BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
BPFTOOL_OUTPUT := $(abs_out)/bpftool
DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
BPFTOOL ?= $(DEFAULT_BPFTOOL)
LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
LIBBPF_OUTPUT := $(abs_out)/libbpf
LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
CFLAGS := -g -Wall
VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
$(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
../../../../vmlinux \
/sys/kernel/btf/vmlinux \
/boot/vmlinux-$(shell uname -r)
VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
ifeq ($(VMLINUX_BTF),)
$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
endif
ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
MAKEFLAGS += --no-print-directory
submake_extras := feature_display=0
endif
.DELETE_ON_ERROR:
.PHONY: all clean
all: entrypoints.lskel.h
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) entrypoints
entrypoints.lskel.h: $(OUTPUT)/entrypoints.bpf.o | $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton -L $< > $@
$(OUTPUT)/entrypoints.bpf.o: entrypoints.bpf.c $(OUTPUT)/vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
ifeq ($(VMLINUX_H),)
$(call msg,GEN,,$@)
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
else
$(call msg,CP,,$@)
$(Q)cp "$(VMLINUX_H)" $@
endif
$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
OUTPUT=$(abspath $(dir $@))/ prefix= \
DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
ifeq ($(CROSS_COMPILE),)
$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ \
LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \
LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
else
$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
endif
WARNING:
If you change "entrypoints.bpf.c" do "make -j" in this directory to rebuild "entrypoints.skel.h".
Make sure to have clang 10 installed.
See Documentation/bpf/bpf_devel_QA.rst
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Benjamin Tissoires */
#include ".output/vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define HID_BPF_MAX_PROGS 1024
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, HID_BPF_MAX_PROGS);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} hid_jmp_table SEC(".maps");
SEC("fmod_ret/__hid_bpf_tail_call")
int BPF_PROG(hid_tail_call, struct hid_bpf_ctx *hctx)
{
bpf_tail_call(ctx, &hid_jmp_table, hctx->index);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* THIS FILE IS AUTOGENERATED BY BPFTOOL! */
#ifndef __ENTRYPOINTS_BPF_SKEL_H__
#define __ENTRYPOINTS_BPF_SKEL_H__
#include <bpf/skel_internal.h>
struct entrypoints_bpf {
struct bpf_loader_ctx ctx;
struct {
struct bpf_map_desc hid_jmp_table;
} maps;
struct {
struct bpf_prog_desc hid_tail_call;
} progs;
struct {
int hid_tail_call_fd;
} links;
};
static inline int
entrypoints_bpf__hid_tail_call__attach(struct entrypoints_bpf *skel)
{
int prog_fd = skel->progs.hid_tail_call.prog_fd;
int fd = skel_raw_tracepoint_open(NULL, prog_fd);
if (fd > 0)
skel->links.hid_tail_call_fd = fd;
return fd;
}
static inline int
entrypoints_bpf__attach(struct entrypoints_bpf *skel)
{
int ret = 0;
ret = ret < 0 ? ret : entrypoints_bpf__hid_tail_call__attach(skel);
return ret < 0 ? ret : 0;
}
static inline void
entrypoints_bpf__detach(struct entrypoints_bpf *skel)
{
skel_closenz(skel->links.hid_tail_call_fd);
}
static void
entrypoints_bpf__destroy(struct entrypoints_bpf *skel)
{
if (!skel)
return;
entrypoints_bpf__detach(skel);
skel_closenz(skel->progs.hid_tail_call.prog_fd);
skel_closenz(skel->maps.hid_jmp_table.map_fd);
skel_free(skel);
}
static inline struct entrypoints_bpf *
entrypoints_bpf__open(void)
{
struct entrypoints_bpf *skel;
skel = skel_alloc(sizeof(*skel));
if (!skel)
goto cleanup;
skel->ctx.sz = (void *)&skel->links - (void *)skel;
return skel;
cleanup:
entrypoints_bpf__destroy(skel);
return NULL;
}
static inline int
entrypoints_bpf__load(struct entrypoints_bpf *skel)
{
struct bpf_load_and_run_opts opts = {};
int err;
opts.ctx = (struct bpf_loader_ctx *)skel;
opts.data_sz = 2856;
opts.data = (void *)"\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x9f\xeb\x01\0\
\x18\0\0\0\0\0\0\0\x60\x02\0\0\x60\x02\0\0\x12\x02\0\0\0\0\0\0\0\0\0\x02\x03\0\
\0\0\x01\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\
\0\0\x04\0\0\0\x03\0\0\0\x05\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\
\x02\x06\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\
\0\0\0\x02\x08\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\x04\0\0\0\0\
\0\0\0\x04\0\0\x04\x20\0\0\0\x19\0\0\0\x01\0\0\0\0\0\0\0\x1e\0\0\0\x05\0\0\0\
\x40\0\0\0\x2a\0\0\0\x07\0\0\0\x80\0\0\0\x33\0\0\0\x07\0\0\0\xc0\0\0\0\x3e\0\0\
\0\0\0\0\x0e\x09\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\x02\x0c\0\0\0\x4c\0\0\0\0\0\0\
\x01\x08\0\0\0\x40\0\0\0\0\0\0\0\x01\0\0\x0d\x02\0\0\0\x5f\0\0\0\x0b\0\0\0\x63\
\0\0\0\x01\0\0\x0c\x0d\0\0\0\x09\x01\0\0\x05\0\0\x04\x20\0\0\0\x15\x01\0\0\x10\
\0\0\0\0\0\0\0\x1b\x01\0\0\x12\0\0\0\x40\0\0\0\x1f\x01\0\0\x10\0\0\0\x80\0\0\0\
\x2e\x01\0\0\x14\0\0\0\xa0\0\0\0\0\0\0\0\x15\0\0\0\xc0\0\0\0\x3a\x01\0\0\0\0\0\
\x08\x11\0\0\0\x40\x01\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\x02\x13\
\0\0\0\0\0\0\0\0\0\0\x0a\x1c\0\0\0\x4d\x01\0\0\x04\0\0\x06\x04\0\0\0\x5d\x01\0\
\0\0\0\0\0\x6e\x01\0\0\x01\0\0\0\x80\x01\0\0\x02\0\0\0\x93\x01\0\0\x03\0\0\0\0\
\0\0\0\x02\0\0\x05\x04\0\0\0\xa4\x01\0\0\x16\0\0\0\0\0\0\0\xab\x01\0\0\x16\0\0\
\0\0\0\0\0\xb0\x01\0\0\0\0\0\x08\x02\0\0\0\xec\x01\0\0\0\0\0\x01\x01\0\0\0\x08\
\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x17\0\0\0\x04\0\0\0\x04\0\0\0\xf1\x01\0\0\0\
\0\0\x0e\x18\0\0\0\x01\0\0\0\xf9\x01\0\0\x01\0\0\x0f\x20\0\0\0\x0a\0\0\0\0\0\0\
\0\x20\0\0\0\xff\x01\0\0\x01\0\0\x0f\x04\0\0\0\x19\0\0\0\0\0\0\0\x04\0\0\0\x07\
\x02\0\0\0\0\0\x07\0\0\0\0\0\x69\x6e\x74\0\x5f\x5f\x41\x52\x52\x41\x59\x5f\x53\
\x49\x5a\x45\x5f\x54\x59\x50\x45\x5f\x5f\0\x74\x79\x70\x65\0\x6d\x61\x78\x5f\
\x65\x6e\x74\x72\x69\x65\x73\0\x6b\x65\x79\x5f\x73\x69\x7a\x65\0\x76\x61\x6c\
\x75\x65\x5f\x73\x69\x7a\x65\0\x68\x69\x64\x5f\x6a\x6d\x70\x5f\x74\x61\x62\x6c\
\x65\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x6c\x6f\x6e\x67\x20\x6c\x6f\x6e\x67\
\0\x63\x74\x78\0\x68\x69\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\x66\x6d\
\x6f\x64\x5f\x72\x65\x74\x2f\x5f\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\
\x69\x6c\x5f\x63\x61\x6c\x6c\0\x2f\x68\x6f\x6d\x65\x2f\x62\x74\x69\x73\x73\x6f\
\x69\x72\x2f\x53\x72\x63\x2f\x68\x69\x64\x2f\x64\x72\x69\x76\x65\x72\x73\x2f\
\x68\x69\x64\x2f\x62\x70\x66\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\
\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\x2e\x62\x70\x66\x2e\x63\0\x69\
\x6e\x74\x20\x42\x50\x46\x5f\x50\x52\x4f\x47\x28\x68\x69\x64\x5f\x74\x61\x69\
\x6c\x5f\x63\x61\x6c\x6c\x2c\x20\x73\x74\x72\x75\x63\x74\x20\x68\x69\x64\x5f\
\x62\x70\x66\x5f\x63\x74\x78\x20\x2a\x68\x63\x74\x78\x29\0\x68\x69\x64\x5f\x62\
\x70\x66\x5f\x63\x74\x78\0\x69\x6e\x64\x65\x78\0\x68\x69\x64\0\x61\x6c\x6c\x6f\
\x63\x61\x74\x65\x64\x5f\x73\x69\x7a\x65\0\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\
\x70\x65\0\x5f\x5f\x75\x33\x32\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x69\x6e\
\x74\0\x68\x69\x64\x5f\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\x70\x65\0\x48\x49\
\x44\x5f\x49\x4e\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x4f\
\x55\x54\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x46\x45\x41\
\x54\x55\x52\x45\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x52\x45\x50\x4f\
\x52\x54\x5f\x54\x59\x50\x45\x53\0\x72\x65\x74\x76\x61\x6c\0\x73\x69\x7a\x65\0\
\x5f\x5f\x73\x33\x32\0\x30\x3a\x30\0\x09\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\
\x63\x61\x6c\x6c\x28\x63\x74\x78\x2c\x20\x26\x68\x69\x64\x5f\x6a\x6d\x70\x5f\
\x74\x61\x62\x6c\x65\x2c\x20\x68\x63\x74\x78\x2d\x3e\x69\x6e\x64\x65\x78\x29\
\x3b\0\x63\x68\x61\x72\0\x4c\x49\x43\x45\x4e\x53\x45\0\x2e\x6d\x61\x70\x73\0\
\x6c\x69\x63\x65\x6e\x73\x65\0\x68\x69\x64\x5f\x64\x65\x76\x69\x63\x65\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x8a\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x03\
\0\0\0\x04\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\x64\x5f\
\x6a\x6d\x70\x5f\x74\x61\x62\x6c\x65\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\x47\x50\x4c\0\0\0\0\0\x79\x12\0\0\0\0\0\0\x61\x23\0\0\0\0\
\0\0\x18\x52\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x85\0\0\0\x0c\0\0\0\xb7\0\0\0\0\0\0\0\
\x95\0\0\0\0\0\0\0\0\0\0\0\x0e\0\0\0\0\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\x48\0\0\
\x01\0\0\0\x8e\0\0\0\xba\x01\0\0\x02\x50\0\0\x05\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\
\x48\0\0\x08\0\0\0\x0f\0\0\0\xb6\x01\0\0\0\0\0\0\x1a\0\0\0\x07\0\0\0\0\0\0\0\0\
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\
\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\0\0\0\0\x1a\0\0\0\0\0\0\0\
\x08\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x10\0\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\x01\0\
\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x10\0\0\0\0\0\0\0\x5f\
\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\
\0\0";
opts.insns_sz = 1192;
opts.insns = (void *)"\
\xbf\x16\0\0\0\0\0\0\xbf\xa1\0\0\0\0\0\0\x07\x01\0\0\x78\xff\xff\xff\xb7\x02\0\
\0\x88\0\0\0\xb7\x03\0\0\0\0\0\0\x85\0\0\0\x71\0\0\0\x05\0\x11\0\0\0\0\0\x61\
\xa1\x78\xff\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x7c\xff\
\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x80\xff\0\0\0\0\xd5\
\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\
\x01\0\0\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\
\xbf\x70\0\0\0\0\0\0\x95\0\0\0\0\0\0\0\x61\x60\x08\0\0\0\0\0\x18\x61\0\0\0\0\0\
\0\0\0\0\0\xa8\x09\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\0\0\0\0\x18\x61\0\0\0\
\0\0\0\0\0\0\0\xa4\x09\0\0\x63\x01\0\0\0\0\0\0\x79\x60\x10\0\0\0\0\0\x18\x61\0\
\0\0\0\0\0\0\0\0\0\x98\x09\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
\0\x05\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\x7b\x01\0\0\0\0\0\0\xb7\x01\
\0\0\x12\0\0\0\x18\x62\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\xb7\x03\0\0\x1c\0\0\0\
\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\xd7\xff\0\0\0\0\x63\x7a\x78\
\xff\0\0\0\0\x61\x60\x1c\0\0\0\0\0\x15\0\x03\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\
\0\0\xbc\x09\0\0\x63\x01\0\0\0\0\0\0\xb7\x01\0\0\0\0\0\0\x18\x62\0\0\0\0\0\0\0\
\0\0\0\xb0\x09\0\0\xb7\x03\0\0\x48\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\
\0\xc5\x07\xca\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x63\x71\0\0\0\0\
\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf8\x09\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\
\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\x0a\0\0\x18\x61\0\0\
\0\0\0\0\0\0\0\0\x88\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
\x38\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xd0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\
\x60\0\0\0\0\0\0\0\0\0\0\x40\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xe0\x0a\0\0\
\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x70\x0a\0\0\x18\x61\0\0\0\0\0\
\0\0\0\0\0\0\x0b\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
\x18\x61\0\0\0\0\0\0\0\0\0\0\xf8\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\x60\x08\0\0\0\
\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x98\x0a\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\
\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x9c\x0a\0\0\x63\x01\0\0\0\0\0\0\x79\x60\
\x10\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xa0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\
\xa0\x78\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xc8\x0a\0\0\x63\x01\0\0\0\0\0\
\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x10\x0b\0\0\xb7\x02\0\0\x14\0\0\0\xb7\x03\0\0\
\x0c\0\0\0\xb7\x04\0\0\0\0\0\0\x85\0\0\0\xa7\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\
\x91\xff\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x80\x0a\0\0\x63\x70\x6c\0\0\0\0\0\
\x77\x07\0\0\x20\0\0\0\x63\x70\x70\0\0\0\0\0\xb7\x01\0\0\x05\0\0\0\x18\x62\0\0\
\0\0\0\0\0\0\0\0\x80\x0a\0\0\xb7\x03\0\0\x8c\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\
\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf0\x0a\0\0\x61\x01\0\0\0\0\0\0\xd5\
\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\xc5\x07\x7f\xff\0\0\
\0\0\x63\x7a\x80\xff\0\0\0\0\x61\xa1\x78\xff\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\
\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa0\x80\xff\0\0\0\0\x63\x06\x28\0\0\0\
\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\x10\0\0\0\0\0\0\x63\x06\x18\0\0\0\
\0\0\xb7\0\0\0\0\0\0\0\x95\0\0\0\0\0\0\0";
err = bpf_load_and_run(&opts);
if (err < 0)
return err;
return 0;
}
static inline struct entrypoints_bpf *
entrypoints_bpf__open_and_load(void)
{
struct entrypoints_bpf *skel;
skel = entrypoints_bpf__open();
if (!skel)
return NULL;
if (entrypoints_bpf__load(skel)) {
entrypoints_bpf__destroy(skel);
return NULL;
}
return skel;
}
__attribute__((unused)) static void
entrypoints_bpf__assert(struct entrypoints_bpf *s __attribute__((unused)))
{
#ifdef __cplusplus
#define _Static_assert static_assert
#endif
#ifdef __cplusplus
#undef _Static_assert
#endif
}
#endif /* __ENTRYPOINTS_BPF_SKEL_H__ */
......@@ -3,7 +3,7 @@
/*
* HID-BPF support for Linux
*
* Copyright (c) 2022 Benjamin Tissoires
* Copyright (c) 2022-2024 Benjamin Tissoires
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
......@@ -17,47 +17,25 @@
#include <linux/kfifo.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
#include "entrypoints/entrypoints.lskel.h"
struct hid_bpf_ops *hid_bpf_ops;
EXPORT_SYMBOL(hid_bpf_ops);
/**
* hid_bpf_device_event - Called whenever an event is coming in from the device
*
* @ctx: The HID-BPF context
*
* @return %0 on success and keep processing; a positive value to change the
* incoming size buffer; a negative error code to interrupt the processing
* of this event
*
* Declare an %fmod_ret tracing bpf program to this function and attach this
* program through hid_bpf_attach_prog() to have this helper called for
* any incoming event from the device itself.
*
* The function is called while on IRQ context, so we can not sleep.
*/
/* never used by the kernel but declared so we can load and attach a tracepoint */
__weak noinline int hid_bpf_device_event(struct hid_bpf_ctx *ctx)
{
return 0;
}
struct hid_ops *hid_ops;
EXPORT_SYMBOL(hid_ops);
u8 *
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
u32 *size, int interrupt)
u32 *size, int interrupt, u64 source, bool from_bpf)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
.report_type = type,
.allocated_size = hdev->bpf.allocated_data,
.size = *size,
},
.data = hdev->bpf.device_data,
.from_bpf = from_bpf,
};
struct hid_bpf_ops *e;
int ret;
if (type >= HID_REPORT_TYPES)
......@@ -70,10 +48,22 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
memset(ctx_kern.data, 0, hdev->bpf.allocated_data);
memcpy(ctx_kern.data, data, *size);
ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
if (ret < 0)
rcu_read_lock();
list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
if (e->hid_device_event) {
ret = e->hid_device_event(&ctx_kern.ctx, type, source);
if (ret < 0) {
rcu_read_unlock();
return ERR_PTR(ret);
}
if (ret)
ctx_kern.ctx.size = ret;
}
}
rcu_read_unlock();
ret = ctx_kern.ctx.size;
if (ret) {
if (ret > ctx_kern.ctx.allocated_size)
return ERR_PTR(-EINVAL);
......@@ -85,25 +75,78 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
/**
* hid_bpf_rdesc_fixup - Called when the probe function parses the report
* descriptor of the HID device
*
* @ctx: The HID-BPF context
*
* @return 0 on success and keep processing; a positive value to change the
* incoming size buffer; a negative error code to interrupt the processing
* of this event
*
* Declare an %fmod_ret tracing bpf program to this function and attach this
* program through hid_bpf_attach_prog() to have this helper called before any
* parsing of the report descriptor by HID.
*/
/* never used by the kernel but declared so we can load and attach a tracepoint */
__weak noinline int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
unsigned char reportnum, u8 *buf,
u32 size, enum hid_report_type rtype,
enum hid_class_request reqtype,
u64 source, bool from_bpf)
{
return 0;
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
.allocated_size = size,
.size = size,
},
.data = buf,
.from_bpf = from_bpf,
};
struct hid_bpf_ops *e;
int ret, idx;
if (rtype >= HID_REPORT_TYPES)
return -EINVAL;
idx = srcu_read_lock(&hdev->bpf.srcu);
list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
srcu_read_lock_held(&hdev->bpf.srcu)) {
if (!e->hid_hw_request)
continue;
ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source);
if (ret)
goto out;
}
ret = 0;
out:
srcu_read_unlock(&hdev->bpf.srcu, idx);
return ret;
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_raw_requests);
int dispatch_hid_bpf_output_report(struct hid_device *hdev,
__u8 *buf, u32 size, u64 source,
bool from_bpf)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
.allocated_size = size,
.size = size,
},
.data = buf,
.from_bpf = from_bpf,
};
struct hid_bpf_ops *e;
int ret, idx;
idx = srcu_read_lock(&hdev->bpf.srcu);
list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
srcu_read_lock_held(&hdev->bpf.srcu)) {
if (!e->hid_hw_output_report)
continue;
ret = e->hid_hw_output_report(&ctx_kern.ctx, source);
if (ret)
goto out;
}
ret = 0;
out:
srcu_read_unlock(&hdev->bpf.srcu, idx);
return ret;
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_output_report);
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
{
......@@ -116,13 +159,16 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
},
};
if (!hdev->bpf.rdesc_ops)
goto ignore_bpf;
ctx_kern.data = kzalloc(ctx_kern.ctx.allocated_size, GFP_KERNEL);
if (!ctx_kern.data)
goto ignore_bpf;
memcpy(ctx_kern.data, rdesc, min_t(unsigned int, *size, HID_MAX_DESCRIPTOR_SIZE));
ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_RDESC_FIXUP, &ctx_kern);
ret = hdev->bpf.rdesc_ops->hid_rdesc_fixup(&ctx_kern.ctx);
if (ret < 0)
goto ignore_bpf;
......@@ -150,6 +196,25 @@ static int device_match_id(struct device *dev, const void *id)
return hdev->id == *(int *)id;
}
struct hid_device *hid_get_device(unsigned int hid_id)
{
struct device *dev;
if (!hid_ops)
return ERR_PTR(-EINVAL);
dev = bus_find_device(hid_ops->bus_type, NULL, &hid_id, device_match_id);
if (!dev)
return ERR_PTR(-EINVAL);
return to_hid_device(dev);
}
void hid_put_device(struct hid_device *hid)
{
put_device(&hid->dev);
}
static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size)
{
u8 *alloc_data;
......@@ -186,7 +251,7 @@ static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size
return 0;
}
static int hid_bpf_allocate_event_data(struct hid_device *hdev)
int hid_bpf_allocate_event_data(struct hid_device *hdev)
{
/* hdev->bpf.device_data is already allocated, abort */
if (hdev->bpf.device_data)
......@@ -203,39 +268,6 @@ int hid_bpf_reconnect(struct hid_device *hdev)
return 0;
}
static int do_hid_bpf_attach_prog(struct hid_device *hdev, int prog_fd, struct bpf_prog *prog,
__u32 flags)
{
int fd, err, prog_type;
prog_type = hid_bpf_get_prog_attach_type(prog);
if (prog_type < 0)
return prog_type;
if (prog_type >= HID_BPF_PROG_TYPE_MAX)
return -EINVAL;
if (prog_type == HID_BPF_PROG_TYPE_DEVICE_EVENT) {
err = hid_bpf_allocate_event_data(hdev);
if (err)
return err;
}
fd = __hid_bpf_attach_prog(hdev, prog_type, prog_fd, prog, flags);
if (fd < 0)
return fd;
if (prog_type == HID_BPF_PROG_TYPE_RDESC_FIXUP) {
err = hid_bpf_reconnect(hdev);
if (err) {
close_fd(fd);
return err;
}
}
return fd;
}
/* Disables missing prototype warnings */
__bpf_kfunc_start_defs();
......@@ -264,63 +296,6 @@ hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr
return ctx_kern->data + offset;
}
/**
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
*
* @hid_id: the system unique identifier of the HID device
* @prog_fd: an fd in the user process representing the program to attach
* @flags: any logical OR combination of &enum hid_bpf_attach_flags
*
* @returns an fd of a bpf_link object on success (> %0), an error code otherwise.
* Closing this fd will detach the program from the HID device (unless the bpf_link
* is pinned to the BPF file system).
*/
/* called from syscall */
__bpf_kfunc int
hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
{
struct hid_device *hdev;
struct bpf_prog *prog;
struct device *dev;
int err, fd;
if (!hid_bpf_ops)
return -EINVAL;
if ((flags & ~HID_BPF_FLAG_MASK))
return -EINVAL;
dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
if (!dev)
return -EINVAL;
hdev = to_hid_device(dev);
/*
* take a ref on the prog itself, it will be released
* on errors or when it'll be detached
*/
prog = bpf_prog_get(prog_fd);
if (IS_ERR(prog)) {
err = PTR_ERR(prog);
goto out_dev_put;
}
fd = do_hid_bpf_attach_prog(hdev, prog_fd, prog, flags);
if (fd < 0) {
err = fd;
goto out_prog_put;
}
return fd;
out_prog_put:
bpf_prog_put(prog);
out_dev_put:
put_device(dev);
return err;
}
/**
* hid_bpf_allocate_context - Allocate a context to the given HID device
*
......@@ -333,20 +308,14 @@ hid_bpf_allocate_context(unsigned int hid_id)
{
struct hid_device *hdev;
struct hid_bpf_ctx_kern *ctx_kern = NULL;
struct device *dev;
if (!hid_bpf_ops)
return NULL;
dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
if (!dev)
hdev = hid_get_device(hid_id);
if (IS_ERR(hdev))
return NULL;
hdev = to_hid_device(dev);
ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL);
if (!ctx_kern) {
put_device(dev);
hid_put_device(hdev);
return NULL;
}
......@@ -373,7 +342,7 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)
kfree(ctx_kern);
/* get_device() is called by bus_find_device() */
put_device(&hid->dev);
hid_put_device(hid);
}
static int
......@@ -386,7 +355,7 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
u32 report_len;
/* check arguments */
if (!ctx || !hid_bpf_ops || !buf)
if (!ctx || !hid_ops || !buf)
return -EINVAL;
switch (rtype) {
......@@ -404,7 +373,7 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
hdev = (struct hid_device *)ctx->hid; /* discard const */
report_enum = hdev->report_enum + rtype;
report = hid_bpf_ops->hid_get_report(report_enum, buf);
report = hid_ops->hid_get_report(report_enum, buf);
if (!report)
return -EINVAL;
......@@ -431,11 +400,17 @@ __bpf_kfunc int
hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
enum hid_report_type rtype, enum hid_class_request reqtype)
{
struct hid_bpf_ctx_kern *ctx_kern;
struct hid_device *hdev;
size_t size = buf__sz;
u8 *dma_data;
int ret;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (ctx_kern->from_bpf)
return -EDEADLOCK;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype);
if (ret)
......@@ -459,12 +434,14 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
if (!dma_data)
return -ENOMEM;
ret = hid_bpf_ops->hid_hw_raw_request(hdev,
ret = hid_ops->hid_hw_raw_request(hdev,
dma_data[0],
dma_data,
size,
rtype,
reqtype);
reqtype,
(u64)(long)ctx,
true); /* prevent infinite recursions */
if (ret > 0)
memcpy(buf, dma_data, ret);
......@@ -485,11 +462,16 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
__bpf_kfunc int
hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
{
struct hid_bpf_ctx_kern *ctx_kern;
struct hid_device *hdev;
size_t size = buf__sz;
u8 *dma_data;
int ret;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (ctx_kern->from_bpf)
return -EDEADLOCK;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT);
if (ret)
......@@ -501,14 +483,56 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
if (!dma_data)
return -ENOMEM;
ret = hid_bpf_ops->hid_hw_output_report(hdev,
dma_data,
size);
ret = hid_ops->hid_hw_output_report(hdev, dma_data, size, (u64)(long)ctx, true);
kfree(dma_data);
return ret;
}
static int
__hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
size_t size, bool lock_already_taken)
{
struct hid_bpf_ctx_kern *ctx_kern;
int ret;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (ctx_kern->from_bpf)
return -EDEADLOCK;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
if (ret)
return ret;
return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
lock_already_taken);
}
/**
* hid_bpf_try_input_report - Inject a HID report in the kernel from a HID device
*
* @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
* @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
* @buf: a %PTR_TO_MEM buffer
* @buf__sz: the size of the data to transfer
*
* Returns %0 on success, a negative error code otherwise. This function will immediately
* fail if the device is not available, thus can be safely used in IRQ context.
*/
__bpf_kfunc int
hid_bpf_try_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
const size_t buf__sz)
{
struct hid_bpf_ctx_kern *ctx_kern;
bool from_hid_event_hook;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
from_hid_event_hook = ctx_kern->data && ctx_kern->data == ctx->hid->bpf.device_data;
return __hid_bpf_input_report(ctx, type, buf, buf__sz, from_hid_event_hook);
}
/**
* hid_bpf_input_report - Inject a HID report in the kernel from a HID device
*
......@@ -517,24 +541,26 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
* @buf: a %PTR_TO_MEM buffer
* @buf__sz: the size of the data to transfer
*
* Returns %0 on success, a negative error code otherwise.
* Returns %0 on success, a negative error code otherwise. This function will wait for the
* device to be available before injecting the event, thus needs to be called in sleepable
* context.
*/
__bpf_kfunc int
hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
const size_t buf__sz)
{
struct hid_device *hdev;
size_t size = buf__sz;
int ret;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
ret = down_interruptible(&ctx->hid->driver_input_lock);
if (ret)
return ret;
hdev = (struct hid_device *)ctx->hid; /* discard const */
/* check arguments */
ret = __hid_bpf_input_report(ctx, type, buf, buf__sz, true /* lock_already_taken */);
up(&ctx->hid->driver_input_lock);
return hid_bpf_ops->hid_input_report(hdev, type, buf, size, 0);
return ret;
}
__bpf_kfunc_end_defs();
......@@ -549,6 +575,7 @@ BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_try_input_report)
BTF_KFUNCS_END(hid_bpf_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
......@@ -556,21 +583,8 @@ static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
.set = &hid_bpf_kfunc_ids,
};
/* our HID-BPF entrypoints */
BTF_SET8_START(hid_bpf_fmodret_ids)
BTF_ID_FLAGS(func, hid_bpf_device_event)
BTF_ID_FLAGS(func, hid_bpf_rdesc_fixup)
BTF_ID_FLAGS(func, __hid_bpf_tail_call)
BTF_SET8_END(hid_bpf_fmodret_ids)
static const struct btf_kfunc_id_set hid_bpf_fmodret_set = {
.owner = THIS_MODULE,
.set = &hid_bpf_fmodret_ids,
};
/* for syscall HID-BPF */
BTF_KFUNCS_START(hid_bpf_syscall_kfunc_ids)
BTF_ID_FLAGS(func, hid_bpf_attach_prog)
BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE)
BTF_ID_FLAGS(func, hid_bpf_hw_request)
......@@ -585,14 +599,20 @@ static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
int hid_bpf_connect_device(struct hid_device *hdev)
{
struct hid_bpf_prog_list *prog_list;
bool need_to_allocate = false;
struct hid_bpf_ops *e;
rcu_read_lock();
prog_list = rcu_dereference(hdev->bpf.progs[HID_BPF_PROG_TYPE_DEVICE_EVENT]);
list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
if (e->hid_device_event) {
need_to_allocate = true;
break;
}
}
rcu_read_unlock();
/* only allocate BPF data if there are programs attached */
if (!prog_list)
if (!need_to_allocate)
return 0;
return hid_bpf_allocate_event_data(hdev);
......@@ -615,13 +635,18 @@ void hid_bpf_destroy_device(struct hid_device *hdev)
/* mark the device as destroyed in bpf so we don't reattach it */
hdev->bpf.destroyed = true;
__hid_bpf_destroy_device(hdev);
__hid_bpf_ops_destroy_device(hdev);
synchronize_srcu(&hdev->bpf.srcu);
cleanup_srcu_struct(&hdev->bpf.srcu);
}
EXPORT_SYMBOL_GPL(hid_bpf_destroy_device);
void hid_bpf_device_init(struct hid_device *hdev)
int hid_bpf_device_init(struct hid_device *hdev)
{
spin_lock_init(&hdev->bpf.progs_lock);
INIT_LIST_HEAD(&hdev->bpf.prog_list);
mutex_init(&hdev->bpf.prog_list_lock);
return init_srcu_struct(&hdev->bpf.srcu);
}
EXPORT_SYMBOL_GPL(hid_bpf_device_init);
......@@ -632,30 +657,15 @@ static int __init hid_bpf_init(void)
/* Note: if we exit with an error any time here, we would entirely break HID, which
* is probably not something we want. So we log an error and return success.
*
* This is not a big deal: the syscall allowing to attach a BPF program to a HID device
* will not be available, so nobody will be able to use the functionality.
* This is not a big deal: nobody will be able to use the functionality.
*/
err = register_btf_fmodret_id_set(&hid_bpf_fmodret_set);
if (err) {
pr_warn("error while registering fmodret entrypoints: %d", err);
return 0;
}
err = hid_bpf_preload_skel();
if (err) {
pr_warn("error while preloading HID BPF dispatcher: %d", err);
return 0;
}
/* register tracing kfuncs after we are sure we can load our preloaded bpf program */
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &hid_bpf_kfunc_set);
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &hid_bpf_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF tracing kfuncs: %d", err);
return 0;
}
/* register syscalls after we are sure we can load our preloaded bpf program */
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &hid_bpf_syscall_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF syscall kfuncs: %d", err);
......@@ -665,15 +675,6 @@ static int __init hid_bpf_init(void)
return 0;
}
static void __exit hid_bpf_exit(void)
{
/* HID depends on us, so if we hit that code, we are guaranteed that hid
* has been removed and thus we do not need to clear the HID devices
*/
hid_bpf_free_links_and_skel();
}
late_initcall(hid_bpf_init);
module_exit(hid_bpf_exit);
MODULE_AUTHOR("Benjamin Tissoires");
MODULE_LICENSE("GPL");
......@@ -8,16 +8,13 @@
struct hid_bpf_ctx_kern {
struct hid_bpf_ctx ctx;
u8 *data;
bool from_bpf;
};
int hid_bpf_preload_skel(void);
void hid_bpf_free_links_and_skel(void);
int hid_bpf_get_prog_attach_type(struct bpf_prog *prog);
int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type, int prog_fd,
struct bpf_prog *prog, __u32 flags);
void __hid_bpf_destroy_device(struct hid_device *hdev);
int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
struct hid_bpf_ctx_kern *ctx_kern);
struct hid_device *hid_get_device(unsigned int hid_id);
void hid_put_device(struct hid_device *hid);
int hid_bpf_allocate_event_data(struct hid_device *hdev);
void __hid_bpf_ops_destroy_device(struct hid_device *hdev);
int hid_bpf_reconnect(struct hid_device *hdev);
struct bpf_prog;
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* HID-BPF support for Linux
*
* Copyright (c) 2022 Benjamin Tissoires
*/
#include <linux/bitops.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <linux/circ_buf.h>
#include <linux/filter.h>
#include <linux/hid.h>
#include <linux/hid_bpf.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
#include "entrypoints/entrypoints.lskel.h"
#define HID_BPF_MAX_PROGS 1024 /* keep this in sync with preloaded bpf,
* needs to be a power of 2 as we use it as
* a circular buffer
*/
#define NEXT(idx) (((idx) + 1) & (HID_BPF_MAX_PROGS - 1))
#define PREV(idx) (((idx) - 1) & (HID_BPF_MAX_PROGS - 1))
/*
* represents one attached program stored in the hid jump table
*/
struct hid_bpf_prog_entry {
struct bpf_prog *prog;
struct hid_device *hdev;
enum hid_bpf_prog_type type;
u16 idx;
};
struct hid_bpf_jmp_table {
struct bpf_map *map;
struct hid_bpf_prog_entry entries[HID_BPF_MAX_PROGS]; /* compacted list, circular buffer */
int tail, head;
struct bpf_prog *progs[HID_BPF_MAX_PROGS]; /* idx -> progs mapping */
unsigned long enabled[BITS_TO_LONGS(HID_BPF_MAX_PROGS)];
};
#define FOR_ENTRIES(__i, __start, __end) \
for (__i = __start; CIRC_CNT(__end, __i, HID_BPF_MAX_PROGS); __i = NEXT(__i))
static struct hid_bpf_jmp_table jmp_table;
static DEFINE_MUTEX(hid_bpf_attach_lock); /* held when attaching/detaching programs */
static void hid_bpf_release_progs(struct work_struct *work);
static DECLARE_WORK(release_work, hid_bpf_release_progs);
BTF_ID_LIST(hid_bpf_btf_ids)
BTF_ID(func, hid_bpf_device_event) /* HID_BPF_PROG_TYPE_DEVICE_EVENT */
BTF_ID(func, hid_bpf_rdesc_fixup) /* HID_BPF_PROG_TYPE_RDESC_FIXUP */
static int hid_bpf_max_programs(enum hid_bpf_prog_type type)
{
switch (type) {
case HID_BPF_PROG_TYPE_DEVICE_EVENT:
return HID_BPF_MAX_PROGS_PER_DEV;
case HID_BPF_PROG_TYPE_RDESC_FIXUP:
return 1;
default:
return -EINVAL;
}
}
static int hid_bpf_program_count(struct hid_device *hdev,
struct bpf_prog *prog,
enum hid_bpf_prog_type type)
{
int i, n = 0;
if (type >= HID_BPF_PROG_TYPE_MAX)
return -EINVAL;
FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
if (type != HID_BPF_PROG_TYPE_UNDEF && entry->type != type)
continue;
if (hdev && entry->hdev != hdev)
continue;
if (prog && entry->prog != prog)
continue;
n++;
}
return n;
}
__weak noinline int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx)
{
return 0;
}
int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
struct hid_bpf_ctx_kern *ctx_kern)
{
struct hid_bpf_prog_list *prog_list;
int i, idx, err = 0;
rcu_read_lock();
prog_list = rcu_dereference(hdev->bpf.progs[type]);
if (!prog_list)
goto out_unlock;
for (i = 0; i < prog_list->prog_cnt; i++) {
idx = prog_list->prog_idx[i];
if (!test_bit(idx, jmp_table.enabled))
continue;
ctx_kern->ctx.index = idx;
err = __hid_bpf_tail_call(&ctx_kern->ctx);
if (err < 0)
break;
if (err)
ctx_kern->ctx.retval = err;
}
out_unlock:
rcu_read_unlock();
return err;
}
/*
* assign the list of programs attached to a given hid device.
*/
static void __hid_bpf_set_hdev_progs(struct hid_device *hdev, struct hid_bpf_prog_list *new_list,
enum hid_bpf_prog_type type)
{
struct hid_bpf_prog_list *old_list;
spin_lock(&hdev->bpf.progs_lock);
old_list = rcu_dereference_protected(hdev->bpf.progs[type],
lockdep_is_held(&hdev->bpf.progs_lock));
rcu_assign_pointer(hdev->bpf.progs[type], new_list);
spin_unlock(&hdev->bpf.progs_lock);
synchronize_rcu();
kfree(old_list);
}
/*
* allocate and populate the list of programs attached to a given hid device.
*
* Must be called under lock.
*/
static int hid_bpf_populate_hdev(struct hid_device *hdev, enum hid_bpf_prog_type type)
{
struct hid_bpf_prog_list *new_list;
int i;
if (type >= HID_BPF_PROG_TYPE_MAX || !hdev)
return -EINVAL;
if (hdev->bpf.destroyed)
return 0;
new_list = kzalloc(sizeof(*new_list), GFP_KERNEL);
if (!new_list)
return -ENOMEM;
FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
if (entry->type == type && entry->hdev == hdev &&
test_bit(entry->idx, jmp_table.enabled))
new_list->prog_idx[new_list->prog_cnt++] = entry->idx;
}
__hid_bpf_set_hdev_progs(hdev, new_list, type);
return 0;
}
static void __hid_bpf_do_release_prog(int map_fd, unsigned int idx)
{
skel_map_delete_elem(map_fd, &idx);
jmp_table.progs[idx] = NULL;
}
static void hid_bpf_release_progs(struct work_struct *work)
{
int i, j, n, map_fd = -1;
bool hdev_destroyed;
if (!jmp_table.map)
return;
/* retrieve a fd of our prog_array map in BPF */
map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
if (map_fd < 0)
return;
mutex_lock(&hid_bpf_attach_lock); /* protects against attaching new programs */
/* detach unused progs from HID devices */
FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
enum hid_bpf_prog_type type;
struct hid_device *hdev;
if (test_bit(entry->idx, jmp_table.enabled))
continue;
/* we have an attached prog */
if (entry->hdev) {
hdev = entry->hdev;
type = entry->type;
/*
* hdev is still valid, even if we are called after hid_destroy_device():
* when hid_bpf_attach() gets called, it takes a ref on the dev through
* bus_find_device()
*/
hdev_destroyed = hdev->bpf.destroyed;
hid_bpf_populate_hdev(hdev, type);
/* mark all other disabled progs from hdev of the given type as detached */
FOR_ENTRIES(j, i, jmp_table.head) {
struct hid_bpf_prog_entry *next;
next = &jmp_table.entries[j];
if (test_bit(next->idx, jmp_table.enabled))
continue;
if (next->hdev == hdev && next->type == type) {
/*
* clear the hdev reference and decrement the device ref
* that was taken during bus_find_device() while calling
* hid_bpf_attach()
*/
next->hdev = NULL;
put_device(&hdev->dev);
}
}
/* if type was rdesc fixup and the device is not gone, reconnect device */
if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP && !hdev_destroyed)
hid_bpf_reconnect(hdev);
}
}
/* remove all unused progs from the jump table */
FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
if (test_bit(entry->idx, jmp_table.enabled))
continue;
if (entry->prog)
__hid_bpf_do_release_prog(map_fd, entry->idx);
}
/* compact the entry list */
n = jmp_table.tail;
FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
if (!test_bit(entry->idx, jmp_table.enabled))
continue;
jmp_table.entries[n] = jmp_table.entries[i];
n = NEXT(n);
}
jmp_table.head = n;
mutex_unlock(&hid_bpf_attach_lock);
if (map_fd >= 0)
close_fd(map_fd);
}
static void hid_bpf_release_prog_at(int idx)
{
int map_fd = -1;
/* retrieve a fd of our prog_array map in BPF */
map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
if (map_fd < 0)
return;
__hid_bpf_do_release_prog(map_fd, idx);
close(map_fd);
}
/*
* Insert the given BPF program represented by its fd in the jmp table.
* Returns the index in the jump table or a negative error.
*/
static int hid_bpf_insert_prog(int prog_fd, struct bpf_prog *prog)
{
int i, index = -1, map_fd = -1, err = -EINVAL;
/* retrieve a fd of our prog_array map in BPF */
map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
if (map_fd < 0) {
err = -EINVAL;
goto out;
}
/* find the first available index in the jmp_table */
for (i = 0; i < HID_BPF_MAX_PROGS; i++) {
if (!jmp_table.progs[i] && index < 0) {
/* mark the index as used */
jmp_table.progs[i] = prog;
index = i;
__set_bit(i, jmp_table.enabled);
}
}
if (index < 0) {
err = -ENOMEM;
goto out;
}
/* insert the program in the jump table */
err = skel_map_update_elem(map_fd, &index, &prog_fd, 0);
if (err)
goto out;
/* return the index */
err = index;
out:
if (err < 0)
__hid_bpf_do_release_prog(map_fd, index);
if (map_fd >= 0)
close_fd(map_fd);
return err;
}
int hid_bpf_get_prog_attach_type(struct bpf_prog *prog)
{
int prog_type = HID_BPF_PROG_TYPE_UNDEF;
int i;
for (i = 0; i < HID_BPF_PROG_TYPE_MAX; i++) {
if (hid_bpf_btf_ids[i] == prog->aux->attach_btf_id) {
prog_type = i;
break;
}
}
return prog_type;
}
static void hid_bpf_link_release(struct bpf_link *link)
{
struct hid_bpf_link *hid_link =
container_of(link, struct hid_bpf_link, link);
__clear_bit(hid_link->hid_table_index, jmp_table.enabled);
schedule_work(&release_work);
}
static void hid_bpf_link_dealloc(struct bpf_link *link)
{
struct hid_bpf_link *hid_link =
container_of(link, struct hid_bpf_link, link);
kfree(hid_link);
}
static void hid_bpf_link_show_fdinfo(const struct bpf_link *link,
struct seq_file *seq)
{
seq_printf(seq,
"attach_type:\tHID-BPF\n");
}
static const struct bpf_link_ops hid_bpf_link_lops = {
.release = hid_bpf_link_release,
.dealloc = hid_bpf_link_dealloc,
.show_fdinfo = hid_bpf_link_show_fdinfo,
};
/* called from syscall */
noinline int
__hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type,
int prog_fd, struct bpf_prog *prog, __u32 flags)
{
struct bpf_link_primer link_primer;
struct hid_bpf_link *link;
struct hid_bpf_prog_entry *prog_entry;
int cnt, err = -EINVAL, prog_table_idx = -1;
mutex_lock(&hid_bpf_attach_lock);
link = kzalloc(sizeof(*link), GFP_USER);
if (!link) {
err = -ENOMEM;
goto err_unlock;
}
bpf_link_init(&link->link, BPF_LINK_TYPE_UNSPEC,
&hid_bpf_link_lops, prog);
/* do not attach too many programs to a given HID device */
cnt = hid_bpf_program_count(hdev, NULL, prog_type);
if (cnt < 0) {
err = cnt;
goto err_unlock;
}
if (cnt >= hid_bpf_max_programs(prog_type)) {
err = -E2BIG;
goto err_unlock;
}
prog_table_idx = hid_bpf_insert_prog(prog_fd, prog);
/* if the jmp table is full, abort */
if (prog_table_idx < 0) {
err = prog_table_idx;
goto err_unlock;
}
if (flags & HID_BPF_FLAG_INSERT_HEAD) {
/* take the previous prog_entry slot */
jmp_table.tail = PREV(jmp_table.tail);
prog_entry = &jmp_table.entries[jmp_table.tail];
} else {
/* take the next prog_entry slot */
prog_entry = &jmp_table.entries[jmp_table.head];
jmp_table.head = NEXT(jmp_table.head);
}
/* we steal the ref here */
prog_entry->prog = prog;
prog_entry->idx = prog_table_idx;
prog_entry->hdev = hdev;
prog_entry->type = prog_type;
/* finally store the index in the device list */
err = hid_bpf_populate_hdev(hdev, prog_type);
if (err) {
hid_bpf_release_prog_at(prog_table_idx);
goto err_unlock;
}
link->hid_table_index = prog_table_idx;
err = bpf_link_prime(&link->link, &link_primer);
if (err)
goto err_unlock;
mutex_unlock(&hid_bpf_attach_lock);
return bpf_link_settle(&link_primer);
err_unlock:
mutex_unlock(&hid_bpf_attach_lock);
kfree(link);
return err;
}
void __hid_bpf_destroy_device(struct hid_device *hdev)
{
int type, i;
struct hid_bpf_prog_list *prog_list;
rcu_read_lock();
for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++) {
prog_list = rcu_dereference(hdev->bpf.progs[type]);
if (!prog_list)
continue;
for (i = 0; i < prog_list->prog_cnt; i++)
__clear_bit(prog_list->prog_idx[i], jmp_table.enabled);
}
rcu_read_unlock();
for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++)
__hid_bpf_set_hdev_progs(hdev, NULL, type);
/* schedule release of all detached progs */
schedule_work(&release_work);
}
#define HID_BPF_PROGS_COUNT 1
static struct bpf_link *links[HID_BPF_PROGS_COUNT];
static struct entrypoints_bpf *skel;
void hid_bpf_free_links_and_skel(void)
{
int i;
/* the following is enough to release all programs attached to hid */
if (jmp_table.map)
bpf_map_put_with_uref(jmp_table.map);
for (i = 0; i < ARRAY_SIZE(links); i++) {
if (!IS_ERR_OR_NULL(links[i]))
bpf_link_put(links[i]);
}
entrypoints_bpf__destroy(skel);
}
#define ATTACH_AND_STORE_LINK(__name) do { \
err = entrypoints_bpf__##__name##__attach(skel); \
if (err) \
goto out; \
\
links[idx] = bpf_link_get_from_fd(skel->links.__name##_fd); \
if (IS_ERR(links[idx])) { \
err = PTR_ERR(links[idx]); \
goto out; \
} \
\
/* Avoid taking over stdin/stdout/stderr of init process. Zeroing out \
* makes skel_closenz() a no-op later in iterators_bpf__destroy(). \
*/ \
close_fd(skel->links.__name##_fd); \
skel->links.__name##_fd = 0; \
idx++; \
} while (0)
int hid_bpf_preload_skel(void)
{
int err, idx = 0;
skel = entrypoints_bpf__open();
if (!skel)
return -ENOMEM;
err = entrypoints_bpf__load(skel);
if (err)
goto out;
jmp_table.map = bpf_map_get_with_uref(skel->maps.hid_jmp_table.map_fd);
if (IS_ERR(jmp_table.map)) {
err = PTR_ERR(jmp_table.map);
goto out;
}
ATTACH_AND_STORE_LINK(hid_tail_call);
return 0;
out:
hid_bpf_free_links_and_skel();
return err;
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* HID-BPF support for Linux
*
* Copyright (c) 2024 Benjamin Tissoires
*/
#include <linux/bitops.h>
#include <linux/bpf_verifier.h>
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <linux/filter.h>
#include <linux/hid.h>
#include <linux/hid_bpf.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/stddef.h>
#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
static struct btf *hid_bpf_ops_btf;
static int hid_bpf_ops_init(struct btf *btf)
{
hid_bpf_ops_btf = btf;
return 0;
}
static bool hid_bpf_ops_is_valid_access(int off, int size,
enum bpf_access_type type,
const struct bpf_prog *prog,
struct bpf_insn_access_aux *info)
{
return bpf_tracing_btf_ctx_access(off, size, type, prog, info);
}
static int hid_bpf_ops_check_member(const struct btf_type *t,
const struct btf_member *member,
const struct bpf_prog *prog)
{
u32 moff = __btf_member_bit_offset(t, member) / 8;
switch (moff) {
case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
case offsetof(struct hid_bpf_ops, hid_hw_request):
case offsetof(struct hid_bpf_ops, hid_hw_output_report):
break;
default:
if (prog->sleepable)
return -EINVAL;
}
return 0;
}
struct hid_bpf_offset_write_range {
const char *struct_name;
u32 struct_length;
u32 start;
u32 end;
};
static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
const struct bpf_reg_state *reg,
int off, int size)
{
#define WRITE_RANGE(_name, _field, _is_string) \
{ \
.struct_name = #_name, \
.struct_length = sizeof(struct _name), \
.start = offsetof(struct _name, _field), \
.end = offsetofend(struct _name, _field) - !!(_is_string), \
}
const struct hid_bpf_offset_write_range write_ranges[] = {
WRITE_RANGE(hid_bpf_ctx, retval, false),
WRITE_RANGE(hid_device, name, true),
WRITE_RANGE(hid_device, uniq, true),
WRITE_RANGE(hid_device, phys, true),
};
#undef WRITE_RANGE
const struct btf_type *state = NULL;
const struct btf_type *t;
const char *cur = NULL;
int i;
t = btf_type_by_id(reg->btf, reg->btf_id);
for (i = 0; i < ARRAY_SIZE(write_ranges); i++) {
const struct hid_bpf_offset_write_range *write_range = &write_ranges[i];
s32 type_id;
/* we already found a writeable struct, but there is a
* new one, let's break the loop.
*/
if (t == state && write_range->struct_name != cur)
break;
/* new struct to look for */
if (write_range->struct_name != cur) {
type_id = btf_find_by_name_kind(reg->btf, write_range->struct_name,
BTF_KIND_STRUCT);
if (type_id < 0)
return -EINVAL;
state = btf_type_by_id(reg->btf, type_id);
}
/* this is not the struct we are looking for */
if (t != state) {
cur = write_range->struct_name;
continue;
}
/* first time we see this struct, check for out of bounds */
if (cur != write_range->struct_name &&
off + size > write_range->struct_length) {
bpf_log(log, "write access for struct %s at off %d with size %d\n",
write_range->struct_name, off, size);
return -EACCES;
}
/* now check if we are in our boundaries */
if (off >= write_range->start && off + size <= write_range->end)
return NOT_INIT;
cur = write_range->struct_name;
}
if (t != state)
bpf_log(log, "write access to this struct is not supported\n");
else
bpf_log(log,
"write access at off %d with size %d on read-only part of %s\n",
off, size, cur);
return -EACCES;
}
static const struct bpf_verifier_ops hid_bpf_verifier_ops = {
.get_func_proto = bpf_base_func_proto,
.is_valid_access = hid_bpf_ops_is_valid_access,
.btf_struct_access = hid_bpf_ops_btf_struct_access,
};
static int hid_bpf_ops_init_member(const struct btf_type *t,
const struct btf_member *member,
void *kdata, const void *udata)
{
const struct hid_bpf_ops *uhid_bpf_ops;
struct hid_bpf_ops *khid_bpf_ops;
u32 moff;
uhid_bpf_ops = (const struct hid_bpf_ops *)udata;
khid_bpf_ops = (struct hid_bpf_ops *)kdata;
moff = __btf_member_bit_offset(t, member) / 8;
switch (moff) {
case offsetof(struct hid_bpf_ops, hid_id):
/* For hid_id and flags fields, this function has to copy it
* and return 1 to indicate that the data has been handled by
* the struct_ops type, or the verifier will reject the map if
* the value of those fields is not zero.
*/
khid_bpf_ops->hid_id = uhid_bpf_ops->hid_id;
return 1;
case offsetof(struct hid_bpf_ops, flags):
if (uhid_bpf_ops->flags & ~BPF_F_BEFORE)
return -EINVAL;
khid_bpf_ops->flags = uhid_bpf_ops->flags;
return 1;
}
return 0;
}
static int hid_bpf_reg(void *kdata)
{
struct hid_bpf_ops *ops = kdata;
struct hid_device *hdev;
int count, err = 0;
hdev = hid_get_device(ops->hid_id);
if (IS_ERR(hdev))
return PTR_ERR(hdev);
ops->hdev = hdev;
mutex_lock(&hdev->bpf.prog_list_lock);
count = list_count_nodes(&hdev->bpf.prog_list);
if (count >= HID_BPF_MAX_PROGS_PER_DEV) {
err = -E2BIG;
goto out_unlock;
}
if (ops->hid_rdesc_fixup) {
if (hdev->bpf.rdesc_ops) {
err = -EINVAL;
goto out_unlock;
}
hdev->bpf.rdesc_ops = ops;
}
if (ops->hid_device_event) {
err = hid_bpf_allocate_event_data(hdev);
if (err)
goto out_unlock;
}
if (ops->flags & BPF_F_BEFORE)
list_add_rcu(&ops->list, &hdev->bpf.prog_list);
else
list_add_tail_rcu(&ops->list, &hdev->bpf.prog_list);
synchronize_srcu(&hdev->bpf.srcu);
out_unlock:
mutex_unlock(&hdev->bpf.prog_list_lock);
if (err) {
if (hdev->bpf.rdesc_ops == ops)
hdev->bpf.rdesc_ops = NULL;
hid_put_device(hdev);
} else if (ops->hid_rdesc_fixup) {
hid_bpf_reconnect(hdev);
}
return err;
}
static void hid_bpf_unreg(void *kdata)
{
struct hid_bpf_ops *ops = kdata;
struct hid_device *hdev;
bool reconnect = false;
hdev = ops->hdev;
/* check if __hid_bpf_ops_destroy_device() has been called */
if (!hdev)
return;
mutex_lock(&hdev->bpf.prog_list_lock);
list_del_rcu(&ops->list);
synchronize_srcu(&hdev->bpf.srcu);
reconnect = hdev->bpf.rdesc_ops == ops;
if (reconnect)
hdev->bpf.rdesc_ops = NULL;
mutex_unlock(&hdev->bpf.prog_list_lock);
if (reconnect)
hid_bpf_reconnect(hdev);
hid_put_device(hdev);
}
static int __hid_bpf_device_event(struct hid_bpf_ctx *ctx, enum hid_report_type type, u64 source)
{
return 0;
}
static int __hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
{
return 0;
}
static struct hid_bpf_ops __bpf_hid_bpf_ops = {
.hid_device_event = __hid_bpf_device_event,
.hid_rdesc_fixup = __hid_bpf_rdesc_fixup,
};
static struct bpf_struct_ops bpf_hid_bpf_ops = {
.verifier_ops = &hid_bpf_verifier_ops,
.init = hid_bpf_ops_init,
.check_member = hid_bpf_ops_check_member,
.init_member = hid_bpf_ops_init_member,
.reg = hid_bpf_reg,
.unreg = hid_bpf_unreg,
.name = "hid_bpf_ops",
.cfi_stubs = &__bpf_hid_bpf_ops,
.owner = THIS_MODULE,
};
void __hid_bpf_ops_destroy_device(struct hid_device *hdev)
{
struct hid_bpf_ops *e;
rcu_read_lock();
list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) {
hid_put_device(hdev);
e->hdev = NULL;
}
rcu_read_unlock();
}
static int __init hid_bpf_struct_ops_init(void)
{
return register_bpf_struct_ops(&bpf_hid_bpf_ops, hid_bpf_ops);
}
late_initcall(hid_bpf_struct_ops_init);
......@@ -133,7 +133,7 @@ HID_BPF_CONFIG(
* integer. We thus divide it by 30 to match what other joysticks are
* doing
*/
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_raptor_mach_2, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
......@@ -152,7 +152,7 @@ int BPF_PROG(hid_fix_rdesc_raptor_mach_2, struct hid_bpf_ctx *hctx)
* divide it by 30.
* Byte 34 is always null, so it is ignored.
*/
SEC("fmod_ret/hid_bpf_device_event")
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(raptor_mach_2_fix_hat_switch, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 64 /* size */);
......@@ -168,6 +168,11 @@ int BPF_PROG(raptor_mach_2_fix_hat_switch, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(raptor_mach_2) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_raptor_mach_2,
.hid_device_event = (void *)raptor_mach_2_fix_hat_switch,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
......@@ -30,7 +30,7 @@ HID_BPF_CONFIG(
* pointer.
*/
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -45,6 +45,10 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(hp_elite_presenter) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Red Hat, Inc
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include "hid_report_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_HUION 0x256C
#define PID_DIAL_2 0x0060
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_DIAL_2),
);
/* Filled in by udev-hid-bpf */
char UDEV_PROP_HUION_FIRMWARE_ID[64];
/* The prefix of the firmware ID we expect for this device. The full firmware
* string has a date suffix, e.g. HUION_T21j_221221
*/
char EXPECTED_FIRMWARE_ID[] = "HUION_T216_";
/* How this BPF program works: the tablet has two modes, firmware mode and
* tablet mode. In firmware mode (out of the box) the tablet sends button events
* and the dial as keyboard combinations. In tablet mode it uses a vendor specific
* hid report to report everything instead.
* Depending on the mode some hid reports are never sent and the corresponding
* devices are mute.
*
* To switch the tablet use e.g. https://github.com/whot/huion-switcher
* or one of the tools from the digimend project
*
* This BPF works for both modes. The huion-switcher tool sets the
* HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
* pad and pen reports (by making them vendor collections that are ignored).
* If that property is not set we fix all hidraw nodes so the tablet works in
* either mode though the drawback is that the device will show up twice if
* you bind it to all event nodes
*
* Default report descriptor for the first exposed hidraw node:
*
* # HUION Huion Tablet_Q630M
* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
* # 0x09, 0x01, // Usage (Vendor Usage 1) 3
* # 0xa1, 0x01, // Collection (Application) 5
* # 0x85, 0x08, // Report ID (8) 7
* # 0x75, 0x58, // Report Size (88) 9
* # 0x95, 0x01, // Report Count (1) 11
* # 0x09, 0x01, // Usage (Vendor Usage 1) 13
* # 0x81, 0x02, // Input (Data,Var,Abs) 15
* # 0xc0, // End Collection 17
* R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
*
* This rdesc does nothing until the tablet is switched to raw mode, see
* https://github.com/whot/huion-switcher
*
*
* Second hidraw node is the Pen. This one sends events until the tablet is
* switched to raw mode, then it's mute.
*
* # Report descriptor length: 93 bytes
* # HUION Huion Tablet_Q630M
* # 0x05, 0x0d, // Usage Page (Digitizers) 0
* # 0x09, 0x02, // Usage (Pen) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x0a, // Report ID (10) 6
* # 0x09, 0x20, // Usage (Stylus) 8
* # 0xa1, 0x01, // Collection (Application) 10
* # 0x09, 0x42, // Usage (Tip Switch) 12
* # 0x09, 0x44, // Usage (Barrel Switch) 14
* # 0x09, 0x45, // Usage (Eraser) 16
* # 0x09, 0x3c, // Usage (Invert) 18
* # 0x15, 0x00, // Logical Minimum (0) 20
* # 0x25, 0x01, // Logical Maximum (1) 22
* # 0x75, 0x01, // Report Size (1) 24
* # 0x95, 0x06, // Report Count (6) 26
* # 0x81, 0x02, // Input (Data,Var,Abs) 28
* # 0x09, 0x32, // Usage (In Range) 30
* # 0x75, 0x01, // Report Size (1) 32
* # 0x95, 0x01, // Report Count (1) 34
* # 0x81, 0x02, // Input (Data,Var,Abs) 36
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
* # 0x05, 0x01, // Usage Page (Generic Desktop) 40
* # 0x09, 0x30, // Usage (X) 42
* # 0x09, 0x31, // Usage (Y) 44
* # 0x55, 0x0d, // Unit Exponent (-3) 46
* # 0x65, 0x33, // Unit (EnglishLinear: in³) 48
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
* # 0x35, 0x00, // Physical Minimum (0) 53
* # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55
* # 0x75, 0x10, // Report Size (16) 58
* # 0x95, 0x02, // Report Count (2) 60
* # 0x81, 0x02, // Input (Data,Var,Abs) 62
* # 0x05, 0x0d, // Usage Page (Digitizers) 64
* # 0x09, 0x30, // Usage (Tip Pressure) 66
* # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
* # 0x75, 0x10, // Report Size (16) 71
* # 0x95, 0x01, // Report Count (1) 73
* # 0x81, 0x02, // Input (Data,Var,Abs) 75
* # 0x09, 0x3d, // Usage (X Tilt) 77
* # 0x09, 0x3e, // Usage (Y Tilt) 79
* # 0x15, 0x81, // Logical Minimum (-127) 81
* # 0x25, 0x7f, // Logical Maximum (127) 83
* # 0x75, 0x08, // Report Size (8) 85
* # 0x95, 0x02, // Report Count (2) 87
* # 0x81, 0x02, // Input (Data,Var,Abs) 89
* # 0xc0, // End Collection 91
* # 0xc0, // End Collection 92
* R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d 09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
*
* Third hidraw node is the pad which sends a combination of keyboard shortcuts until
* the tablet is switched to raw mode, then it's mute:
*
* # Report descriptor length: 148 bytes
* # HUION Huion Tablet_Q630M
* # 0x05, 0x01, // Usage Page (Generic Desktop) 0
* # 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x11, // Report ID (17) 6
* # 0x05, 0x0d, // Usage Page (Digitizers) 8
* # 0x09, 0x21, // Usage (Puck) 10
* # 0xa1, 0x02, // Collection (Logical) 12
* # 0x15, 0x00, // Logical Minimum (0) 14
* # 0x25, 0x01, // Logical Maximum (1) 16
* # 0x75, 0x01, // Report Size (1) 18
* # 0x95, 0x01, // Report Count (1) 20
* # 0xa1, 0x00, // Collection (Physical) 22
* # 0x05, 0x09, // Usage Page (Button) 24
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 26
* # 0x81, 0x02, // Input (Data,Var,Abs) 28
* # 0x05, 0x0d, // Usage Page (Digitizers) 30
* # 0x09, 0x33, // Usage (Touch) 32
* # 0x81, 0x02, // Input (Data,Var,Abs) 34
* # 0x95, 0x06, // Report Count (6) 36
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
* # 0xa1, 0x02, // Collection (Logical) 40
* # 0x05, 0x01, // Usage Page (Generic Desktop) 42
* # 0x09, 0x37, // Usage (Dial) 44
* # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 46
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 49
* # 0x75, 0x10, // Report Size (16) 52
* # 0x95, 0x01, // Report Count (1) 54
* # 0x81, 0x06, // Input (Data,Var,Rel) 56
* # 0x35, 0x00, // Physical Minimum (0) 58
* # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 60
* # 0x15, 0x00, // Logical Minimum (0) 63
* # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 65
* # 0x09, 0x48, // Usage (Resolution Multiplier) 68
* # 0xb1, 0x02, // Feature (Data,Var,Abs) 70
* # 0x45, 0x00, // Physical Maximum (0) 72
* # 0xc0, // End Collection 74
* # 0x75, 0x08, // Report Size (8) 75
* # 0x95, 0x01, // Report Count (1) 77
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 79
* # 0x75, 0x08, // Report Size (8) 81
* # 0x95, 0x01, // Report Count (1) 83
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 85
* # 0x75, 0x08, // Report Size (8) 87
* # 0x95, 0x01, // Report Count (1) 89
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 91
* # 0x75, 0x08, // Report Size (8) 93
* # 0x95, 0x01, // Report Count (1) 95
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 97
* # 0x75, 0x08, // Report Size (8) 99
* # 0x95, 0x01, // Report Count (1) 101
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 103
* # 0xc0, // End Collection 105
* # 0xc0, // End Collection 106
* # 0xc0, // End Collection 107
* # 0x05, 0x01, // Usage Page (Generic Desktop) 108
* # 0x09, 0x06, // Usage (Keyboard) 110
* # 0xa1, 0x01, // Collection (Application) 112
* # 0x85, 0x03, // Report ID (3) 114
* # 0x05, 0x07, // Usage Page (Keyboard) 116
* # 0x19, 0xe0, // Usage Minimum (224) 118
* # 0x29, 0xe7, // Usage Maximum (231) 120
* # 0x15, 0x00, // Logical Minimum (0) 122
* # 0x25, 0x01, // Logical Maximum (1) 124
* # 0x75, 0x01, // Report Size (1) 126
* # 0x95, 0x08, // Report Count (8) 128
* # 0x81, 0x02, // Input (Data,Var,Abs) 130
* # 0x05, 0x07, // Usage Page (Keyboard) 132
* # 0x19, 0x00, // Usage Minimum (0) 134
* # 0x29, 0xff, // Usage Maximum (255) 136
* # 0x26, 0xff, 0x00, // Logical Maximum (255) 138
* # 0x75, 0x08, // Report Size (8) 141
* # 0x95, 0x06, // Report Count (6) 143
* # 0x81, 0x00, // Input (Data,Arr,Abs) 145
* # 0xc0, // End Collection 147
* R: 148 05 01 09 0e a1 01 85 11 05 0d 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0
*/
#define PAD_REPORT_DESCRIPTOR_LENGTH 148
#define PEN_REPORT_DESCRIPTOR_LENGTH 93
#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
#define PAD_REPORT_ID 3
#define DIAL_REPORT_ID 17
#define PEN_REPORT_ID 10
#define VENDOR_REPORT_ID 8
#define PAD_REPORT_LENGTH 9
#define PEN_REPORT_LENGTH 10
#define VENDOR_REPORT_LENGTH 12
__u8 last_button_state;
static const __u8 fixed_rdesc_pad[] = {
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// -- Byte 0 in report
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 1 in report - just exists so we get to be a tablet pad
Usage_Dig_BarrelSwitch // BTN_STYLUS
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // padding
Input(Const)
// Bytes 2/3 in report - just exists so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Byte 4 in report is the dial
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
// Byte 5 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x8)
LogicalRange_i8(0x0, 0x1)
ReportCount(7)
ReportSize(1)
Input(Var|Abs)
ReportCount(1) // padding
Input(Const)
)
// Make sure we match our original report length
FixedSizeVendorReport(PAD_REPORT_LENGTH)
)
};
static const __u8 fixed_rdesc_pen[] = {
UsagePage_Digitizers
Usage_Dig_Pen
CollectionApplication(
// -- Byte 0 in report
ReportId(PEN_REPORT_ID)
Usage_Dig_Pen
CollectionPhysical(
// -- Byte 1 in report
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
LogicalRange_i8(0, 1)
ReportSize(1)
ReportCount(3)
Input(Var|Abs)
ReportCount(4) // Padding
Input(Const)
Usage_Dig_InRange
ReportCount(1)
Input(Var|Abs)
ReportSize(16)
ReportCount(1)
PushPop(
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
PhysicalRange_i16(0, 266)
LogicalRange_i16(0, 32767)
Usage_GD_X
Input(Var|Abs) // Bytes 2+3
PhysicalRange_i16(0, 166)
LogicalRange_i16(0, 32767)
Usage_GD_Y
Input(Var|Abs) // Bytes 4+5
)
UsagePage_Digitizers
Usage_Dig_TipPressure
LogicalRange_i16(0, 8191)
Input(Var|Abs) // Byte 6+7
ReportSize(8)
ReportCount(2)
LogicalRange_i8(-60, 60)
Usage_Dig_XTilt
Usage_Dig_YTilt
Input(Var|Abs) // Byte 8+9
)
)
};
static const __u8 fixed_rdesc_vendor[] = {
UsagePage_Digitizers
Usage_Dig_Pen
CollectionApplication(
// Byte 0
// We leave the pen on the vendor report ID
ReportId(VENDOR_REPORT_ID)
Usage_Dig_Pen
CollectionPhysical(
// Byte 1 are the buttons
LogicalRange_i8(0, 1)
ReportSize(1)
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch
ReportCount(3)
Input(Var|Abs)
ReportCount(4) // Padding
Input(Const)
Usage_Dig_InRange
ReportCount(1)
Input(Var|Abs)
ReportSize(16)
ReportCount(1)
PushPop(
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
// Note: reported logical range differs
// from the pen report ID for x and y
LogicalRange_i16(0, 53340)
PhysicalRange_i16(0, 266)
// Bytes 2/3 in report
Usage_GD_X
Input(Var|Abs)
LogicalRange_i16(0, 33340)
PhysicalRange_i16(0, 166)
// Bytes 4/5 in report
Usage_GD_Y
Input(Var|Abs)
)
// Bytes 6/7 in report
LogicalRange_i16(0, 8191)
Usage_Dig_TipPressure
Input(Var|Abs)
// Bytes 8/9 in report
ReportCount(1) // Padding
Input(Const)
LogicalRange_i8(-60, 60)
// Byte 10 in report
Usage_Dig_XTilt
// Byte 11 in report
Usage_Dig_YTilt
ReportSize(8)
ReportCount(2)
Input(Var|Abs)
)
)
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// Byte 0
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 1 are the buttons
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // Padding
Input(Const)
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Byte 4 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x8)
LogicalRange_i8(0x0, 0x1)
ReportCount(8)
ReportSize(1)
Input(Var|Abs)
// Byte 5 is the top dial
UsagePage_GenericDesktop
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
// Byte 6 is the bottom dial
UsagePage_Consumer
Usage_Con_ACPan
Input(Var|Rel)
)
// Make sure we match our original report length
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
)
};
static const __u8 disabled_rdesc_pen[] = {
FixedSizeVendorReport(PEN_REPORT_LENGTH)
};
static const __u8 disabled_rdesc_pad[] = {
FixedSizeVendorReport(PAD_REPORT_LENGTH)
};
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(dial_2_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
__s32 rdesc_size = hctx->size;
__u8 have_fw_id;
if (!data)
return 0; /* EPERM check */
/* If we have a firmware ID and it matches our expected prefix, we
* disable the default pad/pen nodes. They won't send events
* but cause duplicate devices.
*/
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
EXPECTED_FIRMWARE_ID,
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
if (have_fw_id) {
__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
return sizeof(disabled_rdesc_pad);
}
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
return sizeof(fixed_rdesc_pad);
}
if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
if (have_fw_id) {
__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
return sizeof(disabled_rdesc_pen);
}
__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
return sizeof(fixed_rdesc_pen);
}
/* Always fix the vendor mode so the tablet will work even if nothing sets
* the udev property (e.g. huion-switcher run manually)
*/
if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
return sizeof(fixed_rdesc_vendor);
}
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(dial_2_fix_events, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 16 /* size */);
static __u8 button;
if (!data)
return 0; /* EPERM check */
/* Only sent if tablet is in default mode */
if (data[0] == PAD_REPORT_ID) {
/* Nicely enough, this device only supports one button down at a time so
* the reports are easy to match. Buttons numbered from the top
* Button released: 03 00 00 00 00 00 00 00
* Button 1: 03 00 05 00 00 00 00 00 -> b
* Button 2: 03 00 08 00 00 00 00 00 -> e
* Button 3: 03 00 0c 00 00 00 00 00 -> i
* Button 4: 03 00 e0 16 00 00 00 00 -> Ctrl S
* Button 5: 03 00 2c 00 00 00 00 00 -> space
* Button 6: 03 00 e0 e2 1d 00 00 00 -> Ctrl Alt Z
*/
button &= 0xc0;
switch ((data[2] << 16) | (data[3] << 8) | data[4]) {
case 0x000000:
break;
case 0x050000:
button |= BIT(0);
break;
case 0x080000:
button |= BIT(1);
break;
case 0x0c0000:
button |= BIT(2);
break;
case 0xe01600:
button |= BIT(3);
break;
case 0x2c0000:
button |= BIT(4);
break;
case 0xe0e21d:
button |= BIT(5);
break;
}
__u8 report[8] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, 0x00, button};
__builtin_memcpy(data, report, sizeof(report));
return sizeof(report);
}
/* Only sent if tablet is in default mode */
if (data[0] == DIAL_REPORT_ID) {
/*
* In default mode, both dials are merged together:
*
* Dial down: 11 00 ff ff 00 00 00 00 00 -> Dial -1
* Dial up: 11 00 01 00 00 00 00 00 00 -> Dial 1
*/
__u16 dial = data[3] << 8 | data[2];
button &= 0x3f;
button |= !!data[1] << 6;
__u8 report[] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, dial, button};
__builtin_memcpy(data, report, sizeof(report));
return sizeof(report);
}
/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
/* Only sent if tablet is in raw mode */
if (data[0] == VENDOR_REPORT_ID) {
/* Pad reports */
if (data[1] & 0x20) {
/* See fixed_rdesc_pad */
struct pad_report {
__u8 report_id;
__u8 btn_stylus;
__u8 x;
__u8 y;
__u8 buttons;
__u8 dial_1;
__u8 dial_2;
} __attribute__((packed)) *pad_report;
__u8 dial_1 = 0, dial_2 = 0;
/* Dial report */
if (data[1] == 0xf1) {
__u8 d = 0;
if (data[5] == 2)
d = 0xff;
else
d = data[5];
if (data[3] == 1)
dial_1 = d;
else
dial_2 = d;
} else {
/* data[4] are the buttons, mapped correctly */
last_button_state = data[4];
dial_1 = 0; // dial
dial_2 = 0;
}
pad_report = (struct pad_report *)data;
pad_report->report_id = PAD_REPORT_ID;
pad_report->btn_stylus = 0;
pad_report->x = 0;
pad_report->y = 0;
pad_report->buttons = last_button_state;
pad_report->dial_1 = dial_1;
pad_report->dial_2 = dial_2;
return sizeof(struct pad_report);
}
/* Pen reports need nothing done */
}
return 0;
}
HID_BPF_OPS(inspiroy_dial2) = {
.hid_device_event = (void *)dial_2_fix_events,
.hid_rdesc_fixup = (void *)dial_2_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
switch (ctx->rdesc_size) {
case PAD_REPORT_DESCRIPTOR_LENGTH:
case PEN_REPORT_DESCRIPTOR_LENGTH:
case VENDOR_REPORT_DESCRIPTOR_LENGTH:
ctx->retval = 0;
break;
default:
ctx->retval = -EINVAL;
}
return 0;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Red Hat, Inc
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include "hid_report_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_HUION 0x256C
#define PID_INSPIROY_2_S 0x0066
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_S),
);
/* Filled in by udev-hid-bpf */
char UDEV_PROP_HUION_FIRMWARE_ID[64];
/* The prefix of the firmware ID we expect for this device. The full firmware
* string has a date suffix, e.g. HUION_T21j_221221
*/
char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
/* How this BPF program works: the tablet has two modes, firmware mode and
* tablet mode. In firmware mode (out of the box) the tablet sends button events
* and the dial as keyboard combinations. In tablet mode it uses a vendor specific
* hid report to report everything instead.
* Depending on the mode some hid reports are never sent and the corresponding
* devices are mute.
*
* To switch the tablet use e.g. https://github.com/whot/huion-switcher
* or one of the tools from the digimend project
*
* This BPF works for both modes. The huion-switcher tool sets the
* HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
* pad and pen reports (by making them vendor collections that are ignored).
* If that property is not set we fix all hidraw nodes so the tablet works in
* either mode though the drawback is that the device will show up twice if
* you bind it to all event nodes
*
* Default report descriptor for the first exposed hidraw node:
*
* # HUION Huion Tablet_H641P
* # Report descriptor length: 18 bytes
* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 0
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
* # 0xa1, 0x01, // Collection (Application) 5
* # 0x85, 0x08, // Report ID (8) 7
* # 0x75, 0x58, // Report Size (88) 9
* # 0x95, 0x01, // Report Count (1) 11
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
* # 0x81, 0x02, // Input (Data,Var,Abs) 15
* # 0xc0, // End Collection 17
* R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
*
* This rdesc does nothing until the tablet is switched to raw mode, see
* https://github.com/whot/huion-switcher
*
*
* Second hidraw node is the Pen. This one sends events until the tablet is
* switched to raw mode, then it's mute.
*
* # Report descriptor length: 93 bytes
* # 0x05, 0x0d, // Usage Page (Digitizers) 0
* # 0x09, 0x02, // Usage (Pen) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x0a, // Report ID (10) 6
* # 0x09, 0x20, // Usage (Stylus) 8
* # 0xa1, 0x01, // Collection (Application) 10
* # 0x09, 0x42, // Usage (Tip Switch) 12
* # 0x09, 0x44, // Usage (Barrel Switch) 14
* # 0x09, 0x45, // Usage (Eraser) 16
* # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
* # 0x15, 0x00, // Logical Minimum (0) 20
* # 0x25, 0x01, // Logical Maximum (1) 22
* # 0x75, 0x01, // Report Size (1) 24
* # 0x95, 0x06, // Report Count (6) 26
* # 0x81, 0x02, // Input (Data,Var,Abs) 28
* # 0x09, 0x32, // Usage (In Range) 30
* # 0x75, 0x01, // Report Size (1) 32
* # 0x95, 0x01, // Report Count (1) 34
* # 0x81, 0x02, // Input (Data,Var,Abs) 36
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
* # 0x05, 0x01, // Usage Page (Generic Desktop) 40
* # 0x09, 0x30, // Usage (X) 42
* # 0x09, 0x31, // Usage (Y) 44
* # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
* # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
* # 0x35, 0x00, // Physical Minimum (0) 53
* # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
* # 0x75, 0x10, // Report Size (16) 58
* # 0x95, 0x02, // Report Count (2) 60
* # 0x81, 0x02, // Input (Data,Var,Abs) 62
* # 0x05, 0x0d, // Usage Page (Digitizers) 64
* # 0x09, 0x30, // Usage (Tip Pressure) 66
* # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
* # 0x75, 0x10, // Report Size (16) 71
* # 0x95, 0x01, // Report Count (1) 73
* # 0x81, 0x02, // Input (Data,Var,Abs) 75
* # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
* # 0x09, 0x3e, // Usage (Y Tilt) 79
* # 0x15, 0x81, // Logical Minimum (-127) 81
* # 0x25, 0x7f, // Logical Maximum (127) 83
* # 0x75, 0x08, // Report Size (8) 85
* # 0x95, 0x02, // Report Count (2) 87
* # 0x81, 0x02, // Input (Data,Var,Abs) 89
* # 0xc0, // End Collection 91
* # 0xc0, // End Collection 92
* R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
*
* Third hidraw node is the pad which sends a combination of keyboard shortcuts until
* the tablet is switched to raw mode, then it's mute:
*
* # Report descriptor length: 65 bytes
* # 0x05, 0x01, // Usage Page (Generic Desktop) 0
* # 0x09, 0x06, // Usage (Keyboard) 2
* # 0xa1, 0x01, // Collection (Application) 4
* # 0x85, 0x03, // Report ID (3) 6
* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
* # 0x19, 0xe0, // UsageMinimum (224) 10
* # 0x29, 0xe7, // UsageMaximum (231) 12
* # 0x15, 0x00, // Logical Minimum (0) 14
* # 0x25, 0x01, // Logical Maximum (1) 16
* # 0x75, 0x01, // Report Size (1) 18
* # 0x95, 0x08, // Report Count (8) 20
* # 0x81, 0x02, // Input (Data,Var,Abs) 22
* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24
* # 0x19, 0x00, // UsageMinimum (0) 26
* # 0x29, 0xff, // UsageMaximum (255) 28
* # 0x26, 0xff, 0x00, // Logical Maximum (255) 30
* # 0x75, 0x08, // Report Size (8) 33
* # 0x95, 0x06, // Report Count (6) 35
* # 0x81, 0x00, // Input (Data,Arr,Abs) 37
* # 0xc0, // End Collection 39
* # 0x05, 0x0c, // Usage Page (Consumer) 40
* # 0x09, 0x01, // Usage (Consumer Control) 42
* # 0xa1, 0x01, // Collection (Application) 44
* # 0x85, 0x04, // Report ID (4) 46
* # 0x19, 0x00, // UsageMinimum (0) 48
* # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
* # 0x15, 0x00, // Logical Minimum (0) 53
* # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
* # 0x95, 0x01, // Report Count (1) 58
* # 0x75, 0x10, // Report Size (16) 60
* # 0x81, 0x00, // Input (Data,Arr,Abs) 62
* # 0xc0, // End Collection 64
* R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
* N: HUION Huion Tablet_H641P
*/
#define PAD_REPORT_DESCRIPTOR_LENGTH 65
#define PEN_REPORT_DESCRIPTOR_LENGTH 93
#define VENDOR_REPORT_DESCRIPTOR_LENGTH 18
#define PAD_REPORT_ID 3
#define PEN_REPORT_ID 10
#define VENDOR_REPORT_ID 8
#define PAD_REPORT_LENGTH 8
#define PEN_REPORT_LENGTH 10
#define VENDOR_REPORT_LENGTH 12
__u8 last_button_state;
static const __u8 fixed_rdesc_pad[] = {
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// -- Byte 0 in report
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 1 in report - just exists so we get to be a tablet pad
Usage_Dig_BarrelSwitch // BTN_STYLUS
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // padding
Input(Const)
// Bytes 2/3 in report - just exists so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Byte 4 in report is the wheel
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
// Byte 5 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x6)
LogicalRange_i8(0x01, 0x6)
ReportCount(1)
ReportSize(8)
Input(Arr|Abs)
)
// Make sure we match our original report length
FixedSizeVendorReport(PAD_REPORT_LENGTH)
)
};
static const __u8 fixed_rdesc_pen[] = {
UsagePage_Digitizers
Usage_Dig_Pen
CollectionApplication(
// -- Byte 0 in report
ReportId(PEN_REPORT_ID)
Usage_Dig_Pen
CollectionPhysical(
// -- Byte 1 in report
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
LogicalRange_i8(0, 1)
ReportSize(1)
ReportCount(3)
Input(Var|Abs)
ReportCount(4) // Padding
Input(Const)
Usage_Dig_InRange
ReportCount(1)
Input(Var|Abs)
ReportSize(16)
ReportCount(1)
PushPop(
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
PhysicalRange_i16(0, 160)
LogicalRange_i16(0, 32767)
Usage_GD_X
Input(Var|Abs) // Bytes 2+3
PhysicalRange_i16(0, 100)
LogicalRange_i16(0, 32767)
Usage_GD_Y
Input(Var|Abs) // Bytes 4+5
)
UsagePage_Digitizers
Usage_Dig_TipPressure
LogicalRange_i16(0, 8191)
Input(Var|Abs) // Byte 6+7
// Two bytes padding so we don't need to change the report at all
ReportSize(8)
ReportCount(2)
Input(Const) // Byte 6+7
)
)
};
static const __u8 fixed_rdesc_vendor[] = {
UsagePage_Digitizers
Usage_Dig_Pen
CollectionApplication(
// Byte 0
// We leave the pen on the vendor report ID
ReportId(VENDOR_REPORT_ID)
Usage_Dig_Pen
CollectionPhysical(
// Byte 1 are the buttons
LogicalRange_i8(0, 1)
ReportSize(1)
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch
ReportCount(3)
Input(Var|Abs)
ReportCount(4) // Padding
Input(Const)
Usage_Dig_InRange
ReportCount(1)
Input(Var|Abs)
ReportSize(16)
ReportCount(1)
PushPop(
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
// Note: reported logical range differs
// from the pen report ID for x and y
LogicalRange_i16(0, 32000)
PhysicalRange_i16(0, 160)
// Bytes 2/3 in report
Usage_GD_X
Input(Var|Abs)
LogicalRange_i16(0, 20000)
PhysicalRange_i16(0, 100)
// Bytes 4/5 in report
Usage_GD_Y
Input(Var|Abs)
)
// Bytes 6/7 in report
LogicalRange_i16(0, 8192)
Usage_Dig_TipPressure
Input(Var|Abs)
)
)
UsagePage_GenericDesktop
Usage_GD_Keypad
CollectionApplication(
// Byte 0
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
// Byte 1 are the buttons
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
ReportCount(1)
ReportSize(1)
Input(Var|Abs)
ReportCount(7) // Padding
Input(Const)
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
UsagePage_GenericDesktop
Usage_GD_X
Usage_GD_Y
ReportCount(2)
ReportSize(8)
Input(Var|Abs)
// Byte 4 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x6)
LogicalRange_i8(0x0, 0x1)
ReportCount(6)
ReportSize(1)
Input(Var|Abs)
ReportCount(2)
Input(Const)
// Byte 5 is the wheel
UsagePage_GenericDesktop
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
)
// Make sure we match our original report length
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
)
};
static const __u8 disabled_rdesc_pen[] = {
FixedSizeVendorReport(PEN_REPORT_LENGTH)
};
static const __u8 disabled_rdesc_pad[] = {
FixedSizeVendorReport(PAD_REPORT_LENGTH)
};
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
__s32 rdesc_size = hctx->size;
__u8 have_fw_id;
if (!data)
return 0; /* EPERM check */
/* If we have a firmware ID and it matches our expected prefix, we
* disable the default pad/pen nodes. They won't send events
* but cause duplicate devices.
*/
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
EXPECTED_FIRMWARE_ID,
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
if (have_fw_id) {
__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
return sizeof(disabled_rdesc_pad);
}
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
return sizeof(fixed_rdesc_pad);
}
if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
if (have_fw_id) {
__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
return sizeof(disabled_rdesc_pen);
}
__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
return sizeof(fixed_rdesc_pen);
}
/* Always fix the vendor mode so the tablet will work even if nothing sets
* the udev property (e.g. huion-switcher run manually)
*/
if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
return sizeof(fixed_rdesc_vendor);
}
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
if (!data)
return 0; /* EPERM check */
/* Only sent if tablet is in default mode */
if (data[0] == PAD_REPORT_ID) {
/* Nicely enough, this device only supports one button down at a time so
* the reports are easy to match. Buttons numbered from the top
* Button released: 03 00 00 00 00 00 00 00
* Button 1: 03 00 05 00 00 00 00 00 -> b
* Button 2: 03 00 0c 00 00 00 00 00 -> i
* Button 3: 03 00 08 00 00 00 00 00 -> e
* Button 4: 03 01 16 00 00 00 00 00 -> Ctrl S
* Button 5: 03 00 2c 00 00 00 00 00 -> space
* Button 6: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
*
* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
*/
__u8 button = 0;
__u8 wheel = 0;
switch (data[1] << 8 | data[2]) {
case 0x0000:
break;
case 0x0005:
button = 1;
break;
case 0x000c:
button = 2;
break;
case 0x0008:
button = 3;
break;
case 0x0116:
button = 4;
break;
case 0x002c:
button = 5;
break;
case 0x051d:
button = 6;
break;
case 0x012d:
wheel = -1;
break;
case 0x012e:
wheel = 1;
break;
}
__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
__builtin_memcpy(data, report, sizeof(report));
return sizeof(report);
}
/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
/* Only sent if tablet is in raw mode */
if (data[0] == VENDOR_REPORT_ID) {
/* Pad reports */
if (data[1] & 0x20) {
/* See fixed_rdesc_pad */
struct pad_report {
__u8 report_id;
__u8 btn_stylus;
__u8 x;
__u8 y;
__u8 buttons;
__u8 wheel;
} __attribute__((packed)) *pad_report;
__u8 wheel = 0;
/* Wheel report */
if (data[1] == 0xf1) {
if (data[5] == 2)
wheel = 0xff;
else
wheel = data[5];
} else {
/* data[4] are the buttons, mapped correctly */
last_button_state = data[4];
wheel = 0; // wheel
}
pad_report = (struct pad_report *)data;
pad_report->report_id = PAD_REPORT_ID;
pad_report->btn_stylus = 0;
pad_report->x = 0;
pad_report->y = 0;
pad_report->buttons = last_button_state;
pad_report->wheel = wheel;
return sizeof(struct pad_report);
}
/* Pen reports need nothing done */
}
return 0;
}
HID_BPF_OPS(inspiroy_2) = {
.hid_device_event = (void *)inspiroy_2_fix_events,
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
switch (ctx->rdesc_size) {
case PAD_REPORT_DESCRIPTOR_LENGTH:
case PEN_REPORT_DESCRIPTOR_LENGTH:
case VENDOR_REPORT_DESCRIPTOR_LENGTH:
ctx->retval = 0;
break;
default:
ctx->retval = -EINVAL;
}
return 0;
}
char _license[] SEC("license") = "GPL";
......@@ -191,7 +191,7 @@ static const __u8 fixed_rdesc[] = {
0xc0, // End Collection 327
};
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
......@@ -215,7 +215,7 @@ int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
* - if there was this out-of-proximity event, we are entering
* eraser mode, and we will until the next out-of-proximity.
*/
SEC("fmod_ret/hid_bpf_device_event")
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
......@@ -255,6 +255,11 @@ int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(huion_Kamvas_pro_19) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas_pro_19,
.hid_device_event = (void *)kamvas_pro_19_fix_3rd_button,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
......@@ -21,7 +21,7 @@ HID_BPF_CONFIG(
* We just fix the report descriptor to enable those missing 7 buttons.
*/
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
const u8 offsets[] = {84, 112, 140};
......@@ -45,6 +45,10 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(iogear_kaliber_momentum) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
......@@ -56,7 +56,7 @@ clean:
%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \
$(Q)$(CLANG) -g -O2 --target=bpf -Wall -Werror $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
......
......@@ -15,20 +15,19 @@ HID_BPF_CONFIG(
);
/*
* When using the XBox Wireless Controller Elite 2 over Bluetooth,
* the device exports the paddle on the back of the device as a single
* When using the Xbox Wireless Controller Elite 2 over Bluetooth,
* the device exports the paddles on the back of the device as a single
* bitfield value of usage "Assign Selection".
*
* The kernel doesn't process those usages properly and report KEY_UNKNOWN
* for it.
* The kernel doesn't process the paddles usage properly and reports KEY_UNKNOWN.
*
* SDL doesn't know how to interprete that KEY_UNKNOWN and thus ignores the paddles.
* SDL doesn't know how to interpret KEY_UNKNOWN and thus ignores the paddles.
*
* Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
* can tweak the report descriptor to make the kernel interprete it properly:
* - we need an application collection of gamepad (so we have to close the current
* can tweak the report descriptor to make the kernel interpret it properly:
* - We need an application collection of gamepad (so we have to close the current
* Consumer Control one)
* - we need to change the usage to be buttons from 0x15 to 0x18
* - We need to change the usage to be buttons from 0x15 to 0x18
*/
#define OFFSET_ASSIGN_SELECTION 211
......@@ -93,7 +92,7 @@ _Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selec
_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
"Rdesc at given offset is too big");
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -114,6 +113,10 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(xbox_elite_2) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Kumar Swarnam Iyer (kumar.s.iyer65@gmail.com)
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_THRUSTMASTER 0x044F
#define PID_TCA_YOKE_BOEING 0x0409
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_THRUSTMASTER, PID_TCA_YOKE_BOEING)
);
/* The original HID descriptor of the Thrustmaster TCA Yoke Boeing joystick contains
* an Input field that shows up as an axis, ABS_MISC in Linux. But it is not possible
* to assign an actual physical control to this axis as they're all taken up. There
* are 2 vendor-defined inputs where the Input type appears to be defined wrongly.
* This bpf attempts to fix this by changing the Inputs so that it doesn't show up in
* Linux at all.
* This version is the short version fix that only changes 2 fields in the descriptor
* instead of the whole report descriptor.
* For reference, this is the original report descriptor:
*
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
* 0x09, 0x04, // Usage (Joystick) 2
* 0xa1, 0x01, // Collection (Application) 4
* 0x85, 0x01, // Report ID (1) 6
* 0x09, 0x39, // Usage (Hat switch) 8
* 0x15, 0x00, // Logical Minimum (0) 10
* 0x25, 0x07, // Logical Maximum (7) 12
* 0x35, 0x00, // Physical Minimum (0) 14
* 0x46, 0x3b, 0x01, // Physical Maximum (315) 16
* 0x65, 0x14, // Unit (EnglishRotation: deg) 19
* 0x75, 0x04, // Report Size (4) 21
* 0x95, 0x01, // Report Count (1) 23
* 0x81, 0x42, // Input (Data,Var,Abs,Null) 25
* 0x65, 0x00, // Unit (None) 27
* 0x05, 0x09, // Usage Page (Button) 29
* 0x19, 0x01, // Usage Minimum (1) 31
* 0x29, 0x12, // Usage Maximum (18) 33
* 0x15, 0x00, // Logical Minimum (0) 35
* 0x25, 0x01, // Logical Maximum (1) 37
* 0x75, 0x01, // Report Size (1) 39
* 0x95, 0x12, // Report Count (18) 41
* 0x81, 0x02, // Input (Data,Var,Abs) 43
* 0x95, 0x02, // Report Count (2) 45
* 0x81, 0x03, // Input (Cnst,Var,Abs) 47
* 0x05, 0x01, // Usage Page (Generic Desktop) 49
* 0x09, 0x31, // Usage (Y) 51
* 0x09, 0x30, // Usage (X) 53
* 0x09, 0x32, // Usage (Z) 55
* 0x09, 0x34, // Usage (Ry) 57
* 0x09, 0x33, // Usage (Rx) 59
* 0x09, 0x35, // Usage (Rz) 61
* 0x15, 0x00, // Logical Minimum (0) 63
* 0x27, 0xff, 0xff, 0x00, 0x00, // Logical Maximum (65535) 65
* 0x75, 0x10, // Report Size (16) 70
* 0x95, 0x06, // Report Count (6) 72
* 0x81, 0x02, // Input (Data,Var,Abs) 74
* 0x06, 0xf0, 0xff, // Usage Page (Vendor Usage Page 0xfff0) 76
* 0x09, 0x59, // Usage (Vendor Usage 0x59) 79
* 0x15, 0x00, // Logical Minimum (0) 81
* 0x26, 0xff, 0x00, // Logical Maximum (255) 83
* 0x75, 0x08, // Report Size (8) 86
* 0x95, 0x01, // Report Count (1) 88
* 0x81, 0x02, // Input (Data,Var,Abs) 90 --> Needs to be changed
* 0x09, 0x51, // Usage (Vendor Usage 0x51) 92
* 0x15, 0x00, // Logical Minimum (0) 94
* 0x26, 0xff, 0x00, // Logical Maximum (255) 96
* 0x75, 0x08, // Report Size (8) 99
* 0x95, 0x20, // Report Count (32) 101 --> Needs to be changed
* 0x81, 0x02, // Input (Data,Var,Abs) 103
* 0x09, 0x50, // Usage (Vendor Usage 0x50) 105
* 0x15, 0x00, // Logical Minimum (0) 107
* 0x26, 0xff, 0x00, // Logical Maximum (255) 109
* 0x75, 0x08, // Report Size (8) 112
* 0x95, 0x0f, // Report Count (15) 114
* 0x81, 0x03, // Input (Cnst,Var,Abs) 116
* 0x09, 0x47, // Usage (Vendor Usage 0x47) 118
* 0x85, 0xf2, // Report ID (242) 120
* 0x15, 0x00, // Logical Minimum (0) 122
* 0x26, 0xff, 0x00, // Logical Maximum (255) 124
* 0x75, 0x08, // Report Size (8) 127
* 0x95, 0x3f, // Report Count (63) 129
* 0xb1, 0x02, // Feature (Data,Var,Abs) 131
* 0x09, 0x48, // Usage (Vendor Usage 0x48) 133
* 0x85, 0xf3, // Report ID (243) 135
* 0x15, 0x00, // Logical Minimum (0) 137
* 0x26, 0xff, 0x00, // Logical Maximum (255) 139
* 0x75, 0x08, // Report Size (8) 142
* 0x95, 0x3f, // Report Count (63) 144
* 0xb1, 0x02, // Feature (Data,Var,Abs) 146
* 0xc0, // End Collection 148
*/
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_tca_yoke, struct hid_bpf_ctx *hctx)
{
const int expected_length = 148;
if (hctx->size != expected_length)
return 0;
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
if (!data)
return 0; /* EPERM */
/* Safety check, our probe() should take care of this though */
if (data[1] != 0x01 /* Generic Desktop */ || data[3] != 0x04 /* Joystick */)
return 0;
/* The report descriptor sets incorrect Input items in 2 places, resulting in a
* non-existing axis showing up.
* This change sets the correct Input which prevents the axis from showing up in Linux.
*/
if (data[90] == 0x81 && /* Input */
data[103] == 0x81) { /* Input */
data[91] = 0x03; /* Input set to 0x03 Constant, Variable Absolute */
data[104] = 0x03; /* Input set to 0X03 Constant, Variable Absolute */
}
return 0;
}
HID_BPF_OPS(tca_yoke) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_tca_yoke,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/* ensure the kernel isn't fixed already */
if (ctx->rdesc[91] != 0x02) /* Input for 0x59 Usage type has changed */
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
......@@ -101,7 +101,7 @@ static inline __u8 *get_u8(__u8 *data, unsigned int offset)
return (__u8 *)get_bits(data, offset);
}
SEC("fmod_ret/hid_bpf_device_event")
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
......@@ -139,6 +139,10 @@ int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(wacom_artpen) = {
.hid_device_event = (void *)artpen_pressure_interpolate,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
......@@ -78,8 +78,6 @@ static const __u8 fixed_rdesc[] = {
0xc0, // End Collection 106
};
#define BIT(n) (1UL << n)
#define TIP_SWITCH BIT(0)
#define BARREL_SWITCH BIT(1)
#define ERASER BIT(2)
......@@ -91,7 +89,7 @@ static const __u8 fixed_rdesc[] = {
#define U16(index) (data[index] | (data[index + 1] << 8))
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -152,13 +150,12 @@ static __u8 prev_state = 0;
* E: TipSwitch InRange
*
*/
SEC("fmod_ret/hid_bpf_device_event")
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
__u8 current_state, changed_state;
bool prev_tip;
__u16 tilt;
if (!data)
return 0; /* EPERM check */
......@@ -209,6 +206,11 @@ int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
return 0;
}
HID_BPF_OPS(xppen_artist_24) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artist24,
.hid_device_event = (void *)xppen_24_fix_eraser,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
......@@ -82,7 +82,7 @@ static const __u8 fixed_rdesc[] = {
0xc0, // End Collection 112
};
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -105,8 +105,7 @@ int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
return sizeof(fixed_rdesc);
}
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(xppen_16_fix_eraser, struct hid_bpf_ctx *hctx)
static int xppen_16_fix_eraser(struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
......@@ -207,8 +206,7 @@ static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
data[idx+1] = coords >> 8;
}
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx)
static int xppen_16_fix_angle_offset(struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
......@@ -254,6 +252,22 @@ int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx)
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(xppen_artist_pro_16_device_event, struct hid_bpf_ctx *hctx)
{
int ret = xppen_16_fix_angle_offset(hctx);
if (ret)
return ret;
return xppen_16_fix_eraser(hctx);
}
HID_BPF_OPS(xppen_artist_pro_16) = {
.hid_rdesc_fixup = (void *)hid_fix_rdesc_xppen_artistpro16gen2,
.hid_device_event = (void *)xppen_artist_pro_16_device_event,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
......
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 José Expósito
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_UGEE 0x28BD
#define PID_DECO_MINI_4 0x0929
#define RDESC_SIZE_PAD 177
#define RDESC_SIZE_PEN 109
#define PAD_REPORT_ID 0x06
/*
* XP-Pen devices return a descriptor with the values the driver should use when
* one of its interfaces is queried. For this device the descriptor is:
*
* 0E 03 60 4F 88 3B 06 00 FF 1F D8 13
* ----- ----- ----- -----
* | | | |
* | | | `- Resolution: 5080 (13d8)
* | | `- Maximum pressure: 8191 (1FFF)
* | `- Logical maximum Y: 15240 (3B88)
* `- Logical maximum X: 20320 (4F60)
*
* The physical maximum is calculated as (logical_max * 1000) / resolution.
*/
#define LOGICAL_MAX_X 0x60, 0x4F
#define LOGICAL_MAX_Y 0x88, 0x3B
#define PHYSICAL_MAX_X 0xA0, 0x0F
#define PHYSICAL_MAX_Y 0xB8, 0x0B
#define PRESSURE_MAX 0xFF, 0x1F
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_MINI_4)
);
/*
* The tablet send these values when the pad buttons are pressed individually:
*
* Buttons released: 06 00 00 00 00 00 00 00
* Button 1: 06 00 05 00 00 00 00 00 -> b
* Button 2: 06 00 08 00 00 00 00 00 -> e
* Button 3: 06 04 00 00 00 00 00 00 -> LAlt
* Button 4: 06 00 2c 00 00 00 00 00 -> Space
* Button 5: 06 01 16 00 00 00 00 00 -> LControl + s
* Button 6: 06 01 1d 00 00 00 00 00 -> LControl + z
*
* When multiple buttons are pressed at the same time, the values used to
* identify the buttons are identical, but they appear in different bytes of the
* record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
* this is the report:
*
* Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b
*
* Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
* report.
*
* Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
* and 5 generates this report:
*
* Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s
* -- --
* | |
* | `- Button 5 (0x16)
* `- 0x05 = 0101. Button 3 is pressed
* ^
*
* pad_buttons contains a list of buttons that can be matched in
* HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
*/
static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2C, 0x16, 0x1D };
static const __u8 fixed_pad_rdesc[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x07, /* Usage (Keypad), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x06, /* Report ID (6), */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x39, /* Usage (Tablet Function Keys), */
0xA0, /* Collection (Physical), */
0x05, 0x09, /* Usage Page (Button), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x06, /* Report Count (6), */
0x19, 0x01, /* Usage Minimum (01h), */
0x29, 0x06, /* Usage Maximum (06h), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x32, /* Report Count (50), */
0x81, 0x01, /* Input (Constant), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
static const __u8 fixed_pen_rdesc[] = {
0x05, 0x0d, /* Usage Page (Digitizers), */
0x09, 0x01, /* Usage (Digitizer), */
0xa1, 0x01, /* Collection (Application), */
0x85, 0x07, /* Report ID (7), */
0x09, 0x20, /* Usage (Stylus), */
0xa1, 0x00, /* Collection (Physical), */
0x09, 0x42, /* Usage (Tip Switch), */
0x09, 0x44, /* Usage (Barrel Switch), */
0x09, 0x46, /* Usage (Tablet Pick), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x03, /* Report Count (3), */
0x14, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x81, 0x03, /* Input (Constant, Variable), */
0x09, 0x32, /* Usage (In Range), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x95, 0x02, /* Report Count (2), */
0x81, 0x03, /* Input (Constant, Variable), */
0x75, 0x10, /* Report Size (16), */
0x95, 0x01, /* Report Count (1), */
0x35, 0x00, /* Physical Minimum (0), */
0xa4, /* Push, */
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x30, /* Usage (X), */
0x65, 0x13, /* Unit (Inch), */
0x55, 0x0d, /* Unit Exponent (-3), */
0x26, LOGICAL_MAX_X, /* Logical Maximum, */
0x46, PHYSICAL_MAX_X, /* Physical Maximum, */
0x81, 0x02, /* Input (Variable), */
0x09, 0x31, /* Usage (Y), */
0x26, LOGICAL_MAX_Y, /* Logical Maximum, */
0x46, PHYSICAL_MAX_Y, /* Physical Maximum, */
0x81, 0x02, /* Input (Variable), */
0xb4, /* Pop, */
0x09, 0x30, /* Usage (Tip Pressure), */
0x45, 0x00, /* Physical Maximum (0), */
0x26, PRESSURE_MAX, /* Logical Maximum, */
0x75, 0x0D, /* Report Size (13), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x13, /* Report Count (19), */
0x81, 0x01, /* Input (Constant), */
0xc0, /* End Collection, */
0xc0, /* End Collection */
};
static const size_t fixed_pad_rdesc_size = sizeof(fixed_pad_rdesc);
static const size_t fixed_pen_rdesc_size = sizeof(fixed_pen_rdesc);
SEC(HID_BPF_RDESC_FIXUP)
int BPF_PROG(hid_rdesc_fixup_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
if (!data)
return 0; /* EPERM check */
if (hctx->size == RDESC_SIZE_PAD) {
__builtin_memcpy(data, fixed_pad_rdesc, fixed_pad_rdesc_size);
return fixed_pad_rdesc_size;
} else if (hctx->size == RDESC_SIZE_PEN) {
__builtin_memcpy(data, fixed_pen_rdesc, fixed_pen_rdesc_size);
return fixed_pen_rdesc_size;
}
return 0;
}
SEC(HID_BPF_DEVICE_EVENT)
int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
__u8 button_mask = 0;
int d, b;
if (!data)
return 0; /* EPERM check */
if (data[0] != PAD_REPORT_ID)
return 0;
/* data[1] stores the status of BTN_2 in the 3rd bit*/
if (data[1] & BIT(2))
button_mask |= BIT(2);
/* The rest of the descriptor stores the buttons as in pad_buttons */
for (d = 2; d < 8; d++) {
for (b = 0; b < sizeof(pad_buttons); b++) {
if (data[d] != 0 && data[d] == pad_buttons[b])
button_mask |= BIT(b);
}
}
__u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
__builtin_memcpy(data, report, sizeof(report));
return 0;
}
HID_BPF_OPS(deco_mini_4) = {
.hid_device_event = (void *)hid_device_event_xppen_deco_mini_4,
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_xppen_deco_mini_4,
};
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/*
* The device has 2 modes: The compatibility mode, enabled by default,
* and the raw mode, that can be activated by sending a buffer of magic
* data to a certain USB endpoint.
*
* Depending on the mode, different interfaces of the device are used:
* - First interface: Pad in compatibility mode
* - Second interface: Pen in compatibility mode
* - Third interface: Only used in raw mode
*
* We'll use the device in compatibility mode.
*/
ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD &&
ctx->rdesc_size != RDESC_SIZE_PEN;
if (ctx->retval)
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
......@@ -5,6 +5,12 @@
#ifndef ____HID_BPF__H
#define ____HID_BPF__H
#define HID_BPF_DEVICE_EVENT "struct_ops/hid_device_event"
#define HID_BPF_RDESC_FIXUP "struct_ops/hid_rdesc_fixup"
#define HID_BPF_OPS(name) SEC(".struct_ops.link") \
struct hid_bpf_ops name
#define hid_set_name(_hdev, _name) __builtin_memcpy(_hdev->name, _name, sizeof(_name))
struct hid_bpf_probe_args {
unsigned int hid;
unsigned int rdesc_size;
......
......@@ -66,6 +66,7 @@ extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
#define HID_VID_ANY 0x0000
#define HID_PID_ANY 0x0000
#define BIT(n) (1UL << (n))
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2025,19 +2025,10 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
}
EXPORT_SYMBOL_GPL(hid_report_raw_event);
/**
* hid_input_report - report data from lower layer (usb, bt...)
*
* @hid: hid device
* @type: HID report type (HID_*_REPORT)
* @data: report contents
* @size: size of data parameter
* @interrupt: distinguish between interrupt and control transfers
*
* This is data entry for lower layers.
*/
int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
int interrupt)
static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
bool lock_already_taken)
{
struct hid_report_enum *report_enum;
struct hid_driver *hdrv;
......@@ -2047,8 +2038,13 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
if (!hid)
return -ENODEV;
if (down_trylock(&hid->driver_input_lock))
ret = down_trylock(&hid->driver_input_lock);
if (lock_already_taken && !ret) {
up(&hid->driver_input_lock);
return -EINVAL;
} else if (!lock_already_taken && ret) {
return -EBUSY;
}
if (!hid->driver) {
ret = -ENODEV;
......@@ -2057,7 +2053,7 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
report_enum = hid->report_enum + type;
hdrv = hid->driver;
data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt);
data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto unlock;
......@@ -2089,9 +2085,29 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
ret = hid_report_raw_event(hid, type, data, size, interrupt);
unlock:
if (!lock_already_taken)
up(&hid->driver_input_lock);
return ret;
}
/**
* hid_input_report - report data from lower layer (usb, bt...)
*
* @hid: hid device
* @type: HID report type (HID_*_REPORT)
* @data: report contents
* @size: size of data parameter
* @interrupt: distinguish between interrupt and control transfers
*
* This is data entry for lower layers.
*/
int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
int interrupt)
{
return __hid_input_report(hid, type, data, size, interrupt, 0,
false, /* from_bpf */
false /* lock_already_taken */);
}
EXPORT_SYMBOL_GPL(hid_input_report);
bool hid_match_one_id(const struct hid_device *hdev,
......@@ -2392,6 +2408,30 @@ void hid_hw_request(struct hid_device *hdev,
}
EXPORT_SYMBOL_GPL(hid_hw_request);
int __hid_hw_raw_request(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype,
enum hid_class_request reqtype,
u64 source, bool from_bpf)
{
unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
int ret;
if (hdev->ll_driver->max_buffer_size)
max_buffer_size = hdev->ll_driver->max_buffer_size;
if (len < 1 || len > max_buffer_size || !buf)
return -EINVAL;
ret = dispatch_hid_bpf_raw_requests(hdev, reportnum, buf, len, rtype,
reqtype, source, from_bpf);
if (ret)
return ret;
return hdev->ll_driver->raw_request(hdev, reportnum, buf, len,
rtype, reqtype);
}
/**
* hid_hw_raw_request - send report request to device
*
......@@ -2409,8 +2449,16 @@ EXPORT_SYMBOL_GPL(hid_hw_request);
int hid_hw_raw_request(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype, enum hid_class_request reqtype)
{
return __hid_hw_raw_request(hdev, reportnum, buf, len, rtype, reqtype, 0, false);
}
EXPORT_SYMBOL_GPL(hid_hw_raw_request);
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, u64 source,
bool from_bpf)
{
unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
int ret;
if (hdev->ll_driver->max_buffer_size)
max_buffer_size = hdev->ll_driver->max_buffer_size;
......@@ -2418,10 +2466,15 @@ int hid_hw_raw_request(struct hid_device *hdev,
if (len < 1 || len > max_buffer_size || !buf)
return -EINVAL;
return hdev->ll_driver->raw_request(hdev, reportnum, buf, len,
rtype, reqtype);
ret = dispatch_hid_bpf_output_report(hdev, buf, len, source, from_bpf);
if (ret)
return ret;
if (hdev->ll_driver->output_report)
return hdev->ll_driver->output_report(hdev, buf, len);
return -ENOSYS;
}
EXPORT_SYMBOL_GPL(hid_hw_raw_request);
/**
* hid_hw_output_report - send output report to device
......@@ -2434,18 +2487,7 @@ EXPORT_SYMBOL_GPL(hid_hw_raw_request);
*/
int hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len)
{
unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
if (hdev->ll_driver->max_buffer_size)
max_buffer_size = hdev->ll_driver->max_buffer_size;
if (len < 1 || len > max_buffer_size || !buf)
return -EINVAL;
if (hdev->ll_driver->output_report)
return hdev->ll_driver->output_report(hdev, buf, len);
return -ENOSYS;
return __hid_hw_output_report(hdev, buf, len, 0, false);
}
EXPORT_SYMBOL_GPL(hid_hw_output_report);
......@@ -2854,9 +2896,15 @@ struct hid_device *hid_allocate_device(void)
mutex_init(&hdev->ll_open_lock);
kref_init(&hdev->ref);
hid_bpf_device_init(hdev);
ret = hid_bpf_device_init(hdev);
if (ret)
goto out_err;
return hdev;
out_err:
hid_destroy_device(hdev);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(hid_allocate_device);
......@@ -2970,11 +3018,11 @@ int hid_check_keys_pressed(struct hid_device *hid)
EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
#ifdef CONFIG_HID_BPF
static struct hid_bpf_ops hid_ops = {
static struct hid_ops __hid_ops = {
.hid_get_report = hid_get_report,
.hid_hw_raw_request = hid_hw_raw_request,
.hid_hw_output_report = hid_hw_output_report,
.hid_input_report = hid_input_report,
.hid_hw_raw_request = __hid_hw_raw_request,
.hid_hw_output_report = __hid_hw_output_report,
.hid_input_report = __hid_input_report,
.owner = THIS_MODULE,
.bus_type = &hid_bus_type,
};
......@@ -2991,7 +3039,7 @@ static int __init hid_init(void)
}
#ifdef CONFIG_HID_BPF
hid_bpf_ops = &hid_ops;
hid_ops = &__hid_ops;
#endif
ret = hidraw_init();
......@@ -3010,7 +3058,7 @@ static int __init hid_init(void)
static void __exit hid_exit(void)
{
#ifdef CONFIG_HID_BPF
hid_bpf_ops = NULL;
hid_ops = NULL;
#endif
hid_debug_exit();
hidraw_exit();
......
......@@ -140,7 +140,7 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
if ((report_type == HID_OUTPUT_REPORT) &&
!(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
ret = hid_hw_output_report(dev, buf, count);
ret = __hid_hw_output_report(dev, buf, count, (u64)(long)file, false);
/*
* compatibility with old implementation of USB-HID and I2C-HID:
* if the device does not support receiving output reports,
......@@ -150,8 +150,8 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
goto out_free;
}
ret = hid_hw_raw_request(dev, buf[0], buf, count, report_type,
HID_REQ_SET_REPORT);
ret = __hid_hw_raw_request(dev, buf[0], buf, count, report_type,
HID_REQ_SET_REPORT, (u64)(long)file, false);
out_free:
kfree(buf);
......@@ -227,8 +227,8 @@ static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t
goto out_free;
}
ret = hid_hw_raw_request(dev, report_number, buf, count, report_type,
HID_REQ_GET_REPORT);
ret = __hid_hw_raw_request(dev, report_number, buf, count, report_type,
HID_REQ_GET_REPORT, (u64)(long)file, false);
if (ret < 0)
goto out_free;
......
......@@ -1125,6 +1125,13 @@ int __must_check hid_hw_open(struct hid_device *hdev);
void hid_hw_close(struct hid_device *hdev);
void hid_hw_request(struct hid_device *hdev,
struct hid_report *report, enum hid_class_request reqtype);
int __hid_hw_raw_request(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype,
enum hid_class_request reqtype,
__u64 source, bool from_bpf);
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source,
bool from_bpf);
int hid_hw_raw_request(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype,
......
......@@ -4,7 +4,8 @@
#define __HID_BPF_H
#include <linux/bpf.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/srcu.h>
#include <uapi/linux/hid.h>
struct hid_device;
......@@ -20,15 +21,9 @@ struct hid_device;
* struct hid_bpf_ctx - User accessible data for all HID programs
*
* ``data`` is not directly accessible from the context. We need to issue
* a call to ``hid_bpf_get_data()`` in order to get a pointer to that field.
* a call to hid_bpf_get_data() in order to get a pointer to that field.
*
* All of these fields are currently read-only.
*
* @index: program index in the jump table. No special meaning (a smaller index
* doesn't mean the program will be executed before another program with
* a bigger index).
* @hid: the ``struct hid_device`` representing the device itself
* @report_type: used for ``hid_bpf_device_event()``
* @hid: the &struct hid_device representing the device itself
* @allocated_size: Allocated size of data.
*
* This is how much memory is available and can be requested
......@@ -45,76 +40,147 @@ struct hid_device;
* ``size`` must always be less or equal than ``allocated_size`` (it is enforced
* once all BPF programs have been run).
* @retval: Return value of the previous program.
*
* ``hid`` and ``allocated_size`` are read-only, ``size`` and ``retval`` are read-write.
*/
struct hid_bpf_ctx {
__u32 index;
const struct hid_device *hid;
struct hid_device *hid;
__u32 allocated_size;
enum hid_report_type report_type;
union {
__s32 retval;
__s32 size;
};
};
/**
* enum hid_bpf_attach_flags - flags used when attaching a HIF-BPF program
*
* @HID_BPF_FLAG_NONE: no specific flag is used, the kernel choses where to
* insert the program
* @HID_BPF_FLAG_INSERT_HEAD: insert the given program before any other program
* currently attached to the device. This doesn't
* guarantee that this program will always be first
* @HID_BPF_FLAG_MAX: sentinel value, not to be used by the callers
*/
enum hid_bpf_attach_flags {
HID_BPF_FLAG_NONE = 0,
HID_BPF_FLAG_INSERT_HEAD = _BITUL(0),
HID_BPF_FLAG_MAX,
};
/* Following functions are tracepoints that BPF programs can attach to */
int hid_bpf_device_event(struct hid_bpf_ctx *ctx);
int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx);
/*
* Below is HID internal
*/
/* internal function to call eBPF programs, not to be used by anybody */
int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx);
#define HID_BPF_MAX_PROGS_PER_DEV 64
#define HID_BPF_FLAG_MASK (((HID_BPF_FLAG_MAX - 1) << 1) - 1)
/* types of HID programs to attach to */
enum hid_bpf_prog_type {
HID_BPF_PROG_TYPE_UNDEF = -1,
HID_BPF_PROG_TYPE_DEVICE_EVENT, /* an event is emitted from the device */
HID_BPF_PROG_TYPE_RDESC_FIXUP,
HID_BPF_PROG_TYPE_MAX,
};
struct hid_report_enum;
struct hid_bpf_ops {
struct hid_ops {
struct hid_report *(*hid_get_report)(struct hid_report_enum *report_enum, const u8 *data);
int (*hid_hw_raw_request)(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
size_t len, enum hid_report_type rtype,
enum hid_class_request reqtype);
int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len);
enum hid_class_request reqtype,
u64 source, bool from_bpf);
int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len,
u64 source, bool from_bpf);
int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type,
u8 *data, u32 size, int interrupt);
u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
bool lock_already_taken);
struct module *owner;
const struct bus_type *bus_type;
};
extern struct hid_bpf_ops *hid_bpf_ops;
extern struct hid_ops *hid_ops;
/**
* struct hid_bpf_ops - A BPF struct_ops of callbacks allowing to attach HID-BPF
* programs to a HID device
* @hid_id: the HID uniq ID to attach to. This is writeable before ``load()``, and
* cannot be changed after
* @flags: flags used while attaching the struct_ops to the device. Currently only
* available value is %0 or ``BPF_F_BEFORE``.
* Writeable only before ``load()``
*/
struct hid_bpf_ops {
/* hid_id needs to stay first so we can easily change it
* from userspace.
*/
int hid_id;
u32 flags;
/* private: do not show up in the docs */
struct list_head list;
/* public: rest should show up in the docs */
/**
* @hid_device_event: called whenever an event is coming in from the device
*
* It has the following arguments:
*
* ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
*
* Return: %0 on success and keep processing; a positive
* value to change the incoming size buffer; a negative
* error code to interrupt the processing of this event
*
* Context: Interrupt context.
*/
int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type,
u64 source);
/**
* @hid_rdesc_fixup: called when the probe function parses the report descriptor
* of the HID device
*
* It has the following arguments:
*
* ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
*
* Return: %0 on success and keep processing; a positive
* value to change the incoming size buffer; a negative
* error code to interrupt the processing of this device
*/
int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx);
/**
* @hid_hw_request: called whenever a hid_hw_raw_request() call is emitted
* on the HID device
*
* It has the following arguments:
*
* ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
*
* ``reportnum``: the report number, as in hid_hw_raw_request()
*
* ``rtype``: the report type (``HID_INPUT_REPORT``, ``HID_FEATURE_REPORT``,
* ``HID_OUTPUT_REPORT``)
*
* ``reqtype``: the request
*
* ``source``: a u64 referring to a uniq but identifiable source. If %0, the
* kernel itself emitted that call. For hidraw, ``source`` is set
* to the associated ``struct file *``.
*
* Return: %0 to keep processing the request by hid-core; any other value
* stops hid-core from processing that event. A positive value should be
* returned with the number of bytes returned in the incoming buffer; a
* negative error code interrupts the processing of this call.
*/
int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype,
u64 source);
/**
* @hid_hw_output_report: called whenever a hid_hw_output_report() call is emitted
* on the HID device
*
* It has the following arguments:
*
* ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
*
* ``source``: a u64 referring to a uniq but identifiable source. If %0, the
* kernel itself emitted that call. For hidraw, ``source`` is set
* to the associated ``struct file *``.
*
* Return: %0 to keep processing the request by hid-core; any other value
* stops hid-core from processing that event. A positive value should be
* returned with the number of bytes written to the device; a negative error
* code interrupts the processing of this call.
*/
int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
struct hid_bpf_prog_list {
u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV];
u8 prog_cnt;
/* private: do not show up in the docs */
struct hid_device *hdev;
};
/* stored in each device */
......@@ -124,34 +190,44 @@ struct hid_bpf {
* to this HID device
*/
u32 allocated_data;
struct hid_bpf_prog_list __rcu *progs[HID_BPF_PROG_TYPE_MAX]; /* attached BPF progs */
bool destroyed; /* prevents the assignment of any progs */
spinlock_t progs_lock; /* protects RCU update of progs */
};
/* specific HID-BPF link when a program is attached to a device */
struct hid_bpf_link {
struct bpf_link link;
int hid_table_index;
struct hid_bpf_ops *rdesc_ops;
struct list_head prog_list;
struct mutex prog_list_lock; /* protects prog_list update */
struct srcu_struct srcu; /* protects prog_list read-only access */
};
#ifdef CONFIG_HID_BPF
u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
u32 *size, int interrupt);
u32 *size, int interrupt, u64 source, bool from_bpf);
int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
unsigned char reportnum, __u8 *buf,
u32 size, enum hid_report_type rtype,
enum hid_class_request reqtype,
u64 source, bool from_bpf);
int dispatch_hid_bpf_output_report(struct hid_device *hdev, __u8 *buf, u32 size,
u64 source, bool from_bpf);
int hid_bpf_connect_device(struct hid_device *hdev);
void hid_bpf_disconnect_device(struct hid_device *hdev);
void hid_bpf_destroy_device(struct hid_device *hid);
void hid_bpf_device_init(struct hid_device *hid);
int hid_bpf_device_init(struct hid_device *hid);
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
#else /* CONFIG_HID_BPF */
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
u8 *data, u32 *size, int interrupt) { return data; }
u8 *data, u32 *size, int interrupt,
u64 source, bool from_bpf) { return data; }
static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
unsigned char reportnum, u8 *buf,
u32 size, enum hid_report_type rtype,
enum hid_class_request reqtype,
u64 source, bool from_bpf) { return 0; }
static inline int dispatch_hid_bpf_output_report(struct hid_device *hdev, __u8 *buf, u32 size,
u64 source, bool from_bpf) { return 0; }
static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
static inline void hid_bpf_device_init(struct hid_device *hid) {}
static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
#define call_hid_bpf_rdesc_fixup(_hdev, _rdesc, _size) \
((u8 *)kmemdup(_rdesc, *(_size), GFP_KERNEL))
......
......@@ -16,7 +16,6 @@ LIBBPF_DESTDIR = $(LIBBPF_OUTPUT)
LIBBPF_INCLUDE = $(LIBBPF_DESTDIR)/include
LIBBPF = $(LIBBPF_OUTPUT)/libbpf.a
EXTRA_HEADERS := hid_bpf_attach.h
EXTRA_BPF_HEADERS := hid_bpf_helpers.h
hid_mouse-objs := hid_mouse.o
......@@ -207,8 +206,8 @@ $(obj)/%.bpf.o: $(src)/%.bpf.c $(EXTRA_BPF_HEADERS_SRC) $(obj)/vmlinux.h
LINKED_SKELS := hid_mouse.skel.h hid_surface_dial.skel.h
clean-files += $(LINKED_SKELS)
hid_mouse.skel.h-deps := hid_mouse.bpf.o hid_bpf_attach.bpf.o
hid_surface_dial.skel.h-deps := hid_surface_dial.bpf.o hid_bpf_attach.bpf.o
hid_mouse.skel.h-deps := hid_mouse.bpf.o
hid_surface_dial.skel.h-deps := hid_surface_dial.bpf.o
LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.bpf.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps)))
......
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2022 Benjamin Tissoires
*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "hid_bpf_attach.h"
#include "hid_bpf_helpers.h"
SEC("syscall")
int attach_prog(struct attach_prog_args *ctx)
{
ctx->retval = hid_bpf_attach_prog(ctx->hid,
ctx->prog_fd,
0);
return 0;
}
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Benjamin Tissoires
*/
#ifndef __HID_BPF_ATTACH_H
#define __HID_BPF_ATTACH_H
struct attach_prog_args {
int prog_fd;
unsigned int hid;
int retval;
};
#endif /* __HID_BPF_ATTACH_H */
......@@ -5,8 +5,7 @@
#include <bpf/bpf_tracing.h>
#include "hid_bpf_helpers.h"
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_y_event, struct hid_bpf_ctx *hctx)
static int hid_y_event(struct hid_bpf_ctx *hctx)
{
s16 y;
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
......@@ -51,8 +50,7 @@ int BPF_PROG(hid_y_event, struct hid_bpf_ctx *hctx)
return 0;
}
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_x_event, struct hid_bpf_ctx *hctx)
static int hid_x_event(struct hid_bpf_ctx *hctx)
{
s16 x;
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
......@@ -69,7 +67,19 @@ int BPF_PROG(hid_x_event, struct hid_bpf_ctx *hctx)
return 0;
}
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC("struct_ops/hid_device_event")
int BPF_PROG(hid_event, struct hid_bpf_ctx *hctx, enum hid_report_type type)
{
int ret = hid_y_event(hctx);
if (ret)
return ret;
return hid_x_event(hctx);
}
SEC("struct_ops/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -109,4 +119,10 @@ int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops mouse_invert = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
.hid_device_event = (void *)hid_event,
};
char _license[] SEC("license") = "GPL";
......@@ -29,7 +29,6 @@
#include <bpf/libbpf.h>
#include "hid_mouse.skel.h"
#include "hid_bpf_attach.h"
static bool running = true;
......@@ -76,18 +75,11 @@ static int get_hid_id(const char *path)
int main(int argc, char **argv)
{
struct hid_mouse *skel;
struct bpf_program *prog;
struct bpf_link *link;
int err;
const char *optstr = "";
const char *sysfs_path;
int opt, hid_id, attach_fd;
struct attach_prog_args args = {
.retval = -1,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
int opt, hid_id;
while ((opt = getopt(argc, argv, optstr)) != -1) {
switch (opt) {
......@@ -108,7 +100,7 @@ int main(int argc, char **argv)
return 1;
}
skel = hid_mouse__open_and_load();
skel = hid_mouse__open();
if (!skel) {
fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__);
return -1;
......@@ -120,28 +112,19 @@ int main(int argc, char **argv)
fprintf(stderr, "can not open HID device: %m\n");
return 1;
}
args.hid = hid_id;
skel->struct_ops.mouse_invert->hid_id = hid_id;
attach_fd = bpf_program__fd(skel->progs.attach_prog);
if (attach_fd < 0) {
fprintf(stderr, "can't locate attach prog: %m\n");
err = hid_mouse__load(skel);
if (err < 0) {
fprintf(stderr, "can not load HID-BPF program: %m\n");
return 1;
}
bpf_object__for_each_program(prog, *skel->skeleton->obj) {
/* ignore syscalls */
if (bpf_program__get_type(prog) != BPF_PROG_TYPE_TRACING)
continue;
args.retval = -1;
args.prog_fd = bpf_program__fd(prog);
err = bpf_prog_test_run_opts(attach_fd, &tattr);
if (err) {
fprintf(stderr, "can't attach prog to hid device %d: %m (err: %d)\n",
hid_id, err);
link = bpf_map__attach_struct_ops(skel->maps.mouse_invert);
if (!link) {
fprintf(stderr, "can not attach HID-BPF program: %m\n");
return 1;
}
}
signal(SIGINT, int_exit);
signal(SIGTERM, int_exit);
......
......@@ -10,7 +10,7 @@
#define HID_UP_BUTTON 0x0009
#define HID_GD_WHEEL 0x0038
SEC("fmod_ret/hid_bpf_device_event")
SEC("struct_ops/hid_device_event")
int BPF_PROG(hid_event, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
......@@ -101,7 +101,7 @@ int set_haptic(struct haptic_syscall_args *args)
}
/* Convert REL_DIAL into REL_WHEEL */
SEC("fmod_ret/hid_bpf_rdesc_fixup")
SEC("struct_ops/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
......@@ -130,5 +130,11 @@ int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops surface_dial = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
.hid_device_event = (void *)hid_event,
};
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = 1;
......@@ -31,7 +31,6 @@
#include <bpf/libbpf.h>
#include "hid_surface_dial.skel.h"
#include "hid_bpf_attach.h"
static bool running = true;
......@@ -86,34 +85,6 @@ static int get_hid_id(const char *path)
return (int)strtol(str_id, NULL, 16);
}
static int attach_prog(struct hid_surface_dial *skel, struct bpf_program *prog, int hid_id)
{
struct attach_prog_args args = {
.hid = hid_id,
.retval = -1,
};
int attach_fd, err;
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
attach_fd = bpf_program__fd(skel->progs.attach_prog);
if (attach_fd < 0) {
fprintf(stderr, "can't locate attach prog: %m\n");
return 1;
}
args.prog_fd = bpf_program__fd(prog);
err = bpf_prog_test_run_opts(attach_fd, &tattr);
if (err) {
fprintf(stderr, "can't attach prog to hid device %d: %m (err: %d)\n",
hid_id, err);
return 1;
}
return 0;
}
static int set_haptic(struct hid_surface_dial *skel, int hid_id)
{
struct haptic_syscall_args args = {
......@@ -144,10 +115,10 @@ static int set_haptic(struct hid_surface_dial *skel, int hid_id)
int main(int argc, char **argv)
{
struct hid_surface_dial *skel;
struct bpf_program *prog;
const char *optstr = "r:";
struct bpf_link *link;
const char *sysfs_path;
int opt, hid_id, resolution = 72;
int err, opt, hid_id, resolution = 72;
while ((opt = getopt(argc, argv, optstr)) != -1) {
switch (opt) {
......@@ -189,7 +160,7 @@ int main(int argc, char **argv)
return 1;
}
skel = hid_surface_dial__open_and_load();
skel = hid_surface_dial__open();
if (!skel) {
fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__);
return -1;
......@@ -201,15 +172,21 @@ int main(int argc, char **argv)
return 1;
}
skel->struct_ops.surface_dial->hid_id = hid_id;
err = hid_surface_dial__load(skel);
if (err < 0) {
fprintf(stderr, "can not load HID-BPF program: %m\n");
return 1;
}
skel->data->resolution = resolution;
skel->data->physical = (int)(resolution / 72);
bpf_object__for_each_program(prog, *skel->skeleton->obj) {
/* ignore syscalls */
if (bpf_program__get_type(prog) != BPF_PROG_TYPE_TRACING)
continue;
attach_prog(skel, prog, hid_id);
link = bpf_map__attach_struct_ops(skel->maps.surface_dial);
if (!link) {
fprintf(stderr, "can not attach HID-BPF program: %m\n");
return 1;
}
signal(SIGINT, int_exit);
......
......@@ -460,7 +460,7 @@ FIXTURE(hid_bpf) {
int hid_id;
pthread_t tid;
struct hid *skel;
int hid_links[3]; /* max number of programs loaded in a single test */
struct bpf_link *hid_links[3]; /* max number of programs loaded in a single test */
};
static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
{
......@@ -470,9 +470,14 @@ static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
close(self->hidraw_fd);
self->hidraw_fd = 0;
if (!self->skel)
return;
hid__detach(self->skel);
for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) {
if (self->hid_links[i])
close(self->hid_links[i]);
bpf_link__destroy(self->hid_links[i]);
}
hid__destroy(self->skel);
......@@ -527,14 +532,7 @@ static void load_programs(const struct test_program programs[],
FIXTURE_DATA(hid_bpf) * self,
const FIXTURE_VARIANT(hid_bpf) * variant)
{
int attach_fd, err = -EINVAL;
struct attach_prog_args args = {
.retval = -1,
};
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
.ctx_in = &args,
.ctx_size_in = sizeof(args),
);
int err = -EINVAL;
ASSERT_LE(progs_count, ARRAY_SIZE(self->hid_links))
TH_LOG("too many programs are to be loaded");
......@@ -545,37 +543,45 @@ static void load_programs(const struct test_program programs[],
for (int i = 0; i < progs_count; i++) {
struct bpf_program *prog;
struct bpf_map *map;
int *ops_hid_id;
prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj,
programs[i].name);
ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'", programs[i].name);
bpf_program__set_autoload(prog, true);
map = bpf_object__find_map_by_name(*self->skel->skeleton->obj,
programs[i].name + 4);
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
/* hid_id is the first field of struct hid_bpf_ops */
ops_hid_id = bpf_map__initial_value(map, NULL);
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
*ops_hid_id = self->hid_id;
}
err = hid__load(self->skel);
ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d", err);
attach_fd = bpf_program__fd(self->skel->progs.attach_prog);
ASSERT_GE(attach_fd, 0) TH_LOG("locate attach_prog: %d", attach_fd);
for (int i = 0; i < progs_count; i++) {
struct bpf_program *prog;
struct bpf_map *map;
prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj,
programs[i].name);
ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'", programs[i].name);
args.prog_fd = bpf_program__fd(prog);
args.hid = self->hid_id;
args.insert_head = programs[i].insert_head;
err = bpf_prog_test_run_opts(attach_fd, &tattr);
ASSERT_GE(args.retval, 0)
TH_LOG("attach_hid(%s): %d", programs[i].name, args.retval);
map = bpf_object__find_map_by_name(*self->skel->skeleton->obj,
programs[i].name + 4);
ASSERT_OK_PTR(map) TH_LOG("can not find struct_ops by name '%s'",
programs[i].name + 4);
self->hid_links[i] = args.retval;
self->hid_links[i] = bpf_map__attach_struct_ops(map);
ASSERT_OK_PTR(self->hid_links[i]) TH_LOG("failed to attach struct ops '%s'",
programs[i].name + 4);
}
hid__attach(self->skel);
self->hidraw_fd = open_hidraw(self->dev_id);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
}
......@@ -639,6 +645,47 @@ TEST_F(hid_bpf, raw_event)
ASSERT_EQ(buf[2], 52);
}
/*
* 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
*/
TEST_F(hid_bpf, subprog_raw_event)
{
const struct test_program progs[] = {
{ .name = "hid_subprog_first_event" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[2], 47);
/* inject another event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
buf[1] = 47;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[2], 52);
}
/*
* Ensures that we can attach/detach programs
*/
......@@ -648,13 +695,17 @@ TEST_F(hid_bpf, test_attach_detach)
{ .name = "hid_first_event" },
{ .name = "hid_second_event" },
};
struct bpf_link *link;
__u8 buf[10] = {0};
int err, link;
int err, link_fd;
LOAD_PROGRAMS(progs);
link = self->hid_links[0];
ASSERT_GT(link, 0) TH_LOG("HID-BPF link not created");
ASSERT_OK_PTR(link) TH_LOG("HID-BPF link not created");
link_fd = bpf_link__fd(link);
ASSERT_GE(link_fd, 0) TH_LOG("HID-BPF link FD not valid");
/* inject one event */
buf[0] = 1;
......@@ -673,7 +724,7 @@ TEST_F(hid_bpf, test_attach_detach)
/* pin the first program and immediately unpin it */
#define PIN_PATH "/sys/fs/bpf/hid_first_event"
err = bpf_obj_pin(link, PIN_PATH);
err = bpf_obj_pin(link_fd, PIN_PATH);
ASSERT_OK(err) TH_LOG("error while calling bpf_obj_pin");
remove(PIN_PATH);
#undef PIN_PATH
......@@ -875,6 +926,325 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call)
ASSERT_EQ(args.data[1], 2);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is called and prevents the
* call to uhid.
*/
TEST_F(hid_bpf, test_hid_filter_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_filter_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* first check that we did not attach to device_event */
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
/* now check that our program is preventing hid_hw_raw_request() */
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_LT(err, 0) TH_LOG("unexpected success while reading HIDIOCGFEATURE: %d", err);
ASSERT_EQ(errno, 20) TH_LOG("unexpected error code while reading HIDIOCGFEATURE: %d",
errno);
/* remove our bpf program and check that we can now emit commands */
/* detach the program */
detach_bpf(self);
self->hidraw_fd = open_hidraw(self->dev_id);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGFEATURE: %d", err);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is called and can issue the call
* to uhid and transform the answer.
*/
TEST_F(hid_bpf, test_hid_change_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_hidraw_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err);
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 3);
ASSERT_EQ(buf[2], 4);
}
/*
* Call hid_hw_raw_request against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_raw_request_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_raw_request" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_raw_request from hidraw */
/* Get Feature */
memset(buf, 0, sizeof(buf));
buf[0] = 0x1; /* Report Number */
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
ASSERT_EQ(err, 3) TH_LOG("unexpected returned size while reading HIDIOCGFEATURE: %d", err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is called and prevents the
* call to uhid.
*/
TEST_F(hid_bpf, test_hid_filter_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_filter_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* first check that we did not attach to device_event */
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 42);
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
/* now check that our program is preventing hid_hw_output_report() */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 3);
ASSERT_LT(err, 0) TH_LOG("unexpected success while sending hid_hw_output_report: %d", err);
ASSERT_EQ(errno, 25) TH_LOG("unexpected error code while sending hid_hw_output_report: %d",
errno);
/* remove our bpf program and check that we can now emit commands */
/* detach the program */
detach_bpf(self);
self->hidraw_fd = open_hidraw(self->dev_id);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
err = write(self->hidraw_fd, buf, 3);
ASSERT_GE(err, 0) TH_LOG("error while sending hid_hw_output_report: %d", err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is called and can issue the call
* to uhid and transform the answer.
*/
TEST_F(hid_bpf, test_hid_change_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_hidraw_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 10);
ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d",
err);
}
/*
* Call hid_hw_output_report against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_output_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_output_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
err = write(self->hidraw_fd, buf, 8);
ASSERT_EQ(err, 2) TH_LOG("unexpected returned size while sending hid_hw_output_report: %d",
err);
}
/*
* Attach hid_multiply_event_wq 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 add extra data
*/
TEST_F(hid_bpf, test_multiply_events_wq)
{
const struct test_program progs[] = {
{ .name = "hid_test_multiply_events_wq" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 47);
usleep(100000);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 3);
}
/*
* Attach hid_multiply_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 add extra data
*/
TEST_F(hid_bpf, test_multiply_events)
{
const struct test_program progs[] = {
{ .name = "hid_test_multiply_events" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 47);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 9) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 2);
ASSERT_EQ(buf[1], 52);
}
/*
* Call hid_bpf_input_report against the given uhid device,
* check that the program is not making infinite loops.
*/
TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call)
{
const struct test_program progs[] = {
{ .name = "hid_test_infinite_loop_input_report" },
};
__u8 buf[10] = {0};
int err;
LOAD_PROGRAMS(progs);
/* emit hid_hw_output_report from hidraw */
buf[0] = 1; /* report ID */
buf[1] = 2;
buf[2] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
/* read the data from hidraw */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 3);
/* read the data from hidraw: hid_bpf_try_input_report should work exactly one time */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
ASSERT_EQ(buf[0], 1);
ASSERT_EQ(buf[1], 4);
/* read the data from hidraw: there should be none */
memset(buf, 0, sizeof(buf));
err = read(self->hidraw_fd, buf, sizeof(buf));
ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
}
/*
* Attach hid_insert{0,1,2} to the given uhid device,
* retrieve and open the matching hidraw node,
......
......@@ -14,8 +14,8 @@ struct attach_prog_args {
__u64 callback_check = 52;
__u64 callback2_check = 52;
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx)
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
......@@ -29,8 +29,38 @@ int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx)
return hid_ctx->size;
}
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx)
SEC(".struct_ops.link")
struct hid_bpf_ops first_event = {
.hid_device_event = (void *)hid_first_event,
.hid_id = 2,
};
int __hid_subprog_first_event(struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
if (!rw_data)
return 0; /* EPERM check */
rw_data[2] = rw_data[1] + 5;
return hid_ctx->size;
}
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_subprog_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
return __hid_subprog_first_event(hid_ctx, type);
}
SEC(".struct_ops.link")
struct hid_bpf_ops subprog_first_event = {
.hid_device_event = (void *)hid_subprog_first_event,
.hid_id = 2,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
......@@ -42,8 +72,13 @@ int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx)
return hid_ctx->size;
}
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx)
SEC(".struct_ops.link")
struct hid_bpf_ops second_event = {
.hid_device_event = (void *)hid_second_event,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
......@@ -55,15 +90,10 @@ int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx)
return 9;
}
SEC("syscall")
int attach_prog(struct attach_prog_args *ctx)
{
ctx->retval = hid_bpf_attach_prog(ctx->hid,
ctx->prog_fd,
ctx->insert_head ? HID_BPF_FLAG_INSERT_HEAD :
HID_BPF_FLAG_NONE);
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops change_report_id = {
.hid_device_event = (void *)hid_change_report_id,
};
struct hid_hw_request_syscall_args {
/* data needs to come at offset 0 so we can use it in calls */
......@@ -181,7 +211,12 @@ static const __u8 rdesc[] = {
0xc0, /* END_COLLECTION */
};
SEC("?fmod_ret/hid_bpf_rdesc_fixup")
/*
* the following program is marked as sleepable (struct_ops.s).
* This is not strictly mandatory but is a nice test for
* sleepable struct_ops
*/
SEC("?struct_ops.s/hid_rdesc_fixup")
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hid_ctx)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4096 /* size */);
......@@ -200,8 +235,13 @@ int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hid_ctx)
return sizeof(rdesc) + 73;
}
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx)
SEC(".struct_ops.link")
struct hid_bpf_ops rdesc_fixup = {
.hid_rdesc_fixup = (void *)hid_rdesc_fixup,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
......@@ -217,8 +257,14 @@ int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx)
return 0;
}
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx)
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert1 = {
.hid_device_event = (void *)hid_test_insert1,
.flags = BPF_F_BEFORE,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
......@@ -234,8 +280,13 @@ int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx)
return 0;
}
SEC("?fmod_ret/hid_bpf_device_event")
int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx)
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert2 = {
.hid_device_event = (void *)hid_test_insert2,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
......@@ -250,3 +301,300 @@ int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx)
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_insert3 = {
.hid_device_event = (void *)hid_test_insert3,
};
SEC("?struct_ops/hid_hw_request")
int BPF_PROG(hid_test_filter_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
return -20;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_filter_raw_request = {
.hid_hw_request = (void *)hid_test_filter_raw_request,
};
static struct file *current_file;
SEC("fentry/hidraw_open")
int BPF_PROG(hidraw_open, struct inode *inode, struct file *file)
{
current_file = file;
return 0;
}
SEC("?struct_ops.s/hid_hw_request")
int BPF_PROG(hid_test_hidraw_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* check if the incoming request comes from our hidraw operation */
if (source == (__u64)current_file) {
data[0] = reportnum;
ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype);
if (ret != 2)
return -1;
data[0] = reportnum + 1;
data[1] = reportnum + 2;
data[2] = reportnum + 3;
return 3;
}
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_hidraw_raw_request = {
.hid_hw_request = (void *)hid_test_hidraw_raw_request,
};
SEC("?struct_ops.s/hid_hw_request")
int BPF_PROG(hid_test_infinite_loop_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
*/
data[0] = reportnum;
ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype);
if (ret == 2)
return 3;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_raw_request = {
.hid_hw_request = (void *)hid_test_infinite_loop_raw_request,
};
SEC("?struct_ops/hid_hw_output_report")
int BPF_PROG(hid_test_filter_output_report, struct hid_bpf_ctx *hctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source)
{
return -25;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_filter_output_report = {
.hid_hw_output_report = (void *)hid_test_filter_output_report,
};
SEC("?struct_ops.s/hid_hw_output_report")
int BPF_PROG(hid_test_hidraw_output_report, struct hid_bpf_ctx *hctx, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* check if the incoming request comes from our hidraw operation */
if (source == (__u64)current_file)
return hid_bpf_hw_output_report(hctx, data, 2);
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_hidraw_output_report = {
.hid_hw_output_report = (void *)hid_test_hidraw_output_report,
};
SEC("?struct_ops.s/hid_hw_output_report")
int BPF_PROG(hid_test_infinite_loop_output_report, struct hid_bpf_ctx *hctx, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */);
int ret;
if (!data)
return 0; /* EPERM check */
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
*/
ret = hid_bpf_hw_output_report(hctx, data, 2);
if (ret == 2)
return 2;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_output_report = {
.hid_hw_output_report = (void *)hid_test_infinite_loop_output_report,
};
struct elem {
struct bpf_wq work;
};
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct elem);
} hmap SEC(".maps");
static int wq_cb_sleepable(void *map, int *key, struct bpf_wq *work)
{
__u8 buf[9] = {2, 3, 4, 5, 6, 7, 8, 9, 10};
struct hid_bpf_ctx *hid_ctx;
hid_ctx = hid_bpf_allocate_context(*key);
if (!hid_ctx)
return 0; /* EPERM check */
hid_bpf_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf));
hid_bpf_release_context(hid_ctx);
return 0;
}
static int test_inject_input_report_callback(int *key)
{
struct elem init = {}, *val;
struct bpf_wq *wq;
if (bpf_map_update_elem(&hmap, key, &init, 0))
return -1;
val = bpf_map_lookup_elem(&hmap, key);
if (!val)
return -2;
wq = &val->work;
if (bpf_wq_init(wq, &hmap, 0) != 0)
return -3;
if (bpf_wq_set_callback(wq, wq_cb_sleepable, 0))
return -4;
if (bpf_wq_start(wq, 0))
return -5;
return 0;
}
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_multiply_events_wq, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */);
int hid = hid_ctx->hid->id;
int ret;
if (!data)
return 0; /* EPERM check */
if (data[0] != 1)
return 0;
ret = test_inject_input_report_callback(&hid);
if (ret)
return ret;
data[1] += 5;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_multiply_events_wq = {
.hid_device_event = (void *)hid_test_multiply_events_wq,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_multiply_events, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type)
{
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */);
__u8 buf[9];
int ret;
if (!data)
return 0; /* EPERM check */
if (data[0] != 1)
return 0;
/*
* we have to use an intermediate buffer as hid_bpf_input_report
* will memset data to \0
*/
__builtin_memcpy(buf, data, sizeof(buf));
buf[0] = 2;
buf[1] += 5;
ret = hid_bpf_try_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf));
if (ret < 0)
return ret;
/*
* In real world we should reset the original buffer as data might be garbage now,
* but it actually now has the content of 'buf'
*/
data[1] += 5;
return 9;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_multiply_events = {
.hid_device_event = (void *)hid_test_multiply_events,
};
SEC("?struct_ops/hid_device_event")
int BPF_PROG(hid_test_infinite_loop_input_report, struct hid_bpf_ctx *hctx,
enum hid_report_type report_type, __u64 source)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 6 /* size */);
__u8 buf[6];
if (!data)
return 0; /* EPERM check */
/*
* we have to use an intermediate buffer as hid_bpf_input_report
* will memset data to \0
*/
__builtin_memcpy(buf, data, sizeof(buf));
/* always forward the request as-is to the device, hid-bpf should prevent
* infinite loops.
* the return value is ignored so the event is passing to userspace.
*/
hid_bpf_try_input_report(hctx, report_type, buf, sizeof(buf));
/* each time we process the event, we increment by one data[1]:
* after each successful call to hid_bpf_try_input_report, buf
* has been memcopied into data by the kernel.
*/
data[1] += 1;
return 0;
}
SEC(".struct_ops.link")
struct hid_bpf_ops test_infinite_loop_input_report = {
.hid_device_event = (void *)hid_test_infinite_loop_input_report,
};
......@@ -7,6 +7,7 @@
/* "undefine" structs and enums in vmlinux.h, because we "override" them below */
#define hid_bpf_ctx hid_bpf_ctx___not_used
#define hid_bpf_ops hid_bpf_ops___not_used
#define hid_report_type hid_report_type___not_used
#define hid_class_request hid_class_request___not_used
#define hid_bpf_attach_flags hid_bpf_attach_flags___not_used
......@@ -20,13 +21,11 @@
#define HID_REQ_SET_REPORT HID_REQ_SET_REPORT___not_used
#define HID_REQ_SET_IDLE HID_REQ_SET_IDLE___not_used
#define HID_REQ_SET_PROTOCOL HID_REQ_SET_PROTOCOL___not_used
#define HID_BPF_FLAG_NONE HID_BPF_FLAG_NONE___not_used
#define HID_BPF_FLAG_INSERT_HEAD HID_BPF_FLAG_INSERT_HEAD___not_used
#define HID_BPF_FLAG_MAX HID_BPF_FLAG_MAX___not_used
#include "vmlinux.h"
#undef hid_bpf_ctx
#undef hid_bpf_ops
#undef hid_report_type
#undef hid_class_request
#undef hid_bpf_attach_flags
......@@ -40,9 +39,6 @@
#undef HID_REQ_SET_REPORT
#undef HID_REQ_SET_IDLE
#undef HID_REQ_SET_PROTOCOL
#undef HID_BPF_FLAG_NONE
#undef HID_BPF_FLAG_INSERT_HEAD
#undef HID_BPF_FLAG_MAX
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
......@@ -57,10 +53,8 @@ enum hid_report_type {
};
struct hid_bpf_ctx {
__u32 index;
const struct hid_device *hid;
struct hid_device *hid;
__u32 allocated_size;
enum hid_report_type report_type;
union {
__s32 retval;
__s32 size;
......@@ -76,17 +70,28 @@ enum hid_class_request {
HID_REQ_SET_PROTOCOL = 0x0B,
};
enum hid_bpf_attach_flags {
HID_BPF_FLAG_NONE = 0,
HID_BPF_FLAG_INSERT_HEAD = _BITUL(0),
HID_BPF_FLAG_MAX,
struct hid_bpf_ops {
int hid_id;
u32 flags;
struct list_head list;
int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type,
u64 source);
int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx);
int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum,
enum hid_report_type rtype, enum hid_class_request reqtype,
u64 source);
int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source);
struct hid_device *hdev;
};
#ifndef BPF_F_BEFORE
#define BPF_F_BEFORE (1U << 3)
#endif
/* following are kfuncs exported by HID for HID-BPF */
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,
const size_t __sz) __ksym;
extern int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, u32 flags) __ksym;
extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
......@@ -100,5 +105,18 @@ extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx,
enum hid_report_type type,
__u8 *data,
size_t buf__sz) __ksym;
extern int hid_bpf_try_input_report(struct hid_bpf_ctx *ctx,
enum hid_report_type type,
__u8 *data,
size_t buf__sz) __ksym;
/* bpf_wq implementation */
extern int bpf_wq_init(struct bpf_wq *wq, void *p__map, unsigned int flags) __weak __ksym;
extern int bpf_wq_start(struct bpf_wq *wq, unsigned int flags) __weak __ksym;
extern int bpf_wq_set_callback_impl(struct bpf_wq *wq,
int (callback_fn)(void *map, int *key, struct bpf_wq *wq),
unsigned int flags__k, void *aux__ign) __ksym;
#define bpf_wq_set_callback(timer, cb, flags) \
bpf_wq_set_callback_impl(timer, cb, flags, NULL)
#endif /* __HID_BPF_HELPERS_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment