for-linus-2023022201

-----BEGIN PGP SIGNATURE-----
 
 iQJSBAABCAA8FiEEoEVH9lhNrxiMPSyI7MXwXhnZSjYFAmP15aoeHGJlbmphbWlu
 LnRpc3NvaXJlc0ByZWRoYXQuY29tAAoJEOzF8F4Z2Uo2nwkP/Rcr5lZYKQ59ezoJ
 PwHx/0wO1Qpgd4fRwD5Mvxynmfoq20A6FZFpmtjUjNcP3dm3X2UJtXE3HkDECCFP
 5daqTFOswFiPFtcMlIl+SHxNgIIlTodbqx9MAFj/7n5aihB54JpTHWsgbENj35Y1
 RLHYDi0+wj69y9ctOkqKGWHp8Uf220RWrD7zZf7AJAc5cwot1kM00RSy9dSAJ0vB
 riZdCQqYwXbf4I1uFzthS6AdIIWcpmpZyYFnsF7F2xQADqCNXr1MTmG0uC+Ey/J/
 0PZXjkyMO/pNwxarRiXmBKnsJJlJajxRXGtpgKYZu8AnIaXuNu/z987Y1pZ/kYis
 OQSKib2bGWzT2MycqiuVVrOChIMvqmk2aPRvg73jojpSySYTKn1Jp/XHyXS8qkkQ
 HJ/u6VPpe42GdfOGM7V9ig+80z/5D5u1XJECoPpxyKHWQ8S7ZlczQjVT+a8nUuBV
 hPTiqAYE9NT6SQJ0b5z2uhBdGRzvAbCZzDCgvjE87zsRmLzFk/fzMdMPZrlADKMJ
 3qu7ey2GYOBNfnDcJmPu5HmK/A9BaPZiZAMakqjGpezZbe+LGBNskXTRwPpNC+Dh
 11pna0ns+vlxeT7nonO0JsYvsKWy0pPcBvhyUEHWsmHgyagRLIsvg01ezB/Ivu0O
 xYj3UPVEGTiwHBt9xnaZcIjRSPSZ
 =cBvC
 -----END PGP SIGNATURE-----

Merge tag 'for-linus-2023022201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID updates from Benjamin Tissoires:

 - HID-BPF infrastructure: this allows to start using HID-BPF. Note that
   the mechanism to ship HID-BPF program through the kernel tree is
   still not implemented yet (but is planned).

   This should be a no-op for 99% of users. Also we are gaining
   kselftests for the HID tree (Benjamin Tissoires)

 - Some UAF fixes in workers when using uhid (Pietro Borrello & Benjamin
   Tissoires)

 - Constify hid_ll_driver (Thomas Weißschuh)

 - Allow more custom IIO sensors through HID (Philipp Jungkamp)

 - Logitech HID++ fixes for scroll wheel, protocol and debug (Bastien
   Nocera)

 - Some new device support: Steam Deck (Vicki Pfau), UClogic (José
   Expósito), Logitech G923 Xbox Edition steering wheel (Walt Holman),
   EVision keyboards (Philippe Valembois)

 - other assorted code cleanups and fixes

* tag 'for-linus-2023022201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (99 commits)
  HID: mcp-2221: prevent UAF in delayed work
  hid: bigben_probe(): validate report count
  HID: asus: use spinlock to safely schedule workers
  HID: asus: use spinlock to protect concurrent accesses
  HID: bigben: use spinlock to safely schedule workers
  HID: bigben_worker() remove unneeded check on report_field
  HID: bigben: use spinlock to protect concurrent accesses
  HID: logitech-hidpp: Add myself to authors
  HID: logitech-hidpp: Retry commands when device is busy
  HID: logitech-hidpp: Add more debug statements
  HID: Add support for Logitech G923 Xbox Edition steering wheel
  HID: logitech-hidpp: Add Signature M650
  HID: logitech-hidpp: Remove HIDPP_QUIRK_NO_HIDINPUT quirk
  HID: logitech-hidpp: Don't restart communication if not necessary
  HID: logitech-hidpp: Add constants for HID++ 2.0 error codes
  Revert "HID: logitech-hidpp: add a module parameter to keep firmware gestures"
  HID: logitech-hidpp: Hard-code HID++ 1.0 fast scroll support
  HID: i2c-hid: goodix: Add mainboard-vddio-supply
  dt-bindings: HID: i2c-hid: goodix: Add mainboard-vddio-supply
  HID: i2c-hid: goodix: Stop tying the reset line to the regulator
  ...
This commit is contained in:
Linus Torvalds 2023-02-22 11:24:42 -08:00
commit 6c71297eaf
91 changed files with 6737 additions and 1431 deletions

View file

@ -36,6 +36,13 @@ properties:
vdd-supply:
description: The 3.3V supply to the touchscreen.
mainboard-vddio-supply:
description:
The supply on the main board needed to power up IO signals going
to the touchscreen. This supply need not go to the touchscreen
itself as long as it allows the main board to make signals compatible
with what the touchscreen is expecting for its IO rails.
required:
- compatible
- reg

View file

@ -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
========== ======

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -11,6 +11,7 @@ Human Interface Devices (HID)
hidraw
hid-sensor
hid-transport
hid-bpf
uhid

View file

@ -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,

View file

@ -9069,9 +9069,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>
@ -9079,6 +9082,13 @@ L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-logitech-*
HID++ LOGITECH DRIVERS
R: Filipe Laíns <lains@riseup.net>
R: Bastien Nocera <hadess@hadess.net>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-logitech-hidpp.c
HID PLAYSTATION DRIVER
M: Roderick Colenbrander <roderick.colenbrander@sony.com>
L: linux-input@vger.kernel.org

View file

@ -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/

View file

@ -1,5 +1,6 @@
CONFIG_KUNIT=y
CONFIG_USB=y
CONFIG_USB_HID=y
CONFIG_HID_BATTERY_STRENGTH=y
CONFIG_HID_UCLOGIC=y
CONFIG_HID_KUNIT_TEST=y

View file

@ -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"
@ -329,6 +336,13 @@ config HID_ELO
Support for the ELO USB 4000/4500 touchscreens. Note that this is for
different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
config HID_EVISION
tristate "EVision Keyboards Support"
depends on HID
help
Support for some EVision keyboards. Note that this is needed only when
applying customization using userspace programs.
config HID_EZKEY
tristate "Ezkey BTC 8193 keyboard"
default !EXPERT
@ -1018,13 +1032,21 @@ config HID_SPEEDLINK
Support for Speedlink Vicious and Divine Cezanne mouse.
config HID_STEAM
tristate "Steam Controller support"
tristate "Steam Controller/Deck support"
select POWER_SUPPLY
help
Say Y here if you have a Steam Controller if you want to use it
Say Y here if you have a Steam Controller or Deck if you want to use it
without running the Steam Client. It supports both the wired and
the wireless adaptor.
config STEAM_FF
bool "Steam Deck force feedback support"
depends on HID_STEAM
select INPUT_FF_MEMLESS
help
Say Y here if you want to enable force feedback support for the Steam
Deck.
config HID_STEELSERIES
tristate "Steelseries SRW-S1 steering wheel support"
help
@ -1264,6 +1286,7 @@ config HID_MCP2221
config HID_KUNIT_TEST
tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS
depends on KUNIT=y
depends on HID_BATTERY_STRENGTH
depends on HID_UCLOGIC
default KUNIT_ALL_TESTS
help
@ -1279,6 +1302,8 @@ config HID_KUNIT_TEST
endmenu
source "drivers/hid/bpf/Kconfig"
endif # HID
source "drivers/hid/usbhid/Kconfig"
@ -1291,4 +1316,4 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig"
endmenu
endif # HID_SUPPORT

View file

@ -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
@ -45,6 +47,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELAN) += hid-elan.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EVISION) += hid-evision.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_FT260) += hid-ft260.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o

View file

@ -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.

View file

@ -112,7 +112,7 @@ void amdtp_hid_wakeup(struct hid_device *hid)
}
}
static struct hid_ll_driver amdtp_hid_ll_driver = {
static const struct hid_ll_driver amdtp_hid_ll_driver = {
.parse = amdtp_hid_parse,
.start = amdtp_hid_start,
.stop = amdtp_hid_stop,

16
drivers/hid/bpf/Kconfig Normal file
View file

@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "HID-BPF support"
config HID_BPF
bool "HID-BPF support"
depends on BPF
depends on BPF_SYSCALL
depends on 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
endmenu

11
drivers/hid/bpf/Makefile Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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";

View file

@ -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__ */

View file

@ -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");

View file

@ -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

View file

@ -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;
}

View file

@ -98,6 +98,7 @@ struct asus_kbd_leds {
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
spinlock_t lock;
bool removed;
};
@ -490,21 +491,42 @@ static int rog_nkey_led_init(struct hid_device *hdev)
return ret;
}
static void asus_schedule_work(struct asus_kbd_leds *led)
{
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
if (!led->removed)
schedule_work(&led->work);
spin_unlock_irqrestore(&led->lock, flags);
}
static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
led->brightness = brightness;
schedule_work(&led->work);
spin_unlock_irqrestore(&led->lock, flags);
asus_schedule_work(led);
}
static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
enum led_brightness brightness;
unsigned long flags;
return led->brightness;
spin_lock_irqsave(&led->lock, flags);
brightness = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
return brightness;
}
static void asus_kbd_backlight_work(struct work_struct *work)
@ -512,11 +534,11 @@ static void asus_kbd_backlight_work(struct work_struct *work)
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
int ret;
unsigned long flags;
if (led->removed)
return;
spin_lock_irqsave(&led->lock, flags);
buf[4] = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
if (ret < 0)
@ -584,6 +606,7 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
if (ret < 0) {
@ -1119,9 +1142,13 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
static void asus_remove(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
unsigned long flags;
if (drvdata->kbd_backlight) {
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
cancel_work_sync(&drvdata->kbd_backlight->work);
}

View file

@ -174,6 +174,7 @@ static __u8 pid0902_rdesc_fixed[] = {
struct bigben_device {
struct hid_device *hid;
struct hid_report *report;
spinlock_t lock;
bool removed;
u8 led_state; /* LED1 = 1 .. LED4 = 8 */
u8 right_motor_on; /* right motor off/on 0/1 */
@ -184,18 +185,39 @@ struct bigben_device {
struct work_struct worker;
};
static inline void bigben_schedule_work(struct bigben_device *bigben)
{
unsigned long flags;
spin_lock_irqsave(&bigben->lock, flags);
if (!bigben->removed)
schedule_work(&bigben->worker);
spin_unlock_irqrestore(&bigben->lock, flags);
}
static void bigben_worker(struct work_struct *work)
{
struct bigben_device *bigben = container_of(work,
struct bigben_device, worker);
struct hid_field *report_field = bigben->report->field[0];
bool do_work_led = false;
bool do_work_ff = false;
u8 *buf;
u32 len;
unsigned long flags;
if (bigben->removed || !report_field)
buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL);
if (!buf)
return;
len = hid_report_len(bigben->report);
/* LED work */
spin_lock_irqsave(&bigben->lock, flags);
if (bigben->work_led) {
bigben->work_led = false;
do_work_led = true;
report_field->value[0] = 0x01; /* 1 = led message */
report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->led_state;
@ -204,11 +226,22 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 0x00; /* padding */
report_field->value[7] = 0x00; /* padding */
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT);
hid_output_report(bigben->report, buf);
}
spin_unlock_irqrestore(&bigben->lock, flags);
if (do_work_led) {
hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len,
bigben->report->type, HID_REQ_SET_REPORT);
}
/* FF work */
spin_lock_irqsave(&bigben->lock, flags);
if (bigben->work_ff) {
bigben->work_ff = false;
do_work_ff = true;
report_field->value[0] = 0x02; /* 2 = rumble effect message */
report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->right_motor_on;
@ -217,8 +250,17 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 0x00; /* padding */
report_field->value[7] = 0x00; /* padding */
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT);
hid_output_report(bigben->report, buf);
}
spin_unlock_irqrestore(&bigben->lock, flags);
if (do_work_ff) {
hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len,
bigben->report->type, HID_REQ_SET_REPORT);
}
kfree(buf);
}
static int hid_bigben_play_effect(struct input_dev *dev, void *data,
@ -228,6 +270,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
struct bigben_device *bigben = hid_get_drvdata(hid);
u8 right_motor_on;
u8 left_motor_force;
unsigned long flags;
if (!bigben) {
hid_err(hid, "no device data\n");
@ -242,10 +285,13 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
if (right_motor_on != bigben->right_motor_on ||
left_motor_force != bigben->left_motor_force) {
spin_lock_irqsave(&bigben->lock, flags);
bigben->right_motor_on = right_motor_on;
bigben->left_motor_force = left_motor_force;
bigben->work_ff = true;
schedule_work(&bigben->worker);
spin_unlock_irqrestore(&bigben->lock, flags);
bigben_schedule_work(bigben);
}
return 0;
@ -259,6 +305,7 @@ static void bigben_set_led(struct led_classdev *led,
struct bigben_device *bigben = hid_get_drvdata(hid);
int n;
bool work;
unsigned long flags;
if (!bigben) {
hid_err(hid, "no device data\n");
@ -267,6 +314,7 @@ static void bigben_set_led(struct led_classdev *led,
for (n = 0; n < NUM_LEDS; n++) {
if (led == bigben->leds[n]) {
spin_lock_irqsave(&bigben->lock, flags);
if (value == LED_OFF) {
work = (bigben->led_state & BIT(n));
bigben->led_state &= ~BIT(n);
@ -274,10 +322,11 @@ static void bigben_set_led(struct led_classdev *led,
work = !(bigben->led_state & BIT(n));
bigben->led_state |= BIT(n);
}
spin_unlock_irqrestore(&bigben->lock, flags);
if (work) {
bigben->work_led = true;
schedule_work(&bigben->worker);
bigben_schedule_work(bigben);
}
return;
}
@ -307,8 +356,12 @@ static enum led_brightness bigben_get_led(struct led_classdev *led)
static void bigben_remove(struct hid_device *hid)
{
struct bigben_device *bigben = hid_get_drvdata(hid);
unsigned long flags;
spin_lock_irqsave(&bigben->lock, flags);
bigben->removed = true;
spin_unlock_irqrestore(&bigben->lock, flags);
cancel_work_sync(&bigben->worker);
hid_hw_stop(hid);
}
@ -318,7 +371,6 @@ static int bigben_probe(struct hid_device *hid,
{
struct bigben_device *bigben;
struct hid_input *hidinput;
struct list_head *report_list;
struct led_classdev *led;
char *name;
size_t name_sz;
@ -343,14 +395,12 @@ static int bigben_probe(struct hid_device *hid,
return error;
}
report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
if (list_empty(report_list)) {
bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
if (!bigben->report) {
hid_err(hid, "no output report found\n");
error = -ENODEV;
goto error_hw_stop;
}
bigben->report = list_entry(report_list->next,
struct hid_report, list);
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
@ -362,6 +412,7 @@ static int bigben_probe(struct hid_device *hid,
set_bit(FF_RUMBLE, hidinput->input->ffbit);
INIT_WORK(&bigben->worker, bigben_worker);
spin_lock_init(&bigben->lock);
error = input_ff_create_memless(hidinput->input, NULL,
hid_bigben_play_effect);
@ -402,7 +453,7 @@ static int bigben_probe(struct hid_device *hid,
bigben->left_motor_force = 0;
bigben->work_led = true;
bigben->work_ff = true;
schedule_work(&bigben->worker);
bigben_schedule_work(bigben);
hid_info(hid, "LED and force feedback support for BigBen gamepad\n");

View file

@ -41,11 +41,6 @@
#define DRIVER_DESC "HID core driver"
int hid_debug = 0;
module_param_named(debug, hid_debug, int, 0600);
MODULE_PARM_DESC(debug, "toggle HID debugging messages");
EXPORT_SYMBOL_GPL(hid_debug);
static int hid_ignore_special_drivers = 0;
module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600);
MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
@ -804,7 +799,8 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type)
int i;
if (((parser->global.usage_page << 16) == HID_UP_SENSOR) &&
type == HID_COLLECTION_PHYSICAL)
(type == HID_COLLECTION_PHYSICAL ||
type == HID_COLLECTION_APPLICATION))
hid->group = HID_GROUP_SENSOR_HUB;
if (hid->vendor == USB_VENDOR_ID_MICROSOFT &&
@ -1219,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;
@ -2046,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;
@ -2160,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)
@ -2261,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);
@ -2796,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);
@ -2822,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);
}
@ -2908,20 +2920,29 @@ 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;
if (hid_debug)
pr_warn("hid_debug is now used solely for parser and driver debugging.\n"
"debugfs is now used for inspecting the device (report descriptor, reports)\n");
ret = bus_register(&hid_bus_type);
if (ret) {
pr_err("can't register hid bus\n");
goto err;
}
#ifdef CONFIG_HID_BPF
hid_bpf_ops = &hid_ops;
#endif
ret = hidraw_init();
if (ret)
goto err_bus;
@ -2937,6 +2958,9 @@ static int __init hid_init(void)
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);

View file

@ -975,6 +975,7 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_CAMERA_ACCESS_DISABLE] = "CameraAccessDisable",
[KEY_CAMERA_ACCESS_TOGGLE] = "CameraAccessToggle",
[KEY_DICTATE] = "Dictate",
[KEY_MICMUTE] = "MicrophoneMute",
[KEY_BRIGHTNESS_MIN] = "BrightnessMin",
[KEY_BRIGHTNESS_MAX] = "BrightnessMax",
[KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",

53
drivers/hid/hid-evision.c Normal file
View file

@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for EVision devices
* For now, only ignore bogus consumer reports
* sent after the keyboard has been configured
*
* Copyright (c) 2022 Philippe Valembois
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
/* Ignore key down event */
if ((usage->hid & HID_USAGE) >> 8 == 0x05)
return -1;
/* Ignore key up event */
if ((usage->hid & HID_USAGE) >> 8 == 0x06)
return -1;
switch (usage->hid & HID_USAGE) {
/* Ignore configuration saved event */
case 0x0401: return -1;
/* Ignore reset event */
case 0x0402: return -1;
}
return 0;
}
static const struct hid_device_id evision_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EVISION_ICL01) },
{ }
};
MODULE_DEVICE_TABLE(hid, evision_devices);
static struct hid_driver evision_driver = {
.name = "evision",
.id_table = evision_devices,
.input_mapping = evision_input_mapping,
};
module_hid_driver(evision_driver);
MODULE_LICENSE("GPL");

