Merge branch 'for-6.3/hid-bpf' into for-linus
Initial support of HID-BPF (Benjamin Tissoires) The history is a little long for this series, as it was intended to be sent for v6.2. However some last minute issues forced us to postpone it to v6.3. Conflicts: * drivers/hid/i2c-hid/Kconfig: commitbf7660dab3
("HID: stop drivers from selecting CONFIG_HID") conflicts with commit2afac81dd1
("HID: fix I2C_HID not selected when I2C_HID_OF_ELAN is") the resolution is simple enough: just drop the "default" and "select" lines as the new commit from Arnd is doing
This commit is contained in:
commit
904e28c6de
|
@ -9,7 +9,7 @@ Currently ALPS HID driver supports U1 Touchpad device.
|
|||
U1 device basic information.
|
||||
|
||||
========== ======
|
||||
Vender ID 0x044E
|
||||
Vendor ID 0x044E
|
||||
Product ID 0x120B
|
||||
Version ID 0x0121
|
||||
========== ======
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=======
|
||||
HID-BPF
|
||||
=======
|
||||
|
||||
HID is a standard protocol for input devices but some devices may require
|
||||
custom tweaks, traditionally done with a kernel driver fix. Using the eBPF
|
||||
capabilities instead speeds up development and adds new capabilities to the
|
||||
existing HID interfaces.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
|
||||
When (and why) to use HID-BPF
|
||||
=============================
|
||||
|
||||
There are several use cases when using HID-BPF is better
|
||||
than standard kernel driver fix:
|
||||
|
||||
Dead zone of a joystick
|
||||
-----------------------
|
||||
|
||||
Assuming you have a joystick that is getting older, it is common to see it
|
||||
wobbling around its neutral point. This is usually filtered at the application
|
||||
level by adding a *dead zone* for this specific axis.
|
||||
|
||||
With HID-BPF, we can apply this filtering in the kernel directly so userspace
|
||||
does not get woken up when nothing else is happening on the input controller.
|
||||
|
||||
Of course, given that this dead zone is specific to an individual device, we
|
||||
can not create a generic fix for all of the same joysticks. Adding a custom
|
||||
kernel API for this (e.g. by adding a sysfs entry) does not guarantee this new
|
||||
kernel API will be broadly adopted and maintained.
|
||||
|
||||
HID-BPF allows the userspace program to load the program itself, ensuring we
|
||||
only load the custom API when we have a user.
|
||||
|
||||
Simple fixup of report descriptor
|
||||
---------------------------------
|
||||
|
||||
In the HID tree, half of the drivers only fix one key or one byte
|
||||
in the report descriptor. These fixes all require a kernel patch and the
|
||||
subsequent shepherding into a release, a long and painful process for users.
|
||||
|
||||
We can reduce this burden by providing an eBPF program instead. Once such a
|
||||
program has been verified by the user, we can embed the source code into the
|
||||
kernel tree and ship the eBPF program and load it directly instead of loading
|
||||
a specific kernel module for it.
|
||||
|
||||
Note: distribution of eBPF programs and their inclusion in the kernel is not
|
||||
yet fully implemented
|
||||
|
||||
Add a new feature that requires a new kernel API
|
||||
------------------------------------------------
|
||||
|
||||
An example for such a feature are the Universal Stylus Interface (USI) pens.
|
||||
Basically, USI pens require a new kernel API because there are new
|
||||
channels of communication that our HID and input stack do not support.
|
||||
Instead of using hidraw or creating new sysfs entries or ioctls, we can rely
|
||||
on eBPF to have the kernel API controlled by the consumer and to not
|
||||
impact the performances by waking up userspace every time there is an
|
||||
event.
|
||||
|
||||
Morph a device into something else and control that from userspace
|
||||
------------------------------------------------------------------
|
||||
|
||||
The kernel has a relatively static mapping of HID items to evdev bits.
|
||||
It cannot decide to dynamically transform a given device into something else
|
||||
as it does not have the required context and any such transformation cannot be
|
||||
undone (or even discovered) by userspace.
|
||||
|
||||
However, some devices are useless with that static way of defining devices. For
|
||||
example, the Microsoft Surface Dial is a pushbutton with haptic feedback that
|
||||
is barely usable as of today.
|
||||
|
||||
With eBPF, userspace can morph that device into a mouse, and convert the dial
|
||||
events into wheel events. Also, the userspace program can set/unset the haptic
|
||||
feedback depending on the context. For example, if a menu is visible on the
|
||||
screen we likely need to have a haptic click every 15 degrees. But when
|
||||
scrolling in a web page the user experience is better when the device emits
|
||||
events at the highest resolution.
|
||||
|
||||
Firewall
|
||||
--------
|
||||
|
||||
What if we want to prevent other users to access a specific feature of a
|
||||
device? (think a possibly broken firmware update entry point)
|
||||
|
||||
With eBPF, we can intercept any HID command emitted to the device and
|
||||
validate it or not.
|
||||
|
||||
This also allows to sync the state between the userspace and the
|
||||
kernel/bpf program because we can intercept any incoming command.
|
||||
|
||||
Tracing
|
||||
-------
|
||||
|
||||
The last usage is tracing events and all the fun we can do we BPF to summarize
|
||||
and analyze events.
|
||||
|
||||
Right now, tracing relies on hidraw. It works well except for a couple
|
||||
of issues:
|
||||
|
||||
1. if the driver doesn't export a hidraw node, we can't trace anything
|
||||
(eBPF will be a "god-mode" there, so this may raise some eyebrows)
|
||||
2. hidraw doesn't catch other processes' requests to the device, which
|
||||
means that we have cases where we need to add printks to the kernel
|
||||
to understand what is happening.
|
||||
|
||||
High-level view of HID-BPF
|
||||
==========================
|
||||
|
||||
The main idea behind HID-BPF is that it works at an array of bytes level.
|
||||
Thus, all of the parsing of the HID report and the HID report descriptor
|
||||
must be implemented in the userspace component that loads the eBPF
|
||||
program.
|
||||
|
||||
For example, in the dead zone joystick from above, knowing which fields
|
||||
in the data stream needs to be set to ``0`` needs to be computed by userspace.
|
||||
|
||||
A corollary of this is that HID-BPF doesn't know about the other subsystems
|
||||
available in the kernel. *You can not directly emit input event through the
|
||||
input API from eBPF*.
|
||||
|
||||
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.
|
||||
|
||||
Available types of programs
|
||||
===========================
|
||||
|
||||
HID-BPF is built "on top" of BPF, meaning that we use tracing 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
|
||||
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
|
||||
|
||||
A ``hid_bpf_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.
|
||||
|
||||
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
|
||||
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``
|
||||
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.
|
||||
|
||||
Developer API:
|
||||
==============
|
||||
|
||||
User API data structures available in programs:
|
||||
-----------------------------------------------
|
||||
|
||||
.. kernel-doc:: include/linux/hid_bpf.h
|
||||
|
||||
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
|
||||
|
||||
Available API that can be used in all HID-BPF programs:
|
||||
-------------------------------------------------------
|
||||
|
||||
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
|
||||
:functions: hid_bpf_get_data
|
||||
|
||||
Available API that can be used in syscall HID-BPF programs:
|
||||
-----------------------------------------------------------
|
||||
|
||||
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
|
||||
:functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_allocate_context hid_bpf_release_context
|
||||
|
||||
General overview of a HID-BPF program
|
||||
=====================================
|
||||
|
||||
Accessing the data attached to the context
|
||||
------------------------------------------
|
||||
|
||||
The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access
|
||||
it, a bpf program needs to first call :c:func:`hid_bpf_get_data`.
|
||||
|
||||
``offset`` can be any integer, but ``size`` needs to be constant, known at compile
|
||||
time.
|
||||
|
||||
This allows the following:
|
||||
|
||||
1. for a given device, if we know that the report length will always be of a certain value,
|
||||
we can request the ``data`` pointer to point at the full report length.
|
||||
|
||||
The kernel will ensure we are using a correct size and offset and eBPF will ensure
|
||||
the code will not attempt to read or write outside of the boundaries::
|
||||
|
||||
__u8 *data = hid_bpf_get_data(ctx, 0 /* offset */, 256 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* ensure data is correct, now the verifier knows we
|
||||
* have 256 bytes available */
|
||||
|
||||
bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]);
|
||||
|
||||
2. if the report length is variable, but we know the value of ``X`` is always a 16-bit
|
||||
integer, we can then have a pointer to that value only::
|
||||
|
||||
__u16 *x = hid_bpf_get_data(ctx, offset, sizeof(*x));
|
||||
|
||||
if (!x)
|
||||
return 0; /* something went wrong */
|
||||
|
||||
*x += 1; /* increment X by one */
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Note that if there are multiple programs using the ``HID_BPF_FLAG_INSERT_HEAD`` flag,
|
||||
only the most recently loaded one is actually the first in the list.
|
||||
|
||||
``SEC("fmod_ret/hid_bpf_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.
|
||||
|
||||
If a program changes the data associated with the context, the next one will see
|
||||
the modified data but it will have *no* idea of what the original data was.
|
||||
|
||||
Once all the programs are run and return ``0`` or a positive value, the rest of the
|
||||
HID stack will work on the modified data, with the ``size`` field of the last hid_bpf_ctx
|
||||
being the new size of the input stream of data.
|
||||
|
||||
A BPF program returning a negative error discards the event, i.e. this event will not be
|
||||
processed by the HID stack. Clients (hidraw, input, LEDs) will **not** see this event.
|
||||
|
||||
``SEC("syscall")``
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``syscall`` are not attached to a given device. To tell which device we are working
|
||||
with, userspace needs to refer to the device by its unique system id (the last 4 numbers
|
||||
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`
|
||||
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
|
||||
reports of the given device.
|
||||
|
||||
``SEC("fmod_ret/hid_bpf_rdesc_fixup")``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``hid_bpf_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
|
||||
``HID_MAX_DESCRIPTOR_SIZE`` (currently 4kB).
|
||||
|
||||
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.
|
||||
|
||||
In the same way, when the ``SEC("fmod_ret/hid_bpf_rdesc_fixup")`` program 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.
|
||||
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
|
||||
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)``.
|
||||
|
||||
``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``.
|
||||
|
||||
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
|
||||
even disable it, so that removes the tracing capabilities on those devices
|
||||
(where it is interesting to get the non-hidraw traces).
|
||||
|
||||
On the other hand, the ``hid_id`` is stable for the entire life of the HID device,
|
||||
even if we change its report descriptor.
|
||||
|
||||
Given that hidraw is not stable when the device disconnects/reconnects, we recommend
|
||||
accessing the current report descriptor of the device through the sysfs.
|
||||
This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a
|
||||
binary stream.
|
||||
|
||||
Parsing the report descriptor is the responsibility of the BPF programmer or the userspace
|
||||
component that loads the eBPF program.
|
||||
|
||||
An (almost) complete example of a BPF enhanced HID device
|
||||
=========================================================
|
||||
|
||||
*Foreword: for most parts, this could be implemented as a kernel driver*
|
||||
|
||||
Let's imagine we have a new tablet device that has some haptic capabilities
|
||||
to simulate the surface the user is scratching on. This device would also have
|
||||
a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall*
|
||||
and *brush on a painting canvas*. To make things even better, we can control the
|
||||
physical position of the switch through a feature report.
|
||||
|
||||
And of course, the switch is relying on some userspace component to control the
|
||||
haptic feature of the device itself.
|
||||
|
||||
Filtering events
|
||||
----------------
|
||||
|
||||
The first step consists in filtering events from the device. Given that the switch
|
||||
position is actually reported in the flow of the pen events, using hidraw to implement
|
||||
that filtering would mean that we wake up userspace for every single event.
|
||||
|
||||
This is OK for libinput, but having an external library that is just interested in
|
||||
one byte in the report is less than ideal.
|
||||
|
||||
For that, we can create a basic skeleton for our BPF program::
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
/* HID programs need to be GPL */
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
/* HID-BPF kfunc API definitions */
|
||||
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")
|
||||
int BPF_PROG(filter_switch, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 192 /* size */);
|
||||
__u8 *buf;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (current_value != data[152]) {
|
||||
buf = bpf_ringbuf_reserve(&ringbuf, 1, 0);
|
||||
if (!buf)
|
||||
return 0;
|
||||
|
||||
*buf = data[152];
|
||||
|
||||
bpf_ringbuf_commit(buf, 0);
|
||||
|
||||
current_value = data[152];
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
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);
|
||||
|
||||
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return args.retval; /* 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
|
||||
detach the program from the HID device.
|
||||
|
||||
Of course, in other use cases, the userspace program can also pin the fd to the
|
||||
BPF filesystem through a call to :c:func:`bpf_obj_pin`, as with any bpf_link.
|
||||
|
||||
Controlling the device
|
||||
----------------------
|
||||
|
||||
To be able to change the haptic feedback from the tablet, the userspace program
|
||||
needs to emit a feature report on the device itself.
|
||||
|
||||
Instead of using hidraw for that, we can create a ``SEC("syscall")`` program
|
||||
that talks to the device::
|
||||
|
||||
/* some more HID-BPF kfunc API definitions */
|
||||
extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
|
||||
extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
|
||||
extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
|
||||
__u8* data,
|
||||
size_t len,
|
||||
enum hid_report_type type,
|
||||
enum hid_class_request reqtype) __ksym;
|
||||
|
||||
|
||||
struct hid_send_haptics_args {
|
||||
/* data needs to come at offset 0 so we can do a memcpy into it */
|
||||
__u8 data[10];
|
||||
unsigned int hid;
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int send_haptic(struct hid_send_haptics_args *args)
|
||||
{
|
||||
struct hid_bpf_ctx *ctx;
|
||||
int ret = 0;
|
||||
|
||||
ctx = hid_bpf_allocate_context(args->hid);
|
||||
if (!ctx)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
ret = hid_bpf_hw_request(ctx,
|
||||
args->data,
|
||||
10,
|
||||
HID_FEATURE_REPORT,
|
||||
HID_REQ_SET_REPORT);
|
||||
|
||||
hid_bpf_release_context(ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
And then userspace needs to call that program directly::
|
||||
|
||||
static int set_haptic(struct hid *hid_skel, int hid_id, __u8 haptic_value)
|
||||
{
|
||||
int err, prog_fd;
|
||||
int ret = -1;
|
||||
struct hid_send_haptics_args args = {
|
||||
.hid = hid_id,
|
||||
};
|
||||
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
|
||||
.ctx_in = &args,
|
||||
.ctx_size_in = sizeof(args),
|
||||
);
|
||||
|
||||
args.data[0] = 0x02; /* report ID of the feature on our device */
|
||||
args.data[1] = haptic_value;
|
||||
|
||||
prog_fd = bpf_program__fd(hid_skel->progs.set_haptic);
|
||||
|
||||
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
|
||||
return err;
|
||||
}
|
||||
|
||||
Now our userspace program is aware of the haptic state and can control it. The
|
||||
program could make this state further available to other userspace programs
|
||||
(e.g. via a DBus API).
|
||||
|
||||
The interesting bit here is that we did not created a new kernel API for this.
|
||||
Which means that if there is a bug in our implementation, we can change the
|
||||
interface with the kernel at-will, because the userspace application is
|
||||
responsible for its own usage.
|
|
@ -8,7 +8,7 @@ Introduction
|
|||
In addition to the normal input type HID devices, USB also uses the
|
||||
human interface device protocols for things that are not really human
|
||||
interfaces, but have similar sorts of communication needs. The two big
|
||||
examples for this are power devices (especially uninterruptable power
|
||||
examples for this are power devices (especially uninterruptible power
|
||||
supplies) and monitor control on higher end monitors.
|
||||
|
||||
To support these disparate requirements, the Linux USB system provides
|
||||
|
|
|
@ -163,7 +163,7 @@ HIDIOCGOUTPUT(len):
|
|||
Get an Output Report
|
||||
|
||||
This ioctl will request an output report from the device using the control
|
||||
endpoint. Typically, this is used to retrive the initial state of
|
||||
endpoint. Typically, this is used to retrieve the initial state of
|
||||
an output report of a device, before an application updates it as necessary either
|
||||
via a HIDIOCSOUTPUT request, or the regular device write() interface. The format
|
||||
of the buffer issued with this report is identical to that of HIDIOCGFEATURE.
|
||||
|
|
|
@ -11,6 +11,7 @@ Human Interface Devices (HID)
|
|||
hidraw
|
||||
hid-sensor
|
||||
hid-transport
|
||||
hid-bpf
|
||||
|
||||
uhid
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ the sender that the memory region for that message may be reused.
|
|||
DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message
|
||||
(that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK.
|
||||
Additionally to DMA address communication, this sequence checks capabilities:
|
||||
if thw host doesn't support DMA, then it won't send DMA allocation, so FW can't
|
||||
if the host doesn't support DMA, then it won't send DMA allocation, so FW can't
|
||||
send DMA; if FW doesn't support DMA then it won't respond with
|
||||
DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers.
|
||||
Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER,
|
||||
|
|
|
@ -9207,9 +9207,12 @@ M: Benjamin Tissoires <benjamin.tissoires@redhat.com>
|
|||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
|
||||
F: Documentation/hid/
|
||||
F: drivers/hid/
|
||||
F: include/linux/hid*
|
||||
F: include/uapi/linux/hid*
|
||||
F: samples/hid/
|
||||
F: tools/testing/selftests/hid/
|
||||
|
||||
HID LOGITECH DRIVERS
|
||||
R: Filipe Laíns <lains@riseup.net>
|
||||
|
|
|
@ -137,7 +137,7 @@ obj-$(CONFIG_CRYPTO) += crypto/
|
|||
obj-$(CONFIG_SUPERH) += sh/
|
||||
obj-y += clocksource/
|
||||
obj-$(CONFIG_DCA) += dca/
|
||||
obj-$(CONFIG_HID) += hid/
|
||||
obj-$(CONFIG_HID_SUPPORT) += hid/
|
||||
obj-$(CONFIG_PPC_PS3) += ps3/
|
||||
obj-$(CONFIG_OF) += of/
|
||||
obj-$(CONFIG_SSB) += ssb/
|
||||
|
|
|
@ -2,13 +2,20 @@
|
|||
#
|
||||
# HID driver configuration
|
||||
#
|
||||
menu "HID support"
|
||||
depends on INPUT
|
||||
menuconfig HID_SUPPORT
|
||||
bool "HID bus support"
|
||||
default y
|
||||
depends on INPUT
|
||||
help
|
||||
This option adds core support for human interface device (HID).
|
||||
You will also need drivers from the following menu to make use of it.
|
||||
|
||||
if HID_SUPPORT
|
||||
|
||||
config HID
|
||||
tristate "HID bus support"
|
||||
depends on INPUT
|
||||
tristate "HID bus core support"
|
||||
default y
|
||||
depends on INPUT
|
||||
help
|
||||
A human interface device (HID) is a type of computer device that
|
||||
interacts directly with and takes input from humans. The term "HID"
|
||||
|
@ -1295,6 +1302,8 @@ config HID_KUNIT_TEST
|
|||
|
||||
endmenu
|
||||
|
||||
source "drivers/hid/bpf/Kconfig"
|
||||
|
||||
endif # HID
|
||||
|
||||
source "drivers/hid/usbhid/Kconfig"
|
||||
|
@ -1307,4 +1316,4 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
|
|||
|
||||
source "drivers/hid/surface-hid/Kconfig"
|
||||
|
||||
endmenu
|
||||
endif # HID_SUPPORT
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
hid-y := hid-core.o hid-input.o hid-quirks.o
|
||||
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
|
||||
|
||||
obj-$(CONFIG_HID_BPF) += bpf/
|
||||
|
||||
obj-$(CONFIG_HID) += hid.o
|
||||
obj-$(CONFIG_UHID) += uhid.o
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
menu "AMD SFH HID Support"
|
||||
depends on X86_64 || COMPILE_TEST
|
||||
depends on PCI
|
||||
depends on HID
|
||||
|
||||
config AMD_SFH_HID
|
||||
tristate "AMD Sensor Fusion Hub"
|
||||
depends on HID
|
||||
help
|
||||
If you say yes to this option, support will be included for the
|
||||
AMD Sensor Fusion Hub.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
menu "HID-BPF support"
|
||||
|
||||
config HID_BPF
|
||||
bool "HID-BPF support"
|
||||
default HID_SUPPORT
|
||||
depends on BPF && BPF_SYSCALL && \
|
||||
DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
||||
help
|
||||
This option allows to support eBPF programs on the HID subsystem.
|
||||
eBPF programs can fix HID devices in a lighter way than a full
|
||||
kernel patch and allow a lot more flexibility.
|
||||
|
||||
For documentation, see Documentation/hid/hid-bpf.rst
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
endmenu
|
|
@ -0,0 +1,11 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for HID-BPF
|
||||
#
|
||||
|
||||
LIBBPF_INCLUDE = $(srctree)/tools/lib
|
||||
|
||||
obj-$(CONFIG_HID_BPF) += hid_bpf.o
|
||||
CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
|
||||
CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
|
||||
hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
|
|
@ -0,0 +1,93 @@
|
|||
# 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
|
|
@ -0,0 +1,4 @@
|
|||
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
|
|
@ -0,0 +1,25 @@
|
|||
// 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";
|
|
@ -0,0 +1,248 @@
|
|||
/* 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__ */
|
|
@ -0,0 +1,551 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
/*
|
||||
* HID-BPF support for Linux
|
||||
*
|
||||
* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
#include <linux/bitops.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/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;
|
||||
}
|
||||
|
||||
u8 *
|
||||
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
|
||||
u32 *size, int interrupt)
|
||||
{
|
||||
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,
|
||||
};
|
||||
int ret;
|
||||
|
||||
if (type >= HID_REPORT_TYPES)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* no program has been attached yet */
|
||||
if (!hdev->bpf.device_data)
|
||||
return data;
|
||||
|
||||
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)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (ret) {
|
||||
if (ret > ctx_kern.ctx.allocated_size)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
*size = ret;
|
||||
}
|
||||
|
||||
return ctx_kern.data;
|
||||
}
|
||||
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)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
|
||||
{
|
||||
int ret;
|
||||
struct hid_bpf_ctx_kern ctx_kern = {
|
||||
.ctx = {
|
||||
.hid = hdev,
|
||||
.size = *size,
|
||||
.allocated_size = HID_MAX_DESCRIPTOR_SIZE,
|
||||
},
|
||||
};
|
||||
|
||||
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);
|
||||
if (ret < 0)
|
||||
goto ignore_bpf;
|
||||
|
||||
if (ret) {
|
||||
if (ret > ctx_kern.ctx.allocated_size)
|
||||
goto ignore_bpf;
|
||||
|
||||
*size = ret;
|
||||
}
|
||||
|
||||
rdesc = krealloc(ctx_kern.data, *size, GFP_KERNEL);
|
||||
|
||||
return rdesc;
|
||||
|
||||
ignore_bpf:
|
||||
kfree(ctx_kern.data);
|
||||
return kmemdup(rdesc, *size, GFP_KERNEL);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
|
||||
|
||||
/**
|
||||
* hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
|
||||
*
|
||||
* @ctx: The HID-BPF context
|
||||
* @offset: The offset within the memory
|
||||
* @rdwr_buf_size: the const size of the buffer
|
||||
*
|
||||
* @returns %NULL on error, an %__u8 memory pointer on success
|
||||
*/
|
||||
noinline __u8 *
|
||||
hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
|
||||
{
|
||||
struct hid_bpf_ctx_kern *ctx_kern;
|
||||
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
|
||||
|
||||
if (rdwr_buf_size + offset > ctx->allocated_size)
|
||||
return NULL;
|
||||
|
||||
return ctx_kern->data + offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* The following set contains all functions we agree BPF programs
|
||||
* can use.
|
||||
*/
|
||||
BTF_SET8_START(hid_bpf_kfunc_ids)
|
||||
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
|
||||
BTF_SET8_END(hid_bpf_kfunc_ids)
|
||||
|
||||
static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
|
||||
.owner = THIS_MODULE,
|
||||
.set = &hid_bpf_kfunc_ids,
|
||||
};
|
||||
|
||||
static int device_match_id(struct device *dev, const void *id)
|
||||
{
|
||||
struct hid_device *hdev = to_hid_device(dev);
|
||||
|
||||
return hdev->id == *(int *)id;
|
||||
}
|
||||
|
||||
static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size)
|
||||
{
|
||||
u8 *alloc_data;
|
||||
unsigned int i, j, max_report_len = 0;
|
||||
size_t alloc_size = 0;
|
||||
|
||||
/* compute the maximum report length for this device */
|
||||
for (i = 0; i < HID_REPORT_TYPES; i++) {
|
||||
struct hid_report_enum *report_enum = hdev->report_enum + i;
|
||||
|
||||
for (j = 0; j < HID_MAX_IDS; j++) {
|
||||
struct hid_report *report = report_enum->report_id_hash[j];
|
||||
|
||||
if (report)
|
||||
max_report_len = max(max_report_len, hid_report_len(report));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Give us a little bit of extra space and some predictability in the
|
||||
* buffer length we create. This way, we can tell users that they can
|
||||
* work on chunks of 64 bytes of memory without having the bpf verifier
|
||||
* scream at them.
|
||||
*/
|
||||
alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64;
|
||||
|
||||
alloc_data = kzalloc(alloc_size, GFP_KERNEL);
|
||||
if (!alloc_data)
|
||||
return -ENOMEM;
|
||||
|
||||
*data = alloc_data;
|
||||
*size = alloc_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hid_bpf_allocate_event_data(struct hid_device *hdev)
|
||||
{
|
||||
/* hdev->bpf.device_data is already allocated, abort */
|
||||
if (hdev->bpf.device_data)
|
||||
return 0;
|
||||
|
||||
return __hid_bpf_allocate_data(hdev, &hdev->bpf.device_data, &hdev->bpf.allocated_data);
|
||||
}
|
||||
|
||||
int hid_bpf_reconnect(struct hid_device *hdev)
|
||||
{
|
||||
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
|
||||
return device_reprobe(&hdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 */
|
||||
noinline int
|
||||
hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
struct device *dev;
|
||||
int fd, err, prog_type = hid_bpf_get_prog_attach_type(prog_fd);
|
||||
|
||||
if (!hid_bpf_ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (prog_type < 0)
|
||||
return prog_type;
|
||||
|
||||
if (prog_type >= HID_BPF_PROG_TYPE_MAX)
|
||||
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);
|
||||
|
||||
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, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_allocate_context - Allocate a context to the given HID device
|
||||
*
|
||||
* @hid_id: the system unique identifier of the HID device
|
||||
*
|
||||
* @returns A pointer to &struct hid_bpf_ctx on success, %NULL on error.
|
||||
*/
|
||||
noinline struct hid_bpf_ctx *
|
||||
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)
|
||||
return NULL;
|
||||
|
||||
hdev = to_hid_device(dev);
|
||||
|
||||
ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL);
|
||||
if (!ctx_kern)
|
||||
return NULL;
|
||||
|
||||
ctx_kern->ctx.hid = hdev;
|
||||
|
||||
return &ctx_kern->ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_release_context - Release the previously allocated context @ctx
|
||||
*
|
||||
* @ctx: the HID-BPF context to release
|
||||
*
|
||||
*/
|
||||
noinline void
|
||||
hid_bpf_release_context(struct hid_bpf_ctx *ctx)
|
||||
{
|
||||
struct hid_bpf_ctx_kern *ctx_kern;
|
||||
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
|
||||
|
||||
kfree(ctx_kern);
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_hw_request - Communicate with a HID device
|
||||
*
|
||||
* @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
|
||||
* @buf: a %PTR_TO_MEM buffer
|
||||
* @buf__sz: the size of the data to transfer
|
||||
* @rtype: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
|
||||
* @reqtype: the type of the request (%HID_REQ_GET_REPORT, %HID_REQ_SET_REPORT, ...)
|
||||
*
|
||||
* @returns %0 on success, a negative error code otherwise.
|
||||
*/
|
||||
noinline 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_device *hdev;
|
||||
struct hid_report *report;
|
||||
struct hid_report_enum *report_enum;
|
||||
u8 *dma_data;
|
||||
u32 report_len;
|
||||
int ret;
|
||||
|
||||
/* check arguments */
|
||||
if (!ctx || !hid_bpf_ops || !buf)
|
||||
return -EINVAL;
|
||||
|
||||
switch (rtype) {
|
||||
case HID_INPUT_REPORT:
|
||||
case HID_OUTPUT_REPORT:
|
||||
case HID_FEATURE_REPORT:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (reqtype) {
|
||||
case HID_REQ_GET_REPORT:
|
||||
case HID_REQ_GET_IDLE:
|
||||
case HID_REQ_GET_PROTOCOL:
|
||||
case HID_REQ_SET_REPORT:
|
||||
case HID_REQ_SET_IDLE:
|
||||
case HID_REQ_SET_PROTOCOL:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (buf__sz < 1)
|
||||
return -EINVAL;
|
||||
|
||||
hdev = (struct hid_device *)ctx->hid; /* discard const */
|
||||
|
||||
report_enum = hdev->report_enum + rtype;
|
||||
report = hid_bpf_ops->hid_get_report(report_enum, buf);
|
||||
if (!report)
|
||||
return -EINVAL;
|
||||
|
||||
report_len = hid_report_len(report);
|
||||
|
||||
if (buf__sz > report_len)
|
||||
buf__sz = report_len;
|
||||
|
||||
dma_data = kmemdup(buf, buf__sz, GFP_KERNEL);
|
||||
if (!dma_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_bpf_ops->hid_hw_raw_request(hdev,
|
||||
dma_data[0],
|
||||
dma_data,
|
||||
buf__sz,
|
||||
rtype,
|
||||
reqtype);
|
||||
|
||||
if (ret > 0)
|
||||
memcpy(buf, dma_data, ret);
|
||||
|
||||
kfree(dma_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 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_SET8_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)
|
||||
BTF_SET8_END(hid_bpf_syscall_kfunc_ids)
|
||||
|
||||
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
|
||||
.owner = THIS_MODULE,
|
||||
.set = &hid_bpf_syscall_kfunc_ids,
|
||||
};
|
||||
|
||||
int hid_bpf_connect_device(struct hid_device *hdev)
|
||||
{
|
||||
struct hid_bpf_prog_list *prog_list;
|
||||
|
||||
rcu_read_lock();
|
||||
prog_list = rcu_dereference(hdev->bpf.progs[HID_BPF_PROG_TYPE_DEVICE_EVENT]);
|
||||
rcu_read_unlock();
|
||||
|
||||
/* only allocate BPF data if there are programs attached */
|
||||
if (!prog_list)
|
||||
return 0;
|
||||
|
||||
return hid_bpf_allocate_event_data(hdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_bpf_connect_device);
|
||||
|
||||
void hid_bpf_disconnect_device(struct hid_device *hdev)
|
||||
{
|
||||
kfree(hdev->bpf.device_data);
|
||||
hdev->bpf.device_data = NULL;
|
||||
hdev->bpf.allocated_data = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_bpf_disconnect_device);
|
||||
|
||||
void hid_bpf_destroy_device(struct hid_device *hdev)
|
||||
{
|
||||
if (!hdev)
|
||||
return;
|
||||
|
||||
/* mark the device as destroyed in bpf so we don't reattach it */
|
||||
hdev->bpf.destroyed = true;
|
||||
|
||||
__hid_bpf_destroy_device(hdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_bpf_destroy_device);
|
||||
|
||||
void hid_bpf_device_init(struct hid_device *hdev)
|
||||
{
|
||||
spin_lock_init(&hdev->bpf.progs_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_bpf_device_init);
|
||||
|
||||
static int __init hid_bpf_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
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);
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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");
|
|
@ -0,0 +1,25 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#ifndef _BPF_HID_BPF_DISPATCH_H
|
||||
#define _BPF_HID_BPF_DISPATCH_H
|
||||
|
||||
#include <linux/hid.h>
|
||||
|
||||
struct hid_bpf_ctx_kern {
|
||||
struct hid_bpf_ctx ctx;
|
||||
u8 *data;
|
||||
};
|
||||
|
||||
int hid_bpf_preload_skel(void);
|
||||
void hid_bpf_free_links_and_skel(void);
|
||||
int hid_bpf_get_prog_attach_type(int prog_fd);
|
||||
int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type, int prog_fd,
|
||||
__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);
|
||||
int hid_bpf_reconnect(struct hid_device *hdev);
|
||||
|
||||
struct bpf_prog;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,565 @@
|
|||
// 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;
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
next->hdev = NULL;
|
||||
}
|
||||
|
||||
/* if type was rdesc fixup, reconnect device */
|
||||
if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP)
|
||||
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(int prog_fd)
|
||||
{
|
||||
struct bpf_prog *prog = NULL;
|
||||
int i;
|
||||
int prog_type = HID_BPF_PROG_TYPE_UNDEF;
|
||||
|
||||
prog = bpf_prog_get(prog_fd);
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bpf_prog_put(prog);
|
||||
|
||||
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, __u32 flags)
|
||||
{
|
||||
struct bpf_link_primer link_primer;
|
||||
struct hid_bpf_link *link;
|
||||
struct bpf_prog *prog = NULL;
|
||||
struct hid_bpf_prog_entry *prog_entry;
|
||||
int cnt, err = -EINVAL, prog_table_idx = -1;
|
||||
|
||||
/* take a ref on the prog itself */
|
||||
prog = bpf_prog_get(prog_fd);
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
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);
|
||||
|
||||
bpf_prog_put(prog);
|
||||
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;
|
||||
}
|
|
@ -1215,7 +1215,8 @@ int hid_open_report(struct hid_device *device)
|
|||
return -ENODEV;
|
||||
size = device->dev_rsize;
|
||||
|
||||
buf = kmemdup(start, size, GFP_KERNEL);
|
||||
/* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
|
||||
buf = call_hid_bpf_rdesc_fixup(device, start, &size);
|
||||
if (buf == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -2042,6 +2043,12 @@ 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);
|
||||
if (IS_ERR(data)) {
|
||||
ret = PTR_ERR(data);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
if (!size) {
|
||||
dbg_hid("empty report\n");
|
||||
ret = -1;
|
||||
|
@ -2156,6 +2163,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
|
|||
int len;
|
||||
int ret;
|
||||
|
||||
ret = hid_bpf_connect_device(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
|
||||
connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
|
||||
if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE)
|
||||
|
@ -2257,6 +2268,8 @@ void hid_disconnect(struct hid_device *hdev)
|
|||
if (hdev->claimed & HID_CLAIMED_HIDRAW)
|
||||
hidraw_disconnect(hdev);
|
||||
hdev->claimed = 0;
|
||||
|
||||
hid_bpf_disconnect_device(hdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_disconnect);
|
||||
|
||||
|
@ -2792,6 +2805,8 @@ struct hid_device *hid_allocate_device(void)
|
|||
sema_init(&hdev->driver_input_lock, 1);
|
||||
mutex_init(&hdev->ll_open_lock);
|
||||
|
||||
hid_bpf_device_init(hdev);
|
||||
|
||||
return hdev;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_allocate_device);
|
||||
|
@ -2818,6 +2833,7 @@ static void hid_remove_device(struct hid_device *hdev)
|
|||
*/
|
||||
void hid_destroy_device(struct hid_device *hdev)
|
||||
{
|
||||
hid_bpf_destroy_device(hdev);
|
||||
hid_remove_device(hdev);
|
||||
put_device(&hdev->dev);
|
||||
}
|
||||
|
@ -2904,6 +2920,15 @@ 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 = {
|
||||
.hid_get_report = hid_get_report,
|
||||
.hid_hw_raw_request = hid_hw_raw_request,
|
||||
.owner = THIS_MODULE,
|
||||
.bus_type = &hid_bus_type,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init hid_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
@ -2914,6 +2939,10 @@ static int __init hid_init(void)
|
|||
goto err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HID_BPF
|
||||
hid_bpf_ops = &hid_ops;
|
||||
#endif
|
||||
|
||||
ret = hidraw_init();
|
||||
if (ret)
|
||||
goto err_bus;
|
||||
|
@ -2929,6 +2958,9 @@ err:
|
|||
|
||||
static void __exit hid_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_HID_BPF
|
||||
hid_bpf_ops = NULL;
|
||||
#endif
|
||||
hid_debug_exit();
|
||||
hidraw_exit();
|
||||
bus_unregister(&hid_bus_type);
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
menu "I2C HID support"
|
||||
depends on I2C
|
||||
menuconfig I2C_HID
|
||||
tristate "I2C HID support"
|
||||
default y
|
||||
depends on I2C && INPUT && HID
|
||||
|
||||
if I2C_HID
|
||||
|
||||
config I2C_HID_ACPI
|
||||
tristate "HID over I2C transport layer ACPI driver"
|
||||
default n
|
||||
depends on I2C && INPUT && ACPI
|
||||
depends on ACPI
|
||||
select I2C_HID_CORE
|
||||
help
|
||||
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
|
||||
other HID based devices which is connected to your computer via I2C.
|
||||
|
@ -19,8 +23,8 @@ config I2C_HID_ACPI
|
|||
|
||||
config I2C_HID_OF
|
||||
tristate "HID over I2C transport layer Open Firmware driver"
|
||||
default n
|
||||
depends on I2C && INPUT && OF
|
||||
depends on OF
|
||||
select I2C_HID_CORE
|
||||
help
|
||||
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
|
||||
other HID based devices which is connected to your computer via I2C.
|
||||
|
@ -34,8 +38,8 @@ config I2C_HID_OF
|
|||
|
||||
config I2C_HID_OF_ELAN
|
||||
tristate "Driver for Elan hid-i2c based devices on OF systems"
|
||||
default n
|
||||
depends on I2C && INPUT && OF
|
||||
depends on OF
|
||||
select I2C_HID_CORE
|
||||
help
|
||||
Say Y here if you want support for Elan i2c devices that use
|
||||
the i2c-hid protocol on Open Firmware (Device Tree)-based
|
||||
|
@ -49,8 +53,8 @@ config I2C_HID_OF_ELAN
|
|||
|
||||
config I2C_HID_OF_GOODIX
|
||||
tristate "Driver for Goodix hid-i2c based devices on OF systems"
|
||||
default n
|
||||
depends on I2C && INPUT && OF
|
||||
depends on OF
|
||||
select I2C_HID_CORE
|
||||
help
|
||||
Say Y here if you want support for Goodix i2c devices that use
|
||||
the i2c-hid protocol on Open Firmware (Device Tree)-based
|
||||
|
@ -62,10 +66,7 @@ config I2C_HID_OF_GOODIX
|
|||
will be called i2c-hid-of-goodix. It will also build/depend on
|
||||
the module i2c-hid.
|
||||
|
||||
endmenu
|
||||
|
||||
config I2C_HID_CORE
|
||||
tristate
|
||||
default y if I2C_HID_ACPI=y || I2C_HID_OF=y || I2C_HID_OF_ELAN=y || I2C_HID_OF_GOODIX=y
|
||||
default m if I2C_HID_ACPI=m || I2C_HID_OF=m || I2C_HID_OF_ELAN=m || I2C_HID_OF_GOODIX=m
|
||||
select HID
|
||||
endif
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ config INTEL_ISH_HID
|
|||
tristate "Intel Integrated Sensor Hub"
|
||||
default n
|
||||
depends on X86
|
||||
select HID
|
||||
depends on HID
|
||||
help
|
||||
The Integrated Sensor Hub (ISH) enables the ability to offload
|
||||
sensor polling and algorithm processing to a dedicated low power
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <linux/mutex.h>
|
||||
#include <linux/power_supply.h>
|
||||
#include <uapi/linux/hid.h>
|
||||
#include <linux/hid_bpf.h>
|
||||
|
||||
/*
|
||||
* We parse each description item into this structure. Short items data
|
||||
|
@ -654,6 +655,10 @@ struct hid_device { /* device report descriptor */
|
|||
wait_queue_head_t debug_wait;
|
||||
|
||||
unsigned int id; /* system unique id */
|
||||
|
||||
#ifdef CONFIG_BPF
|
||||
struct hid_bpf bpf; /* hid-bpf data */
|
||||
#endif /* CONFIG_BPF */
|
||||
};
|
||||
|
||||
#define to_hid_device(pdev) \
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
|
||||
#ifndef __HID_BPF_H
|
||||
#define __HID_BPF_H
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <uapi/linux/hid.h>
|
||||
|
||||
struct hid_device;
|
||||
|
||||
/*
|
||||
* The following is the user facing HID BPF API.
|
||||
*
|
||||
* Extra care should be taken when editing this part, as
|
||||
* it might break existing out of the tree bpf programs.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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()``
|
||||
* @allocated_size: Allocated size of data.
|
||||
*
|
||||
* This is how much memory is available and can be requested
|
||||
* by the HID program.
|
||||
* Note that for ``HID_BPF_RDESC_FIXUP``, that memory is set to
|
||||
* ``4096`` (4 KB)
|
||||
* @size: Valid data in the data field.
|
||||
*
|
||||
* Programs can get the available valid size in data by fetching this field.
|
||||
* Programs can also change this value by returning a positive number in the
|
||||
* program.
|
||||
* To discard the event, return a negative error code.
|
||||
*
|
||||
* ``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.
|
||||
*/
|
||||
struct hid_bpf_ctx {
|
||||
__u32 index;
|
||||
const 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);
|
||||
|
||||
/* Following functions are kfunc that we export to BPF programs */
|
||||
/* available everywhere in HID-BPF */
|
||||
__u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t __sz);
|
||||
|
||||
/* only available in syscall */
|
||||
int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags);
|
||||
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 *hid_bpf_allocate_context(unsigned int hid_id);
|
||||
void hid_bpf_release_context(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_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);
|
||||
struct module *owner;
|
||||
struct bus_type *bus_type;
|
||||
};
|
||||
|
||||
extern struct hid_bpf_ops *hid_bpf_ops;
|
||||
|
||||
struct hid_bpf_prog_list {
|
||||
u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV];
|
||||
u8 prog_cnt;
|
||||
};
|
||||
|
||||
/* stored in each device */
|
||||
struct hid_bpf {
|
||||
u8 *device_data; /* allocated when a bpf program of type
|
||||
* SEC(f.../hid_bpf_device_event) has been attached
|
||||
* 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;
|
||||
};
|
||||
|
||||
#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);
|
||||
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);
|
||||
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; }
|
||||
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 u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
|
||||
{
|
||||
return kmemdup(rdesc, *size, GFP_KERNEL);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_HID_BPF */
|
||||
|
||||
#endif /* __HID_BPF_H */
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config BT_HIDP
|
||||
tristate "HIDP protocol support"
|
||||
depends on BT_BREDR && INPUT
|
||||
depends on BT_BREDR && INPUT && HID_SUPPORT
|
||||
select HID
|
||||
help
|
||||
HIDP (Human Interface Device Protocol) is a transport layer
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
hid_mouse
|
||||
hid_surface_dial
|
||||
*.out
|
||||
*.skel.h
|
||||
/vmlinux.h
|
||||
/bpftool/
|
||||
/libbpf/
|
|
@ -0,0 +1,250 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
HID_SAMPLES_PATH ?= $(abspath $(srctree)/$(src))
|
||||
TOOLS_PATH := $(HID_SAMPLES_PATH)/../../tools
|
||||
|
||||
pound := \#
|
||||
|
||||
# List of programs to build
|
||||
tprogs-y += hid_mouse
|
||||
tprogs-y += hid_surface_dial
|
||||
|
||||
# Libbpf dependencies
|
||||
LIBBPF_SRC = $(TOOLS_PATH)/lib/bpf
|
||||
LIBBPF_OUTPUT = $(abspath $(HID_SAMPLES_PATH))/libbpf
|
||||
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
|
||||
hid_surface_dial-objs := hid_surface_dial.o
|
||||
|
||||
# Tell kbuild to always build the programs
|
||||
always-y := $(tprogs-y)
|
||||
|
||||
ifeq ($(ARCH), arm)
|
||||
# Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux
|
||||
# headers when arm instruction set identification is requested.
|
||||
ARM_ARCH_SELECTOR := $(filter -D__LINUX_ARM_ARCH__%, $(KBUILD_CFLAGS))
|
||||
BPF_EXTRA_CFLAGS := $(ARM_ARCH_SELECTOR)
|
||||
TPROGS_CFLAGS += $(ARM_ARCH_SELECTOR)
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH), mips)
|
||||
TPROGS_CFLAGS += -D__SANE_USERSPACE_TYPES__
|
||||
ifdef CONFIG_MACH_LOONGSON64
|
||||
BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-loongson64
|
||||
BPF_EXTRA_CFLAGS += -I$(srctree)/arch/mips/include/asm/mach-generic
|
||||
endif
|
||||
endif
|
||||
|
||||
TPROGS_CFLAGS += -Wall -O2
|
||||
TPROGS_CFLAGS += -Wmissing-prototypes
|
||||
TPROGS_CFLAGS += -Wstrict-prototypes
|
||||
|
||||
TPROGS_CFLAGS += -I$(objtree)/usr/include
|
||||
TPROGS_CFLAGS += -I$(LIBBPF_INCLUDE)
|
||||
TPROGS_CFLAGS += -I$(srctree)/tools/include
|
||||
|
||||
ifdef SYSROOT
|
||||
TPROGS_CFLAGS += --sysroot=$(SYSROOT)
|
||||
TPROGS_LDFLAGS := -L$(SYSROOT)/usr/lib
|
||||
endif
|
||||
|
||||
TPROGS_LDLIBS += $(LIBBPF) -lelf -lz
|
||||
|
||||
# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
|
||||
# make M=samples/bpf LLC=~/git/llvm-project/llvm/build/bin/llc CLANG=~/git/llvm-project/llvm/build/bin/clang
|
||||
LLC ?= llc
|
||||
CLANG ?= clang
|
||||
OPT ?= opt
|
||||
LLVM_DIS ?= llvm-dis
|
||||
LLVM_OBJCOPY ?= llvm-objcopy
|
||||
LLVM_READELF ?= llvm-readelf
|
||||
BTF_PAHOLE ?= pahole
|
||||
|
||||
# Detect that we're cross compiling and use the cross compiler
|
||||
ifdef CROSS_COMPILE
|
||||
CLANG_ARCH_ARGS = --target=$(notdir $(CROSS_COMPILE:%-=%))
|
||||
endif
|
||||
|
||||
# Don't evaluate probes and warnings if we need to run make recursively
|
||||
ifneq ($(src),)
|
||||
HDR_PROBE := $(shell printf "$(pound)include <linux/types.h>\n struct list_head { int a; }; int main() { return 0; }" | \
|
||||
$(CC) $(TPROGS_CFLAGS) $(TPROGS_LDFLAGS) -x c - \
|
||||
-o /dev/null 2>/dev/null && echo okay)
|
||||
|
||||
ifeq ($(HDR_PROBE),)
|
||||
$(warning WARNING: Detected possible issues with include path.)
|
||||
$(warning WARNING: Please install kernel headers locally (make headers_install).)
|
||||
endif
|
||||
|
||||
BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris)
|
||||
BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF)
|
||||
BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm')
|
||||
BTF_LLVM_PROBE := $(shell echo "int main() { return 0; }" | \
|
||||
$(CLANG) -target bpf -O2 -g -c -x c - -o ./llvm_btf_verify.o; \
|
||||
$(LLVM_READELF) -S ./llvm_btf_verify.o | grep BTF; \
|
||||
/bin/rm -f ./llvm_btf_verify.o)
|
||||
|
||||
BPF_EXTRA_CFLAGS += -fno-stack-protector
|
||||
ifneq ($(BTF_LLVM_PROBE),)
|
||||
BPF_EXTRA_CFLAGS += -g
|
||||
else
|
||||
ifneq ($(and $(BTF_LLC_PROBE),$(BTF_PAHOLE_PROBE),$(BTF_OBJCOPY_PROBE)),)
|
||||
BPF_EXTRA_CFLAGS += -g
|
||||
LLC_FLAGS += -mattr=dwarfris
|
||||
DWARF2BTF = y
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# Trick to allow make to be run from this directory
|
||||
all:
|
||||
$(MAKE) -C ../../ M=$(CURDIR) HID_SAMPLES_PATH=$(CURDIR)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C ../../ M=$(CURDIR) clean
|
||||
@find $(CURDIR) -type f -name '*~' -delete
|
||||
@$(RM) -r $(CURDIR)/libbpf $(CURDIR)/bpftool
|
||||
|
||||
$(LIBBPF): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
|
||||
# Fix up variables inherited from Kbuild that tools/ build system won't like
|
||||
$(MAKE) -C $(LIBBPF_SRC) RM='rm -rf' EXTRA_CFLAGS="$(TPROGS_CFLAGS)" \
|
||||
LDFLAGS=$(TPROGS_LDFLAGS) srctree=$(HID_SAMPLES_PATH)/../../ \
|
||||
O= OUTPUT=$(LIBBPF_OUTPUT)/ DESTDIR=$(LIBBPF_DESTDIR) prefix= \
|
||||
$@ install_headers
|
||||
|
||||
BPFTOOLDIR := $(TOOLS_PATH)/bpf/bpftool
|
||||
BPFTOOL_OUTPUT := $(abspath $(HID_SAMPLES_PATH))/bpftool
|
||||
BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
|
||||
$(BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) | $(BPFTOOL_OUTPUT)
|
||||
$(MAKE) -C $(BPFTOOLDIR) srctree=$(HID_SAMPLES_PATH)/../../ \
|
||||
OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
|
||||
|
||||
$(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
|
||||
$(call msg,MKDIR,$@)
|
||||
$(Q)mkdir -p $@
|
||||
|
||||
FORCE:
|
||||
|
||||
|
||||
# Verify LLVM compiler tools are available and bpf target is supported by llc
|
||||
.PHONY: verify_cmds verify_target_bpf $(CLANG) $(LLC)
|
||||
|
||||
verify_cmds: $(CLANG) $(LLC)
|
||||
@for TOOL in $^ ; do \
|
||||
if ! (which -- "$${TOOL}" > /dev/null 2>&1); then \
|
||||
echo "*** ERROR: Cannot find LLVM tool $${TOOL}" ;\
|
||||
exit 1; \
|
||||
else true; fi; \
|
||||
done
|
||||
|
||||
verify_target_bpf: verify_cmds
|
||||
@if ! (${LLC} -march=bpf -mattr=help > /dev/null 2>&1); then \
|
||||
echo "*** ERROR: LLVM (${LLC}) does not support 'bpf' target" ;\
|
||||
echo " NOTICE: LLVM version >= 3.7.1 required" ;\
|
||||
exit 2; \
|
||||
else true; fi
|
||||
|
||||
$(HID_SAMPLES_PATH)/*.c: verify_target_bpf $(LIBBPF)
|
||||
$(src)/*.c: verify_target_bpf $(LIBBPF)
|
||||
|
||||
libbpf_hdrs: $(LIBBPF)
|
||||
|
||||
.PHONY: libbpf_hdrs
|
||||
|
||||
$(obj)/hid_mouse.o: $(obj)/hid_mouse.skel.h
|
||||
$(obj)/hid_surface_dial.o: $(obj)/hid_surface_dial.skel.h
|
||||
|
||||
-include $(HID_SAMPLES_PATH)/Makefile.target
|
||||
|
||||
VMLINUX_BTF_PATHS ?= $(abspath $(if $(O),$(O)/vmlinux)) \
|
||||
$(abspath $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux)) \
|
||||
$(abspath ./vmlinux)
|
||||
VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
|
||||
|
||||
$(obj)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL)
|
||||
ifeq ($(VMLINUX_H),)
|
||||
ifeq ($(VMLINUX_BTF),)
|
||||
$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)",\
|
||||
build the kernel or set VMLINUX_BTF or VMLINUX_H variable)
|
||||
endif
|
||||
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
|
||||
else
|
||||
$(Q)cp "$(VMLINUX_H)" $@
|
||||
endif
|
||||
|
||||
clean-files += vmlinux.h
|
||||
|
||||
# Get Clang's default includes on this system, as opposed to those seen by
|
||||
# '-target bpf'. This fixes "missing" files on some architectures/distros,
|
||||
# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
define get_sys_includes
|
||||
$(shell $(1) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
|
||||
$(shell $(1) -dM -E - </dev/null | grep '#define __riscv_xlen ' | sed 's/#define /-D/' | sed 's/ /=/')
|
||||
endef
|
||||
|
||||
CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
|
||||
|
||||
EXTRA_BPF_HEADERS_SRC := $(addprefix $(src)/,$(EXTRA_BPF_HEADERS))
|
||||
|
||||
$(obj)/%.bpf.o: $(src)/%.bpf.c $(EXTRA_BPF_HEADERS_SRC) $(obj)/vmlinux.h
|
||||
@echo " CLANG-BPF " $@
|
||||
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(SRCARCH) \
|
||||
-Wno-compare-distinct-pointer-types -I$(srctree)/include \
|
||||
-I$(srctree)/samples/bpf -I$(srctree)/tools/include \
|
||||
-I$(LIBBPF_INCLUDE) $(CLANG_SYS_INCLUDES) \
|
||||
-c $(filter %.bpf.c,$^) -o $@
|
||||
|
||||
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
|
||||
|
||||
LINKED_BPF_SRCS := $(patsubst %.bpf.o,%.bpf.c,$(foreach skel,$(LINKED_SKELS),$($(skel)-deps)))
|
||||
|
||||
BPF_SRCS_LINKED := $(notdir $(wildcard $(src)/*.bpf.c))
|
||||
BPF_OBJS_LINKED := $(patsubst %.bpf.c,$(obj)/%.bpf.o, $(BPF_SRCS_LINKED))
|
||||
BPF_SKELS_LINKED := $(addprefix $(obj)/,$(LINKED_SKELS))
|
||||
|
||||
$(BPF_SKELS_LINKED): $(BPF_OBJS_LINKED) $(BPFTOOL)
|
||||
@echo " BPF GEN-OBJ " $(@:.skel.h=)
|
||||
$(Q)$(BPFTOOL) gen object $(@:.skel.h=.lbpf.o) $(addprefix $(obj)/,$($(@F)-deps))
|
||||
@echo " BPF GEN-SKEL" $(@:.skel.h=)
|
||||
$(Q)$(BPFTOOL) gen skeleton $(@:.skel.h=.lbpf.o) name $(notdir $(@:.skel.h=)) > $@
|
||||
|
||||
# asm/sysreg.h - inline assembly used by it is incompatible with llvm.
|
||||
# But, there is no easy way to fix it, so just exclude it since it is
|
||||
# useless for BPF samples.
|
||||
# below we use long chain of commands, clang | opt | llvm-dis | llc,
|
||||
# to generate final object file. 'clang' compiles the source into IR
|
||||
# with native target, e.g., x64, arm64, etc. 'opt' does bpf CORE IR builtin
|
||||
# processing (llvm12) and IR optimizations. 'llvm-dis' converts
|
||||
# 'opt' output to IR, and finally 'llc' generates bpf byte code.
|
||||
$(obj)/%.o: $(src)/%.c
|
||||
@echo " CLANG-bpf " $@
|
||||
$(Q)$(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(BPF_EXTRA_CFLAGS) \
|
||||
-I$(obj) -I$(srctree)/tools/testing/selftests/bpf/ \
|
||||
-I$(LIBBPF_INCLUDE) \
|
||||
-D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \
|
||||
-D__TARGET_ARCH_$(SRCARCH) -Wno-compare-distinct-pointer-types \
|
||||
-Wno-gnu-variable-sized-type-not-at-end \
|
||||
-Wno-address-of-packed-member -Wno-tautological-compare \
|
||||
-Wno-unknown-warning-option $(CLANG_ARCH_ARGS) \
|
||||
-fno-asynchronous-unwind-tables \
|
||||
-I$(srctree)/samples/hid/ \
|
||||
-O2 -emit-llvm -Xclang -disable-llvm-passes -c $< -o - | \
|
||||
$(OPT) -O2 -mtriple=bpf-pc-linux | $(LLVM_DIS) | \
|
||||
$(LLC) -march=bpf $(LLC_FLAGS) -filetype=obj -o $@
|
||||
ifeq ($(DWARF2BTF),y)
|
||||
$(BTF_PAHOLE) -J $@
|
||||
endif
|
|
@ -0,0 +1,75 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# ==========================================================================
|
||||
# Building binaries on the host system
|
||||
# Binaries are not used during the compilation of the kernel, and intended
|
||||
# to be build for target board, target board can be host of course. Added to
|
||||
# build binaries to run not on host system.
|
||||
#
|
||||
# Sample syntax
|
||||
# tprogs-y := xsk_example
|
||||
# Will compile xsk_example.c and create an executable named xsk_example
|
||||
#
|
||||
# tprogs-y := xdpsock
|
||||
# xdpsock-objs := xdpsock_1.o xdpsock_2.o
|
||||
# Will compile xdpsock_1.c and xdpsock_2.c, and then link the executable
|
||||
# xdpsock, based on xdpsock_1.o and xdpsock_2.o
|
||||
#
|
||||
# Derived from scripts/Makefile.host
|
||||
#
|
||||
__tprogs := $(sort $(tprogs-y))
|
||||
|
||||
# C code
|
||||
# Executables compiled from a single .c file
|
||||
tprog-csingle := $(foreach m,$(__tprogs), \
|
||||
$(if $($(m)-objs),,$(m)))
|
||||
|
||||
# C executables linked based on several .o files
|
||||
tprog-cmulti := $(foreach m,$(__tprogs),\
|
||||
$(if $($(m)-objs),$(m)))
|
||||
|
||||
# Object (.o) files compiled from .c files
|
||||
tprog-cobjs := $(sort $(foreach m,$(__tprogs),$($(m)-objs)))
|
||||
|
||||
tprog-csingle := $(addprefix $(obj)/,$(tprog-csingle))
|
||||
tprog-cmulti := $(addprefix $(obj)/,$(tprog-cmulti))
|
||||
tprog-cobjs := $(addprefix $(obj)/,$(tprog-cobjs))
|
||||
|
||||
#####
|
||||
# Handle options to gcc. Support building with separate output directory
|
||||
|
||||
_tprogc_flags = $(TPROGS_CFLAGS) \
|
||||
$(TPROGCFLAGS_$(basetarget).o)
|
||||
|
||||
# $(objtree)/$(obj) for including generated headers from checkin source files
|
||||
ifeq ($(KBUILD_EXTMOD),)
|
||||
ifdef building_out_of_srctree
|
||||
_tprogc_flags += -I $(objtree)/$(obj)
|
||||
endif
|
||||
endif
|
||||
|
||||
tprogc_flags = -Wp,-MD,$(depfile) $(_tprogc_flags)
|
||||
|
||||
# Create executable from a single .c file
|
||||
# tprog-csingle -> Executable
|
||||
quiet_cmd_tprog-csingle = CC $@
|
||||
cmd_tprog-csingle = $(CC) $(tprogc_flags) $(TPROGS_LDFLAGS) -o $@ $< \
|
||||
$(TPROGS_LDLIBS) $(TPROGLDLIBS_$(@F))
|
||||
$(tprog-csingle): $(obj)/%: $(src)/%.c FORCE
|
||||
$(call if_changed_dep,tprog-csingle)
|
||||
|
||||
# Link an executable based on list of .o files, all plain c
|
||||
# tprog-cmulti -> executable
|
||||
quiet_cmd_tprog-cmulti = LD $@
|
||||
cmd_tprog-cmulti = $(CC) $(tprogc_flags) $(TPROGS_LDFLAGS) -o $@ \
|
||||
$(addprefix $(obj)/,$($(@F)-objs)) \
|
||||
$(TPROGS_LDLIBS) $(TPROGLDLIBS_$(@F))
|
||||
$(tprog-cmulti): $(tprog-cobjs) FORCE
|
||||
$(call if_changed,tprog-cmulti)
|
||||
$(call multi_depend, $(tprog-cmulti), , -objs)
|
||||
|
||||
# Create .o file from a single .c file
|
||||
# tprog-cobjs -> .o
|
||||
quiet_cmd_tprog-cobjs = CC $@
|
||||
cmd_tprog-cobjs = $(CC) $(tprogc_flags) -c -o $@ $<
|
||||
$(tprog-cobjs): $(obj)/%.o: $(src)/%.c FORCE
|
||||
$(call if_changed_dep,tprog-cobjs)
|
|
@ -0,0 +1,18 @@
|
|||
// 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;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* 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 */
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#ifndef __HID_BPF_HELPERS_H
|
||||
#define __HID_BPF_HELPERS_H
|
||||
|
||||
/* 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,
|
||||
__u8 *data,
|
||||
size_t buf__sz,
|
||||
enum hid_report_type type,
|
||||
enum hid_class_request reqtype) __ksym;
|
||||
|
||||
#endif /* __HID_BPF_HELPERS_H */
|
|
@ -0,0 +1,112 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#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)
|
||||
{
|
||||
s16 y;
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
bpf_printk("event: size: %d", hctx->size);
|
||||
bpf_printk("incoming event: %02x %02x %02x",
|
||||
data[0],
|
||||
data[1],
|
||||
data[2]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
data[3],
|
||||
data[4],
|
||||
data[5]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
data[6],
|
||||
data[7],
|
||||
data[8]);
|
||||
|
||||
y = data[3] | (data[4] << 8);
|
||||
|
||||
y = -y;
|
||||
|
||||
data[3] = y & 0xFF;
|
||||
data[4] = (y >> 8) & 0xFF;
|
||||
|
||||
bpf_printk("modified event: %02x %02x %02x",
|
||||
data[0],
|
||||
data[1],
|
||||
data[2]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
data[3],
|
||||
data[4],
|
||||
data[5]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
data[6],
|
||||
data[7],
|
||||
data[8]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_x_event, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
s16 x;
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
x = data[1] | (data[2] << 8);
|
||||
|
||||
x = -x;
|
||||
|
||||
data[1] = x & 0xFF;
|
||||
data[2] = (x >> 8) & 0xFF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
bpf_printk("rdesc: %02x %02x %02x",
|
||||
data[0],
|
||||
data[1],
|
||||
data[2]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
data[3],
|
||||
data[4],
|
||||
data[5]);
|
||||
bpf_printk(" %02x %02x %02x ...",
|
||||
data[6],
|
||||
data[7],
|
||||
data[8]);
|
||||
|
||||
/*
|
||||
* The original report descriptor contains:
|
||||
*
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 30
|
||||
* 0x16, 0x01, 0x80, // Logical Minimum (-32767) 32
|
||||
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 35
|
||||
* 0x09, 0x30, // Usage (X) 38
|
||||
* 0x09, 0x31, // Usage (Y) 40
|
||||
*
|
||||
* So byte 39 contains Usage X and byte 41 Usage Y.
|
||||
*
|
||||
* We simply swap the axes here.
|
||||
*/
|
||||
data[39] = 0x31;
|
||||
data[41] = 0x30;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,155 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*
|
||||
* This is a pure HID-BPF example, and should be considered as such:
|
||||
* on the Etekcity Scroll 6E, the X and Y axes will be swapped and
|
||||
* inverted. On any other device... Not sure what this will do.
|
||||
*
|
||||
* This C main file is generic though. To adapt the code and test, users
|
||||
* must amend only the .bpf.c file, which this program will load any
|
||||
* eBPF program it finds.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include "hid_mouse.skel.h"
|
||||
#include "hid_bpf_attach.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
static void int_exit(int sig)
|
||||
{
|
||||
running = false;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"%s: %s /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n",
|
||||
__func__, prog);
|
||||
fprintf(stderr,
|
||||
"This program will upload and attach a HID-BPF program to the given device.\n"
|
||||
"On the Etekcity Scroll 6E, the X and Y axis will be inverted, but on any other\n"
|
||||
"device, chances are high that the device will not be working anymore\n\n"
|
||||
"consider this as a demo and adapt the eBPF program to your needs\n"
|
||||
"Hit Ctrl-C to unbind the program and reset the device\n");
|
||||
}
|
||||
|
||||
static int get_hid_id(const char *path)
|
||||
{
|
||||
const char *str_id, *dir;
|
||||
char uevent[1024];
|
||||
int fd;
|
||||
|
||||
memset(uevent, 0, sizeof(uevent));
|
||||
snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path);
|
||||
|
||||
fd = open(uevent, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
return -ENOENT;
|
||||
|
||||
close(fd);
|
||||
|
||||
dir = basename((char *)path);
|
||||
|
||||
str_id = dir + sizeof("0003:0001:0A37.");
|
||||
return (int)strtol(str_id, NULL, 16);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct hid_mouse *skel;
|
||||
struct bpf_program *prog;
|
||||
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),
|
||||
);
|
||||
|
||||
while ((opt = getopt(argc, argv, optstr)) != -1) {
|
||||
switch (opt) {
|
||||
default:
|
||||
usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
|
||||
sysfs_path = argv[optind];
|
||||
if (!sysfs_path) {
|
||||
perror("sysfs");
|
||||
return 1;
|
||||
}
|
||||
|
||||
skel = hid_mouse__open_and_load();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hid_id = get_hid_id(sysfs_path);
|
||||
|
||||
if (hid_id < 0) {
|
||||
fprintf(stderr, "can not open HID device: %m\n");
|
||||
return 1;
|
||||
}
|
||||
args.hid = hid_id;
|
||||
|
||||
attach_fd = bpf_program__fd(skel->progs.attach_prog);
|
||||
if (attach_fd < 0) {
|
||||
fprintf(stderr, "can't locate attach prog: %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);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGINT, int_exit);
|
||||
signal(SIGTERM, int_exit);
|
||||
|
||||
while (running)
|
||||
sleep(1);
|
||||
|
||||
hid_mouse__destroy(skel);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
// 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_helpers.h"
|
||||
|
||||
#define HID_UP_BUTTON 0x0009
|
||||
#define HID_GD_WHEEL 0x0038
|
||||
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_event, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 9 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Touch */
|
||||
data[1] &= 0xfd;
|
||||
|
||||
/* X */
|
||||
data[4] = 0;
|
||||
data[5] = 0;
|
||||
|
||||
/* Y */
|
||||
data[6] = 0;
|
||||
data[7] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 72 == 360 / 5 -> 1 report every 5 degrees */
|
||||
int resolution = 72;
|
||||
int physical = 5;
|
||||
|
||||
struct haptic_syscall_args {
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
};
|
||||
|
||||
static __u8 haptic_data[8];
|
||||
|
||||
SEC("syscall")
|
||||
int set_haptic(struct haptic_syscall_args *args)
|
||||
{
|
||||
struct hid_bpf_ctx *ctx;
|
||||
const size_t size = sizeof(haptic_data);
|
||||
u16 *res;
|
||||
int ret;
|
||||
|
||||
if (size > sizeof(haptic_data))
|
||||
return -7; /* -E2BIG */
|
||||
|
||||
ctx = hid_bpf_allocate_context(args->hid);
|
||||
if (!ctx)
|
||||
return -1; /* EPERM check */
|
||||
|
||||
haptic_data[0] = 1; /* report ID */
|
||||
|
||||
ret = hid_bpf_hw_request(ctx, haptic_data, size, HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
||||
|
||||
bpf_printk("probed/remove event ret value: %d", ret);
|
||||
bpf_printk("buf: %02x %02x %02x",
|
||||
haptic_data[0],
|
||||
haptic_data[1],
|
||||
haptic_data[2]);
|
||||
bpf_printk(" %02x %02x %02x",
|
||||
haptic_data[3],
|
||||
haptic_data[4],
|
||||
haptic_data[5]);
|
||||
bpf_printk(" %02x %02x",
|
||||
haptic_data[6],
|
||||
haptic_data[7]);
|
||||
|
||||
/* whenever resolution multiplier is not 3600, we have the fixed report descriptor */
|
||||
res = (u16 *)&haptic_data[1];
|
||||
if (*res != 3600) {
|
||||
// haptic_data[1] = 72; /* resolution multiplier */
|
||||
// haptic_data[2] = 0; /* resolution multiplier */
|
||||
// haptic_data[3] = 0; /* Repeat Count */
|
||||
haptic_data[4] = 3; /* haptic Auto Trigger */
|
||||
// haptic_data[5] = 5; /* Waveform Cutoff Time */
|
||||
// haptic_data[6] = 80; /* Retrigger Period */
|
||||
// haptic_data[7] = 0; /* Retrigger Period */
|
||||
} else {
|
||||
haptic_data[4] = 0;
|
||||
}
|
||||
|
||||
ret = hid_bpf_hw_request(ctx, haptic_data, size, HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
||||
|
||||
bpf_printk("set haptic ret value: %d -> %d", ret, haptic_data[4]);
|
||||
|
||||
args->retval = ret;
|
||||
|
||||
hid_bpf_release_context(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert REL_DIAL into REL_WHEEL */
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
__u16 *res, *phys;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Convert TOUCH into a button */
|
||||
data[31] = HID_UP_BUTTON;
|
||||
data[33] = 2;
|
||||
|
||||
/* Convert REL_DIAL into REL_WHEEL */
|
||||
data[45] = HID_GD_WHEEL;
|
||||
|
||||
/* Change Resolution Multiplier */
|
||||
phys = (__u16 *)&data[61];
|
||||
*phys = physical;
|
||||
res = (__u16 *)&data[66];
|
||||
*res = resolution;
|
||||
|
||||
/* Convert X,Y from Abs to Rel */
|
||||
data[88] = 0x06;
|
||||
data[98] = 0x06;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
u32 _version SEC("version") = 1;
|
|
@ -0,0 +1,226 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*
|
||||
* This program will morph the Microsoft Surface Dial into a mouse,
|
||||
* and depending on the chosen resolution enable or not the haptic feedback:
|
||||
* - a resolution (-r) of 3600 will report 3600 "ticks" in one full rotation
|
||||
* without haptic feedback
|
||||
* - any other resolution will report N "ticks" in a full rotation with haptic
|
||||
* feedback
|
||||
*
|
||||
* A good default for low resolution haptic scrolling is 72 (1 "tick" every 5
|
||||
* degrees), and set to 3600 for smooth scrolling.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include "hid_surface_dial.skel.h"
|
||||
#include "hid_bpf_attach.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
struct haptic_syscall_args {
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
};
|
||||
|
||||
static void int_exit(int sig)
|
||||
{
|
||||
running = false;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static void usage(const char *prog)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"%s: %s [OPTIONS] /sys/bus/hid/devices/0BUS:0VID:0PID:00ID\n\n"
|
||||
" OPTIONS:\n"
|
||||
" -r N\t set the given resolution to the device (number of ticks per 360°)\n\n",
|
||||
__func__, prog);
|
||||
fprintf(stderr,
|
||||
"This program will morph the Microsoft Surface Dial into a mouse,\n"
|
||||
"and depending on the chosen resolution enable or not the haptic feedback:\n"
|
||||
"- a resolution (-r) of 3600 will report 3600 'ticks' in one full rotation\n"
|
||||
" without haptic feedback\n"
|
||||
"- any other resolution will report N 'ticks' in a full rotation with haptic\n"
|
||||
" feedback\n"
|
||||
"\n"
|
||||
"A good default for low resolution haptic scrolling is 72 (1 'tick' every 5\n"
|
||||
"degrees), and set to 3600 for smooth scrolling.\n");
|
||||
}
|
||||
|
||||
static int get_hid_id(const char *path)
|
||||
{
|
||||
const char *str_id, *dir;
|
||||
char uevent[1024];
|
||||
int fd;
|
||||
|
||||
memset(uevent, 0, sizeof(uevent));
|
||||
snprintf(uevent, sizeof(uevent) - 1, "%s/uevent", path);
|
||||
|
||||
fd = open(uevent, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
return -ENOENT;
|
||||
|
||||
close(fd);
|
||||
|
||||
dir = basename((char *)path);
|
||||
|
||||
str_id = dir + sizeof("0003:0001:0A37.");
|
||||
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 = {
|
||||
.hid = hid_id,
|
||||
.retval = -1,
|
||||
};
|
||||
int haptic_fd, err;
|
||||
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr,
|
||||
.ctx_in = &args,
|
||||
.ctx_size_in = sizeof(args),
|
||||
);
|
||||
|
||||
haptic_fd = bpf_program__fd(skel->progs.set_haptic);
|
||||
if (haptic_fd < 0) {
|
||||
fprintf(stderr, "can't locate haptic prog: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
err = bpf_prog_test_run_opts(haptic_fd, &tattr);
|
||||
if (err) {
|
||||
fprintf(stderr, "can't set haptic configuration to hid device %d: %m (err: %d)\n",
|
||||
hid_id, err);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct hid_surface_dial *skel;
|
||||
struct bpf_program *prog;
|
||||
const char *optstr = "r:";
|
||||
const char *sysfs_path;
|
||||
int opt, hid_id, resolution = 72;
|
||||
|
||||
while ((opt = getopt(argc, argv, optstr)) != -1) {
|
||||
switch (opt) {
|
||||
case 'r':
|
||||
{
|
||||
char *endp = NULL;
|
||||
long l = -1;
|
||||
|
||||
if (optarg) {
|
||||
l = strtol(optarg, &endp, 10);
|
||||
if (endp && *endp)
|
||||
l = -1;
|
||||
}
|
||||
|
||||
if (l < 0) {
|
||||
fprintf(stderr,
|
||||
"invalid r option %s - expecting a number\n",
|
||||
optarg ? optarg : "");
|
||||
exit(EXIT_FAILURE);
|
||||
};
|
||||
|
||||
resolution = (int) l;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
|
||||
sysfs_path = argv[optind];
|
||||
if (!sysfs_path) {
|
||||
perror("sysfs");
|
||||
return 1;
|
||||
}
|
||||
|
||||
skel = hid_surface_dial__open_and_load();
|
||||
if (!skel) {
|
||||
fprintf(stderr, "%s %s:%d", __func__, __FILE__, __LINE__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
hid_id = get_hid_id(sysfs_path);
|
||||
if (hid_id < 0) {
|
||||
fprintf(stderr, "can not open HID device: %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);
|
||||
}
|
||||
|
||||
signal(SIGINT, int_exit);
|
||||
signal(SIGTERM, int_exit);
|
||||
|
||||
set_haptic(skel, hid_id);
|
||||
|
||||
while (running)
|
||||
sleep(1);
|
||||
|
||||
hid_surface_dial__destroy(skel);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -26,6 +26,7 @@ TARGETS += fpu
|
|||
TARGETS += ftrace
|
||||
TARGETS += futex
|
||||
TARGETS += gpio
|
||||
TARGETS += hid
|
||||
TARGETS += intel_pstate
|
||||
TARGETS += iommu
|
||||
TARGETS += ipc
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
bpftool
|
||||
*.skel.h
|
||||
/tools
|
||||
hid_bpf
|
||||
results
|
|
@ -0,0 +1,231 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# based on tools/testing/selftest/bpf/Makefile
|
||||
include ../../../build/Build.include
|
||||
include ../../../scripts/Makefile.arch
|
||||
include ../../../scripts/Makefile.include
|
||||
|
||||
CXX ?= $(CROSS_COMPILE)g++
|
||||
|
||||
HOSTPKG_CONFIG := pkg-config
|
||||
|
||||
CFLAGS += -g -O0 -rdynamic -Wall -Werror -I$(KHDR_INCLUDES) -I$(OUTPUT)
|
||||
LDLIBS += -lelf -lz -lrt -lpthread
|
||||
|
||||
# Silence some warnings when compiled with clang
|
||||
ifneq ($(LLVM),)
|
||||
CFLAGS += -Wno-unused-command-line-argument
|
||||
endif
|
||||
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_GEN_PROGS = hid_bpf
|
||||
|
||||
# Emit succinct information message describing current building step
|
||||
# $1 - generic step name (e.g., CC, LINK, etc);
|
||||
# $2 - optional "flavor" specifier; if provided, will be emitted as [flavor];
|
||||
# $3 - target (assumed to be file); only file name will be emitted;
|
||||
# $4 - optional extra arg, emitted as-is, if provided.
|
||||
ifeq ($(V),1)
|
||||
Q =
|
||||
msg =
|
||||
else
|
||||
Q = @
|
||||
msg = @printf ' %-8s%s %s%s\n' "$(1)" "$(if $(2), [$(2)])" "$(notdir $(3))" "$(if $(4), $(4))";
|
||||
MAKEFLAGS += --no-print-directory
|
||||
submake_extras := feature_display=0
|
||||
endif
|
||||
|
||||
# override lib.mk's default rules
|
||||
OVERRIDE_TARGETS := 1
|
||||
override define CLEAN
|
||||
$(call msg,CLEAN)
|
||||
$(Q)$(RM) -r $(TEST_GEN_PROGS)
|
||||
$(Q)$(RM) -r $(EXTRA_CLEAN)
|
||||
endef
|
||||
|
||||
include ../lib.mk
|
||||
|
||||
TOOLSDIR := $(top_srcdir)/tools
|
||||
LIBDIR := $(TOOLSDIR)/lib
|
||||
BPFDIR := $(LIBDIR)/bpf
|
||||
TOOLSINCDIR := $(TOOLSDIR)/include
|
||||
BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool
|
||||
SCRATCH_DIR := $(OUTPUT)/tools
|
||||
BUILD_DIR := $(SCRATCH_DIR)/build
|
||||
INCLUDE_DIR := $(SCRATCH_DIR)/include
|
||||
KHDR_INCLUDES := $(SCRATCH_DIR)/uapi/include
|
||||
BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a
|
||||
ifneq ($(CROSS_COMPILE),)
|
||||
HOST_BUILD_DIR := $(BUILD_DIR)/host
|
||||
HOST_SCRATCH_DIR := $(OUTPUT)/host-tools
|
||||
HOST_INCLUDE_DIR := $(HOST_SCRATCH_DIR)/include
|
||||
else
|
||||
HOST_BUILD_DIR := $(BUILD_DIR)
|
||||
HOST_SCRATCH_DIR := $(SCRATCH_DIR)
|
||||
HOST_INCLUDE_DIR := $(INCLUDE_DIR)
|
||||
endif
|
||||
HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a
|
||||
RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids
|
||||
|
||||
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
|
||||
|
||||
# Define simple and short `make test_progs`, `make test_sysctl`, etc targets
|
||||
# to build individual tests.
|
||||
# NOTE: Semicolon at the end is critical to override lib.mk's default static
|
||||
# rule for binaries.
|
||||
$(notdir $(TEST_GEN_PROGS)): %: $(OUTPUT)/% ;
|
||||
|
||||
# sort removes libbpf duplicates when not cross-building
|
||||
MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \
|
||||
$(HOST_BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/resolve_btfids \
|
||||
$(INCLUDE_DIR))
|
||||
$(MAKE_DIRS):
|
||||
$(call msg,MKDIR,,$@)
|
||||
$(Q)mkdir -p $@
|
||||
|
||||
# LLVM's ld.lld doesn't support all the architectures, so use it only on x86
|
||||
ifeq ($(SRCARCH),x86)
|
||||
LLD := lld
|
||||
else
|
||||
LLD := ld
|
||||
endif
|
||||
|
||||
DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool
|
||||
|
||||
TEST_GEN_PROGS_EXTENDED += $(DEFAULT_BPFTOOL)
|
||||
|
||||
$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): $(BPFOBJ)
|
||||
|
||||
BPFTOOL ?= $(DEFAULT_BPFTOOL)
|
||||
$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \
|
||||
$(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \
|
||||
ARCH= CROSS_COMPILE= CC=$(HOSTCC) LD=$(HOSTLD) \
|
||||
EXTRA_CFLAGS='-g -O0' \
|
||||
OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \
|
||||
LIBBPF_OUTPUT=$(HOST_BUILD_DIR)/libbpf/ \
|
||||
LIBBPF_DESTDIR=$(HOST_SCRATCH_DIR)/ \
|
||||
prefix= DESTDIR=$(HOST_SCRATCH_DIR)/ install-bin
|
||||
|
||||
$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
|
||||
| $(BUILD_DIR)/libbpf
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \
|
||||
EXTRA_CFLAGS='-g -O0' \
|
||||
DESTDIR=$(SCRATCH_DIR) prefix= all install_headers
|
||||
|
||||
ifneq ($(BPFOBJ),$(HOST_BPFOBJ))
|
||||
$(HOST_BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \
|
||||
| $(HOST_BUILD_DIR)/libbpf
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) \
|
||||
EXTRA_CFLAGS='-g -O0' ARCH= CROSS_COMPILE= \
|
||||
OUTPUT=$(HOST_BUILD_DIR)/libbpf/ CC=$(HOSTCC) LD=$(HOSTLD) \
|
||||
DESTDIR=$(HOST_SCRATCH_DIR)/ prefix= all install_headers
|
||||
endif
|
||||
|
||||
$(INCLUDE_DIR)/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
|
||||
|
||||
$(KHDR_INCLUDES)/linux/hid.h: $(top_srcdir)/include/uapi/linux/hid.h
|
||||
$(MAKE) -C $(top_srcdir) INSTALL_HDR_PATH=$(SCRATCH_DIR)/uapi headers_install
|
||||
|
||||
$(RESOLVE_BTFIDS): $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/resolve_btfids \
|
||||
$(TOOLSDIR)/bpf/resolve_btfids/main.c \
|
||||
$(TOOLSDIR)/lib/rbtree.c \
|
||||
$(TOOLSDIR)/lib/zalloc.c \
|
||||
$(TOOLSDIR)/lib/string.c \
|
||||
$(TOOLSDIR)/lib/ctype.c \
|
||||
$(TOOLSDIR)/lib/str_error_r.c
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(TOOLSDIR)/bpf/resolve_btfids \
|
||||
CC=$(HOSTCC) LD=$(HOSTLD) AR=$(HOSTAR) \
|
||||
LIBBPF_INCLUDE=$(HOST_INCLUDE_DIR) \
|
||||
OUTPUT=$(HOST_BUILD_DIR)/resolve_btfids/ BPFOBJ=$(HOST_BPFOBJ)
|
||||
|
||||
# Get Clang's default includes on this system, as opposed to those seen by
|
||||
# '-target bpf'. This fixes "missing" files on some architectures/distros,
|
||||
# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
define get_sys_includes
|
||||
$(shell $(1) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \
|
||||
$(shell $(1) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}')
|
||||
endef
|
||||
|
||||
# Determine target endianness.
|
||||
IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - </dev/null | \
|
||||
grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__')
|
||||
MENDIAN=$(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian)
|
||||
|
||||
CLANG_SYS_INCLUDES = $(call get_sys_includes,$(CLANG))
|
||||
BPF_CFLAGS = -g -Werror -D__TARGET_ARCH_$(SRCARCH) $(MENDIAN) \
|
||||
-I$(INCLUDE_DIR)
|
||||
|
||||
CLANG_CFLAGS = $(CLANG_SYS_INCLUDES) \
|
||||
-Wno-compare-distinct-pointer-types
|
||||
|
||||
# Build BPF object using Clang
|
||||
# $1 - input .c file
|
||||
# $2 - output .o file
|
||||
# $3 - CFLAGS
|
||||
define CLANG_BPF_BUILD_RULE
|
||||
$(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
|
||||
$(Q)$(CLANG) $3 -O2 -target bpf -c $1 -mcpu=v3 -o $2
|
||||
endef
|
||||
# Similar to CLANG_BPF_BUILD_RULE, but with disabled alu32
|
||||
define CLANG_NOALU32_BPF_BUILD_RULE
|
||||
$(call msg,CLNG-BPF,$(TRUNNER_BINARY),$2)
|
||||
$(Q)$(CLANG) $3 -O2 -target bpf -c $1 -mcpu=v2 -o $2
|
||||
endef
|
||||
# Build BPF object using GCC
|
||||
define GCC_BPF_BUILD_RULE
|
||||
$(call msg,GCC-BPF,$(TRUNNER_BINARY),$2)
|
||||
$(Q)$(BPF_GCC) $3 -O2 -c $1 -o $2
|
||||
endef
|
||||
|
||||
BPF_PROGS_DIR := progs
|
||||
BPF_BUILD_RULE := CLANG_BPF_BUILD_RULE
|
||||
BPF_SRCS := $(notdir $(wildcard $(BPF_PROGS_DIR)/*.c))
|
||||
BPF_OBJS := $(patsubst %.c,$(OUTPUT)/%.bpf.o, $(BPF_SRCS))
|
||||
BPF_SKELS := $(patsubst %.c,$(OUTPUT)/%.skel.h, $(BPF_SRCS))
|
||||
TEST_GEN_FILES += $(BPF_OBJS)
|
||||
|
||||
$(BPF_PROGS_DIR)-bpfobjs := y
|
||||
$(BPF_OBJS): $(OUTPUT)/%.bpf.o: \
|
||||
$(BPF_PROGS_DIR)/%.c \
|
||||
$(wildcard $(BPF_PROGS_DIR)/*.h) \
|
||||
$(INCLUDE_DIR)/vmlinux.h \
|
||||
$(wildcard $(BPFDIR)/hid_bpf_*.h) \
|
||||
$(wildcard $(BPFDIR)/*.bpf.h) \
|
||||
| $(OUTPUT) $(BPFOBJ)
|
||||
$(call $(BPF_BUILD_RULE),$<,$@, $(BPF_CFLAGS))
|
||||
|
||||
$(BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(OUTPUT)
|
||||
$(call msg,GEN-SKEL,$(BINARY),$@)
|
||||
$(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $<
|
||||
$(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked1.o) name $(notdir $(<:.bpf.o=)) > $@
|
||||
|
||||
$(OUTPUT)/%.o: %.c $(BPF_SKELS) $(KHDR_INCLUDES)/linux/hid.h
|
||||
$(call msg,CC,,$@)
|
||||
$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
|
||||
|
||||
$(OUTPUT)/%: $(OUTPUT)/%.o
|
||||
$(call msg,BINARY,,$@)
|
||||
$(Q)$(LINK.c) $^ $(LDLIBS) -o $@
|
||||
|
||||
EXTRA_CLEAN := $(SCRATCH_DIR) $(HOST_SCRATCH_DIR) feature bpftool \
|
||||
$(addprefix $(OUTPUT)/,*.o *.skel.h no_alu32)
|
|
@ -0,0 +1,21 @@
|
|||
CONFIG_BPF_EVENTS=y
|
||||
CONFIG_BPFILTER=y
|
||||
CONFIG_BPF_JIT_ALWAYS_ON=y
|
||||
CONFIG_BPF_JIT=y
|
||||
CONFIG_BPF_KPROBE_OVERRIDE=y
|
||||
CONFIG_BPF_LSM=y
|
||||
CONFIG_BPF_PRELOAD_UMD=y
|
||||
CONFIG_BPF_PRELOAD=y
|
||||
CONFIG_BPF_STREAM_PARSER=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_BPF=y
|
||||
CONFIG_CGROUP_BPF=y
|
||||
CONFIG_DEBUG_INFO_BTF=y
|
||||
CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
|
||||
CONFIG_FPROBE=y
|
||||
CONFIG_FTRACE_SYSCALLS=y
|
||||
CONFIG_FUNCTION_TRACER=y
|
||||
CONFIG_HIDRAW=y
|
||||
CONFIG_HID=y
|
||||
CONFIG_INPUT_EVDEV=y
|
||||
CONFIG_UHID=y
|
|
@ -0,0 +1,241 @@
|
|||
CONFIG_9P_FS_POSIX_ACL=y
|
||||
CONFIG_9P_FS_SECURITY=y
|
||||
CONFIG_9P_FS=y
|
||||
CONFIG_AUDIT=y
|
||||
CONFIG_BINFMT_MISC=y
|
||||
CONFIG_BLK_CGROUP_IOLATENCY=y
|
||||
CONFIG_BLK_CGROUP=y
|
||||
CONFIG_BLK_DEV_BSGLIB=y
|
||||
CONFIG_BLK_DEV_IO_TRACE=y
|
||||
CONFIG_BLK_DEV_RAM_SIZE=16384
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
CONFIG_BLK_DEV_THROTTLING=y
|
||||
CONFIG_BONDING=y
|
||||
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
|
||||
CONFIG_BOOTTIME_TRACING=y
|
||||
CONFIG_BSD_DISKLABEL=y
|
||||
CONFIG_BSD_PROCESS_ACCT=y
|
||||
CONFIG_CFS_BANDWIDTH=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_DEBUG=y
|
||||
CONFIG_CGROUP_DEVICE=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
CONFIG_CGROUP_HUGETLB=y
|
||||
CONFIG_CGROUP_NET_CLASSID=y
|
||||
CONFIG_CGROUP_NET_PRIO=y
|
||||
CONFIG_CGROUP_PERF=y
|
||||
CONFIG_CGROUP_PIDS=y
|
||||
CONFIG_CGROUP_RDMA=y
|
||||
CONFIG_CGROUP_SCHED=y
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_WRITEBACK=y
|
||||
CONFIG_CMA_AREAS=7
|
||||
CONFIG_CMA=y
|
||||
CONFIG_COMPAT_32BIT_TIME=y
|
||||
CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y
|
||||
CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
|
||||
CONFIG_CPU_FREQ_GOV_ONDEMAND=y
|
||||
CONFIG_CPU_FREQ_GOV_USERSPACE=y
|
||||
CONFIG_CPU_FREQ_STAT=y
|
||||
CONFIG_CPU_IDLE_GOV_LADDER=y
|
||||
CONFIG_CPUSETS=y
|
||||
CONFIG_CRC_T10DIF=y
|
||||
CONFIG_CRYPTO_BLAKE2B=y
|
||||
CONFIG_CRYPTO_DEV_VIRTIO=y
|
||||
CONFIG_CRYPTO_SEQIV=y
|
||||
CONFIG_CRYPTO_XXHASH=y
|
||||
CONFIG_DCB=y
|
||||
CONFIG_DEBUG_ATOMIC_SLEEP=y
|
||||
CONFIG_DEBUG_CREDENTIALS=y
|
||||
CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT=y
|
||||
CONFIG_DEBUG_MEMORY_INIT=y
|
||||
CONFIG_DEFAULT_FQ_CODEL=y
|
||||
CONFIG_DEFAULT_RENO=y
|
||||
CONFIG_DEFAULT_SECURITY_DAC=y
|
||||
CONFIG_DEVTMPFS_MOUNT=y
|
||||
CONFIG_DEVTMPFS=y
|
||||
CONFIG_DMA_CMA=y
|
||||
CONFIG_DNS_RESOLVER=y
|
||||
CONFIG_EFI_STUB=y
|
||||
CONFIG_EFI=y
|
||||
CONFIG_EXPERT=y
|
||||
CONFIG_EXT4_FS_POSIX_ACL=y
|
||||
CONFIG_EXT4_FS_SECURITY=y
|
||||
CONFIG_EXT4_FS=y
|
||||
CONFIG_FAIL_FUNCTION=y
|
||||
CONFIG_FAULT_INJECTION_DEBUG_FS=y
|
||||
CONFIG_FAULT_INJECTION=y
|
||||
CONFIG_FB_MODE_HELPERS=y
|
||||
CONFIG_FB_TILEBLITTING=y
|
||||
CONFIG_FB_VESA=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_FONT_8x16=y
|
||||
CONFIG_FONT_MINI_4x6=y
|
||||
CONFIG_FONTS=y
|
||||
CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y
|
||||
CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
|
||||
CONFIG_FRAMEBUFFER_CONSOLE=y
|
||||
CONFIG_FUSE_FS=y
|
||||
CONFIG_FW_LOADER_USER_HELPER=y
|
||||
CONFIG_GART_IOMMU=y
|
||||
CONFIG_GENERIC_PHY=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR=y
|
||||
CONFIG_HIGH_RES_TIMERS=y
|
||||
CONFIG_HPET=y
|
||||
CONFIG_HUGETLBFS=y
|
||||
CONFIG_HUGETLB_PAGE=y
|
||||
CONFIG_HWPOISON_INJECT=y
|
||||
CONFIG_HZ_1000=y
|
||||
CONFIG_INET=y
|
||||
CONFIG_INTEL_POWERCLAMP=y
|
||||
CONFIG_IP6_NF_FILTER=y
|
||||
CONFIG_IP6_NF_IPTABLES=y
|
||||
CONFIG_IP6_NF_NAT=y
|
||||
CONFIG_IP6_NF_TARGET_MASQUERADE=y
|
||||
CONFIG_IP_ADVANCED_ROUTER=y
|
||||
CONFIG_IP_MROUTE=y
|
||||
CONFIG_IP_MULTICAST=y
|
||||
CONFIG_IP_MULTIPLE_TABLES=y
|
||||
CONFIG_IP_NF_FILTER=y
|
||||
CONFIG_IP_NF_IPTABLES=y
|
||||
CONFIG_IP_NF_NAT=y
|
||||
CONFIG_IP_NF_TARGET_MASQUERADE=y
|
||||
CONFIG_IP_PIMSM_V1=y
|
||||
CONFIG_IP_PIMSM_V2=y
|
||||
CONFIG_IP_ROUTE_MULTIPATH=y
|
||||
CONFIG_IP_ROUTE_VERBOSE=y
|
||||
CONFIG_IPV6_MIP6=y
|
||||
CONFIG_IPV6_ROUTE_INFO=y
|
||||
CONFIG_IPV6_ROUTER_PREF=y
|
||||
CONFIG_IPV6_SEG6_LWTUNNEL=y
|
||||
CONFIG_IPV6_SUBTREES=y
|
||||
CONFIG_IRQ_POLL=y
|
||||
CONFIG_JUMP_LABEL=y
|
||||
CONFIG_KARMA_PARTITION=y
|
||||
CONFIG_KEXEC=y
|
||||
CONFIG_KPROBES=y
|
||||
CONFIG_KSM=y
|
||||
CONFIG_LEGACY_VSYSCALL_NONE=y
|
||||
CONFIG_LOG_BUF_SHIFT=21
|
||||
CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
|
||||
CONFIG_LOGO=y
|
||||
CONFIG_LSM="selinux,bpf,integrity"
|
||||
CONFIG_MAC_PARTITION=y
|
||||
CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_MCORE2=y
|
||||
CONFIG_MEMCG=y
|
||||
CONFIG_MEMORY_FAILURE=y
|
||||
CONFIG_MINIX_SUBPARTITION=y
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_NAMESPACES=y
|
||||
CONFIG_NET_9P_VIRTIO=y
|
||||
CONFIG_NET_9P=y
|
||||
CONFIG_NET_ACT_BPF=y
|
||||
CONFIG_NET_CLS_CGROUP=y
|
||||
CONFIG_NETDEVICES=y
|
||||
CONFIG_NET_EMATCH=y
|
||||
CONFIG_NETFILTER_NETLINK_LOG=y
|
||||
CONFIG_NETFILTER_NETLINK_QUEUE=y
|
||||
CONFIG_NETFILTER_XTABLES=y
|
||||
CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=y
|
||||
CONFIG_NETFILTER_XT_MATCH_BPF=y
|
||||
CONFIG_NETFILTER_XT_MATCH_COMMENT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_MARK=y
|
||||
CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y
|
||||
CONFIG_NETFILTER_XT_MATCH_STATISTIC=y
|
||||
CONFIG_NETFILTER_XT_NAT=y
|
||||
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
|
||||
CONFIG_NET_IPGRE_BROADCAST=y
|
||||
CONFIG_NET_L3_MASTER_DEV=y
|
||||
CONFIG_NETLABEL=y
|
||||
CONFIG_NET_SCH_DEFAULT=y
|
||||
CONFIG_NET_SCHED=y
|
||||
CONFIG_NET_SCH_FQ_CODEL=y
|
||||
CONFIG_NET_TC_SKB_EXT=y
|
||||
CONFIG_NET_VRF=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_NF_CONNTRACK=y
|
||||
CONFIG_NF_NAT_MASQUERADE=y
|
||||
CONFIG_NF_NAT=y
|
||||
CONFIG_NLS_ASCII=y
|
||||
CONFIG_NLS_CODEPAGE_437=y
|
||||
CONFIG_NLS_DEFAULT="utf8"
|
||||
CONFIG_NO_HZ=y
|
||||
CONFIG_NR_CPUS=128
|
||||
CONFIG_NUMA_BALANCING=y
|
||||
CONFIG_NUMA=y
|
||||
CONFIG_NVMEM=y
|
||||
CONFIG_OSF_PARTITION=y
|
||||
CONFIG_OVERLAY_FS_INDEX=y
|
||||
CONFIG_OVERLAY_FS_METACOPY=y
|
||||
CONFIG_OVERLAY_FS_XINO_AUTO=y
|
||||
CONFIG_OVERLAY_FS=y
|
||||
CONFIG_PACKET=y
|
||||
CONFIG_PANIC_ON_OOPS=y
|
||||
CONFIG_PARTITION_ADVANCED=y
|
||||
CONFIG_PCIEPORTBUS=y
|
||||
CONFIG_PCI_IOV=y
|
||||
CONFIG_PCI_MSI=y
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PHYSICAL_ALIGN=0x1000000
|
||||
CONFIG_POSIX_MQUEUE=y
|
||||
CONFIG_POWER_SUPPLY=y
|
||||
CONFIG_PREEMPT=y
|
||||
CONFIG_PRINTK_TIME=y
|
||||
CONFIG_PROC_KCORE=y
|
||||
CONFIG_PROFILING=y
|
||||
CONFIG_PROVE_LOCKING=y
|
||||
CONFIG_PTP_1588_CLOCK=y
|
||||
CONFIG_RC_DEVICES=y
|
||||
CONFIG_RC_LOOPBACK=y
|
||||
CONFIG_RCU_CPU_STALL_TIMEOUT=60
|
||||
CONFIG_SCHED_STACK_END_CHECK=y
|
||||
CONFIG_SCHEDSTATS=y
|
||||
CONFIG_SECURITY_NETWORK=y
|
||||
CONFIG_SECURITY_SELINUX=y
|
||||
CONFIG_SERIAL_8250_CONSOLE=y
|
||||
CONFIG_SERIAL_8250_DETECT_IRQ=y
|
||||
CONFIG_SERIAL_8250_EXTENDED=y
|
||||
CONFIG_SERIAL_8250_MANY_PORTS=y
|
||||
CONFIG_SERIAL_8250_NR_UARTS=32
|
||||
CONFIG_SERIAL_8250_RSA=y
|
||||
CONFIG_SERIAL_8250_SHARE_IRQ=y
|
||||
CONFIG_SERIAL_8250=y
|
||||
CONFIG_SERIAL_NONSTANDARD=y
|
||||
CONFIG_SERIO_LIBPS2=y
|
||||
CONFIG_SGI_PARTITION=y
|
||||
CONFIG_SMP=y
|
||||
CONFIG_SOCK_CGROUP_DATA=y
|
||||
CONFIG_SOLARIS_X86_PARTITION=y
|
||||
CONFIG_SUN_PARTITION=y
|
||||
CONFIG_SYNC_FILE=y
|
||||
CONFIG_SYSVIPC=y
|
||||
CONFIG_TASK_DELAY_ACCT=y
|
||||
CONFIG_TASK_IO_ACCOUNTING=y
|
||||
CONFIG_TASKSTATS=y
|
||||
CONFIG_TASK_XACCT=y
|
||||
CONFIG_TCP_CONG_ADVANCED=y
|
||||
CONFIG_TCP_MD5SIG=y
|
||||
CONFIG_TLS=y
|
||||
CONFIG_TMPFS_POSIX_ACL=y
|
||||
CONFIG_TMPFS=y
|
||||
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
|
||||
CONFIG_TRANSPARENT_HUGEPAGE=y
|
||||
CONFIG_TUN=y
|
||||
CONFIG_UNIXWARE_DISKLABEL=y
|
||||
CONFIG_UNIX=y
|
||||
CONFIG_USER_NS=y
|
||||
CONFIG_VALIDATE_FS_PARSER=y
|
||||
CONFIG_VETH=y
|
||||
CONFIG_VIRT_DRIVERS=y
|
||||
CONFIG_VIRTIO_BALLOON=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
CONFIG_VIRTIO_CONSOLE=y
|
||||
CONFIG_VIRTIO_FS=y
|
||||
CONFIG_VIRTIO_NET=y
|
||||
CONFIG_VIRTIO_PCI=y
|
||||
CONFIG_VLAN_8021Q=y
|
||||
CONFIG_XFRM_SUB_POLICY=y
|
||||
CONFIG_XFRM_USER=y
|
||||
CONFIG_ZEROPLUS_FF=y
|
|
@ -0,0 +1,4 @@
|
|||
CONFIG_X86_ACPI_CPUFREQ=y
|
||||
CONFIG_X86_CPUID=y
|
||||
CONFIG_X86_MSR=y
|
||||
CONFIG_X86_POWERNOW_K8=y
|
|
@ -0,0 +1,869 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2022 Red Hat */
|
||||
#include "hid.skel.h"
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <fcntl.h>
|
||||
#include <fnmatch.h>
|
||||
#include <dirent.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <linux/hidraw.h>
|
||||
#include <linux/uhid.h>
|
||||
|
||||
#define SHOW_UHID_DEBUG 0
|
||||
|
||||
static unsigned char rdesc[] = {
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x09, 0x21, /* Usage (Vendor Usage 0x21) */
|
||||
0xa1, 0x01, /* COLLECTION (Application) */
|
||||
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
|
||||
0xa1, 0x00, /* COLLECTION (Physical) */
|
||||
0x85, 0x02, /* REPORT_ID (2) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (1) */
|
||||
0x29, 0x08, /* USAGE_MAXIMUM (3) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0xff, /* LOGICAL_MAXIMUM (255) */
|
||||
0x95, 0x08, /* REPORT_COUNT (8) */
|
||||
0x75, 0x08, /* REPORT_SIZE (8) */
|
||||
0x81, 0x02, /* INPUT (Data,Var,Abs) */
|
||||
0xc0, /* END_COLLECTION */
|
||||
0x09, 0x01, /* Usage (Vendor Usage 0x01) */
|
||||
0xa1, 0x00, /* COLLECTION (Physical) */
|
||||
0x85, 0x01, /* REPORT_ID (1) */
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (1) */
|
||||
0x29, 0x03, /* USAGE_MAXIMUM (3) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x95, 0x03, /* REPORT_COUNT (3) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x81, 0x02, /* INPUT (Data,Var,Abs) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x05, /* REPORT_SIZE (5) */
|
||||
0x81, 0x01, /* INPUT (Cnst,Var,Abs) */
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x30, /* USAGE (X) */
|
||||
0x09, 0x31, /* USAGE (Y) */
|
||||
0x15, 0x81, /* LOGICAL_MINIMUM (-127) */
|
||||
0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */
|
||||
0x75, 0x10, /* REPORT_SIZE (16) */
|
||||
0x95, 0x02, /* REPORT_COUNT (2) */
|
||||
0x81, 0x06, /* INPUT (Data,Var,Rel) */
|
||||
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (1) */
|
||||
0x29, 0x03, /* USAGE_MAXIMUM (3) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x95, 0x03, /* REPORT_COUNT (3) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x91, 0x02, /* Output (Data,Var,Abs) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x05, /* REPORT_SIZE (5) */
|
||||
0x91, 0x01, /* Output (Cnst,Var,Abs) */
|
||||
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x19, 0x06, /* USAGE_MINIMUM (6) */
|
||||
0x29, 0x08, /* USAGE_MAXIMUM (8) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x95, 0x03, /* REPORT_COUNT (3) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0xb1, 0x02, /* Feature (Data,Var,Abs) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x05, /* REPORT_SIZE (5) */
|
||||
0x91, 0x01, /* Output (Cnst,Var,Abs) */
|
||||
|
||||
0xc0, /* END_COLLECTION */
|
||||
0xc0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
static __u8 feature_data[] = { 1, 2 };
|
||||
|
||||
struct attach_prog_args {
|
||||
int prog_fd;
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
int insert_head;
|
||||
};
|
||||
|
||||
struct hid_hw_request_syscall_args {
|
||||
__u8 data[10];
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
size_t size;
|
||||
enum hid_report_type type;
|
||||
__u8 request_type;
|
||||
};
|
||||
|
||||
#define ASSERT_OK(data) ASSERT_FALSE(data)
|
||||
#define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr)
|
||||
|
||||
#define UHID_LOG(fmt, ...) do { \
|
||||
if (SHOW_UHID_DEBUG) \
|
||||
TH_LOG(fmt, ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
/* no need to protect uhid_stopped, only one thread accesses it */
|
||||
static bool uhid_stopped;
|
||||
|
||||
static int uhid_write(struct __test_metadata *_metadata, int fd, const struct uhid_event *ev)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
ret = write(fd, ev, sizeof(*ev));
|
||||
if (ret < 0) {
|
||||
TH_LOG("Cannot write to uhid: %m");
|
||||
return -errno;
|
||||
} else if (ret != sizeof(*ev)) {
|
||||
TH_LOG("Wrong size written to uhid: %zd != %zu",
|
||||
ret, sizeof(ev));
|
||||
return -EFAULT;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
char buf[25];
|
||||
|
||||
sprintf(buf, "test-uhid-device-%d", rand_nb);
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_CREATE;
|
||||
strcpy((char *)ev.u.create.name, buf);
|
||||
ev.u.create.rd_data = rdesc;
|
||||
ev.u.create.rd_size = sizeof(rdesc);
|
||||
ev.u.create.bus = BUS_USB;
|
||||
ev.u.create.vendor = 0x0001;
|
||||
ev.u.create.product = 0x0a37;
|
||||
ev.u.create.version = 0;
|
||||
ev.u.create.country = 0;
|
||||
|
||||
sprintf(buf, "%d", rand_nb);
|
||||
strcpy((char *)ev.u.create.phys, buf);
|
||||
|
||||
return uhid_write(_metadata, fd, &ev);
|
||||
}
|
||||
|
||||
static void uhid_destroy(struct __test_metadata *_metadata, int fd)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_DESTROY;
|
||||
|
||||
uhid_write(_metadata, fd, &ev);
|
||||
}
|
||||
|
||||
static int uhid_event(struct __test_metadata *_metadata, int fd)
|
||||
{
|
||||
struct uhid_event ev, answer;
|
||||
ssize_t ret;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ret = read(fd, &ev, sizeof(ev));
|
||||
if (ret == 0) {
|
||||
UHID_LOG("Read HUP on uhid-cdev");
|
||||
return -EFAULT;
|
||||
} else if (ret < 0) {
|
||||
UHID_LOG("Cannot read uhid-cdev: %m");
|
||||
return -errno;
|
||||
} else if (ret != sizeof(ev)) {
|
||||
UHID_LOG("Invalid size read from uhid-dev: %zd != %zu",
|
||||
ret, sizeof(ev));
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
switch (ev.type) {
|
||||
case UHID_START:
|
||||
pthread_mutex_lock(&uhid_started_mtx);
|
||||
pthread_cond_signal(&uhid_started);
|
||||
pthread_mutex_unlock(&uhid_started_mtx);
|
||||
|
||||
UHID_LOG("UHID_START from uhid-dev");
|
||||
break;
|
||||
case UHID_STOP:
|
||||
uhid_stopped = true;
|
||||
|
||||
UHID_LOG("UHID_STOP from uhid-dev");
|
||||
break;
|
||||
case UHID_OPEN:
|
||||
UHID_LOG("UHID_OPEN from uhid-dev");
|
||||
break;
|
||||
case UHID_CLOSE:
|
||||
UHID_LOG("UHID_CLOSE from uhid-dev");
|
||||
break;
|
||||
case UHID_OUTPUT:
|
||||
UHID_LOG("UHID_OUTPUT from uhid-dev");
|
||||
break;
|
||||
case UHID_GET_REPORT:
|
||||
UHID_LOG("UHID_GET_REPORT from uhid-dev");
|
||||
|
||||
answer.type = UHID_GET_REPORT_REPLY;
|
||||
answer.u.get_report_reply.id = ev.u.get_report.id;
|
||||
answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO;
|
||||
answer.u.get_report_reply.size = sizeof(feature_data);
|
||||
memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data));
|
||||
|
||||
uhid_write(_metadata, fd, &answer);
|
||||
|
||||
break;
|
||||
case UHID_SET_REPORT:
|
||||
UHID_LOG("UHID_SET_REPORT from uhid-dev");
|
||||
break;
|
||||
default:
|
||||
TH_LOG("Invalid event from uhid-dev: %u", ev.type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct uhid_thread_args {
|
||||
int fd;
|
||||
struct __test_metadata *_metadata;
|
||||
};
|
||||
static void *uhid_read_events_thread(void *arg)
|
||||
{
|
||||
struct uhid_thread_args *args = (struct uhid_thread_args *)arg;
|
||||
struct __test_metadata *_metadata = args->_metadata;
|
||||
struct pollfd pfds[1];
|
||||
int fd = args->fd;
|
||||
int ret = 0;
|
||||
|
||||
pfds[0].fd = fd;
|
||||
pfds[0].events = POLLIN;
|
||||
|
||||
uhid_stopped = false;
|
||||
|
||||
while (!uhid_stopped) {
|
||||
ret = poll(pfds, 1, 100);
|
||||
if (ret < 0) {
|
||||
TH_LOG("Cannot poll for fds: %m");
|
||||
break;
|
||||
}
|
||||
if (pfds[0].revents & POLLIN) {
|
||||
ret = uhid_event(_metadata, fd);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (void *)(long)ret;
|
||||
}
|
||||
|
||||
static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid, int uhid_fd)
|
||||
{
|
||||
struct uhid_thread_args args = {
|
||||
.fd = uhid_fd,
|
||||
._metadata = _metadata,
|
||||
};
|
||||
int err;
|
||||
|
||||
pthread_mutex_lock(&uhid_started_mtx);
|
||||
err = pthread_create(tid, NULL, uhid_read_events_thread, (void *)&args);
|
||||
ASSERT_EQ(0, err) {
|
||||
TH_LOG("Could not start the uhid thread: %d", err);
|
||||
pthread_mutex_unlock(&uhid_started_mtx);
|
||||
close(uhid_fd);
|
||||
return -EIO;
|
||||
}
|
||||
pthread_cond_wait(&uhid_started, &uhid_started_mtx);
|
||||
pthread_mutex_unlock(&uhid_started_mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf, size_t size)
|
||||
{
|
||||
struct uhid_event ev;
|
||||
|
||||
if (size > sizeof(ev.u.input.data))
|
||||
return -E2BIG;
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.type = UHID_INPUT2;
|
||||
ev.u.input2.size = size;
|
||||
|
||||
memcpy(ev.u.input2.data, buf, size);
|
||||
|
||||
return uhid_write(_metadata, fd, &ev);
|
||||
}
|
||||
|
||||
static int setup_uhid(struct __test_metadata *_metadata, int rand_nb)
|
||||
{
|
||||
int fd;
|
||||
const char *path = "/dev/uhid";
|
||||
int ret;
|
||||
|
||||
fd = open(path, O_RDWR | O_CLOEXEC);
|
||||
ASSERT_GE(fd, 0) TH_LOG("open uhid-cdev failed; %d", fd);
|
||||
|
||||
ret = uhid_create(_metadata, fd, rand_nb);
|
||||
ASSERT_EQ(0, ret) {
|
||||
TH_LOG("create uhid device failed: %d", ret);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *dir)
|
||||
{
|
||||
const char *target = "0003:0001:0A37.*";
|
||||
char phys[512];
|
||||
char uevent[1024];
|
||||
char temp[512];
|
||||
int fd, nread;
|
||||
bool found = false;
|
||||
|
||||
if (fnmatch(target, dir->d_name, 0))
|
||||
return false;
|
||||
|
||||
/* we found the correct VID/PID, now check for phys */
|
||||
sprintf(uevent, "%s/%s/uevent", workdir, dir->d_name);
|
||||
|
||||
fd = open(uevent, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
sprintf(phys, "PHYS=%d", dev_id);
|
||||
|
||||
nread = read(fd, temp, ARRAY_SIZE(temp));
|
||||
if (nread > 0 && (strstr(temp, phys)) != NULL)
|
||||
found = true;
|
||||
|
||||
close(fd);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static int get_hid_id(int dev_id)
|
||||
{
|
||||
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
||||
const char *str_id;
|
||||
DIR *d;
|
||||
struct dirent *dir;
|
||||
int found = -1, attempts = 3;
|
||||
|
||||
/* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */
|
||||
|
||||
while (found < 0 && attempts > 0) {
|
||||
attempts--;
|
||||
d = opendir(workdir);
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!match_sysfs_device(dev_id, workdir, dir))
|
||||
continue;
|
||||
|
||||
str_id = dir->d_name + sizeof("0003:0001:0A37.");
|
||||
found = (int)strtol(str_id, NULL, 16);
|
||||
|
||||
break;
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
if (found < 0)
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static int get_hidraw(int dev_id)
|
||||
{
|
||||
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
||||
char sysfs[1024];
|
||||
DIR *d, *subd;
|
||||
struct dirent *dir, *subdir;
|
||||
int i, found = -1;
|
||||
|
||||
/* retry 5 times in case the system is loaded */
|
||||
for (i = 5; i > 0; i--) {
|
||||
usleep(10);
|
||||
d = opendir(workdir);
|
||||
|
||||
if (!d)
|
||||
continue;
|
||||
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (!match_sysfs_device(dev_id, workdir, dir))
|
||||
continue;
|
||||
|
||||
sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
|
||||
|
||||
subd = opendir(sysfs);
|
||||
if (!subd)
|
||||
continue;
|
||||
|
||||
while ((subdir = readdir(subd)) != NULL) {
|
||||
if (fnmatch("hidraw*", subdir->d_name, 0))
|
||||
continue;
|
||||
|
||||
found = atoi(subdir->d_name + strlen("hidraw"));
|
||||
}
|
||||
|
||||
closedir(subd);
|
||||
|
||||
if (found > 0)
|
||||
break;
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static int open_hidraw(int dev_id)
|
||||
{
|
||||
int hidraw_number;
|
||||
char hidraw_path[64] = { 0 };
|
||||
|
||||
hidraw_number = get_hidraw(dev_id);
|
||||
if (hidraw_number < 0)
|
||||
return hidraw_number;
|
||||
|
||||
/* open hidraw node to check the other side of the pipe */
|
||||
sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
|
||||
return open(hidraw_path, O_RDWR | O_NONBLOCK);
|
||||
}
|
||||
|
||||
FIXTURE(hid_bpf) {
|
||||
int dev_id;
|
||||
int uhid_fd;
|
||||
int hidraw_fd;
|
||||
int hid_id;
|
||||
pthread_t tid;
|
||||
struct hid *skel;
|
||||
int hid_links[3]; /* max number of programs loaded in a single test */
|
||||
};
|
||||
static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (self->hidraw_fd)
|
||||
close(self->hidraw_fd);
|
||||
self->hidraw_fd = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) {
|
||||
if (self->hid_links[i])
|
||||
close(self->hid_links[i]);
|
||||
}
|
||||
|
||||
hid__destroy(self->skel);
|
||||
self->skel = NULL;
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(hid_bpf) {
|
||||
void *uhid_err;
|
||||
|
||||
uhid_destroy(_metadata, self->uhid_fd);
|
||||
|
||||
detach_bpf(self);
|
||||
pthread_join(self->tid, &uhid_err);
|
||||
}
|
||||
#define TEARDOWN_LOG(fmt, ...) do { \
|
||||
TH_LOG(fmt, ##__VA_ARGS__); \
|
||||
hid_bpf_teardown(_metadata, self, variant); \
|
||||
} while (0)
|
||||
|
||||
FIXTURE_SETUP(hid_bpf)
|
||||
{
|
||||
time_t t;
|
||||
int err;
|
||||
|
||||
/* initialize random number generator */
|
||||
srand((unsigned int)time(&t));
|
||||
|
||||
self->dev_id = rand() % 1024;
|
||||
|
||||
self->uhid_fd = setup_uhid(_metadata, self->dev_id);
|
||||
|
||||
/* locate the uev, self, variant);ent file of the created device */
|
||||
self->hid_id = get_hid_id(self->dev_id);
|
||||
ASSERT_GT(self->hid_id, 0)
|
||||
TEARDOWN_LOG("Could not locate uhid device id: %d", self->hid_id);
|
||||
|
||||
err = uhid_start_listener(_metadata, &self->tid, self->uhid_fd);
|
||||
ASSERT_EQ(0, err) TEARDOWN_LOG("could not start udev listener: %d", err);
|
||||
}
|
||||
|
||||
struct test_program {
|
||||
const char *name;
|
||||
int insert_head;
|
||||
};
|
||||
#define LOAD_PROGRAMS(progs) \
|
||||
load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant)
|
||||
#define LOAD_BPF \
|
||||
load_programs(NULL, 0, _metadata, self, variant)
|
||||
static void load_programs(const struct test_program programs[],
|
||||
const size_t progs_count,
|
||||
struct __test_metadata *_metadata,
|
||||
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),
|
||||
);
|
||||
|
||||
ASSERT_LE(progs_count, ARRAY_SIZE(self->hid_links))
|
||||
TH_LOG("too many programs are to be loaded");
|
||||
|
||||
/* open the bpf file */
|
||||
self->skel = hid__open();
|
||||
ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open");
|
||||
|
||||
for (int i = 0; i < progs_count; i++) {
|
||||
struct bpf_program *prog;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
self->hid_links[i] = args.retval;
|
||||
}
|
||||
|
||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||
}
|
||||
|
||||
/*
|
||||
* A simple test to see if the fixture is working fine.
|
||||
* If this fails, none of the other tests will pass.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_create_uhid)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, raw_event)
|
||||
{
|
||||
const struct test_program progs[] = {
|
||||
{ .name = "hid_first_event" },
|
||||
};
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
LOAD_PROGRAMS(progs);
|
||||
|
||||
/* check that the program is correctly loaded */
|
||||
ASSERT_EQ(self->skel->data->callback_check, 52) TH_LOG("callback_check1");
|
||||
ASSERT_EQ(self->skel->data->callback2_check, 52) TH_LOG("callback2_check1");
|
||||
|
||||
/* inject one event */
|
||||
buf[0] = 1;
|
||||
buf[1] = 42;
|
||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
||||
|
||||
/* check that hid_first_event() was executed */
|
||||
ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1");
|
||||
|
||||
/* 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);
|
||||
|
||||
/* check that hid_first_event() was executed */
|
||||
ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1");
|
||||
|
||||
/* 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
|
||||
*/
|
||||
TEST_F(hid_bpf, test_attach_detach)
|
||||
{
|
||||
const struct test_program progs[] = {
|
||||
{ .name = "hid_first_event" },
|
||||
{ .name = "hid_second_event" },
|
||||
};
|
||||
__u8 buf[10] = {0};
|
||||
int err, link;
|
||||
|
||||
LOAD_PROGRAMS(progs);
|
||||
|
||||
link = self->hid_links[0];
|
||||
ASSERT_GT(link, 0) TH_LOG("HID-BPF link not created");
|
||||
|
||||
/* 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);
|
||||
|
||||
/* make sure both programs are run */
|
||||
ASSERT_EQ(buf[3], 52);
|
||||
|
||||
/* pin the first program and immediately unpin it */
|
||||
#define PIN_PATH "/sys/fs/bpf/hid_first_event"
|
||||
err = bpf_obj_pin(link, PIN_PATH);
|
||||
ASSERT_OK(err) TH_LOG("error while calling bpf_obj_pin");
|
||||
remove(PIN_PATH);
|
||||
#undef PIN_PATH
|
||||
usleep(100000);
|
||||
|
||||
/* detach the program */
|
||||
detach_bpf(self);
|
||||
|
||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||
|
||||
/* 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_no_bpf");
|
||||
ASSERT_EQ(buf[0], 1);
|
||||
ASSERT_EQ(buf[1], 47);
|
||||
ASSERT_EQ(buf[2], 0);
|
||||
ASSERT_EQ(buf[3], 0);
|
||||
|
||||
/* re-attach our program */
|
||||
|
||||
LOAD_PROGRAMS(progs);
|
||||
|
||||
/* inject one event */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
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);
|
||||
ASSERT_EQ(buf[3], 52);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach hid_change_report_id 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, test_hid_change_report)
|
||||
{
|
||||
const struct test_program progs[] = {
|
||||
{ .name = "hid_change_report_id" },
|
||||
};
|
||||
__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], 42);
|
||||
ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test");
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach hid_user_raw_request to the given uhid device,
|
||||
* call the bpf program from userspace
|
||||
* check that the program is called and does the expected.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_hid_user_raw_request_call)
|
||||
{
|
||||
struct hid_hw_request_syscall_args args = {
|
||||
.retval = -1,
|
||||
.type = HID_FEATURE_REPORT,
|
||||
.request_type = HID_REQ_GET_REPORT,
|
||||
.size = 10,
|
||||
};
|
||||
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
|
||||
.ctx_in = &args,
|
||||
.ctx_size_in = sizeof(args),
|
||||
);
|
||||
int err, prog_fd;
|
||||
|
||||
LOAD_BPF;
|
||||
|
||||
args.hid = self->hid_id;
|
||||
args.data[0] = 1; /* report ID */
|
||||
|
||||
prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request);
|
||||
|
||||
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
|
||||
ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
|
||||
|
||||
ASSERT_EQ(args.retval, 2);
|
||||
|
||||
ASSERT_EQ(args.data[1], 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach hid_insert{0,1,2} to the given uhid device,
|
||||
* retrieve and open the matching hidraw node,
|
||||
* inject one event in the uhid device,
|
||||
* check that the programs have been inserted in the correct order.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_hid_attach_flags)
|
||||
{
|
||||
const struct test_program progs[] = {
|
||||
{
|
||||
.name = "hid_test_insert2",
|
||||
.insert_head = 0,
|
||||
},
|
||||
{
|
||||
.name = "hid_test_insert1",
|
||||
.insert_head = 1,
|
||||
},
|
||||
{
|
||||
.name = "hid_test_insert3",
|
||||
.insert_head = 0,
|
||||
},
|
||||
};
|
||||
__u8 buf[10] = {0};
|
||||
int err;
|
||||
|
||||
LOAD_PROGRAMS(progs);
|
||||
|
||||
/* inject one event */
|
||||
buf[0] = 1;
|
||||
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[1], 1);
|
||||
ASSERT_EQ(buf[2], 2);
|
||||
ASSERT_EQ(buf[3], 3);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attach hid_rdesc_fixup to the given uhid device,
|
||||
* retrieve and open the matching hidraw node,
|
||||
* check that the hidraw report descriptor has been updated.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_rdesc_fixup)
|
||||
{
|
||||
struct hidraw_report_descriptor rpt_desc = {0};
|
||||
const struct test_program progs[] = {
|
||||
{ .name = "hid_rdesc_fixup" },
|
||||
};
|
||||
int err, desc_size;
|
||||
|
||||
LOAD_PROGRAMS(progs);
|
||||
|
||||
/* check that hid_rdesc_fixup() was executed */
|
||||
ASSERT_EQ(self->skel->data->callback2_check, 0x21);
|
||||
|
||||
/* read the exposed report descriptor from hidraw */
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
|
||||
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESCSIZE: %d", err);
|
||||
|
||||
/* ensure the new size of the rdesc is bigger than the old one */
|
||||
ASSERT_GT(desc_size, sizeof(rdesc));
|
||||
|
||||
rpt_desc.size = desc_size;
|
||||
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &rpt_desc);
|
||||
ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESC: %d", err);
|
||||
|
||||
ASSERT_EQ(rpt_desc.value[4], 0x42);
|
||||
}
|
||||
|
||||
static int libbpf_print_fn(enum libbpf_print_level level,
|
||||
const char *format, va_list args)
|
||||
{
|
||||
char buf[1024];
|
||||
|
||||
if (level == LIBBPF_DEBUG)
|
||||
return 0;
|
||||
|
||||
snprintf(buf, sizeof(buf), "# %s", format);
|
||||
|
||||
vfprintf(stdout, buf, args);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __attribute__((constructor)) __constructor_order_last(void)
|
||||
{
|
||||
if (!__constructor_order)
|
||||
__constructor_order = _CONSTRUCTOR_ORDER_BACKWARD;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
/* Use libbpf 1.0 API mode */
|
||||
libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
|
||||
return test_harness_run(argc, argv);
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2022 Red hat */
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "hid_bpf_helpers.h"
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct attach_prog_args {
|
||||
int prog_fd;
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
int insert_head;
|
||||
};
|
||||
|
||||
__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)
|
||||
{
|
||||
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
|
||||
|
||||
if (!rw_data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
callback_check = rw_data[1];
|
||||
|
||||
rw_data[2] = rw_data[1] + 5;
|
||||
|
||||
return hid_ctx->size;
|
||||
}
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
|
||||
|
||||
if (!rw_data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
rw_data[3] = rw_data[2] + 5;
|
||||
|
||||
return hid_ctx->size;
|
||||
}
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */);
|
||||
|
||||
if (!rw_data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
rw_data[0] = 2;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
struct hid_hw_request_syscall_args {
|
||||
/* data needs to come at offset 0 so we can use it in calls */
|
||||
__u8 data[10];
|
||||
unsigned int hid;
|
||||
int retval;
|
||||
size_t size;
|
||||
enum hid_report_type type;
|
||||
__u8 request_type;
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int hid_user_raw_request(struct hid_hw_request_syscall_args *args)
|
||||
{
|
||||
struct hid_bpf_ctx *ctx;
|
||||
const size_t size = args->size;
|
||||
int i, ret = 0;
|
||||
|
||||
if (size > sizeof(args->data))
|
||||
return -7; /* -E2BIG */
|
||||
|
||||
ctx = hid_bpf_allocate_context(args->hid);
|
||||
if (!ctx)
|
||||
return -1; /* EPERM check */
|
||||
|
||||
ret = hid_bpf_hw_request(ctx,
|
||||
args->data,
|
||||
size,
|
||||
args->type,
|
||||
args->request_type);
|
||||
args->retval = ret;
|
||||
|
||||
hid_bpf_release_context(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const __u8 rdesc[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x32, /* USAGE (Z) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x81, 0x06, /* INPUT (Data,Var,Rel) */
|
||||
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x19, 0x01, /* USAGE_MINIMUM (1) */
|
||||
0x29, 0x03, /* USAGE_MAXIMUM (3) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x95, 0x03, /* REPORT_COUNT (3) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0x91, 0x02, /* Output (Data,Var,Abs) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x05, /* REPORT_SIZE (5) */
|
||||
0x91, 0x01, /* Output (Cnst,Var,Abs) */
|
||||
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x19, 0x06, /* USAGE_MINIMUM (6) */
|
||||
0x29, 0x08, /* USAGE_MAXIMUM (8) */
|
||||
0x15, 0x00, /* LOGICAL_MINIMUM (0) */
|
||||
0x25, 0x01, /* LOGICAL_MAXIMUM (1) */
|
||||
0x95, 0x03, /* REPORT_COUNT (3) */
|
||||
0x75, 0x01, /* REPORT_SIZE (1) */
|
||||
0xb1, 0x02, /* Feature (Data,Var,Abs) */
|
||||
0x95, 0x01, /* REPORT_COUNT (1) */
|
||||
0x75, 0x05, /* REPORT_SIZE (5) */
|
||||
0x91, 0x01, /* Output (Cnst,Var,Abs) */
|
||||
|
||||
0xc0, /* END_COLLECTION */
|
||||
0xc0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_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 */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
callback2_check = data[4];
|
||||
|
||||
/* insert rdesc at offset 73 */
|
||||
__builtin_memcpy(&data[73], rdesc, sizeof(rdesc));
|
||||
|
||||
/* Change Usage Vendor globally */
|
||||
data[4] = 0x42;
|
||||
|
||||
return sizeof(rdesc) + 73;
|
||||
}
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* we need to be run first */
|
||||
if (data[2] || data[3])
|
||||
return -1;
|
||||
|
||||
data[1] = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* after insert0 and before insert2 */
|
||||
if (!data[1] || data[3])
|
||||
return -1;
|
||||
|
||||
data[2] = 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("?fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* at the end */
|
||||
if (!data[1] || !data[2])
|
||||
return -1;
|
||||
|
||||
data[3] = 3;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#ifndef __HID_BPF_HELPERS_H
|
||||
#define __HID_BPF_HELPERS_H
|
||||
|
||||
/* 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,
|
||||
__u8 *data,
|
||||
size_t buf__sz,
|
||||
enum hid_report_type type,
|
||||
enum hid_class_request reqtype) __ksym;
|
||||
|
||||
#endif /* __HID_BPF_HELPERS_H */
|
|
@ -0,0 +1,284 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
set -u
|
||||
set -e
|
||||
|
||||
# This script currently only works for x86_64
|
||||
ARCH="$(uname -m)"
|
||||
case "${ARCH}" in
|
||||
x86_64)
|
||||
QEMU_BINARY=qemu-system-x86_64
|
||||
BZIMAGE="arch/x86/boot/bzImage"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
DEFAULT_COMMAND="./hid_bpf"
|
||||
SCRIPT_DIR="$(dirname $(realpath $0))"
|
||||
OUTPUT_DIR="$SCRIPT_DIR/results"
|
||||
KCONFIG_REL_PATHS=("${SCRIPT_DIR}/config" "${SCRIPT_DIR}/config.common" "${SCRIPT_DIR}/config.${ARCH}")
|
||||
B2C_URL="https://gitlab.freedesktop.org/mupuf/boot2container/-/raw/master/vm2c.py"
|
||||
NUM_COMPILE_JOBS="$(nproc)"
|
||||
LOG_FILE_BASE="$(date +"hid_selftests.%Y-%m-%d_%H-%M-%S")"
|
||||
LOG_FILE="${LOG_FILE_BASE}.log"
|
||||
EXIT_STATUS_FILE="${LOG_FILE_BASE}.exit_status"
|
||||
CONTAINER_IMAGE="registry.fedoraproject.org/fedora:36"
|
||||
|
||||
usage()
|
||||
{
|
||||
cat <<EOF
|
||||
Usage: $0 [-i] [-s] [-d <output_dir>] -- [<command>]
|
||||
|
||||
<command> is the command you would normally run when you are in
|
||||
tools/testing/selftests/bpf. e.g:
|
||||
|
||||
$0 -- ./hid_bpf
|
||||
|
||||
If no command is specified and a debug shell (-s) is not requested,
|
||||
"${DEFAULT_COMMAND}" will be run by default.
|
||||
|
||||
If you build your kernel using KBUILD_OUTPUT= or O= options, these
|
||||
can be passed as environment variables to the script:
|
||||
|
||||
O=<kernel_build_path> $0 -- ./hid_bpf
|
||||
|
||||
or
|
||||
|
||||
KBUILD_OUTPUT=<kernel_build_path> $0 -- ./hid_bpf
|
||||
|
||||
Options:
|
||||
|
||||
-u) Update the boot2container script to a newer version.
|
||||
-d) Update the output directory (default: ${OUTPUT_DIR})
|
||||
-j) Number of jobs for compilation, similar to -j in make
|
||||
(default: ${NUM_COMPILE_JOBS})
|
||||
-s) Instead of powering off the VM, start an interactive
|
||||
shell. If <command> is specified, the shell runs after
|
||||
the command finishes executing
|
||||
EOF
|
||||
}
|
||||
|
||||
download()
|
||||
{
|
||||
local file="$1"
|
||||
|
||||
echo "Downloading $file..." >&2
|
||||
curl -Lsf "$file" -o "${@:2}"
|
||||
}
|
||||
|
||||
recompile_kernel()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local make_command="$2"
|
||||
|
||||
cd "${kernel_checkout}"
|
||||
|
||||
${make_command} olddefconfig
|
||||
${make_command}
|
||||
}
|
||||
|
||||
update_selftests()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local selftests_dir="${kernel_checkout}/tools/testing/selftests/hid"
|
||||
|
||||
cd "${selftests_dir}"
|
||||
${make_command}
|
||||
}
|
||||
|
||||
run_vm()
|
||||
{
|
||||
local b2c="$1"
|
||||
local kernel_bzimage="$2"
|
||||
local command="$3"
|
||||
local post_command=""
|
||||
|
||||
if ! which "${QEMU_BINARY}" &> /dev/null; then
|
||||
cat <<EOF
|
||||
Could not find ${QEMU_BINARY}
|
||||
Please install qemu or set the QEMU_BINARY environment variable.
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# alpine (used in post-container requires the PATH to have /bin
|
||||
export PATH=$PATH:/bin
|
||||
|
||||
if [[ "${debug_shell}" != "yes" ]]
|
||||
then
|
||||
touch ${OUTPUT_DIR}/${LOG_FILE}
|
||||
command="mount bpffs -t bpf /sys/fs/bpf/; set -o pipefail ; ${command} 2>&1 | tee ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
post_command="cat ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
else
|
||||
command="mount bpffs -t bpf /sys/fs/bpf/; ${command}"
|
||||
fi
|
||||
|
||||
set +e
|
||||
$b2c --command "${command}" \
|
||||
--kernel ${kernel_bzimage} \
|
||||
--workdir ${OUTPUT_DIR} \
|
||||
--image ${CONTAINER_IMAGE}
|
||||
|
||||
echo $? > ${OUTPUT_DIR}/${EXIT_STATUS_FILE}
|
||||
|
||||
set -e
|
||||
|
||||
${post_command}
|
||||
}
|
||||
|
||||
is_rel_path()
|
||||
{
|
||||
local path="$1"
|
||||
|
||||
[[ ${path:0:1} != "/" ]]
|
||||
}
|
||||
|
||||
do_update_kconfig()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local kconfig_file="$2"
|
||||
|
||||
rm -f "$kconfig_file" 2> /dev/null
|
||||
|
||||
for config in "${KCONFIG_REL_PATHS[@]}"; do
|
||||
local kconfig_src="${config}"
|
||||
cat "$kconfig_src" >> "$kconfig_file"
|
||||
done
|
||||
}
|
||||
|
||||
update_kconfig()
|
||||
{
|
||||
local kernel_checkout="$1"
|
||||
local kconfig_file="$2"
|
||||
|
||||
if [[ -f "${kconfig_file}" ]]; then
|
||||
local local_modified="$(stat -c %Y "${kconfig_file}")"
|
||||
|
||||
for config in "${KCONFIG_REL_PATHS[@]}"; do
|
||||
local kconfig_src="${config}"
|
||||
local src_modified="$(stat -c %Y "${kconfig_src}")"
|
||||
# Only update the config if it has been updated after the
|
||||
# previously cached config was created. This avoids
|
||||
# unnecessarily compiling the kernel and selftests.
|
||||
if [[ "${src_modified}" -gt "${local_modified}" ]]; then
|
||||
do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
||||
# Once we have found one outdated configuration
|
||||
# there is no need to check other ones.
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
do_update_kconfig "$kernel_checkout" "$kconfig_file"
|
||||
fi
|
||||
}
|
||||
|
||||
main()
|
||||
{
|
||||
local script_dir="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
||||
local kernel_checkout=$(realpath "${script_dir}"/../../../../)
|
||||
# By default the script searches for the kernel in the checkout directory but
|
||||
# it also obeys environment variables O= and KBUILD_OUTPUT=
|
||||
local kernel_bzimage="${kernel_checkout}/${BZIMAGE}"
|
||||
local command="${DEFAULT_COMMAND}"
|
||||
local update_b2c="no"
|
||||
local debug_shell="no"
|
||||
|
||||
while getopts ':hsud:j:' opt; do
|
||||
case ${opt} in
|
||||
u)
|
||||
update_b2c="yes"
|
||||
;;
|
||||
d)
|
||||
OUTPUT_DIR="$OPTARG"
|
||||
;;
|
||||
j)
|
||||
NUM_COMPILE_JOBS="$OPTARG"
|
||||
;;
|
||||
s)
|
||||
command="/bin/sh"
|
||||
debug_shell="yes"
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
\? )
|
||||
echo "Invalid Option: -$OPTARG"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
: )
|
||||
echo "Invalid Option: -$OPTARG requires an argument"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND -1))
|
||||
|
||||
# trap 'catch "$?"' EXIT
|
||||
|
||||
if [[ "${debug_shell}" == "no" ]]; then
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "No command specified, will run ${DEFAULT_COMMAND} in the vm"
|
||||
else
|
||||
command="$@"
|
||||
|
||||
if [[ "${command}" == "/bin/bash" || "${command}" == "bash" ]]
|
||||
then
|
||||
debug_shell="yes"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
local kconfig_file="${OUTPUT_DIR}/latest.config"
|
||||
local make_command="make -j ${NUM_COMPILE_JOBS} KCONFIG_CONFIG=${kconfig_file}"
|
||||
|
||||
# Figure out where the kernel is being built.
|
||||
# O takes precedence over KBUILD_OUTPUT.
|
||||
if [[ "${O:=""}" != "" ]]; then
|
||||
if is_rel_path "${O}"; then
|
||||
O="$(realpath "${PWD}/${O}")"
|
||||
fi
|
||||
kernel_bzimage="${O}/${BZIMAGE}"
|
||||
make_command="${make_command} O=${O}"
|
||||
elif [[ "${KBUILD_OUTPUT:=""}" != "" ]]; then
|
||||
if is_rel_path "${KBUILD_OUTPUT}"; then
|
||||
KBUILD_OUTPUT="$(realpath "${PWD}/${KBUILD_OUTPUT}")"
|
||||
fi
|
||||
kernel_bzimage="${KBUILD_OUTPUT}/${BZIMAGE}"
|
||||
make_command="${make_command} KBUILD_OUTPUT=${KBUILD_OUTPUT}"
|
||||
fi
|
||||
|
||||
local b2c="${OUTPUT_DIR}/vm2c.py"
|
||||
|
||||
echo "Output directory: ${OUTPUT_DIR}"
|
||||
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
update_kconfig "${kernel_checkout}" "${kconfig_file}"
|
||||
|
||||
recompile_kernel "${kernel_checkout}" "${make_command}"
|
||||
|
||||
if [[ "${update_b2c}" == "no" && ! -f "${b2c}" ]]; then
|
||||
echo "vm2c script not found in ${b2c}"
|
||||
update_b2c="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_b2c}" == "yes" ]]; then
|
||||
download $B2C_URL $b2c
|
||||
chmod +x $b2c
|
||||
fi
|
||||
|
||||
update_selftests "${kernel_checkout}" "${make_command}"
|
||||
run_vm $b2c "${kernel_bzimage}" "${command}"
|
||||
if [[ "${debug_shell}" != "yes" ]]; then
|
||||
echo "Logs saved in ${OUTPUT_DIR}/${LOG_FILE}"
|
||||
fi
|
||||
|
||||
exit $(cat ${OUTPUT_DIR}/${EXIT_STATUS_FILE})
|
||||
}
|
||||
|
||||
main "$@"
|
Loading…
Reference in New Issue