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:
commit
6c71297eaf
@ -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
|
||||
|
@ -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
|
||||
========== ======
|
||||
|
522
Documentation/hid/hid-bpf.rst
Normal file
522
Documentation/hid/hid-bpf.rst
Normal 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.
|
@ -8,7 +8,7 @@ Introduction
|
||||
In addition to the normal input type HID devices, USB also uses the
|
||||
human interface device protocols for things that are not really human
|
||||
interfaces, but have similar sorts of communication needs. The two big
|
||||
examples for this are power devices (especially uninterruptable power
|
||||
examples for this are power devices (especially uninterruptible power
|
||||
supplies) and monitor control on higher end monitors.
|
||||
|
||||
To support these disparate requirements, the Linux USB system provides
|
||||
|
@ -163,7 +163,7 @@ HIDIOCGOUTPUT(len):
|
||||
Get an Output Report
|
||||
|
||||
This ioctl will request an output report from the device using the control
|
||||
endpoint. Typically, this is used to retrive the initial state of
|
||||
endpoint. Typically, this is used to retrieve the initial state of
|
||||
an output report of a device, before an application updates it as necessary either
|
||||
via a HIDIOCSOUTPUT request, or the regular device write() interface. The format
|
||||
of the buffer issued with this report is identical to that of HIDIOCGFEATURE.
|
||||
|
@ -11,6 +11,7 @@ Human Interface Devices (HID)
|
||||
hidraw
|
||||
hid-sensor
|
||||
hid-transport
|
||||
hid-bpf
|
||||
|
||||
uhid
|
||||
|
||||
|
@ -199,7 +199,7 @@ the sender that the memory region for that message may be reused.
|
||||
DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message
|
||||
(that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK.
|
||||
Additionally to DMA address communication, this sequence checks capabilities:
|
||||
if thw host doesn't support DMA, then it won't send DMA allocation, so FW can't
|
||||
if the host doesn't support DMA, then it won't send DMA allocation, so FW can't
|
||||
send DMA; if FW doesn't support DMA then it won't respond with
|
||||
DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers.
|
||||
Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER,
|
||||
|
10
MAINTAINERS
10
MAINTAINERS
@ -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
|
||||
|
@ -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/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
16
drivers/hid/bpf/Kconfig
Normal 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
11
drivers/hid/bpf/Makefile
Normal 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
|
93
drivers/hid/bpf/entrypoints/Makefile
Normal file
93
drivers/hid/bpf/entrypoints/Makefile
Normal 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
|
4
drivers/hid/bpf/entrypoints/README
Normal file
4
drivers/hid/bpf/entrypoints/README
Normal 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
|
25
drivers/hid/bpf/entrypoints/entrypoints.bpf.c
Normal file
25
drivers/hid/bpf/entrypoints/entrypoints.bpf.c
Normal 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";
|
248
drivers/hid/bpf/entrypoints/entrypoints.lskel.h
Normal file
248
drivers/hid/bpf/entrypoints/entrypoints.lskel.h
Normal 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__ */
|
551
drivers/hid/bpf/hid_bpf_dispatch.c
Normal file
551
drivers/hid/bpf/hid_bpf_dispatch.c
Normal 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");
|
25
drivers/hid/bpf/hid_bpf_dispatch.h
Normal file
25
drivers/hid/bpf/hid_bpf_dispatch.h
Normal 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
|
565
drivers/hid/bpf/hid_bpf_jmp_table.c
Normal file
565
drivers/hid/bpf/hid_bpf_jmp_table.c
Normal 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;
|
||||
}
|
@ -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 @@ err_stop_hw:
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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 @@ err:
|
||||
|
||||
static void __exit hid_exit(void)
|
||||
{
|
||||
#ifdef CONFIG_HID_BPF
|
||||
hid_bpf_ops = NULL;
|
||||
#endif
|
||||
hid_debug_exit();
|
||||
hidraw_exit();
|
||||
bus_unregister(&hid_bus_type);
|
||||
|
@ -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
53
drivers/hid/hid-evision.c
Normal 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");
|
@ -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,
|
||||
|
@ -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
|
||||
|
80
drivers/hid/hid-input-test.c
Normal file
80
drivers/hid/hid-input-test.c
Normal 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>");
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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) },
|
||||
{}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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
@ -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 @@ hid_hw_start_fail:
|
||||
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
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
|
105
drivers/hid/hid-uclogic-core-test.c
Normal file
105
drivers/hid/hid-uclogic-core-test.c
Normal 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>");
|
@ -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
|
||||
|
@ -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),
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -615,6 +615,31 @@ cleanup:
|
||||
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, ¶ms->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(¶ms->frame_list[i]);
|
||||
|
||||
uclogic_params_cleanup_event_hooks(params);
|
||||
memset(params, 0, sizeof(*params));
|
||||
}
|
||||
}
|
||||
@ -1021,8 +1047,8 @@ cleanup:
|
||||
* 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;
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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), */
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 },
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
170
include/linux/hid_bpf.h
Normal 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 */
|
@ -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
|
||||
|
@ -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
8
samples/hid/.gitignore
vendored
Normal 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
250
samples/hid/Makefile
Normal 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
|
75
samples/hid/Makefile.target
Normal file
75
samples/hid/Makefile.target
Normal 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)
|
18
samples/hid/hid_bpf_attach.bpf.c
Normal file
18
samples/hid/hid_bpf_attach.bpf.c
Normal 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;
|
||||
}
|
14
samples/hid/hid_bpf_attach.h
Normal file
14
samples/hid/hid_bpf_attach.h
Normal 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 */
|
21
samples/hid/hid_bpf_helpers.h
Normal file
21
samples/hid/hid_bpf_helpers.h
Normal 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
112
samples/hid/hid_mouse.bpf.c
Normal 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
155
samples/hid/hid_mouse.c
Normal 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;
|
||||
}
|
134
samples/hid/hid_surface_dial.bpf.c
Normal file
134
samples/hid/hid_surface_dial.bpf.c
Normal 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;
|
226
samples/hid/hid_surface_dial.c
Normal file
226
samples/hid/hid_surface_dial.c
Normal 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;
|
||||
}
|
@ -26,6 +26,7 @@ TARGETS += fpu
|
||||
TARGETS += ftrace
|
||||
TARGETS += futex
|
||||
TARGETS += gpio
|
||||
TARGETS += hid
|
||||
TARGETS += intel_pstate
|
||||
TARGETS += iommu
|
||||
TARGETS += ipc
|
||||
|
5
tools/testing/selftests/hid/.gitignore
vendored
Normal file
5
tools/testing/selftests/hid/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bpftool
|
||||
*.skel.h
|
||||
/tools
|
||||
hid_bpf
|
||||
results
|
231
tools/testing/selftests/hid/Makefile
Normal file
231
tools/testing/selftests/hid/Makefile
Normal 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)
|
21
tools/testing/selftests/hid/config
Normal file
21
tools/testing/selftests/hid/config
Normal 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
|
241
tools/testing/selftests/hid/config.common
Normal file
241
tools/testing/selftests/hid/config.common
Normal 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
|
4
tools/testing/selftests/hid/config.x86_64
Normal file
4
tools/testing/selftests/hid/config.x86_64
Normal file
@ -0,0 +1,4 @@
|
||||
CONFIG_X86_ACPI_CPUFREQ=y
|
||||
CONFIG_X86_CPUID=y
|
||||
CONFIG_X86_MSR=y
|
||||
CONFIG_X86_POWERNOW_K8=y
|
869
tools/testing/selftests/hid/hid_bpf.c
Normal file
869
tools/testing/selftests/hid/hid_bpf.c
Normal 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);
|
||||
}
|
209
tools/testing/selftests/hid/progs/hid.c
Normal file
209
tools/testing/selftests/hid/progs/hid.c
Normal 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;
|
||||
}
|
21
tools/testing/selftests/hid/progs/hid_bpf_helpers.h
Normal file
21
tools/testing/selftests/hid/progs/hid_bpf_helpers.h
Normal 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 */
|
284
tools/testing/selftests/hid/vmtest.sh
Executable file
284
tools/testing/selftests/hid/vmtest.sh
Executable 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 "$@"
|
Loading…
x
Reference in New Issue
Block a user