View file

@ -424,7 +424,7 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
return 0;
}
static struct hid_ll_driver mousevsc_ll_driver = {
static const struct hid_ll_driver mousevsc_ll_driver = {
.parse = mousevsc_hid_parse,
.open = mousevsc_hid_open,
.close = mousevsc_hid_close,

View file

@ -448,6 +448,9 @@
#define USB_VENDOR_ID_EMS 0x2006
#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
#define USB_VENDOR_ID_EVISION 0x320f
#define USB_DEVICE_ID_EVISION_ICL01 0x5041
#define USB_VENDOR_ID_FLATFROG 0x25b5
#define USB_DEVICE_ID_MULTITOUCH_3200 0x0002
@ -822,6 +825,7 @@
#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e
#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
#define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262
#define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL 0xc26e
#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283
#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286
#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287
@ -1185,6 +1189,7 @@
#define USB_VENDOR_ID_VALVE 0x28de
#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
#define USB_DEVICE_ID_STEAM_DECK 0x1205
#define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
@ -1299,7 +1304,9 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2 0x0905
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW 0x0934
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071

View file

@ -0,0 +1,80 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HID to Linux Input mapping
*
* Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com>
*/
#include <kunit/test.h>
static void hid_test_input_set_battery_charge_status(struct kunit *test)
{
struct hid_device *dev;
bool handled;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
handled = hidinput_set_battery_charge_status(dev, HID_DG_HEIGHT, 0);
KUNIT_EXPECT_FALSE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 0);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 1);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
}
static void hid_test_input_get_battery_property(struct kunit *test)
{
struct power_supply *psy;
struct hid_device *dev;
union power_supply_propval val;
int ret;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
dev->battery_avoid_query = true;
psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
psy->drv_data = dev;
dev->battery_status = HID_BATTERY_UNKNOWN;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN);
dev->battery_status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING);
dev->battery_status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING);
}
static struct kunit_case hid_input_tests[] = {
KUNIT_CASE(hid_test_input_set_battery_charge_status),
KUNIT_CASE(hid_test_input_get_battery_property),
{ }
};
static struct kunit_suite hid_input_test_suite = {
.name = "hid_input",
.test_cases = hid_input_tests,
};
kunit_test_suite(hid_input_test_suite);
MODULE_DESCRIPTION("HID input KUnit tests");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");

View file

@ -378,6 +378,10 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15),
HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100),
@ -486,7 +490,7 @@ static int hidinput_get_battery_property(struct power_supply *psy,
if (dev->battery_status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
val->intval = dev->battery_charge_status;
break;
case POWER_SUPPLY_PROP_SCOPE:
@ -554,6 +558,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
dev->battery_max = max;
dev->battery_report_type = report_type;
dev->battery_report_id = field->report->id;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
/*
* Stylus is normally not connected to the device and thus we
@ -620,6 +625,20 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
power_supply_changed(dev->battery);
}
}
static bool hidinput_set_battery_charge_status(struct hid_device *dev,
unsigned int usage, int value)
{
switch (usage) {
case HID_BAT_CHARGING:
dev->battery_charge_status = value ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
return true;
}
return false;
}
#else /* !CONFIG_HID_BATTERY_STRENGTH */
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage)
@ -634,6 +653,12 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
static void hidinput_update_battery(struct hid_device *dev, int value)
{
}
static bool hidinput_set_battery_charge_status(struct hid_device *dev,
unsigned int usage, int value)
{
return false;
}
#endif /* CONFIG_HID_BATTERY_STRENGTH */
static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field,
@ -793,6 +818,14 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break;
}
if ((usage->hid & 0xf0) == 0xa0) { /* SystemControl */
switch (usage->hid & 0xf) {
case 0x9: map_key_clear(KEY_MICMUTE); break;
default: goto ignore;
}
break;
}
if ((usage->hid & 0xf0) == 0xb0) { /* SC - Display */
switch (usage->hid & 0xf) {
case 0x05: map_key_clear(KEY_SWITCHVIDEOMODE); break;
@ -1223,6 +1256,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
usage->type = EV_PWR;
return;
case HID_BAT_CHARGING:
usage->type = EV_PWR;
return;
}
goto unknown;
@ -1465,7 +1501,11 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return;
if (usage->type == EV_PWR) {
hidinput_update_battery(hid, value);
bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value);
if (!handled)
hidinput_update_battery(hid, value);
return;
}
@ -2321,3 +2361,7 @@ void hidinput_disconnect(struct hid_device *hid)
cancel_work_sync(&hid->led_work);
}
EXPORT_SYMBOL_GPL(hidinput_disconnect);
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-input-test.c"
#endif

View file

@ -238,7 +238,7 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id *
char buf[256];
int i, ret;
if (!hid_is_using_ll_driver(hdev, &usb_hid_driver))
if (!hid_is_usb(hdev))
return -ENODEV;
intf = to_usb_interface(hdev->dev.parent);

View file

@ -554,7 +554,7 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
#define LOGITECH_DJ_INTERFACE_NUMBER 0x02
static struct hid_ll_driver logi_dj_ll_driver;
static const struct hid_ll_driver logi_dj_ll_driver;
static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);
static void delayedwork_callback(struct work_struct *work);
@ -1506,7 +1506,7 @@ static bool logi_dj_ll_may_wakeup(struct hid_device *hid)
return hid_hw_may_wakeup(djrcv_dev->hidpp);
}
static struct hid_ll_driver logi_dj_ll_driver = {
static const struct hid_ll_driver logi_dj_ll_driver = {
.parse = logi_dj_ll_parse,
.start = logi_dj_ll_start,
.stop = logi_dj_ll_stop,

View file

@ -30,11 +30,7 @@
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
static bool disable_raw_mode;
module_param(disable_raw_mode, bool, 0644);
MODULE_PARM_DESC(disable_raw_mode,
"Disable Raw mode reporting for touchpads and keep firmware gestures.");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
static bool disable_tap_to_click;
module_param(disable_tap_to_click, bool, 0644);
@ -71,12 +67,13 @@ MODULE_PARM_DESC(disable_tap_to_click,
/* bits 2..20 are reserved for classes */
/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23)
#define HIDPP_QUIRK_DELAYED_INIT BIT(23)
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
#define HIDPP_QUIRK_UNIFYING BIT(25)
#define HIDPP_QUIRK_HIDPP_WHEELS BIT(26)
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(29)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@ -87,8 +84,6 @@ MODULE_PARM_DESC(disable_tap_to_click,
HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL)
#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT
#define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0)
#define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1)
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
@ -225,6 +220,16 @@ struct hidpp_device {
#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b
#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c
/* HID++ 2.0 error codes */
#define HIDPP20_ERROR_NO_ERROR 0x00
#define HIDPP20_ERROR_UNKNOWN 0x01
#define HIDPP20_ERROR_INVALID_ARGS 0x02
#define HIDPP20_ERROR_OUT_OF_RANGE 0x03
#define HIDPP20_ERROR_HW_ERROR 0x04
#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05
#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
#define HIDPP20_ERROR_BUSY 0x08
#define HIDPP20_ERROR_UNSUPPORTED 0x09
#define HIDPP20_ERROR 0xff
static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
@ -279,6 +284,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
struct hidpp_report *response)
{
int ret;
int max_retries = 3;
mutex_lock(&hidpp->send_mutex);
@ -291,34 +297,39 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
*/
*response = *message;
ret = __hidpp_send_report(hidpp->hid_dev, message);
for (; max_retries != 0; max_retries--) {
ret = __hidpp_send_report(hidpp->hid_dev, message);
if (ret) {
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
memset(response, 0, sizeof(struct hidpp_report));
goto exit;
}
if (ret) {
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
memset(response, 0, sizeof(struct hidpp_report));
goto exit;
}
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
5*HZ)) {
dbg_hid("%s:timeout waiting for response\n", __func__);
memset(response, 0, sizeof(struct hidpp_report));
ret = -ETIMEDOUT;
}
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
5*HZ)) {
dbg_hid("%s:timeout waiting for response\n", __func__);
memset(response, 0, sizeof(struct hidpp_report));
ret = -ETIMEDOUT;
}
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
response->rap.sub_id == HIDPP_ERROR) {
ret = response->rap.params[1];
dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
goto exit;
}
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
response->rap.sub_id == HIDPP_ERROR) {
ret = response->rap.params[1];
dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
goto exit;
}
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
response->fap.feature_index == HIDPP20_ERROR) {
ret = response->fap.params[1];
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
goto exit;
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
response->fap.feature_index == HIDPP20_ERROR) {
ret = response->fap.params[1];
if (ret != HIDPP20_ERROR_BUSY) {
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
goto exit;
}
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
}
}
exit:
@ -334,8 +345,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
struct hidpp_report *message;
int ret;
if (param_count > sizeof(message->fap.params))
if (param_count > sizeof(message->fap.params)) {
hid_dbg(hidpp->hid_dev,
"Invalid number of parameters passed to command (%d != %llu)\n",
param_count,
(unsigned long long) sizeof(message->fap.params));
return -EINVAL;
}
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
if (!message)
@ -3436,11 +3452,17 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
ret = hidpp10_enable_scrolling_acceleration(hidpp);
multiplier = 8;
}
if (ret)
if (ret) {
hid_dbg(hidpp->hid_dev,
"Could not enable hi-res scrolling: %d\n", ret);
return ret;
}
if (multiplier == 0)
if (multiplier == 0) {
hid_dbg(hidpp->hid_dev,
"Invalid multiplier 0 from device, setting it to 1\n");
multiplier = 1;
}
hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier);
@ -3472,14 +3494,8 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
}
} else {
struct hidpp_report response;
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_REGISTER,
HIDPP_ENABLE_FAST_SCROLL,
NULL, 0, &response);
if (!ret) {
/* We cannot detect fast scrolling support on HID++ 1.0 devices */
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n");
}
@ -4002,7 +4018,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
hi_res_scroll_enable(hidpp);
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input)
if (!(hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) || hidpp->delayed_input)
/* if the input nodes are already created, we can stop now */
return;
@ -4107,6 +4123,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
bool connected;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
struct hidpp_ff_private_data data;
bool will_restart = false;
/* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
@ -4147,11 +4164,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp_application_equals(hdev, HID_GD_KEYBOARD))
hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS;
if (disable_raw_mode) {
hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
ret = wtp_allocate(hdev, id);
if (ret)
@ -4162,6 +4174,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT ||
hidpp->quirks & HIDPP_QUIRK_UNIFYING)
will_restart = true;
INIT_WORK(&hidpp->work, delayed_work_cb);
mutex_init(&hidpp->send_mutex);
init_waitqueue_head(&hidpp->wait);
@ -4176,7 +4192,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
* Plain USB connections need to actually call start and open
* on the transport driver to allow incoming data.
*/
ret = hid_hw_start(hdev, 0);
ret = hid_hw_start(hdev, will_restart ? 0 : connect_mask);
if (ret) {
hid_err(hdev, "hw start failed\n");
goto hid_hw_start_fail;
@ -4213,6 +4229,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->wireless_feature_index = 0;
else if (ret)
goto hid_hw_init_fail;
ret = 0;
}
if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
@ -4227,19 +4244,21 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp_connect_event(hidpp);
/* Reset the HID node state */
hid_device_io_stop(hdev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
if (will_restart) {
/* Reset the HID node state */
hid_device_io_stop(hdev);
hid_hw_close(hdev);
hid_hw_stop(hdev);
if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
connect_mask &= ~HID_CONNECT_HIDINPUT;
/* Now export the actual inputs and hidraw nodes to the world */
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
goto hid_hw_start_fail;
/* Now export the actual inputs and hidraw nodes to the world */
ret = hid_hw_start(hdev, connect_mask);
if (ret) {
hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
goto hid_hw_start_fail;
}
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
@ -4297,9 +4316,15 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_T651),
.driver_data = HIDPP_QUIRK_CLASS_WTP },
{ /* Mouse Logitech Anywhere MX */
LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Mouse logitech M560 */
LDJ_DEVICE(0x402d),
.driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
{ /* Mouse Logitech M705 (firmware RQM17) */
LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Mouse Logitech Performance MX */
LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Keyboard logitech K400 */
LDJ_DEVICE(0x4024),
.driver_data = HIDPP_QUIRK_CLASS_K400 },
@ -4348,6 +4373,9 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Logitech G920 Wheel over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
{ /* Logitech G923 Wheel (Xbox version) over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
{ /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
@ -4367,6 +4395,8 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX Ergo trackball over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
{ /* Signature M650 over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
{ /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
{}

View file

@ -922,6 +922,9 @@ static void mcp2221_hid_unregister(void *ptr)
/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
static void mcp2221_remove(struct hid_device *hdev)
{
struct mcp2221 *mcp = hid_get_drvdata(hdev);
cancel_delayed_work_sync(&mcp->init_work);
}
#if IS_REACHABLE(CONFIG_IIO)

View file

@ -71,6 +71,7 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_SEPARATE_APP_REPORT BIT(19)
#define MT_QUIRK_FORCE_MULTI_INPUT BIT(20)
#define MT_QUIRK_DISABLE_WAKEUP BIT(21)
#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
#define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03
@ -1009,6 +1010,7 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
struct mt_usages *slot)
{
struct input_mt *mt = input->mt;
struct hid_device *hdev = td->hdev;
__s32 quirks = app->quirks;
bool valid = true;
bool confidence_state = true;
@ -1086,6 +1088,10 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
int orientation = wide;
int max_azimuth;
int azimuth;
int x;
int y;
int cx;
int cy;
if (slot->a != DEFAULT_ZERO) {
/*
@ -1104,6 +1110,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
if (azimuth > max_azimuth * 2)
azimuth -= max_azimuth * 4;
orientation = -azimuth;
if (quirks & MT_QUIRK_ORIENTATION_INVERT)
orientation = -orientation;
}
if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) {
@ -1115,10 +1124,23 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1;
}
input_event(input, EV_ABS, ABS_MT_POSITION_X, *slot->x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, *slot->y);
input_event(input, EV_ABS, ABS_MT_TOOL_X, *slot->cx);
input_event(input, EV_ABS, ABS_MT_TOOL_Y, *slot->cy);
x = hdev->quirks & HID_QUIRK_X_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
*slot->x;
y = hdev->quirks & HID_QUIRK_Y_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->y :
*slot->y;
cx = hdev->quirks & HID_QUIRK_X_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->cx :
*slot->cx;
cy = hdev->quirks & HID_QUIRK_Y_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->cy :
*slot->cy;
input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
input_event(input, EV_ABS, ABS_MT_TOOL_X, cx);
input_event(input, EV_ABS, ABS_MT_TOOL_Y, cy);
input_event(input, EV_ABS, ABS_MT_DISTANCE, !*slot->tip_state);
input_event(input, EV_ABS, ABS_MT_ORIENTATION, orientation);
input_event(input, EV_ABS, ABS_MT_PRESSURE, *slot->p);
@ -1735,6 +1757,15 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
td->serial_maybe = true;
/* Orientation is inverted if the X or Y axes are
* flipped, but normalized if both are inverted.
*/
if (hdev->quirks & (HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT) &&
!((hdev->quirks & HID_QUIRK_X_INVERT)
&& (hdev->quirks & HID_QUIRK_Y_INVERT)))
td->mtclass.quirks = MT_QUIRK_ORIENTATION_INVERT;
/* This allows the driver to correctly support devices
* that emit events over several HID messages.
*/

View file

@ -993,19 +993,22 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds->gyro_calib_data[0].abs_code = ABS_RX;
ds->gyro_calib_data[0].bias = gyro_pitch_bias;
ds->gyro_calib_data[0].bias = 0;
ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds->gyro_calib_data[1].abs_code = ABS_RY;
ds->gyro_calib_data[1].bias = gyro_yaw_bias;
ds->gyro_calib_data[1].bias = 0;
ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds->gyro_calib_data[2].abs_code = ABS_RZ;
ds->gyro_calib_data[2].bias = gyro_roll_bias;
ds->gyro_calib_data[2].bias = 0;
ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
/*
* Sanity check gyro calibration data. This is needed to prevent crashes
@ -1388,8 +1391,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);
int calib_data = mult_frac(ds->gyro_calib_data[i].sens_numer,
raw_data - ds->gyro_calib_data[i].bias,
ds->gyro_calib_data[i].sens_denom);
raw_data, ds->gyro_calib_data[i].sens_denom);
input_report_abs(ds->sensors, ds->gyro_calib_data[i].abs_code, calib_data);
}
@ -1792,11 +1794,10 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
if (retries < 2) {
hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
continue;
} else {
ret = -EILSEQ;
goto err_free;
}
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
ret = -EILSEQ;
goto err_free;
} else {
break;
@ -1849,19 +1850,22 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
*/
speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds4->gyro_calib_data[0].abs_code = ABS_RX;
ds4->gyro_calib_data[0].bias = gyro_pitch_bias;
ds4->gyro_calib_data[0].bias = 0;
ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds4->gyro_calib_data[1].abs_code = ABS_RY;
ds4->gyro_calib_data[1].bias = gyro_yaw_bias;
ds4->gyro_calib_data[1].bias = 0;
ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds4->gyro_calib_data[2].abs_code = ABS_RZ;
ds4->gyro_calib_data[2].bias = gyro_roll_bias;
ds4->gyro_calib_data[2].bias = 0;
ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
/*
* Sanity check gyro calibration data. This is needed to prevent crashes
@ -2242,8 +2246,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
int calib_data = mult_frac(ds4->gyro_calib_data[i].sens_numer,
raw_data - ds4->gyro_calib_data[i].bias,
ds4->gyro_calib_data[i].sens_denom);
raw_data, ds4->gyro_calib_data[i].sens_denom);
input_report_abs(ds4->sensors, ds4->gyro_calib_data[i].abs_code, calib_data);
}

View file

@ -1237,7 +1237,7 @@ EXPORT_SYMBOL_GPL(hid_quirks_exit);
static unsigned long hid_gets_squirk(const struct hid_device *hdev)
{
const struct hid_device_id *bl_entry;
unsigned long quirks = 0;
unsigned long quirks = hdev->initial_quirks;
if (hid_match_id(hdev, hid_ignore_list))
quirks |= HID_QUIRK_IGNORE;

View file

@ -5,6 +5,7 @@
*/
#include <linux/ctype.h>
#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@ -750,114 +751,209 @@ static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
}
/* luid defined in FW (e.g. ISH). Maybe used to identify sensor. */
static const char *const known_sensor_luid[] = { "020B000000000000" };
/*
* Match a known custom sensor.
* tag and luid is mandatory.
*/
struct hid_sensor_custom_match {
const char *tag;
const char *luid;
const char *model;
const char *manufacturer;
bool check_dmi;
struct dmi_system_id dmi;
};
static int get_luid_table_index(unsigned char *usage_str)
/*
* Custom sensor properties used for matching.
*/
struct hid_sensor_custom_properties {
u16 serial_num[HID_CUSTOM_MAX_FEATURE_BYTES];
u16 model[HID_CUSTOM_MAX_FEATURE_BYTES];
u16 manufacturer[HID_CUSTOM_MAX_FEATURE_BYTES];
};
static const struct hid_sensor_custom_match hid_sensor_custom_known_table[] = {
/*
* Intel Integrated Sensor Hub (ISH)
*/
{ /* Intel ISH hinge */
.tag = "INT",
.luid = "020B000000000000",
.manufacturer = "INTEL",
},
/*
* Lenovo Intelligent Sensing Solution (LISS)
*/
{ /* ambient light */
.tag = "LISS",
.luid = "0041010200000082",
.model = "STK3X3X Sensor",
.manufacturer = "Vendor 258",
.check_dmi = true,
.dmi.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
}
},
{ /* human presence */
.tag = "LISS",
.luid = "0226000171AC0081",
.model = "VL53L1_HOD Sensor",
.manufacturer = "ST_MICRO",
.check_dmi = true,
.dmi.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
}
},
{}
};
static bool hid_sensor_custom_prop_match_str(const u16 *prop, const char *match,
size_t count)
{
int i;
for (i = 0; i < ARRAY_SIZE(known_sensor_luid); i++) {
if (!strncmp(usage_str, known_sensor_luid[i],
strlen(known_sensor_luid[i])))
return i;
while (count-- && *prop && *match) {
if (*prop != (u16) *match)
return false;
prop++;
match++;
}
return -ENODEV;
return (count == -1) || *prop == (u16)*match;
}
static int get_known_custom_sensor_index(struct hid_sensor_hub_device *hsdev)
static int hid_sensor_custom_get_prop(struct hid_sensor_hub_device *hsdev,
u32 prop_usage_id, size_t prop_size,
u16 *prop)
{
struct hid_sensor_hub_attribute_info sensor_manufacturer = { 0 };
struct hid_sensor_hub_attribute_info sensor_luid_info = { 0 };
int report_size;
struct hid_sensor_hub_attribute_info prop_attr = { 0 };
int ret;
static u16 w_buf[HID_CUSTOM_MAX_FEATURE_BYTES];
static char buf[HID_CUSTOM_MAX_FEATURE_BYTES];
int i;
memset(w_buf, 0, sizeof(w_buf));
memset(buf, 0, sizeof(buf));
memset(prop, 0, prop_size);
/* get manufacturer info */
ret = sensor_hub_input_get_attribute_info(hsdev,
HID_FEATURE_REPORT, hsdev->usage,
HID_USAGE_SENSOR_PROP_MANUFACTURER, &sensor_manufacturer);
ret = sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT,
hsdev->usage, prop_usage_id,
&prop_attr);
if (ret < 0)
return ret;
report_size =
sensor_hub_get_feature(hsdev, sensor_manufacturer.report_id,
sensor_manufacturer.index, sizeof(w_buf),
w_buf);
if (report_size <= 0) {
hid_err(hsdev->hdev,
"Failed to get sensor manufacturer info %d\n",
report_size);
return -ENODEV;
ret = sensor_hub_get_feature(hsdev, prop_attr.report_id,
prop_attr.index, prop_size, prop);
if (ret < 0) {
hid_err(hsdev->hdev, "Failed to get sensor property %08x %d\n",
prop_usage_id, ret);
return ret;
}
/* convert from wide char to char */
for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++)
buf[i] = (char)w_buf[i];
return 0;
}
/* ensure it's ISH sensor */
if (strncmp(buf, "INTEL", strlen("INTEL")))
return -ENODEV;
static bool
hid_sensor_custom_do_match(struct hid_sensor_hub_device *hsdev,
const struct hid_sensor_custom_match *match,
const struct hid_sensor_custom_properties *prop)
{
struct dmi_system_id dmi[] = { match->dmi, { 0 } };
memset(w_buf, 0, sizeof(w_buf));
memset(buf, 0, sizeof(buf));
if (!hid_sensor_custom_prop_match_str(prop->serial_num, "LUID:", 5) ||
!hid_sensor_custom_prop_match_str(prop->serial_num + 5, match->luid,
HID_CUSTOM_MAX_FEATURE_BYTES - 5))
return false;
/* get real usage id */
ret = sensor_hub_input_get_attribute_info(hsdev,
HID_FEATURE_REPORT, hsdev->usage,
HID_USAGE_SENSOR_PROP_SERIAL_NUM, &sensor_luid_info);
if (match->model &&
!hid_sensor_custom_prop_match_str(prop->model, match->model,
HID_CUSTOM_MAX_FEATURE_BYTES))
return false;
if (match->manufacturer &&
!hid_sensor_custom_prop_match_str(prop->manufacturer, match->manufacturer,
HID_CUSTOM_MAX_FEATURE_BYTES))
return false;
if (match->check_dmi && !dmi_check_system(dmi))
return false;
return true;
}
static int
hid_sensor_custom_properties_get(struct hid_sensor_hub_device *hsdev,
struct hid_sensor_custom_properties *prop)
{
int ret;
ret = hid_sensor_custom_get_prop(hsdev,
HID_USAGE_SENSOR_PROP_SERIAL_NUM,
HID_CUSTOM_MAX_FEATURE_BYTES,
prop->serial_num);
if (ret < 0)
return ret;
report_size = sensor_hub_get_feature(hsdev, sensor_luid_info.report_id,
sensor_luid_info.index, sizeof(w_buf),
w_buf);
if (report_size <= 0) {
hid_err(hsdev->hdev, "Failed to get real usage info %d\n",
report_size);
return -ENODEV;
/*
* Ignore errors on the following model and manufacturer properties.
* Because these are optional, it is not an error if they are missing.
*/
hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MODEL,
HID_CUSTOM_MAX_FEATURE_BYTES,
prop->model);
hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MANUFACTURER,
HID_CUSTOM_MAX_FEATURE_BYTES,
prop->manufacturer);
return 0;
}
static int
hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev,
const struct hid_sensor_custom_match **known)
{
int ret;
const struct hid_sensor_custom_match *match =
hid_sensor_custom_known_table;
struct hid_sensor_custom_properties *prop;
prop = kmalloc(sizeof(struct hid_sensor_custom_properties), GFP_KERNEL);
if (!prop)
return -ENOMEM;
ret = hid_sensor_custom_properties_get(hsdev, prop);
if (ret < 0)
goto out;
while (match->tag) {
if (hid_sensor_custom_do_match(hsdev, match, prop)) {
*known = match;
ret = 0;
goto out;
}
match++;
}
/* convert from wide char to char */
for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++)
buf[i] = (char)w_buf[i];
if (strlen(buf) != strlen(known_sensor_luid[0]) + 5) {
hid_err(hsdev->hdev,
"%s luid length not match %zu != (%zu + 5)\n", __func__,
strlen(buf), strlen(known_sensor_luid[0]));
return -ENODEV;
}
/* get table index with luid (not matching 'LUID: ' in luid) */
return get_luid_table_index(&buf[5]);
ret = -ENODATA;
out:
kfree(prop);
return ret;
}
static struct platform_device *
hid_sensor_register_platform_device(struct platform_device *pdev,
struct hid_sensor_hub_device *hsdev,
int index)
const struct hid_sensor_custom_match *match)
{
char real_usage[HID_SENSOR_USAGE_LENGTH] = { 0 };
char real_usage[HID_SENSOR_USAGE_LENGTH];
struct platform_device *custom_pdev;
const char *dev_name;
char *c;
/* copy real usage id */
memcpy(real_usage, known_sensor_luid[index], 4);
memcpy(real_usage, match->luid, 4);
/* usage id are all lowcase */
for (c = real_usage; *c != '\0'; c++)
*c = tolower(*c);
/* HID-SENSOR-INT-REAL_USAGE_ID */
dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-INT-%s", real_usage);
/* HID-SENSOR-TAG-REAL_USAGE_ID */
dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-%s-%s",
match->tag, real_usage);
if (!dev_name)
return ERR_PTR(-ENOMEM);
@ -873,7 +969,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
struct hid_sensor_custom *sensor_inst;
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
int ret;
int index;
const struct hid_sensor_custom_match *match;
sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
GFP_KERNEL);
@ -888,10 +984,10 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
mutex_init(&sensor_inst->mutex);
platform_set_drvdata(pdev, sensor_inst);
index = get_known_custom_sensor_index(hsdev);
if (index >= 0 && index < ARRAY_SIZE(known_sensor_luid)) {
ret = hid_sensor_custom_get_known(hsdev, &match);
if (!ret) {
sensor_inst->custom_pdev =
hid_sensor_register_platform_device(pdev, hsdev, index);
hid_sensor_register_platform_device(pdev, hsdev, match);
ret = PTR_ERR_OR_ZERO(sensor_inst->custom_pdev);
if (ret) {

View file

@ -397,7 +397,8 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev,
for (i = 0; i < report->maxfield; ++i) {
field = report->field[i];
if (field->maxusage) {
if (field->physical == usage_id &&
if ((field->physical == usage_id ||
field->application == usage_id) &&
(field->logical == attr_usage_id ||
field->usage[0].hid ==
attr_usage_id) &&
@ -506,7 +507,8 @@ static int sensor_hub_raw_event(struct hid_device *hdev,
collection->usage);
callback = sensor_hub_get_callback(hdev,
report->field[i]->physical,
report->field[i]->physical ? report->field[i]->physical :
report->field[i]->application,
report->field[i]->usage[0].collection_index,
&hsdev, &priv);
if (!callback) {

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@
* HID driver for Valve Steam Controller
*
* Copyright (c) 2018 Rodrigo Rivas Costa <rodrigorivascosta@gmail.com>
* Copyright (c) 2022 Valve Software
*
* Supports both the wired and wireless interfaces.
*
@ -53,6 +54,7 @@ static DEFINE_MUTEX(steam_devices_lock);
static LIST_HEAD(steam_devices);
#define STEAM_QUIRK_WIRELESS BIT(0)
#define STEAM_QUIRK_DECK BIT(1)
/* Touch pads are 40 mm in diameter and 65535 units */
#define STEAM_PAD_RESOLUTION 1638
@ -60,6 +62,10 @@ static LIST_HEAD(steam_devices);
#define STEAM_TRIGGER_RESOLUTION 51
/* Joystick runs are about 5 mm and 256 units */
#define STEAM_JOYSTICK_RESOLUTION 51
/* Trigger runs are about 6 mm and 32768 units */
#define STEAM_DECK_TRIGGER_RESOLUTION 5461
/* Joystick runs are about 5 mm and 32768 units */
#define STEAM_DECK_JOYSTICK_RESOLUTION 6553
#define STEAM_PAD_FUZZ 256
@ -85,6 +91,7 @@ static LIST_HEAD(steam_devices);
#define STEAM_CMD_FORCEFEEDBAK 0x8f
#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
#define STEAM_CMD_GET_SERIAL 0xae
#define STEAM_CMD_HAPTIC_RUMBLE 0xeb
/* Some useful register ids */
#define STEAM_REG_LPAD_MODE 0x07
@ -92,11 +99,14 @@ static LIST_HEAD(steam_devices);
#define STEAM_REG_RPAD_MARGIN 0x18
#define STEAM_REG_LED 0x2d
#define STEAM_REG_GYRO_MODE 0x30
#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
/* Raw event identifiers */
#define STEAM_EV_INPUT_DATA 0x01
#define STEAM_EV_CONNECT 0x03
#define STEAM_EV_BATTERY 0x04
#define STEAM_EV_DECK_INPUT_DATA 0x09
/* Values for GYRO_MODE (bitmask) */
#define STEAM_GYRO_MODE_OFF 0x0000
@ -124,6 +134,10 @@ struct steam_device {
struct power_supply __rcu *battery;
u8 battery_charge;
u16 voltage;
struct delayed_work heartbeat;
struct work_struct rumble_work;
u16 rumble_left;
u16 rumble_right;
};
static int steam_recv_report(struct steam_device *steam,
@ -193,7 +207,7 @@ static int steam_send_report(struct steam_device *steam,
*/
do {
ret = hid_hw_raw_request(steam->hdev, 0,
buf, size + 1,
buf, max(size, 64) + 1,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret != -EPIPE)
break;
@ -219,6 +233,7 @@ static int steam_write_registers(struct steam_device *steam,
u8 reg;
u16 val;
u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
int ret;
va_list args;
va_start(args, steam);
@ -234,7 +249,16 @@ static int steam_write_registers(struct steam_device *steam,
}
va_end(args);
return steam_send_report(steam, cmd, 2 + cmd[1]);
ret = steam_send_report(steam, cmd, 2 + cmd[1]);
if (ret < 0)
return ret;
/*
* Sometimes a lingering report for this command can
* get read back instead of the last set report if
* this isn't explicitly queried
*/
return steam_recv_report(steam, cmd, 2 + cmd[1]);
}
static int steam_get_serial(struct steam_device *steam)
@ -270,6 +294,45 @@ static inline int steam_request_conn_status(struct steam_device *steam)
return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
}
static inline int steam_haptic_rumble(struct steam_device *steam,
u16 intensity, u16 left_speed, u16 right_speed,
u8 left_gain, u8 right_gain)
{
u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
report[3] = intensity & 0xFF;
report[4] = intensity >> 8;
report[5] = left_speed & 0xFF;
report[6] = left_speed >> 8;
report[7] = right_speed & 0xFF;
report[8] = right_speed >> 8;
report[9] = left_gain;
report[10] = right_gain;
return steam_send_report(steam, report, sizeof(report));
}
static void steam_haptic_rumble_cb(struct work_struct *work)
{
struct steam_device *steam = container_of(work, struct steam_device,
rumble_work);
steam_haptic_rumble(steam, 0, steam->rumble_left,
steam->rumble_right, 2, 0);
}
#ifdef CONFIG_STEAM_FF
static int steam_play_effect(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct steam_device *steam = input_get_drvdata(dev);
steam->rumble_left = effect->u.rumble.strong_magnitude;
steam->rumble_right = effect->u.rumble.weak_magnitude;
return schedule_work(&steam->rumble_work);
}
#endif
static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
{
if (enable) {
@ -280,13 +343,33 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
steam_write_registers(steam,
STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
0);
cancel_delayed_work_sync(&steam->heartbeat);
} else {
/* disable esc, enter, cursor */
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
steam_write_registers(steam,
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
0);
if (steam->quirks & STEAM_QUIRK_DECK) {
steam_write_registers(steam,
STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
0);
/*
* The Steam Deck has a watchdog that automatically enables
* lizard mode if it doesn't see any traffic for too long
*/
if (!work_busy(&steam->heartbeat.work))
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
} else {
steam_write_registers(steam,
STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
0);
}
}
}
@ -413,8 +496,8 @@ static int steam_input_register(struct steam_device *steam)
input->open = steam_input_open;
input->close = steam_input_close;
input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ?
"Wireless Steam Controller" :
input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ? "Wireless Steam Controller" :
(steam->quirks & STEAM_QUIRK_DECK) ? "Steam Deck" :
"Steam Controller";
input->phys = hdev->phys;
input->uniq = steam->serial_no;
@ -438,33 +521,76 @@ static int steam_input_register(struct steam_device *steam)
input_set_capability(input, EV_KEY, BTN_SELECT);
input_set_capability(input, EV_KEY, BTN_MODE);
input_set_capability(input, EV_KEY, BTN_START);
input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
input_set_capability(input, EV_KEY, BTN_GEAR_UP);
input_set_capability(input, EV_KEY, BTN_THUMBR);
input_set_capability(input, EV_KEY, BTN_THUMBL);
input_set_capability(input, EV_KEY, BTN_THUMB);
input_set_capability(input, EV_KEY, BTN_THUMB2);
if (steam->quirks & STEAM_QUIRK_DECK) {
input_set_capability(input, EV_KEY, BTN_BASE);
input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY1);
input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY2);
input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY3);
input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY4);
} else {
input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
input_set_capability(input, EV_KEY, BTN_GEAR_UP);
}
input_set_abs_params(input, ABS_HAT2Y, 0, 255, 0, 0);
input_set_abs_params(input, ABS_HAT2X, 0, 255, 0, 0);
input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
input_set_abs_params(input, ABS_Y, -32767, 32767, 0, 0);
input_set_abs_params(input, ABS_RX, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_RY, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_HAT0X, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_HAT0Y, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
if (steam->quirks & STEAM_QUIRK_DECK) {
input_set_abs_params(input, ABS_HAT2Y, 0, 32767, 0, 0);
input_set_abs_params(input, ABS_HAT2X, 0, 32767, 0, 0);
input_set_abs_params(input, ABS_RX, -32767, 32767, 0, 0);
input_set_abs_params(input, ABS_RY, -32767, 32767, 0, 0);
input_set_abs_params(input, ABS_HAT1X, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_HAT1Y, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_abs_set_res(input, ABS_X, STEAM_DECK_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_Y, STEAM_DECK_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_RX, STEAM_DECK_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_RY, STEAM_DECK_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_HAT1X, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT1Y, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT2Y, STEAM_DECK_TRIGGER_RESOLUTION);
input_abs_set_res(input, ABS_HAT2X, STEAM_DECK_TRIGGER_RESOLUTION);
} else {
input_set_abs_params(input, ABS_HAT2Y, 0, 255, 0, 0);
input_set_abs_params(input, ABS_HAT2X, 0, 255, 0, 0);
input_set_abs_params(input, ABS_RX, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_RY, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT2Y, STEAM_TRIGGER_RESOLUTION);
input_abs_set_res(input, ABS_HAT2X, STEAM_TRIGGER_RESOLUTION);
}
input_abs_set_res(input, ABS_HAT0X, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT2Y, STEAM_TRIGGER_RESOLUTION);
input_abs_set_res(input, ABS_HAT2X, STEAM_TRIGGER_RESOLUTION);
#ifdef CONFIG_STEAM_FF
if (steam->quirks & STEAM_QUIRK_DECK) {
input_set_capability(input, EV_FF, FF_RUMBLE);
ret = input_ff_create_memless(input, NULL, steam_play_effect);
if (ret)
goto input_register_fail;
}
#endif
ret = input_register_device(input);
if (ret)
@ -612,6 +738,22 @@ static bool steam_is_valve_interface(struct hid_device *hdev)
return !list_empty(&rep_enum->report_list);
}
static void steam_lizard_mode_heartbeat(struct work_struct *work)
{
struct steam_device *steam = container_of(work, struct steam_device,
heartbeat.work);
mutex_lock(&steam->mutex);
if (!steam->client_opened && steam->client_hdev) {
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
steam_write_registers(steam,
STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
0);
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
}
mutex_unlock(&steam->mutex);
}
static int steam_client_ll_parse(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
@ -674,7 +816,7 @@ static int steam_client_ll_raw_request(struct hid_device *hdev,
report_type, reqtype);
}
static struct hid_ll_driver steam_client_ll_driver = {
static const struct hid_ll_driver steam_client_ll_driver = {
.parse = steam_client_ll_parse,
.start = steam_client_ll_start,
.stop = steam_client_ll_stop,
@ -750,6 +892,8 @@ static int steam_probe(struct hid_device *hdev,
steam->quirks = id->driver_data;
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
INIT_LIST_HEAD(&steam->list);
INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
steam->client_hdev = steam_create_client_hid(hdev);
if (IS_ERR(steam->client_hdev)) {
@ -805,6 +949,8 @@ static int steam_probe(struct hid_device *hdev,
hid_destroy_device(steam->client_hdev);
client_hdev_fail:
cancel_work_sync(&steam->work_connect);
cancel_delayed_work_sync(&steam->heartbeat);
cancel_work_sync(&steam->rumble_work);
steam_alloc_fail:
hid_err(hdev, "%s: failed with error %d\n",
__func__, ret);
@ -821,7 +967,11 @@ static void steam_remove(struct hid_device *hdev)
}
hid_destroy_device(steam->client_hdev);
mutex_lock(&steam->mutex);
steam->client_hdev = NULL;
steam->client_opened = false;
cancel_delayed_work_sync(&steam->heartbeat);
mutex_unlock(&steam->mutex);
cancel_work_sync(&steam->work_connect);
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
hid_info(hdev, "Steam wireless receiver disconnected");
@ -906,10 +1056,10 @@ static inline s16 steam_le16(u8 *data)
* 8.5 | BTN_B | button B
* 8.6 | BTN_X | button X
* 8.7 | BTN_A | button A
* 9.0 | BTN_DPAD_UP | lef-pad up
* 9.1 | BTN_DPAD_RIGHT | lef-pad right
* 9.2 | BTN_DPAD_LEFT | lef-pad left
* 9.3 | BTN_DPAD_DOWN | lef-pad down
* 9.0 | BTN_DPAD_UP | left-pad up
* 9.1 | BTN_DPAD_RIGHT | left-pad right
* 9.2 | BTN_DPAD_LEFT | left-pad left
* 9.3 | BTN_DPAD_DOWN | left-pad down
* 9.4 | BTN_SELECT | menu left
* 9.5 | BTN_MODE | steam logo
* 9.6 | BTN_START | menu right
@ -993,6 +1143,172 @@ static void steam_do_input_event(struct steam_device *steam,
input_sync(input);
}
/*
* The size for this message payload is 56.
* The known values are:
* Offset| Type | Mapped to |Meaning
* -------+-------+-----------+--------------------------
* 4-7 | u32 | -- | sequence number
* 8-15 | u64 | see below | buttons
* 16-17 | s16 | ABS_HAT0X | left-pad X value
* 18-19 | s16 | ABS_HAT0Y | left-pad Y value
* 20-21 | s16 | ABS_HAT1X | right-pad X value
* 22-23 | s16 | ABS_HAT1Y | right-pad Y value
* 24-25 | s16 | -- | accelerometer X value
* 26-27 | s16 | -- | accelerometer Y value
* 28-29 | s16 | -- | accelerometer Z value
* 30-31 | s16 | -- | gyro X value
* 32-33 | s16 | -- | gyro Y value
* 34-35 | s16 | -- | gyro Z value
* 36-37 | s16 | -- | quaternion W value
* 38-39 | s16 | -- | quaternion X value
* 40-41 | s16 | -- | quaternion Y value
* 42-43 | s16 | -- | quaternion Z value
* 44-45 | u16 | ABS_HAT2Y | left trigger (uncalibrated)
* 46-47 | u16 | ABS_HAT2X | right trigger (uncalibrated)
* 48-49 | s16 | ABS_X | left joystick X
* 50-51 | s16 | ABS_Y | left joystick Y
* 52-53 | s16 | ABS_RX | right joystick X
* 54-55 | s16 | ABS_RY | right joystick Y
* 56-57 | u16 | -- | left pad pressure
* 58-59 | u16 | -- | right pad pressure
*
* The buttons are:
* Bit | Mapped to | Description
* ------+------------+--------------------------------
* 8.0 | BTN_TR2 | right trigger fully pressed
* 8.1 | BTN_TL2 | left trigger fully pressed
* 8.2 | BTN_TR | right shoulder
* 8.3 | BTN_TL | left shoulder
* 8.4 | BTN_Y | button Y
* 8.5 | BTN_B | button B
* 8.6 | BTN_X | button X
* 8.7 | BTN_A | button A
* 9.0 | BTN_DPAD_UP | left-pad up
* 9.1 | BTN_DPAD_RIGHT | left-pad right
* 9.2 | BTN_DPAD_LEFT | left-pad left
* 9.3 | BTN_DPAD_DOWN | left-pad down
* 9.4 | BTN_SELECT | menu left
* 9.5 | BTN_MODE | steam logo
* 9.6 | BTN_START | menu right
* 9.7 | BTN_TRIGGER_HAPPY3 | left bottom grip button
* 10.0 | BTN_TRIGGER_HAPPY4 | right bottom grip button
* 10.1 | BTN_THUMB | left pad pressed
* 10.2 | BTN_THUMB2 | right pad pressed
* 10.3 | -- | left pad touched
* 10.4 | -- | right pad touched
* 10.5 | -- | unknown
* 10.6 | BTN_THUMBL | left joystick clicked
* 10.7 | -- | unknown
* 11.0 | -- | unknown
* 11.1 | -- | unknown
* 11.2 | BTN_THUMBR | right joystick clicked
* 11.3 | -- | unknown
* 11.4 | -- | unknown
* 11.5 | -- | unknown
* 11.6 | -- | unknown
* 11.7 | -- | unknown
* 12.0 | -- | unknown
* 12.1 | -- | unknown
* 12.2 | -- | unknown
* 12.3 | -- | unknown
* 12.4 | -- | unknown
* 12.5 | -- | unknown
* 12.6 | -- | unknown
* 12.7 | -- | unknown
* 13.0 | -- | unknown
* 13.1 | BTN_TRIGGER_HAPPY1 | left top grip button
* 13.2 | BTN_TRIGGER_HAPPY2 | right top grip button
* 13.3 | -- | unknown
* 13.4 | -- | unknown
* 13.5 | -- | unknown
* 13.6 | -- | left joystick touched
* 13.7 | -- | right joystick touched
* 14.0 | -- | unknown
* 14.1 | -- | unknown
* 14.2 | BTN_BASE | quick access button
* 14.3 | -- | unknown
* 14.4 | -- | unknown
* 14.5 | -- | unknown
* 14.6 | -- | unknown
* 14.7 | -- | unknown
* 15.0 | -- | unknown
* 15.1 | -- | unknown
* 15.2 | -- | unknown
* 15.3 | -- | unknown
* 15.4 | -- | unknown
* 15.5 | -- | unknown
* 15.6 | -- | unknown
* 15.7 | -- | unknown
*/
static void steam_do_deck_input_event(struct steam_device *steam,
struct input_dev *input, u8 *data)
{
u8 b8, b9, b10, b11, b13, b14;
bool lpad_touched, rpad_touched;
b8 = data[8];
b9 = data[9];
b10 = data[10];
b11 = data[11];
b13 = data[13];
b14 = data[14];
lpad_touched = b10 & BIT(3);
rpad_touched = b10 & BIT(4);
if (lpad_touched) {
input_report_abs(input, ABS_HAT0X, steam_le16(data + 16));
input_report_abs(input, ABS_HAT0Y, steam_le16(data + 18));
} else {
input_report_abs(input, ABS_HAT0X, 0);
input_report_abs(input, ABS_HAT0Y, 0);
}
if (rpad_touched) {
input_report_abs(input, ABS_HAT1X, steam_le16(data + 20));
input_report_abs(input, ABS_HAT1Y, steam_le16(data + 22));
} else {
input_report_abs(input, ABS_HAT1X, 0);
input_report_abs(input, ABS_HAT1Y, 0);
}
input_report_abs(input, ABS_X, steam_le16(data + 48));
input_report_abs(input, ABS_Y, -steam_le16(data + 50));
input_report_abs(input, ABS_RX, steam_le16(data + 52));
input_report_abs(input, ABS_RY, -steam_le16(data + 54));
input_report_abs(input, ABS_HAT2Y, steam_le16(data + 44));
input_report_abs(input, ABS_HAT2X, steam_le16(data + 46));
input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0)));
input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1)));
input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2)));
input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3)));
input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4)));
input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5)));
input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6)));
input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7)));
input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4)));
input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5)));
input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6)));
input_event(input, EV_KEY, BTN_TRIGGER_HAPPY3, !!(b9 & BIT(7)));
input_event(input, EV_KEY, BTN_TRIGGER_HAPPY4, !!(b10 & BIT(0)));
input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6)));
input_event(input, EV_KEY, BTN_THUMBR, !!(b11 & BIT(2)));
input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0)));
input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1)));
input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2)));
input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3)));
input_event(input, EV_KEY, BTN_THUMB, !!(b10 & BIT(1)));
input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(2)));
input_event(input, EV_KEY, BTN_TRIGGER_HAPPY1, !!(b13 & BIT(1)));
input_event(input, EV_KEY, BTN_TRIGGER_HAPPY2, !!(b13 & BIT(2)));
input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2)));
input_sync(input);
}
/*
* The size for this message payload is 11.
* The known values are:
@ -1052,6 +1368,7 @@ static int steam_raw_event(struct hid_device *hdev,
* 0x01: input data (60 bytes)
* 0x03: wireless connect/disconnect (1 byte)
* 0x04: battery status (11 bytes)
* 0x09: Steam Deck input data (56 bytes)
*/
if (size != 64 || data[0] != 1 || data[1] != 0)
@ -1067,6 +1384,15 @@ static int steam_raw_event(struct hid_device *hdev,
steam_do_input_event(steam, input, data);
rcu_read_unlock();
break;
case STEAM_EV_DECK_INPUT_DATA:
if (steam->client_opened)
return 0;
rcu_read_lock();
input = rcu_dereference(steam->input);
if (likely(input))
steam_do_deck_input_event(steam, input, data);
rcu_read_unlock();
break;
case STEAM_EV_CONNECT:
/*
* The payload of this event is a single byte:
@ -1141,6 +1467,11 @@ static const struct hid_device_id steam_controllers[] = {
USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS),
.driver_data = STEAM_QUIRK_WIRELESS
},
{ /* Steam Deck */
HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
USB_DEVICE_ID_STEAM_DECK),
.driver_data = STEAM_QUIRK_DECK
},
{}
};

View file

@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* HID driver for UC-Logic devices not fully compliant with HID standard
*
* Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com>
*/
#include <kunit/test.h>
#include "./hid-uclogic-params.h"
#define MAX_EVENT_SIZE 12
struct uclogic_raw_event_hook_test {
u8 event[MAX_EVENT_SIZE];
size_t size;
bool expected;
};
static struct uclogic_raw_event_hook_test hook_events[] = {
{
.event = { 0xA1, 0xB2, 0xC3, 0xD4 },
.size = 4,
},
{
.event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A },
.size = 6,
},
};
static struct uclogic_raw_event_hook_test test_events[] = {
{
.event = { 0xA1, 0xB2, 0xC3, 0xD4 },
.size = 4,
.expected = true,
},
{
.event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A },
.size = 6,
.expected = true,
},
{
.event = { 0xA1, 0xB2, 0xC3 },
.size = 3,
.expected = false,
},
{
.event = { 0xA1, 0xB2, 0xC3, 0xD4, 0x00 },
.size = 5,
.expected = false,
},
{
.event = { 0x2E, 0x3D, 0x4C, 0x5B, 0x6A, 0x1F },
.size = 6,
.expected = false,
},
};
static void hid_test_uclogic_exec_event_hook_test(struct kunit *test)
{
struct uclogic_params p = {0, };
struct uclogic_raw_event_hook *filter;
bool res;
int n;
/* Initialize the list of events to hook */
p.event_hooks = kunit_kzalloc(test, sizeof(*p.event_hooks), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p.event_hooks);
INIT_LIST_HEAD(&p.event_hooks->list);
for (n = 0; n < ARRAY_SIZE(hook_events); n++) {
filter = kunit_kzalloc(test, sizeof(*filter), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter);
filter->size = hook_events[n].size;
filter->event = kunit_kzalloc(test, filter->size, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter->event);
memcpy(filter->event, &hook_events[n].event[0], filter->size);
list_add_tail(&filter->list, &p.event_hooks->list);
}
/* Test uclogic_exec_event_hook() */
for (n = 0; n < ARRAY_SIZE(test_events); n++) {
res = uclogic_exec_event_hook(&p, &test_events[n].event[0],
test_events[n].size);
KUNIT_ASSERT_EQ(test, res, test_events[n].expected);
}
}
static struct kunit_case hid_uclogic_core_test_cases[] = {
KUNIT_CASE(hid_test_uclogic_exec_event_hook_test),
{}
};
static struct kunit_suite hid_uclogic_core_test_suite = {
.name = "hid_uclogic_core_test",
.test_cases = hid_uclogic_core_test_cases,
};
kunit_test_suite(hid_uclogic_core_test_suite);
MODULE_DESCRIPTION("KUnit tests for the UC-Logic driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");

View file

@ -22,25 +22,6 @@
#include "hid-ids.h"
/* Driver data */
struct uclogic_drvdata {
/* Interface parameters */
struct uclogic_params params;
/* Pointer to the replacement report descriptor. NULL if none. */
__u8 *desc_ptr;
/*
* Size of the replacement report descriptor.
* Only valid if desc_ptr is not NULL
*/
unsigned int desc_size;
/* Pen input device */
struct input_dev *pen_input;
/* In-range timer */
struct timer_list inrange_timer;
/* Last rotary encoder state, or U8_MAX for none */
u8 re_state;
};
/**
* uclogic_inrange_timeout - handle pen in-range state timeout.
* Emulate input events normally generated when pen goes out of range for
@ -202,6 +183,7 @@ static int uclogic_probe(struct hid_device *hdev,
}
timer_setup(&drvdata->inrange_timer, uclogic_inrange_timeout, 0);
drvdata->re_state = U8_MAX;
drvdata->quirks = id->driver_data;
hid_set_drvdata(hdev, drvdata);
/* Initialize the device and retrieve interface parameters */
@ -267,6 +249,34 @@ static int uclogic_resume(struct hid_device *hdev)
}
#endif
/**
* uclogic_exec_event_hook - if the received event is hooked schedules the
* associated work.
*
* @p: Tablet interface report parameters.
* @event: Raw event.
* @size: The size of event.
*
* Returns:
* Whether the event was hooked or not.
*/
static bool uclogic_exec_event_hook(struct uclogic_params *p, u8 *event, int size)
{
struct uclogic_raw_event_hook *curr;
if (!p->event_hooks)
return false;
list_for_each_entry(curr, &p->event_hooks->list, list) {
if (curr->size == size && memcmp(curr->event, event, size) == 0) {
schedule_work(&curr->work);
return true;
}
}
return false;
}
/**
* uclogic_raw_event_pen - handle raw pen events (pen HID reports).
*
@ -425,6 +435,9 @@ static int uclogic_raw_event(struct hid_device *hdev,
if (report->type != HID_INPUT_REPORT)
return 0;
if (uclogic_exec_event_hook(params, data, size))
return 0;
while (true) {
/* Tweak pen reports, if necessary */
if ((report_id == params->pen.id) && (size >= 2)) {
@ -529,8 +542,14 @@ static const struct hid_device_id uclogic_devices[] = {
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
.driver_data = UCLOGIC_MOUSE_FRAME_QUIRK | UCLOGIC_BATTERY_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
.driver_data = UCLOGIC_MOUSE_FRAME_QUIRK | UCLOGIC_BATTERY_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) },
{ }
@ -556,3 +575,7 @@ module_hid_driver(uclogic_driver);
MODULE_AUTHOR("Martin Rusko");
MODULE_AUTHOR("Nikolai Kondrashov");
MODULE_LICENSE("GPL");
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-uclogic-core-test.c"
#endif

View file

@ -174,9 +174,25 @@ static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test)
KUNIT_EXPECT_EQ(test, params->frame_type, frame_type);
}
static void hid_test_uclogic_params_cleanup_event_hooks(struct kunit *test)
{
int res, n;
struct uclogic_params p = {0, };
res = uclogic_params_ugee_v2_init_event_hooks(NULL, &p);
KUNIT_ASSERT_EQ(test, res, 0);
/* Check that the function can be called repeatedly */
for (n = 0; n < 4; n++) {
uclogic_params_cleanup_event_hooks(&p);
KUNIT_EXPECT_PTR_EQ(test, p.event_hooks, NULL);
}
}
static struct kunit_case hid_uclogic_params_test_cases[] = {
KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc,
uclogic_parse_ugee_v2_desc_gen_params),
KUNIT_CASE(hid_test_uclogic_params_cleanup_event_hooks),
{}
};

View file

@ -615,6 +615,31 @@ static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame,
return rc;
}
/**
* uclogic_params_cleanup_event_hooks - free resources used by the list of raw
* event hooks.
* Can be called repeatedly.
*
* @params: Input parameters to cleanup. Cannot be NULL.
*/
static void uclogic_params_cleanup_event_hooks(struct uclogic_params *params)
{
struct uclogic_raw_event_hook *curr, *n;
if (!params || !params->event_hooks)
return;
list_for_each_entry_safe(curr, n, &params->event_hooks->list, list) {
cancel_work_sync(&curr->work);
list_del(&curr->list);
kfree(curr->event);
kfree(curr);
}
kfree(params->event_hooks);
params->event_hooks = NULL;
}
/**
* uclogic_params_cleanup - free resources used by struct uclogic_params
* (tablet interface's parameters).
@ -631,6 +656,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
uclogic_params_frame_cleanup(&params->frame_list[i]);
uclogic_params_cleanup_event_hooks(params);
memset(params, 0, sizeof(*params));
}
}
@ -1021,8 +1047,8 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_probe_interface(struct hid_device *hdev, u8 *magic_arr,
int magic_size, int endpoint)
static int uclogic_probe_interface(struct hid_device *hdev, const u8 *magic_arr,
size_t magic_size, int endpoint)
{
struct usb_device *udev;
unsigned int pipe = 0;
@ -1222,6 +1248,11 @@ static int uclogic_params_ugee_v2_init_frame_mouse(struct uclogic_params *p)
*/
static bool uclogic_params_ugee_v2_has_battery(struct hid_device *hdev)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
if (drvdata->quirks & UCLOGIC_BATTERY_QUIRK)
return true;
/* The XP-PEN Deco LW vendor, product and version are identical to the
* Deco L. The only difference reported by their firmware is the product
* name. Add a quirk to support battery reporting on the wireless
@ -1275,6 +1306,72 @@ static int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
return rc;
}
/**
* uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
* connection to the USB dongle and reconnects, either because of its physical
* distance or because it was switches off and on using the frame's switch,
* uclogic_probe_interface() needs to be called again to enable the tablet.
*
* @work: The work that triggered this function.
*/
static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
{
struct uclogic_raw_event_hook *event_hook;
event_hook = container_of(work, struct uclogic_raw_event_hook, work);
uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
uclogic_ugee_v2_probe_size,
uclogic_ugee_v2_probe_endpoint);
}
/**
* uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
* to be hooked for UGEE v2 devices.
* @hdev: The HID device of the tablet interface to initialize and get
* parameters from.
* @p: Parameters to fill in, cannot be NULL.
*
* Returns:
* Zero, if successful. A negative errno code on error.
*/
static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
struct uclogic_params *p)
{
struct uclogic_raw_event_hook *event_hook;
__u8 reconnect_event[] = {
/* Event received on wireless tablet reconnection */
0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
if (!p)
return -EINVAL;
/* The reconnection event is only received if the tablet has battery */
if (!uclogic_params_ugee_v2_has_battery(hdev))
return 0;
p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
if (!p->event_hooks)
return -ENOMEM;
INIT_LIST_HEAD(&p->event_hooks->list);
event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
if (!event_hook)
return -ENOMEM;
INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
event_hook->hdev = hdev;
event_hook->size = ARRAY_SIZE(reconnect_event);
event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
if (!event_hook->event)
return -ENOMEM;
list_add_tail(&event_hook->list, &p->event_hooks->list);
return 0;
}
/**
* uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
* discovering their parameters.
@ -1298,6 +1395,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
struct hid_device *hdev)
{
int rc = 0;
struct uclogic_drvdata *drvdata;
struct usb_interface *iface;
__u8 bInterfaceNumber;
const int str_desc_len = 12;
@ -1305,9 +1403,6 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
__u8 *rdesc_pen = NULL;
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
enum uclogic_params_frame_type frame_type;
__u8 magic_arr[] = {
0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* The resulting parameters (noop) */
struct uclogic_params p = {0, };
@ -1316,6 +1411,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
goto cleanup;
}
drvdata = hid_get_drvdata(hdev);
iface = to_usb_interface(hdev->dev.parent);
bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
@ -1337,7 +1433,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
* The specific data was discovered by sniffing the Windows driver
* traffic.
*/
rc = uclogic_probe_interface(hdev, magic_arr, sizeof(magic_arr), 0x03);
rc = uclogic_probe_interface(hdev, uclogic_ugee_v2_probe_arr,
uclogic_ugee_v2_probe_size,
uclogic_ugee_v2_probe_endpoint);
if (rc) {
uclogic_params_init_invalid(&p);
goto output;
@ -1382,6 +1480,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
/* Initialize the frame interface */
if (drvdata->quirks & UCLOGIC_MOUSE_FRAME_QUIRK)
frame_type = UCLOGIC_PARAMS_FRAME_MOUSE;
switch (frame_type) {
case UCLOGIC_PARAMS_FRAME_DIAL:
case UCLOGIC_PARAMS_FRAME_MOUSE:
@ -1407,6 +1508,13 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
}
}
/* Create a list of raw events to be ignored */
rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
if (rc) {
hid_err(hdev, "error initializing event hook list: %d\n", rc);
goto cleanup;
}
output:
/* Output parameters */
memcpy(params, &p, sizeof(*params));
@ -1659,8 +1767,12 @@ int uclogic_params_init(struct uclogic_params *params,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2):
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW):
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S):
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW):
rc = uclogic_params_ugee_v2_init(&p, hdev);
if (rc != 0)
goto cleanup;

View file

@ -18,6 +18,10 @@
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/list.h>
#define UCLOGIC_MOUSE_FRAME_QUIRK BIT(0)
#define UCLOGIC_BATTERY_QUIRK BIT(1)
/* Types of pen in-range reporting */
enum uclogic_params_pen_inrange {
@ -173,6 +177,17 @@ struct uclogic_params_frame {
unsigned int bitmap_dial_byte;
};
/*
* List of works to be performed when a certain raw event is received.
*/
struct uclogic_raw_event_hook {
struct hid_device *hdev;
__u8 *event;
size_t size;
struct work_struct work;
struct list_head list;
};
/*
* Tablet interface report parameters.
*
@ -213,6 +228,31 @@ struct uclogic_params {
* parts. Only valid, if "invalid" is false.
*/
struct uclogic_params_frame frame_list[3];
/*
* List of event hooks.
*/
struct uclogic_raw_event_hook *event_hooks;
};
/* Driver data */
struct uclogic_drvdata {
/* Interface parameters */
struct uclogic_params params;
/* Pointer to the replacement report descriptor. NULL if none. */
__u8 *desc_ptr;
/*
* Size of the replacement report descriptor.
* Only valid if desc_ptr is not NULL
*/
unsigned int desc_size;
/* Pen input device */
struct input_dev *pen_input;
/* In-range timer */
struct timer_list inrange_timer;
/* Last rotary encoder state, or U8_MAX for none */
u8 re_state;
/* Device quirks */
unsigned long quirks;
};
/* Initialize a tablet interface and discover its parameters */

View file

@ -197,8 +197,7 @@ static void hid_test_uclogic_template(struct kunit *test)
params->param_list,
params->param_num);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res);
KUNIT_EXPECT_EQ(test, 0,
memcmp(res, params->expected, params->template_size));
KUNIT_EXPECT_MEMEQ(test, res, params->expected, params->template_size);
kfree(res);
}

View file

@ -859,6 +859,12 @@ const __u8 uclogic_rdesc_v2_frame_dial_arr[] = {
const size_t uclogic_rdesc_v2_frame_dial_size =
sizeof(uclogic_rdesc_v2_frame_dial_arr);
const __u8 uclogic_ugee_v2_probe_arr[] = {
0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const size_t uclogic_ugee_v2_probe_size = sizeof(uclogic_ugee_v2_probe_arr);
const int uclogic_ugee_v2_probe_endpoint = 0x03;
/* Fixed report descriptor template for UGEE v2 pen reports */
const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[] = {
0x05, 0x0d, /* Usage Page (Digitizers), */

View file

@ -164,6 +164,11 @@ extern const size_t uclogic_rdesc_v2_frame_dial_size;
/* Report ID for tweaked UGEE v2 battery reports */
#define UCLOGIC_RDESC_UGEE_V2_BATTERY_ID 0xba
/* Magic data expected by UGEEv2 devices on probe */
extern const __u8 uclogic_ugee_v2_probe_arr[];
extern const size_t uclogic_ugee_v2_probe_size;
extern const int uclogic_ugee_v2_probe_endpoint;
/* Fixed report descriptor template for UGEE v2 pen reports */
extern const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[];
extern const size_t uclogic_rdesc_ugee_v2_pen_template_size;

View file

@ -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

View file

@ -39,8 +39,8 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
* The CHPN0001 ACPI device, which is used to describe the Chipone
* ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
*/
{"CHPN0001", 0 },
{ },
{ "CHPN0001" },
{ }
};
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
@ -48,8 +48,9 @@ static guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
{
struct acpi_device *adev = ihid_acpi->adev;
acpi_handle handle = acpi_device_handle(adev);
union acpi_object *obj;
u16 hid_descriptor_address;
@ -81,38 +82,31 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct i2c_hid_acpi *ihid_acpi;
struct acpi_device *adev;
u16 hid_descriptor_address;
int ret;
adev = ACPI_COMPANION(dev);
if (!adev) {
dev_err(&client->dev, "Error could not get ACPI device\n");
return -ENODEV;
}
ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;
ihid_acpi->adev = adev;
ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
ret = i2c_hid_acpi_get_descriptor(adev);
ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
if (ret < 0)
return ret;
hid_descriptor_address = ret;
acpi_device_fix_up_power(adev);
acpi_device_fix_up_power(ihid_acpi->adev);
return i2c_hid_core_probe(client, &ihid_acpi->ops,
hid_descriptor_address, 0);
}
static const struct acpi_device_id i2c_hid_acpi_match[] = {
{"ACPI0C50", 0 },
{"PNP0C50", 0 },
{ },
{ "ACPI0C50" },
{ "PNP0C50" },
{ }
};
MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);

View file

@ -67,16 +67,7 @@
#define I2C_HID_PWR_ON 0x00
#define I2C_HID_PWR_SLEEP 0x01
/* debug option */
static bool debug;
module_param(debug, bool, 0444);
MODULE_PARM_DESC(debug, "print a lot of debug information");
#define i2c_hid_dbg(ihid, fmt, arg...) \
do { \
if (debug) \
dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg); \
} while (0)
#define i2c_hid_dbg(ihid, ...) dev_dbg(&(ihid)->client->dev, __VA_ARGS__)
struct i2c_hid_desc {
__le16 wHIDDescLength;
@ -842,7 +833,7 @@ static void i2c_hid_close(struct hid_device *hid)
clear_bit(I2C_HID_STARTED, &ihid->flags);
}
struct hid_ll_driver i2c_hid_ll_driver = {
static const struct hid_ll_driver i2c_hid_ll_driver = {
.parse = i2c_hid_parse,
.start = i2c_hid_start,
.stop = i2c_hid_stop,
@ -851,7 +842,6 @@ struct hid_ll_driver i2c_hid_ll_driver = {
.output_report = i2c_hid_output_report,
.raw_request = i2c_hid_raw_request,
};
EXPORT_SYMBOL_GPL(i2c_hid_ll_driver);
static int i2c_hid_init_irq(struct i2c_client *client)
{
@ -859,7 +849,7 @@ static int i2c_hid_init_irq(struct i2c_client *client)
unsigned long irqflags = 0;
int ret;
dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
i2c_hid_dbg(ihid, "Requesting IRQ: %d\n", client->irq);
if (!irq_get_trigger_type(client->irq))
irqflags = IRQF_TRIGGER_LOW;
@ -1003,7 +993,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
/* Make sure there is something at this address */
ret = i2c_smbus_read_byte(client);
if (ret < 0) {
dev_dbg(&client->dev, "nothing at this address: %d\n", ret);
i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
ret = -ENXIO;
goto err_powered;
}
@ -1035,6 +1025,10 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
hid->product = le16_to_cpu(ihid->hdesc.wProductID);
hid->initial_quirks = quirks;
hid->initial_quirks |= i2c_hid_get_dmi_quirks(hid->vendor,
hid->product);
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
client->name, (u16)hid->vendor, (u16)hid->product);
strscpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
@ -1048,8 +1042,6 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
goto err_mem_free;
}
hid->quirks |= quirks;
return 0;
err_mem_free:

View file

@ -10,8 +10,10 @@
#include <linux/types.h>
#include <linux/dmi.h>
#include <linux/mod_devicetable.h>
#include <linux/hid.h>
#include "i2c-hid.h"
#include "../hid-ids.h"
struct i2c_hid_desc_override {
@ -416,6 +418,28 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
{ } /* Terminate list */
};
static const struct hid_device_id i2c_hid_elan_flipped_quirks = {
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_ELAN, 0x2dcd),
HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT
};
/*
* This list contains devices which have specific issues based on the system
* they're on and not just the device itself. The driver_data will have a
* specific hid device to match against.
*/
static const struct dmi_system_id i2c_hid_dmi_quirk_table[] = {
{
.ident = "DynaBook K50/FR",
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dynabook Inc."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "dynabook K50/FR"),
},
.driver_data = (void *)&i2c_hid_elan_flipped_quirks,
},
{ } /* Terminate list */
};
struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name)
{
@ -450,3 +474,21 @@ char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
*size = override->hid_report_desc_size;
return override->hid_report_desc;
}
u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
{
u32 quirks = 0;
const struct dmi_system_id *system_id =
dmi_first_match(i2c_hid_dmi_quirk_table);
if (system_id) {
const struct hid_device_id *device_id =
(struct hid_device_id *)(system_id->driver_data);
if (device_id && device_id->vendor == vendor &&
device_id->product == product)
quirks = device_id->driver_data;
}
return quirks;
}

View file

@ -26,28 +26,33 @@ struct i2c_hid_of_goodix {
struct i2chid_ops ops;
struct regulator *vdd;
struct notifier_block nb;
struct regulator *vddio;
struct gpio_desc *reset_gpio;
const struct goodix_i2c_hid_timing_data *timings;
};
static void goodix_i2c_hid_deassert_reset(struct i2c_hid_of_goodix *ihid_goodix,
bool regulator_just_turned_on)
{
if (regulator_just_turned_on && ihid_goodix->timings->post_power_delay_ms)
msleep(ihid_goodix->timings->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0);
if (ihid_goodix->timings->post_gpio_reset_delay_ms)
msleep(ihid_goodix->timings->post_gpio_reset_delay_ms);
}
static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
{
struct i2c_hid_of_goodix *ihid_goodix =
container_of(ops, struct i2c_hid_of_goodix, ops);
int ret;
return regulator_enable(ihid_goodix->vdd);
ret = regulator_enable(ihid_goodix->vdd);
if (ret)
return ret;
ret = regulator_enable(ihid_goodix->vddio);
if (ret)
return ret;
if (ihid_goodix->timings->post_power_delay_ms)
msleep(ihid_goodix->timings->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0);
if (ihid_goodix->timings->post_gpio_reset_delay_ms)
msleep(ihid_goodix->timings->post_gpio_reset_delay_ms);
return 0;
}
static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
@ -55,42 +60,15 @@ static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
struct i2c_hid_of_goodix *ihid_goodix =
container_of(ops, struct i2c_hid_of_goodix, ops);
gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
regulator_disable(ihid_goodix->vddio);
regulator_disable(ihid_goodix->vdd);
}
static int ihid_goodix_vdd_notify(struct notifier_block *nb,
unsigned long event,
void *ignored)
{
struct i2c_hid_of_goodix *ihid_goodix =
container_of(nb, struct i2c_hid_of_goodix, nb);
int ret = NOTIFY_OK;
switch (event) {
case REGULATOR_EVENT_PRE_DISABLE:
gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
break;
case REGULATOR_EVENT_ENABLE:
goodix_i2c_hid_deassert_reset(ihid_goodix, true);
break;
case REGULATOR_EVENT_ABORT_DISABLE:
goodix_i2c_hid_deassert_reset(ihid_goodix, false);
break;
default:
ret = NOTIFY_DONE;
break;
}
return ret;
}
static int i2c_hid_of_goodix_probe(struct i2c_client *client)
{
struct i2c_hid_of_goodix *ihid_goodix;
int ret;
ihid_goodix = devm_kzalloc(&client->dev, sizeof(*ihid_goodix),
GFP_KERNEL);
if (!ihid_goodix)
@ -109,42 +87,12 @@ static int i2c_hid_of_goodix_probe(struct i2c_client *client)
if (IS_ERR(ihid_goodix->vdd))
return PTR_ERR(ihid_goodix->vdd);
ihid_goodix->vddio = devm_regulator_get(&client->dev, "mainboard-vddio");
if (IS_ERR(ihid_goodix->vddio))
return PTR_ERR(ihid_goodix->vddio);
ihid_goodix->timings = device_get_match_data(&client->dev);
/*
* We need to control the "reset" line in lockstep with the regulator
* actually turning on an off instead of just when we make the request.
* This matters if the regulator is shared with another consumer.
* - If the regulator is off then we must assert reset. The reset
* line is active low and on some boards it could cause a current
* leak if left high.
* - If the regulator is on then we don't want reset asserted for very
* long. Holding the controller in reset apparently draws extra
* power.
*/
ihid_goodix->nb.notifier_call = ihid_goodix_vdd_notify;
ret = devm_regulator_register_notifier(ihid_goodix->vdd, &ihid_goodix->nb);
if (ret)
return dev_err_probe(&client->dev, ret,
"regulator notifier request failed\n");
/*
* If someone else is holding the regulator on (or the regulator is
* an always-on one) we might never be told to deassert reset. Do it
* now... and temporarily bump the regulator reference count just to
* make sure it is impossible for this to race with our own notifier!
* We also assume that someone else might have _just barely_ turned
* the regulator on so we'll do the full "post_power_delay" just in
* case.
*/
if (ihid_goodix->reset_gpio && regulator_is_enabled(ihid_goodix->vdd)) {
ret = regulator_enable(ihid_goodix->vdd);
if (ret)
return ret;
goodix_i2c_hid_deassert_reset(ihid_goodix, true);
regulator_disable(ihid_goodix->vdd);
}
return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001, 0);
}

View file

@ -9,6 +9,7 @@
struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name);
char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
unsigned int *size);
u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product);
#else
static inline struct i2c_hid_desc
*i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name)
@ -16,6 +17,8 @@ static inline struct i2c_hid_desc
static inline char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
unsigned int *size)
{ return NULL; }
static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
{ return 0; }
#endif
/**

View file

@ -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

View file

@ -183,7 +183,7 @@ void ishtp_hid_wakeup(struct hid_device *hid)
wake_up_interruptible(&hid_data->hid_wait);
}
static struct hid_ll_driver ishtp_hid_ll_driver = {
static const struct hid_ll_driver ishtp_hid_ll_driver = {
.parse = ishtp_hid_parse,
.start = ishtp_hid_start,
.stop = ishtp_hid_stop,

View file

@ -174,7 +174,7 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn
return -EIO;
}
static struct hid_ll_driver surface_hid_ll_driver = {
static const struct hid_ll_driver surface_hid_ll_driver = {
.start = surface_hid_start,
.stop = surface_hid_stop,
.open = surface_hid_open,

View file

@ -387,7 +387,7 @@ static int uhid_hid_output_report(struct hid_device *hid, __u8 *buf,
return uhid_hid_output_raw(hid, buf, count, HID_OUTPUT_REPORT);
}
struct hid_ll_driver uhid_hid_driver = {
static const struct hid_ll_driver uhid_hid_driver = {
.start = uhid_hid_start,
.stop = uhid_hid_stop,
.open = uhid_hid_open,
@ -396,7 +396,6 @@ struct hid_ll_driver uhid_hid_driver = {
.raw_request = uhid_hid_raw_request,
.output_report = uhid_hid_output_report,
};
EXPORT_SYMBOL_GPL(uhid_hid_driver);
#ifdef CONFIG_COMPAT

View file

@ -1318,7 +1318,7 @@ static bool usbhid_may_wakeup(struct hid_device *hid)
return device_may_wakeup(&dev->dev);
}
struct hid_ll_driver usb_hid_driver = {
static const struct hid_ll_driver usb_hid_driver = {
.parse = usbhid_parse,
.start = usbhid_start,
.stop = usbhid_stop,
@ -1332,7 +1332,12 @@ struct hid_ll_driver usb_hid_driver = {
.idle = usbhid_idle,
.may_wakeup = usbhid_may_wakeup,
};
EXPORT_SYMBOL_GPL(usb_hid_driver);
bool hid_is_usb(const struct hid_device *hdev)
{
return hdev->ll_driver == &usb_hid_driver;
}
EXPORT_SYMBOL_GPL(hid_is_usb);
static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{

View file

@ -86,6 +86,7 @@ static int als_read_raw(struct iio_dev *indio_dev,
long mask)
{
struct als_state *als_state = iio_priv(indio_dev);
struct hid_sensor_hub_device *hsdev = als_state->common_attributes.hsdev;
int report_id = -1;
u32 address;
int ret_type;
@ -110,11 +111,8 @@ static int als_read_raw(struct iio_dev *indio_dev,
hid_sensor_power_state(&als_state->common_attributes,
true);
*val = sensor_hub_input_attr_get_raw_value(
als_state->common_attributes.hsdev,
HID_USAGE_SENSOR_ALS, address,
report_id,
SENSOR_HUB_SYNC,
min < 0);
hsdev, hsdev->usage, address, report_id,
SENSOR_HUB_SYNC, min < 0);
hid_sensor_power_state(&als_state->common_attributes,
false);
} else {
@ -259,9 +257,7 @@ static int als_parse_report(struct platform_device *pdev,
dev_dbg(&pdev->dev, "als %x:%x\n", st->als_illum.index,
st->als_illum.report_id);
st->scale_precision = hid_sensor_format_scale(
HID_USAGE_SENSOR_ALS,
&st->als_illum,
st->scale_precision = hid_sensor_format_scale(usage_id, &st->als_illum,
&st->scale_pre_decml, &st->scale_post_decml);
return ret;
@ -285,7 +281,8 @@ static int hid_als_probe(struct platform_device *pdev)
als_state->common_attributes.hsdev = hsdev;
als_state->common_attributes.pdev = pdev;
ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_ALS,
ret = hid_sensor_parse_common_attributes(hsdev,
hsdev->usage,
&als_state->common_attributes,
als_sensitivity_addresses,
ARRAY_SIZE(als_sensitivity_addresses));
@ -303,7 +300,8 @@ static int hid_als_probe(struct platform_device *pdev)
ret = als_parse_report(pdev, hsdev,
(struct iio_chan_spec *)indio_dev->channels,
HID_USAGE_SENSOR_ALS, als_state);
hsdev->usage,
als_state);
if (ret) {
dev_err(&pdev->dev, "failed to setup attributes\n");
return ret;
@ -333,8 +331,7 @@ static int hid_als_probe(struct platform_device *pdev)
als_state->callbacks.send_event = als_proc_event;
als_state->callbacks.capture_sample = als_capture_sample;
als_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_ALS,
&als_state->callbacks);
ret = sensor_hub_register_callback(hsdev, hsdev->usage, &als_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
@ -356,7 +353,7 @@ static int hid_als_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct als_state *als_state = iio_priv(indio_dev);
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_ALS);
sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
hid_sensor_remove_trigger(indio_dev, &als_state->common_attributes);
@ -368,6 +365,10 @@ static const struct platform_device_id hid_als_ids[] = {
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200041",
},
{
/* Format: HID-SENSOR-custom_sensor_tag-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-LISS-0041",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, hid_als_ids);

View file

@ -61,6 +61,7 @@ static int prox_read_raw(struct iio_dev *indio_dev,
long mask)
{
struct prox_state *prox_state = iio_priv(indio_dev);
struct hid_sensor_hub_device *hsdev;
int report_id = -1;
u32 address;
int ret_type;
@ -75,6 +76,7 @@ static int prox_read_raw(struct iio_dev *indio_dev,
report_id = prox_state->prox_attr.report_id;
min = prox_state->prox_attr.logical_minimum;
address = HID_USAGE_SENSOR_HUMAN_PRESENCE;
hsdev = prox_state->common_attributes.hsdev;
break;
default:
report_id = -1;
@ -84,11 +86,8 @@ static int prox_read_raw(struct iio_dev *indio_dev,
hid_sensor_power_state(&prox_state->common_attributes,
true);
*val = sensor_hub_input_attr_get_raw_value(
prox_state->common_attributes.hsdev,
HID_USAGE_SENSOR_PROX, address,
report_id,
SENSOR_HUB_SYNC,
min < 0);
hsdev, hsdev->usage, address, report_id,
SENSOR_HUB_SYNC, min < 0);
hid_sensor_power_state(&prox_state->common_attributes,
false);
} else {
@ -191,10 +190,16 @@ static int prox_capture_sample(struct hid_sensor_hub_device *hsdev,
switch (usage_id) {
case HID_USAGE_SENSOR_HUMAN_PRESENCE:
prox_state->human_presence = *(u32 *)raw_data;
ret = 0;
break;
default:
switch (raw_len) {
case 1:
prox_state->human_presence = *(u8 *)raw_data;
return 0;
case 4:
prox_state->human_presence = *(u32 *)raw_data;
return 0;
default:
break;
}
break;
}
@ -244,7 +249,7 @@ static int hid_prox_probe(struct platform_device *pdev)
prox_state->common_attributes.hsdev = hsdev;
prox_state->common_attributes.pdev = pdev;
ret = hid_sensor_parse_common_attributes(hsdev, HID_USAGE_SENSOR_PROX,
ret = hid_sensor_parse_common_attributes(hsdev, hsdev->usage,
&prox_state->common_attributes,
prox_sensitivity_addresses,
ARRAY_SIZE(prox_sensitivity_addresses));
@ -262,7 +267,7 @@ static int hid_prox_probe(struct platform_device *pdev)
ret = prox_parse_report(pdev, hsdev,
(struct iio_chan_spec *)indio_dev->channels,
HID_USAGE_SENSOR_PROX, prox_state);
hsdev->usage, prox_state);
if (ret) {
dev_err(&pdev->dev, "failed to setup attributes\n");
return ret;
@ -291,8 +296,8 @@ static int hid_prox_probe(struct platform_device *pdev)
prox_state->callbacks.send_event = prox_proc_event;
prox_state->callbacks.capture_sample = prox_capture_sample;
prox_state->callbacks.pdev = pdev;
ret = sensor_hub_register_callback(hsdev, HID_USAGE_SENSOR_PROX,
&prox_state->callbacks);
ret = sensor_hub_register_callback(hsdev, hsdev->usage,
&prox_state->callbacks);
if (ret < 0) {
dev_err(&pdev->dev, "callback reg failed\n");
goto error_iio_unreg;
@ -314,7 +319,7 @@ static int hid_prox_remove(struct platform_device *pdev)
struct iio_dev *indio_dev = platform_get_drvdata(pdev);
struct prox_state *prox_state = iio_priv(indio_dev);
sensor_hub_remove_callback(hsdev, HID_USAGE_SENSOR_PROX);
sensor_hub_remove_callback(hsdev, hsdev->usage);
iio_device_unregister(indio_dev);
hid_sensor_remove_trigger(indio_dev, &prox_state->common_attributes);
@ -326,6 +331,10 @@ static const struct platform_device_id hid_prox_ids[] = {
/* Format: HID-SENSOR-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-200011",
},
{
/* Format: HID-SENSOR-tag-usage_id_in_hex_lowercase */
.name = "HID-SENSOR-LISS-0226",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, hid_prox_ids);

View file

@ -250,7 +250,7 @@ static int tf103c_dock_hid_raw_request(struct hid_device *hid, u8 reportnum,
return 0;
}
static struct hid_ll_driver tf103c_dock_hid_ll_driver = {
static const struct hid_ll_driver tf103c_dock_hid_ll_driver = {
.parse = tf103c_dock_hid_parse,
.start = tf103c_dock_hid_start,
.stop = tf103c_dock_hid_stop,
@ -259,7 +259,7 @@ static struct hid_ll_driver tf103c_dock_hid_ll_driver = {
.raw_request = tf103c_dock_hid_raw_request,
};
static int tf103c_dock_toprow_codes[13][2] = {
static const int tf103c_dock_toprow_codes[13][2] = {
/* Normal, AltGr pressed */
{ KEY_POWER, KEY_F1 },
{ KEY_RFKILL, KEY_F2 },

View file

@ -381,7 +381,7 @@ static int gb_hid_power(struct hid_device *hid, int lvl)
}
/* HID structure to pass callbacks */
static struct hid_ll_driver gb_hid_ll_driver = {
static const struct hid_ll_driver gb_hid_ll_driver = {
.parse = gb_hid_parse,
.start = gb_hid_start,
.stop = gb_hid_stop,

View file

@ -132,6 +132,7 @@
#define HID_USAGE_SENSOR_PROP_FRIENDLY_NAME 0x200301
#define HID_USAGE_SENSOR_PROP_SERIAL_NUM 0x200307
#define HID_USAGE_SENSOR_PROP_MANUFACTURER 0x200305
#define HID_USAGE_SENSOR_PROP_MODEL 0x200306
#define HID_USAGE_SENSOR_PROP_REPORT_INTERVAL 0x20030E
#define HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS 0x20030F
#define HID_USAGE_SENSOR_PROP_SENSITIVITY_RANGE_PCT 0x200310

View file

@ -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
@ -312,6 +313,7 @@ struct hid_item {
#define HID_DG_LATENCYMODE 0x000d0060
#define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
#define HID_BAT_CHARGING 0x00850044
#define HID_VD_ASUS_CUSTOM_MEDIA_KEYS 0xff310076
@ -595,7 +597,7 @@ struct hid_device { /* device report descriptor */
struct device dev; /* device */
struct hid_driver *driver;
struct hid_ll_driver *ll_driver;
const struct hid_ll_driver *ll_driver;
struct mutex ll_open_lock;
unsigned int ll_open_count;
@ -611,6 +613,7 @@ struct hid_device { /* device report descriptor */
__s32 battery_max;
__s32 battery_report_type;
__s32 battery_report_id;
__s32 battery_charge_status;
enum hid_battery_status battery_status;
bool battery_avoid_query;
ktime_t battery_ratelimit_time;
@ -619,6 +622,7 @@ struct hid_device { /* device report descriptor */
unsigned long status; /* see STAT flags above */
unsigned claimed; /* Claimed by hidinput, hiddev? */
unsigned quirks; /* Various quirks the device can pull on us */
unsigned initial_quirks; /* Initial set of quirks supplied when creating device */
bool io_started; /* If IO has started */
struct list_head inputs; /* The list of inputs */
@ -651,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) \
@ -853,21 +861,7 @@ struct hid_ll_driver {
bool (*may_wakeup)(struct hid_device *hdev);
};
extern struct hid_ll_driver i2c_hid_ll_driver;
extern struct hid_ll_driver hidp_hid_driver;
extern struct hid_ll_driver uhid_hid_driver;
extern struct hid_ll_driver usb_hid_driver;
static inline bool hid_is_using_ll_driver(struct hid_device *hdev,
struct hid_ll_driver *driver)
{
return hdev->ll_driver == driver;
}
static inline bool hid_is_usb(struct hid_device *hdev)
{
return hid_is_using_ll_driver(hdev, &usb_hid_driver);
}
extern bool hid_is_usb(const struct hid_device *hdev);
#define PM_HINT_FULLON 1<<5
#define PM_HINT_NORMAL 1<<1
@ -882,8 +876,6 @@ static inline bool hid_is_usb(struct hid_device *hdev)
/* HID core API */
extern int hid_debug;
extern bool hid_ignore(struct hid_device *);
extern int hid_add_device(struct hid_device *);
extern void hid_destroy_device(struct hid_device *);
@ -1191,11 +1183,7 @@ int hid_pidff_init(struct hid_device *hid);
#define hid_pidff_init NULL
#endif
#define dbg_hid(fmt, ...) \
do { \
if (hid_debug) \
printk(KERN_DEBUG "%s: " fmt, __FILE__, ##__VA_ARGS__); \
} while (0)
#define dbg_hid(fmt, ...) pr_debug("%s: " fmt, __FILE__, ##__VA_ARGS__)
#define hid_err(hid, fmt, ...) \
dev_err(&(hid)->dev, fmt, ##__VA_ARGS__)

170
include/linux/hid_bpf.h Normal file
View file

@ -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 */

View file

@ -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

View file

@ -739,7 +739,7 @@ static void hidp_stop(struct hid_device *hid)
hid->claimed = 0;
}
struct hid_ll_driver hidp_hid_driver = {
static const struct hid_ll_driver hidp_hid_driver = {
.parse = hidp_parse,
.start = hidp_start,
.stop = hidp_stop,
@ -748,7 +748,6 @@ struct hid_ll_driver hidp_hid_driver = {
.raw_request = hidp_raw_request,
.output_report = hidp_output_report,
};
EXPORT_SYMBOL_GPL(hidp_hid_driver);
/* This function sets up the hid device. It does not add it
to the HID system. That is done in hidp_add_connection(). */

8
samples/hid/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
hid_mouse
hid_surface_dial
*.out
*.skel.h
/vmlinux.h
/bpftool/
/libbpf/

250
samples/hid/Makefile Normal file
View file

@ -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

View file

@ -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)

View file

@ -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;
}

View file

@ -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 */

View file

@ -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 */

112
samples/hid/hid_mouse.bpf.c Normal file
View file

@ -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";

155
samples/hid/hid_mouse.c Normal file
View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -26,6 +26,7 @@ TARGETS += fpu
TARGETS += ftrace
TARGETS += futex
TARGETS += gpio
TARGETS += hid
TARGETS += intel_pstate
TARGETS += iommu
TARGETS += ipc

View file

@ -0,0 +1,5 @@
bpftool
*.skel.h
/tools
hid_bpf
results

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,4 @@
CONFIG_X86_ACPI_CPUFREQ=y
CONFIG_X86_CPUID=y
CONFIG_X86_MSR=y
CONFIG_X86_POWERNOW_K8=y

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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 */

View file

@ -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 "$@"