hid-for-linus-2024051401
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIVAwUAZkNSnKZi849r7WBJAQLcQQ/9FEBxLEgnPWxz1k75NBcPXPTsxZmb9jAR qyEENrYc0bAhpp7kVtGeTyK4C7GgKyBD4jG4PNc7CBwmKyYObxZiXbbdO8n0RXI/ MzWVwcu1avm9q6dEYhutaZFuKxJIkWzykPQNO7ebZb49qPSzsNDCfrM6L5735rIU LNR761lMizypIzkuOfHBY+7wTFJuY8SQZtcSSYa27Iio2aagnAAdb5QJyKP7HeOr iFloEFLtW0Fq6VDWEE7Ud0Hbe0qgIRkRCTqlLiUpJu6Fal/ews4TA8NtFMBOUUGN tNTMj5VvZZPTIgRLH4kDSO2EceISLXPLZdGC2HoMplgHINhBLw+bL/RoQKxdk3VA 9eEErl/px+sUWsXXM0UznzQc65QbhsXw+nP+4UcQwUnyYhj/hzk09FNUxotIfySH hDExN42j65upN50NV4bJufyEYrosjXOsr/XfrDlaCShjv7TJaG9kuq2sA1ZUSLf6 JPhXvQcNCH4ZAUZ77nbE4+yX5wgNR1U3TXyanOFcfV5MXmu6T2expQdAUXwE26RM UICyhj86ahtShe5NL8wIZtfDJV4eRqlSbW97beGDmk7+fX3KujOS+Y+PC1fzBoNG 65rJsGKq+G5DGtwa56mmysJgBtlc2uIxhKeA1hPXvUmtRim0cIlKa5463It/7wqo PAK086lJ1m8= =M+05 -----END PGP SIGNATURE----- Merge tag 'hid-for-linus-2024051401' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid Pull HID updates from Jiri Kosina: - Firmware loading from host support in intel-ish driver, needed to support Lunar Lake and later (Zhang Lixu) - updates to HID-BPF infrastructure, with some of the specific fixes (e.g. rdesc fixups) abstracted into separate BPF programs for consumption from libevdev/udev-hid-bpf (Benjamin Tissoires) - support for Deck IMU in hid-steam (Max Maisel) - fixes for better support of 3rd party playstation DS4 controllers (Max Staudt) - support for missing mappings and codes from HUT 1.5 in hid-debug (Thomas Kuehne) - initial support for ROG Ally and ROG X13 devices (Luke D. Jones) - full support for WinWing Orion2 (Ivan Gorinov) * tag 'hid-for-linus-2024051401' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (74 commits) selftests/hid: skip tests with HID-BPF if udev-hid-bpf is not installed selftests/hid: add tests for the Raptor Mach 2 joystick selftests/hid: move the gamepads definitions in the test file selftests/hid: import base_gamepad.py from hid-tools selftests/hid: add Huion Kamvas Pro 19 tests selftests/hid: tablets: also check for XP-Pen offset correction selftests/hid: tablets: add a couple of XP-PEN tablets selftests/hid: tablets: reduce the number of pen state selftests/hid: add support for HID-BPF pre-loading before starting a test selftests/hid: import base_device.py from hid-tools HID: bpf: add in-tree HID-BPF fix for the Raptor Mach 2 HID: bpf: add in-tree HID-BPF fix for the Huion Kamvas Pro 19 HID: bpf: add in-tree HID-BPF fix for the XBox Elite 2 over Bluetooth HID: bpf: add in-tree HID-BPF fix for the Wacom ArtPen HID: bpf: add in-tree HID-BPF fix for the IOGear Kaliber Gaming MMOmentum mouse HID: bpf: add in-tree HID-BPF fix for the HP Elite Presenter Mouse HID: bpf: add in-tree HID-BPF fix for the XPPen Artist 16 HID: bpf: add first in-tree HID-BPF fix for the XPPen Artist 24 HID: do not assume HAT Switch logical max < 8 HID: amd_sfh: Use amd_get_c2p_val() to read C2P register ...
This commit is contained in:
commit
614da38e2f
|
@ -179,7 +179,7 @@ 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
|
||||
:functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_allocate_context hid_bpf_release_context
|
||||
|
||||
General overview of a HID-BPF program
|
||||
=====================================
|
||||
|
|
|
@ -18,8 +18,8 @@ These ISH also comply to HID sensor specification, but the difference is the
|
|||
transport protocol used for communication. The current external sensor hubs
|
||||
mainly use HID over I2C or USB. But ISH doesn't use either I2C or USB.
|
||||
|
||||
1. Overview
|
||||
===========
|
||||
Overview
|
||||
========
|
||||
|
||||
Using a analogy with a usbhid implementation, the ISH follows a similar model
|
||||
for a very high speed communication::
|
||||
|
@ -58,8 +58,8 @@ implemented as a bus. Each client application executing in the ISH processor
|
|||
is registered as a device on this bus. The driver, which binds each device
|
||||
(ISH HID driver) identifies the device type and registers with the HID core.
|
||||
|
||||
2. ISH Implementation: Block Diagram
|
||||
====================================
|
||||
ISH Implementation: Block Diagram
|
||||
=================================
|
||||
|
||||
::
|
||||
|
||||
|
@ -96,27 +96,27 @@ is registered as a device on this bus. The driver, which binds each device
|
|||
| ISH Hardware/Firmware(FW) |
|
||||
----------------------------
|
||||
|
||||
3. High level processing in above blocks
|
||||
========================================
|
||||
High level processing in above blocks
|
||||
=====================================
|
||||
|
||||
3.1 Hardware Interface
|
||||
----------------------
|
||||
Hardware Interface
|
||||
------------------
|
||||
|
||||
The ISH is exposed as "Non-VGA unclassified PCI device" to the host. The PCI
|
||||
product and vendor IDs are changed from different generations of processors. So
|
||||
the source code which enumerates drivers needs to update from generation to
|
||||
generation.
|
||||
|
||||
3.2 Inter Processor Communication (IPC) driver
|
||||
----------------------------------------------
|
||||
Inter Processor Communication (IPC) driver
|
||||
------------------------------------------
|
||||
|
||||
Location: drivers/hid/intel-ish-hid/ipc
|
||||
|
||||
The IPC message uses memory mapped I/O. The registers are defined in
|
||||
hw-ish-regs.h.
|
||||
|
||||
3.2.1 IPC/FW message types
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
IPC/FW message types
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are two types of messages, one for management of link and another for
|
||||
messages to and from transport layers.
|
||||
|
@ -142,20 +142,20 @@ register has the following format::
|
|||
Bit 31: doorbell trigger (signal H/W interrupt to the other side)
|
||||
Other bits are reserved, should be 0.
|
||||
|
||||
3.2.2 Transport layer interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Transport layer interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To abstract HW level IPC communication, a set of callbacks is registered.
|
||||
The transport layer uses them to send and receive messages.
|
||||
Refer to struct ishtp_hw_ops for callbacks.
|
||||
|
||||
3.3 ISH Transport layer
|
||||
-----------------------
|
||||
ISH Transport layer
|
||||
-------------------
|
||||
|
||||
Location: drivers/hid/intel-ish-hid/ishtp/
|
||||
|
||||
3.3.1 A Generic Transport Layer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A Generic Transport Layer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The transport layer is a bi-directional protocol, which defines:
|
||||
- Set of commands to start, stop, connect, disconnect and flow control
|
||||
|
@ -166,8 +166,8 @@ This protocol resembles bus messages described in the following document:
|
|||
http://www.intel.com/content/dam/www/public/us/en/documents/technical-\
|
||||
specifications/dcmi-hi-1-0-spec.pdf "Chapter 7: Bus Message Layer"
|
||||
|
||||
3.3.2 Connection and Flow Control Mechanism
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Connection and Flow Control Mechanism
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Each FW client and a protocol is identified by a UUID. In order to communicate
|
||||
to a FW client, a connection must be established using connect request and
|
||||
|
@ -181,8 +181,8 @@ before receiving the next flow control credit.
|
|||
Either side can send disconnect request bus message to end communication. Also
|
||||
the link will be dropped if major FW reset occurs.
|
||||
|
||||
3.3.3 Peer to Peer data transfer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Peer to Peer data transfer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Peer to Peer data transfer can happen with or without using DMA. Depending on
|
||||
the sensor bandwidth requirement DMA can be enabled by using module parameter
|
||||
|
@ -217,8 +217,8 @@ In principle, multiple DMA_XFER and DMA_XFER_ACK messages may be sent at once
|
|||
Currently, ISH FW decides to send over DMA if ISHTP message is more than 3 IPC
|
||||
fragments and via IPC otherwise.
|
||||
|
||||
3.3.4 Ring Buffers
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
Ring Buffers
|
||||
^^^^^^^^^^^^
|
||||
|
||||
When a client initiates a connection, a ring of RX and TX buffers is allocated.
|
||||
The size of ring can be specified by the client. HID client sets 16 and 32 for
|
||||
|
@ -228,8 +228,8 @@ bus message protocol. These buffers are required because the FW may have not
|
|||
have processed the last message and may not have enough flow control credits
|
||||
to send. Same thing holds true on receive side and flow control is required.
|
||||
|
||||
3.3.5 Host Enumeration
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Host Enumeration
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
The host enumeration bus command allows discovery of clients present in the FW.
|
||||
There can be multiple sensor clients and clients for calibration function.
|
||||
|
@ -252,8 +252,8 @@ Enumeration sequence of messages:
|
|||
- Once host received properties for that last discovered client, it considers
|
||||
ISHTP device fully functional (and allocates DMA buffers)
|
||||
|
||||
3.4 HID over ISH Client
|
||||
-----------------------
|
||||
HID over ISH Client
|
||||
-------------------
|
||||
|
||||
Location: drivers/hid/intel-ish-hid
|
||||
|
||||
|
@ -265,16 +265,16 @@ The ISHTP client driver is responsible for:
|
|||
- Process Get/Set feature request
|
||||
- Get input reports
|
||||
|
||||
3.5 HID Sensor Hub MFD and IIO sensor drivers
|
||||
---------------------------------------------
|
||||
HID Sensor Hub MFD and IIO sensor drivers
|
||||
-----------------------------------------
|
||||
|
||||
The functionality in these drivers is the same as an external sensor hub.
|
||||
Refer to
|
||||
Documentation/hid/hid-sensor.rst for HID sensor
|
||||
Documentation/ABI/testing/sysfs-bus-iio for IIO ABIs to user space.
|
||||
|
||||
3.6 End to End HID transport Sequence Diagram
|
||||
---------------------------------------------
|
||||
End to End HID transport Sequence Diagram
|
||||
-----------------------------------------
|
||||
|
||||
::
|
||||
|
||||
|
@ -339,16 +339,81 @@ Documentation/ABI/testing/sysfs-bus-iio for IIO ABIs to user space.
|
|||
| | | |
|
||||
|
||||
|
||||
3.7 ISH Debugging
|
||||
-----------------
|
||||
ISH Firmware Loading from Host Flow
|
||||
-----------------------------------
|
||||
|
||||
Starting from the Lunar Lake generation, the ISH firmware has been divided into two components for better space optimization and increased flexibility. These components include a bootloader that is integrated into the BIOS, and a main firmware that is stored within the operating system's file system.
|
||||
|
||||
The process works as follows:
|
||||
|
||||
- Initially, the ISHTP driver sends a command, HOST_START_REQ_CMD, to the ISH bootloader. In response, the bootloader sends back a HOST_START_RES_CMD. This response includes the ISHTP_SUPPORT_CAP_LOADER bit. Subsequently, the ISHTP driver checks if this bit is set. If it is, the firmware loading process from the host begins.
|
||||
|
||||
- During this process, the ISHTP driver first invokes the request_firmware() function, followed by sending a LOADER_CMD_XFER_QUERY command. Upon receiving a response from the bootloader, the ISHTP driver sends a LOADER_CMD_XFER_FRAGMENT command. After receiving another response, the ISHTP driver sends a LOADER_CMD_START command. The bootloader responds and then proceeds to the Main Firmware.
|
||||
|
||||
- After the process concludes, the ISHTP driver calls the release_firmware() function.
|
||||
|
||||
For more detailed information, please refer to the flow descriptions provided below:
|
||||
|
||||
::
|
||||
|
||||
+---------------+ +-----------------+
|
||||
| ISHTP Driver | | ISH Bootloader |
|
||||
+---------------+ +-----------------+
|
||||
| |
|
||||
|~~~Send HOST_START_REQ_CMD~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
|
||||
| |
|
||||
|<--Send HOST_START_RES_CMD(Includes ISHTP_SUPPORT_CAP_LOADER bit)----|
|
||||
| |
|
||||
****************************************************************************************
|
||||
* if ISHTP_SUPPORT_CAP_LOADER bit is set *
|
||||
****************************************************************************************
|
||||
| |
|
||||
|~~~start loading firmware from host process~~~+ |
|
||||
| | |
|
||||
|<---------------------------------------------+ |
|
||||
| |
|
||||
--------------------------- |
|
||||
| Call request_firmware() | |
|
||||
--------------------------- |
|
||||
| |
|
||||
|~~~Send LOADER_CMD_XFER_QUERY~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
|
||||
| |
|
||||
|<--Send response-----------------------------------------------------|
|
||||
| |
|
||||
|~~~Send LOADER_CMD_XFER_FRAGMENT~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
|
||||
| |
|
||||
|<--Send response-----------------------------------------------------|
|
||||
| |
|
||||
|~~~Send LOADER_CMD_START~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>|
|
||||
| |
|
||||
|<--Send response-----------------------------------------------------|
|
||||
| |
|
||||
| |~~~Jump to Main Firmware~~~+
|
||||
| | |
|
||||
| |<--------------------------+
|
||||
| |
|
||||
--------------------------- |
|
||||
| Call release_firmware() | |
|
||||
--------------------------- |
|
||||
| |
|
||||
****************************************************************************************
|
||||
* end if *
|
||||
****************************************************************************************
|
||||
| |
|
||||
+---------------+ +-----------------+
|
||||
| ISHTP Driver | | ISH Bootloader |
|
||||
+---------------+ +-----------------+
|
||||
|
||||
ISH Debugging
|
||||
-------------
|
||||
|
||||
To debug ISH, event tracing mechanism is used. To enable debug logs::
|
||||
|
||||
echo 1 > /sys/kernel/tracing/events/intel_ish/enable
|
||||
cat /sys/kernel/tracing/trace
|
||||
|
||||
3.8 ISH IIO sysfs Example on Lenovo thinkpad Yoga 260
|
||||
-----------------------------------------------------
|
||||
ISH IIO sysfs Example on Lenovo thinkpad Yoga 260
|
||||
-------------------------------------------------
|
||||
|
||||
::
|
||||
|
||||
|
|
|
@ -1236,6 +1236,22 @@ config HID_WIIMOTE
|
|||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-wiimote.
|
||||
|
||||
config HID_WINWING
|
||||
tristate "WinWing Orion2 throttle support"
|
||||
depends on USB_HID
|
||||
depends on NEW_LEDS
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
Support for WinWing Orion2 throttle base with the following grips:
|
||||
|
||||
* TGRIP-16EX
|
||||
* TGRIP-18
|
||||
|
||||
This driver enables all buttons and switches on the throttle base.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-winwing.
|
||||
|
||||
config HID_XINMO
|
||||
tristate "Xin-Mo non-fully compliant devices"
|
||||
help
|
||||
|
|
|
@ -150,6 +150,7 @@ wacom-objs := wacom_wac.o wacom_sys.o
|
|||
obj-$(CONFIG_HID_WACOM) += wacom.o
|
||||
obj-$(CONFIG_HID_WALTOP) += hid-waltop.o
|
||||
obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
|
||||
obj-$(CONFIG_HID_WINWING) += hid-winwing.o
|
||||
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
|
||||
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
|
||||
|
||||
|
|
|
@ -333,14 +333,11 @@ static const struct dmi_system_id dmi_nodevs[] = {
|
|||
static void sfh1_1_init_work(struct work_struct *work)
|
||||
{
|
||||
struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
|
||||
struct pci_dev *pdev = mp2->pdev;
|
||||
int rc;
|
||||
|
||||
rc = mp2->sfh1_1_ops->init(mp2);
|
||||
if (rc) {
|
||||
dev_err(&pdev->dev, "sfh1_1_init failed err %d\n", rc);
|
||||
if (rc)
|
||||
return;
|
||||
}
|
||||
|
||||
amd_sfh_clear_intr(mp2);
|
||||
mp2->init_done = 1;
|
||||
|
|
|
@ -202,7 +202,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
|
|||
}
|
||||
|
||||
if (!cl_data->is_any_sensor_enabled) {
|
||||
dev_warn(dev, "Failed to discover, sensors not enabled is %d\n",
|
||||
dev_warn(dev, "No sensor registered, sensors not enabled is %d\n",
|
||||
cl_data->is_any_sensor_enabled);
|
||||
rc = -EOPNOTSUPP;
|
||||
goto cleanup;
|
||||
|
@ -227,6 +227,11 @@ static void amd_sfh_resume(struct amd_mp2_dev *mp2)
|
|||
struct amd_mp2_sensor_info info;
|
||||
int i, status;
|
||||
|
||||
if (!cl_data->is_any_sensor_enabled) {
|
||||
amd_sfh_clear_intr(mp2);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < cl_data->num_hid_devices; i++) {
|
||||
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
|
||||
info.sensor_idx = cl_data->sensor_idx[i];
|
||||
|
@ -252,6 +257,11 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
|
|||
struct amdtp_cl_data *cl_data = mp2->cl_data;
|
||||
int i, status;
|
||||
|
||||
if (!cl_data->is_any_sensor_enabled) {
|
||||
amd_sfh_clear_intr(mp2);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < cl_data->num_hid_devices; i++) {
|
||||
if (cl_data->sensor_idx[i] != HPD_IDX &&
|
||||
cl_data->sensor_sts[i] == SENSOR_ENABLED) {
|
||||
|
@ -320,7 +330,7 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
|
|||
|
||||
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
|
||||
if (binfo.sbase.fw_info.fw_ver == 0 || binfo.sbase.s_list.sl.sensors == 0) {
|
||||
dev_dbg(dev, "failed to get sensors\n");
|
||||
dev_dbg(dev, "No sensor registered\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
dev_dbg(dev, "firmware version 0x%x\n", binfo.sbase.fw_info.fw_ver);
|
||||
|
@ -337,7 +347,8 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
|
|||
rc = amd_sfh1_1_hid_client_init(mp2);
|
||||
if (rc) {
|
||||
sfh_deinit_emp2();
|
||||
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
|
||||
if ((rc != -ENODEV) && (rc != -EOPNOTSUPP))
|
||||
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ static int amd_sfh_hpd_info(u8 *user_present)
|
|||
if (!emp2 || !emp2->dev_en.is_hpd_present)
|
||||
return -ENODEV;
|
||||
|
||||
hpdstatus.val = readl(emp2->mmio + AMD_C2P_MSG(4));
|
||||
hpdstatus.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 4));
|
||||
*user_present = hpdstatus.shpd.presence;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -143,48 +143,6 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
|
||||
|
||||
/* Disables missing prototype warnings */
|
||||
__bpf_kfunc_start_defs();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
__bpf_kfunc __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;
|
||||
}
|
||||
__bpf_kfunc_end_defs();
|
||||
|
||||
/*
|
||||
* The following set contains all functions we agree BPF programs
|
||||
* can use.
|
||||
*/
|
||||
BTF_KFUNCS_START(hid_bpf_kfunc_ids)
|
||||
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
|
||||
BTF_KFUNCS_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);
|
||||
|
@ -281,6 +239,31 @@ static int do_hid_bpf_attach_prog(struct hid_device *hdev, int prog_fd, struct b
|
|||
/* Disables missing prototype warnings */
|
||||
__bpf_kfunc_start_defs();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
__bpf_kfunc __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;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
|
||||
*
|
||||
|
@ -393,6 +376,46 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)
|
|||
put_device(&hid->dev);
|
||||
}
|
||||
|
||||
static int
|
||||
__hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
|
||||
enum hid_report_type rtype)
|
||||
{
|
||||
struct hid_report_enum *report_enum;
|
||||
struct hid_report *report;
|
||||
struct hid_device *hdev;
|
||||
u32 report_len;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_hw_request - Communicate with a HID device
|
||||
*
|
||||
|
@ -409,24 +432,14 @@ 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;
|
||||
size_t size = buf__sz;
|
||||
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;
|
||||
}
|
||||
ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (reqtype) {
|
||||
case HID_REQ_GET_REPORT:
|
||||
|
@ -440,29 +453,16 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
|
|||
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);
|
||||
dma_data = kmemdup(buf, size, GFP_KERNEL);
|
||||
if (!dma_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_bpf_ops->hid_hw_raw_request(hdev,
|
||||
dma_data[0],
|
||||
dma_data,
|
||||
buf__sz,
|
||||
size,
|
||||
rtype,
|
||||
reqtype);
|
||||
|
||||
|
@ -472,8 +472,90 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
|
|||
kfree(dma_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_hw_output_report - Send an output report to 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
|
||||
*
|
||||
* Returns the number of bytes transferred on success, a negative error code otherwise.
|
||||
*/
|
||||
__bpf_kfunc int
|
||||
hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
size_t size = buf__sz;
|
||||
u8 *dma_data;
|
||||
int ret;
|
||||
|
||||
/* check arguments */
|
||||
ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hdev = (struct hid_device *)ctx->hid; /* discard const */
|
||||
|
||||
dma_data = kmemdup(buf, size, GFP_KERNEL);
|
||||
if (!dma_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_bpf_ops->hid_hw_output_report(hdev,
|
||||
dma_data,
|
||||
size);
|
||||
|
||||
kfree(dma_data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_bpf_input_report - Inject a HID report in the kernel from a HID device
|
||||
*
|
||||
* @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
|
||||
* @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
|
||||
* @buf: a %PTR_TO_MEM buffer
|
||||
* @buf__sz: the size of the data to transfer
|
||||
*
|
||||
* Returns %0 on success, a negative error code otherwise.
|
||||
*/
|
||||
__bpf_kfunc int
|
||||
hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
|
||||
const size_t buf__sz)
|
||||
{
|
||||
struct hid_device *hdev;
|
||||
size_t size = buf__sz;
|
||||
int ret;
|
||||
|
||||
/* check arguments */
|
||||
ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hdev = (struct hid_device *)ctx->hid; /* discard const */
|
||||
|
||||
return hid_bpf_ops->hid_input_report(hdev, type, buf, size, 0);
|
||||
}
|
||||
__bpf_kfunc_end_defs();
|
||||
|
||||
/*
|
||||
* The following set contains all functions we agree BPF programs
|
||||
* can use.
|
||||
*/
|
||||
BTF_KFUNCS_START(hid_bpf_kfunc_ids)
|
||||
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
|
||||
BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
|
||||
BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE)
|
||||
BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE)
|
||||
BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE)
|
||||
BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE)
|
||||
BTF_KFUNCS_END(hid_bpf_kfunc_ids)
|
||||
|
||||
static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
|
||||
.owner = THIS_MODULE,
|
||||
.set = &hid_bpf_kfunc_ids,
|
||||
};
|
||||
|
||||
/* our HID-BPF entrypoints */
|
||||
BTF_SET8_START(hid_bpf_fmodret_ids)
|
||||
BTF_ID_FLAGS(func, hid_bpf_device_event)
|
||||
|
@ -492,6 +574,8 @@ 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_ID_FLAGS(func, hid_bpf_hw_output_report)
|
||||
BTF_ID_FLAGS(func, hid_bpf_input_report)
|
||||
BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids)
|
||||
|
||||
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_BETOP_2185PC 0x11C0
|
||||
#define PID_RAPTOR_MACH_2 0x5606
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_BETOP_2185PC, PID_RAPTOR_MACH_2),
|
||||
);
|
||||
|
||||
/*
|
||||
* For reference, this is the fixed report descriptor
|
||||
*
|
||||
* static const __u8 fixed_rdesc[] = {
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* 0x09, 0x04, // Usage (Joystick) 2
|
||||
* 0xa1, 0x01, // Collection (Application) 4
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 6
|
||||
* 0x85, 0x01, // Report ID (1) 8
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 10
|
||||
* 0x09, 0x30, // Usage (X) 12
|
||||
* 0x75, 0x10, // Report Size (16) 14
|
||||
* 0x95, 0x01, // Report Count (1) 16
|
||||
* 0x15, 0x00, // Logical Minimum (0) 18
|
||||
* 0x26, 0xff, 0x07, // Logical Maximum (2047) 20
|
||||
* 0x46, 0xff, 0x07, // Physical Maximum (2047) 23
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 26
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 28
|
||||
* 0x09, 0x31, // Usage (Y) 30
|
||||
* 0x75, 0x10, // Report Size (16) 32
|
||||
* 0x95, 0x01, // Report Count (1) 34
|
||||
* 0x15, 0x00, // Logical Minimum (0) 36
|
||||
* 0x26, 0xff, 0x07, // Logical Maximum (2047) 38
|
||||
* 0x46, 0xff, 0x07, // Physical Maximum (2047) 41
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 44
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 46
|
||||
* 0x09, 0x33, // Usage (Rx) 48
|
||||
* 0x75, 0x10, // Report Size (16) 50
|
||||
* 0x95, 0x01, // Report Count (1) 52
|
||||
* 0x15, 0x00, // Logical Minimum (0) 54
|
||||
* 0x26, 0xff, 0x03, // Logical Maximum (1023) 56
|
||||
* 0x46, 0xff, 0x03, // Physical Maximum (1023) 59
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 62
|
||||
* 0x05, 0x00, // Usage Page (Undefined) 64
|
||||
* 0x09, 0x00, // Usage (Undefined) 66
|
||||
* 0x75, 0x10, // Report Size (16) 68
|
||||
* 0x95, 0x01, // Report Count (1) 70
|
||||
* 0x15, 0x00, // Logical Minimum (0) 72
|
||||
* 0x26, 0xff, 0x03, // Logical Maximum (1023) 74
|
||||
* 0x46, 0xff, 0x03, // Physical Maximum (1023) 77
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 80
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 82
|
||||
* 0x09, 0x32, // Usage (Z) 84
|
||||
* 0x75, 0x10, // Report Size (16) 86
|
||||
* 0x95, 0x01, // Report Count (1) 88
|
||||
* 0x15, 0x00, // Logical Minimum (0) 90
|
||||
* 0x26, 0xff, 0x03, // Logical Maximum (1023) 92
|
||||
* 0x46, 0xff, 0x03, // Physical Maximum (1023) 95
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 98
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 100
|
||||
* 0x09, 0x35, // Usage (Rz) 102
|
||||
* 0x75, 0x10, // Report Size (16) 104
|
||||
* 0x95, 0x01, // Report Count (1) 106
|
||||
* 0x15, 0x00, // Logical Minimum (0) 108
|
||||
* 0x26, 0xff, 0x03, // Logical Maximum (1023) 110
|
||||
* 0x46, 0xff, 0x03, // Physical Maximum (1023) 113
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 116
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 118
|
||||
* 0x09, 0x34, // Usage (Ry) 120
|
||||
* 0x75, 0x10, // Report Size (16) 122
|
||||
* 0x95, 0x01, // Report Count (1) 124
|
||||
* 0x15, 0x00, // Logical Minimum (0) 126
|
||||
* 0x26, 0xff, 0x07, // Logical Maximum (2047) 128
|
||||
* 0x46, 0xff, 0x07, // Physical Maximum (2047) 131
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 134
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 136
|
||||
* 0x09, 0x36, // Usage (Slider) 138
|
||||
* 0x75, 0x10, // Report Size (16) 140
|
||||
* 0x95, 0x01, // Report Count (1) 142
|
||||
* 0x15, 0x00, // Logical Minimum (0) 144
|
||||
* 0x26, 0xff, 0x03, // Logical Maximum (1023) 146
|
||||
* 0x46, 0xff, 0x03, // Physical Maximum (1023) 149
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 152
|
||||
* 0x05, 0x09, // Usage Page (Button) 154
|
||||
* 0x19, 0x01, // Usage Minimum (1) 156
|
||||
* 0x2a, 0x1d, 0x00, // Usage Maximum (29) 158
|
||||
* 0x15, 0x00, // Logical Minimum (0) 161
|
||||
* 0x25, 0x01, // Logical Maximum (1) 163
|
||||
* 0x75, 0x01, // Report Size (1) 165
|
||||
* 0x96, 0x80, 0x00, // Report Count (128) 167
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 170
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 172
|
||||
* 0x09, 0x39, // Usage (Hat switch) 174
|
||||
* 0x26, 0x07, 0x00, // Logical Maximum (7) 176 // changed (was 239)
|
||||
* 0x46, 0x68, 0x01, // Physical Maximum (360) 179
|
||||
* 0x65, 0x14, // Unit (EnglishRotation: deg) 182
|
||||
* 0x75, 0x10, // Report Size (16) 184
|
||||
* 0x95, 0x01, // Report Count (1) 186
|
||||
* 0x81, 0x42, // Input (Data,Var,Abs,Null) 188
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 190
|
||||
* 0x09, 0x00, // Usage (Undefined) 192
|
||||
* 0x75, 0x08, // Report Size (8) 194
|
||||
* 0x95, 0x1d, // Report Count (29) 196
|
||||
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 198
|
||||
* 0x15, 0x00, // Logical Minimum (0) 200
|
||||
* 0x26, 0xef, 0x00, // Logical Maximum (239) 202
|
||||
* 0x85, 0x58, // Report ID (88) 205
|
||||
* 0x26, 0xff, 0x00, // Logical Maximum (255) 207
|
||||
* 0x46, 0xff, 0x00, // Physical Maximum (255) 210
|
||||
* 0x75, 0x08, // Report Size (8) 213
|
||||
* 0x95, 0x3f, // Report Count (63) 215
|
||||
* 0x09, 0x00, // Usage (Undefined) 217
|
||||
* 0x91, 0x02, // Output (Data,Var,Abs) 219
|
||||
* 0x85, 0x59, // Report ID (89) 221
|
||||
* 0x75, 0x08, // Report Size (8) 223
|
||||
* 0x95, 0x80, // Report Count (128) 225
|
||||
* 0x09, 0x00, // Usage (Undefined) 227
|
||||
* 0xb1, 0x02, // Feature (Data,Var,Abs) 229
|
||||
* 0xc0, // End Collection 231
|
||||
* };
|
||||
*/
|
||||
|
||||
/*
|
||||
* We need to amend the report descriptor for the following:
|
||||
* - the joystick sends its hat_switch data between 0 and 239 but
|
||||
* the kernel expects the logical max to stick into a signed 8 bits
|
||||
* integer. We thus divide it by 30 to match what other joysticks are
|
||||
* doing
|
||||
*/
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc_raptor_mach_2, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
data[177] = 0x07;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The hat_switch value at offsets 33 and 34 (16 bits) needs
|
||||
* to be reduced to a single 8 bit signed integer. So we
|
||||
* divide it by 30.
|
||||
* Byte 34 is always null, so it is ignored.
|
||||
*/
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(raptor_mach_2_fix_hat_switch, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 64 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (data[0] != 0x01) /* not the joystick report ID */
|
||||
return 0;
|
||||
|
||||
data[33] /= 30;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
ctx->retval = ctx->rdesc_size != 232;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
/* ensure the kernel isn't fixed already */
|
||||
if (ctx->rdesc[177] != 0xef) /* Logical Max of 239 */
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_HP 0x03F0
|
||||
#define PID_ELITE_PRESENTER 0x464A
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HP, PID_ELITE_PRESENTER)
|
||||
);
|
||||
|
||||
/*
|
||||
* Already fixed as of commit 0db117359e47 ("HID: add quirk for 03f0:464a
|
||||
* HP Elite Presenter Mouse") in the kernel, but this is a slightly better
|
||||
* fix.
|
||||
*
|
||||
* The HP Elite Presenter Mouse HID Record Descriptor shows
|
||||
* two mice (Report ID 0x1 and 0x2), one keypad (Report ID 0x5),
|
||||
* two Consumer Controls (Report IDs 0x6 and 0x3).
|
||||
* Prior to these fixes it registers one mouse, one keypad
|
||||
* and one Consumer Control, and it was usable only as a
|
||||
* digital laser pointer (one of the two mouses).
|
||||
* We replace the second mouse collection with a pointer collection,
|
||||
* allowing to use the device both as a mouse and a digital laser
|
||||
* pointer.
|
||||
*/
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* replace application mouse by application pointer on the second collection */
|
||||
if (data[79] == 0x02)
|
||||
data[79] = 0x01;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
ctx->retval = ctx->rdesc_size != 264;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,290 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_HUION 0x256C
|
||||
#define PID_KAMVAS_PRO_19 0x006B
|
||||
#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
|
||||
|
||||
#define TEST_PREFIX "uhid test "
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
|
||||
);
|
||||
|
||||
bool prev_was_out_of_range;
|
||||
bool in_eraser_mode;
|
||||
|
||||
/*
|
||||
* We need to amend the report descriptor for the following:
|
||||
* - the second button is reported through Secondary Tip Switch instead of Secondary Barrel Switch
|
||||
* - the third button is reported through Invert, and we need some room to report it.
|
||||
*
|
||||
*/
|
||||
static const __u8 fixed_rdesc[] = {
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 0
|
||||
0x09, 0x02, // Usage (Pen) 2
|
||||
0xa1, 0x01, // Collection (Application) 4
|
||||
0x85, 0x0a, // Report ID (10) 6
|
||||
0x09, 0x20, // Usage (Stylus) 8
|
||||
0xa1, 0x01, // Collection (Application) 10
|
||||
0x09, 0x42, // Usage (Tip Switch) 12
|
||||
0x09, 0x44, // Usage (Barrel Switch) 14
|
||||
0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from Secondary Tip Switch */
|
||||
0x09, 0x3c, // Usage (Invert) 18
|
||||
0x09, 0x45, // Usage (Eraser) 20
|
||||
0x15, 0x00, // Logical Minimum (0) 22
|
||||
0x25, 0x01, // Logical Maximum (1) 24
|
||||
0x75, 0x01, // Report Size (1) 26
|
||||
0x95, 0x05, // Report Count (5) 28 /* changed (was 5) */
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 30
|
||||
0x05, 0x09, // Usage Page (Button) /* inserted */
|
||||
0x09, 0x4a, // Usage (0x4a) /* inserted to be translated as input usage 0x149: BTN_STYLUS3 */
|
||||
0x95, 0x01, // Report Count (1) /* inserted */
|
||||
0x81, 0x02, // Input (Data,Var,Abs) /* inserted */
|
||||
0x05, 0x0d, // Usage Page (Digitizers) /* inserted */
|
||||
0x09, 0x32, // Usage (In Range) 32
|
||||
0x75, 0x01, // Report Size (1) 34
|
||||
0x95, 0x01, // Report Count (1) 36
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 38
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 40
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 42
|
||||
0x09, 0x30, // Usage (X) 44
|
||||
0x09, 0x31, // Usage (Y) 46
|
||||
0x55, 0x0d, // Unit Exponent (-3) 48
|
||||
0x65, 0x33, // Unit (EnglishLinear: in³) 50
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 52
|
||||
0x35, 0x00, // Physical Minimum (0) 55
|
||||
0x46, 0x00, 0x08, // Physical Maximum (2048) 57
|
||||
0x75, 0x10, // Report Size (16) 60
|
||||
0x95, 0x02, // Report Count (2) 62
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 64
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 66
|
||||
0x09, 0x30, // Usage (Tip Pressure) 68
|
||||
0x26, 0xff, 0x3f, // Logical Maximum (16383) 70
|
||||
0x75, 0x10, // Report Size (16) 73
|
||||
0x95, 0x01, // Report Count (1) 75
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 77
|
||||
0x09, 0x3d, // Usage (X Tilt) 79
|
||||
0x09, 0x3e, // Usage (Y Tilt) 81
|
||||
0x15, 0xa6, // Logical Minimum (-90) 83
|
||||
0x25, 0x5a, // Logical Maximum (90) 85
|
||||
0x75, 0x08, // Report Size (8) 87
|
||||
0x95, 0x02, // Report Count (2) 89
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 91
|
||||
0xc0, // End Collection 93
|
||||
0xc0, // End Collection 94
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 95
|
||||
0x09, 0x04, // Usage (Touch Screen) 97
|
||||
0xa1, 0x01, // Collection (Application) 99
|
||||
0x85, 0x04, // Report ID (4) 101
|
||||
0x09, 0x22, // Usage (Finger) 103
|
||||
0xa1, 0x02, // Collection (Logical) 105
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 107
|
||||
0x95, 0x01, // Report Count (1) 109
|
||||
0x75, 0x06, // Report Size (6) 111
|
||||
0x09, 0x51, // Usage (Contact Id) 113
|
||||
0x15, 0x00, // Logical Minimum (0) 115
|
||||
0x25, 0x3f, // Logical Maximum (63) 117
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 119
|
||||
0x09, 0x42, // Usage (Tip Switch) 121
|
||||
0x25, 0x01, // Logical Maximum (1) 123
|
||||
0x75, 0x01, // Report Size (1) 125
|
||||
0x95, 0x01, // Report Count (1) 127
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 129
|
||||
0x75, 0x01, // Report Size (1) 131
|
||||
0x95, 0x01, // Report Count (1) 133
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 135
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 137
|
||||
0x75, 0x10, // Report Size (16) 139
|
||||
0x55, 0x0e, // Unit Exponent (-2) 141
|
||||
0x65, 0x11, // Unit (SILinear: cm) 143
|
||||
0x09, 0x30, // Usage (X) 145
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 147
|
||||
0x35, 0x00, // Physical Minimum (0) 150
|
||||
0x46, 0x15, 0x0c, // Physical Maximum (3093) 152
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 155
|
||||
0x09, 0x31, // Usage (Y) 157
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 159
|
||||
0x46, 0xcb, 0x06, // Physical Maximum (1739) 162
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 165
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 167
|
||||
0x09, 0x30, // Usage (Tip Pressure) 169
|
||||
0x26, 0xff, 0x1f, // Logical Maximum (8191) 171
|
||||
0x75, 0x10, // Report Size (16) 174
|
||||
0x95, 0x01, // Report Count (1) 176
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 178
|
||||
0xc0, // End Collection 180
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 181
|
||||
0x09, 0x22, // Usage (Finger) 183
|
||||
0xa1, 0x02, // Collection (Logical) 185
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 187
|
||||
0x95, 0x01, // Report Count (1) 189
|
||||
0x75, 0x06, // Report Size (6) 191
|
||||
0x09, 0x51, // Usage (Contact Id) 193
|
||||
0x15, 0x00, // Logical Minimum (0) 195
|
||||
0x25, 0x3f, // Logical Maximum (63) 197
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 199
|
||||
0x09, 0x42, // Usage (Tip Switch) 201
|
||||
0x25, 0x01, // Logical Maximum (1) 203
|
||||
0x75, 0x01, // Report Size (1) 205
|
||||
0x95, 0x01, // Report Count (1) 207
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 209
|
||||
0x75, 0x01, // Report Size (1) 211
|
||||
0x95, 0x01, // Report Count (1) 213
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 215
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 217
|
||||
0x75, 0x10, // Report Size (16) 219
|
||||
0x55, 0x0e, // Unit Exponent (-2) 221
|
||||
0x65, 0x11, // Unit (SILinear: cm) 223
|
||||
0x09, 0x30, // Usage (X) 225
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 227
|
||||
0x35, 0x00, // Physical Minimum (0) 230
|
||||
0x46, 0x15, 0x0c, // Physical Maximum (3093) 232
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 235
|
||||
0x09, 0x31, // Usage (Y) 237
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 239
|
||||
0x46, 0xcb, 0x06, // Physical Maximum (1739) 242
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 245
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 247
|
||||
0x09, 0x30, // Usage (Tip Pressure) 249
|
||||
0x26, 0xff, 0x1f, // Logical Maximum (8191) 251
|
||||
0x75, 0x10, // Report Size (16) 254
|
||||
0x95, 0x01, // Report Count (1) 256
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 258
|
||||
0xc0, // End Collection 260
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 261
|
||||
0x09, 0x56, // Usage (Scan Time) 263
|
||||
0x55, 0x00, // Unit Exponent (0) 265
|
||||
0x65, 0x00, // Unit (None) 267
|
||||
0x27, 0xff, 0xff, 0xff, 0x7f, // Logical Maximum (2147483647) 269
|
||||
0x95, 0x01, // Report Count (1) 274
|
||||
0x75, 0x20, // Report Size (32) 276
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 278
|
||||
0x09, 0x54, // Usage (Contact Count) 280
|
||||
0x25, 0x7f, // Logical Maximum (127) 282
|
||||
0x95, 0x01, // Report Count (1) 284
|
||||
0x75, 0x08, // Report Size (8) 286
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 288
|
||||
0x75, 0x08, // Report Size (8) 290
|
||||
0x95, 0x08, // Report Count (8) 292
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 294
|
||||
0x85, 0x05, // Report ID (5) 296
|
||||
0x09, 0x55, // Usage (Contact Max) 298
|
||||
0x25, 0x0a, // Logical Maximum (10) 300
|
||||
0x75, 0x08, // Report Size (8) 302
|
||||
0x95, 0x01, // Report Count (1) 304
|
||||
0xb1, 0x02, // Feature (Data,Var,Abs) 306
|
||||
0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 308
|
||||
0x09, 0xc5, // Usage (Vendor Usage 0xc5) 311
|
||||
0x85, 0x06, // Report ID (6) 313
|
||||
0x15, 0x00, // Logical Minimum (0) 315
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255) 317
|
||||
0x75, 0x08, // Report Size (8) 320
|
||||
0x96, 0x00, 0x01, // Report Count (256) 322
|
||||
0xb1, 0x02, // Feature (Data,Var,Abs) 325
|
||||
0xc0, // End Collection 327
|
||||
};
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc_huion_kamvas_pro_19, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
|
||||
|
||||
return sizeof(fixed_rdesc);
|
||||
}
|
||||
|
||||
/*
|
||||
* This tablet reports the 3rd button through invert, but this conflict
|
||||
* with the normal eraser mode.
|
||||
* Fortunately, before entering eraser mode, (so Invert = 1),
|
||||
* the tablet always sends an out-of-proximity event.
|
||||
* So we can detect that single event and:
|
||||
* - if there was none but the invert bit was toggled: this is the
|
||||
* third button
|
||||
* - if there was this out-of-proximity event, we are entering
|
||||
* eraser mode, and we will until the next out-of-proximity.
|
||||
*/
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(kamvas_pro_19_fix_3rd_button, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (data[0] != 0x0a) /* not the pen report ID */
|
||||
return 0;
|
||||
|
||||
/* stylus is out of range */
|
||||
if (!(data[1] & 0x40)) {
|
||||
prev_was_out_of_range = true;
|
||||
in_eraser_mode = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* going into eraser mode (Invert = 1) only happens after an
|
||||
* out of range event
|
||||
*/
|
||||
if (prev_was_out_of_range && (data[1] & 0x18))
|
||||
in_eraser_mode = true;
|
||||
|
||||
/* eraser mode works fine */
|
||||
if (in_eraser_mode)
|
||||
return 0;
|
||||
|
||||
/* copy the Invert bit reported for the 3rd button in bit 7 */
|
||||
if (data[1] & 0x08)
|
||||
data[1] |= 0x20;
|
||||
|
||||
/* clear Invert bit now that it was copied */
|
||||
data[1] &= 0xf7;
|
||||
|
||||
prev_was_out_of_range = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
ctx->retval = ctx->rdesc_size != 328;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
/* ensure the kernel isn't fixed already */
|
||||
if (ctx->rdesc[17] != 0x43) /* Secondary Tip Switch */
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
struct hid_bpf_ctx *hctx = hid_bpf_allocate_context(ctx->hid);
|
||||
|
||||
if (!hctx) {
|
||||
return ctx->retval = -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *name = hctx->hid->name;
|
||||
|
||||
/* strip out TEST_PREFIX */
|
||||
if (!__builtin_memcmp(name, TEST_PREFIX, sizeof(TEST_PREFIX) - 1))
|
||||
name += sizeof(TEST_PREFIX) - 1;
|
||||
|
||||
if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)))
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
hid_bpf_release_context(hctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_IOGEAR 0x258A /* VID is shared with SinoWealth and Glorious and prob others */
|
||||
#define PID_MOMENTUM 0x0027
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_IOGEAR, PID_MOMENTUM)
|
||||
);
|
||||
|
||||
/*
|
||||
* The IOGear Kaliber Gaming MMOmentum Pro mouse has multiple buttons (12)
|
||||
* but only 5 are accessible out of the box because the report descriptor
|
||||
* marks the other buttons as constants.
|
||||
* We just fix the report descriptor to enable those missing 7 buttons.
|
||||
*/
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
const u8 offsets[] = {84, 112, 140};
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* if not Keyboard */
|
||||
if (data[3] != 0x06)
|
||||
return 0;
|
||||
|
||||
for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
|
||||
u8 offset = offsets[idx];
|
||||
|
||||
/* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
|
||||
if (data[offset] == 0x81 && data[offset + 1] == 0x03)
|
||||
data[offset + 1] = 0x02;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/* only bind to the keyboard interface */
|
||||
ctx->retval = ctx->rdesc_size != 213;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,91 @@
|
|||
# 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
|
||||
|
||||
SOURCES = $(wildcard *.bpf.c)
|
||||
TARGETS = $(SOURCES:.bpf.c=.bpf.o)
|
||||
|
||||
all: $(TARGETS)
|
||||
|
||||
clean:
|
||||
$(call msg,CLEAN)
|
||||
$(Q)rm -rf $(OUTPUT) $(TARGETS)
|
||||
|
||||
%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
|
||||
$(call msg,BPF,$@)
|
||||
$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \
|
||||
-c $(filter %.c,$^) -o $@ && \
|
||||
$(LLVM_STRIP) -g $@
|
||||
|
||||
vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
|
||||
ifeq ($(VMLINUX_H),)
|
||||
$(call msg,GEN,,$@)
|
||||
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
|
||||
else
|
||||
$(call msg,CP,,$@)
|
||||
$(Q)cp "$(VMLINUX_H)" $@
|
||||
endif
|
||||
|
||||
$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
|
||||
$(call msg,MKDIR,$@)
|
||||
$(Q)mkdir -p $@
|
||||
|
||||
$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
|
||||
OUTPUT=$(abspath $(dir $@))/ prefix= \
|
||||
DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
|
||||
|
||||
ifeq ($(CROSS_COMPILE),)
|
||||
$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
|
||||
OUTPUT=$(BPFTOOL_OUTPUT)/ \
|
||||
LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \
|
||||
LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
|
||||
else
|
||||
$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
|
||||
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
|
||||
OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
|
||||
endif
|
|
@ -0,0 +1,133 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_MICROSOFT 0x045e
|
||||
#define PID_XBOX_ELITE_2 0x0b22
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
|
||||
);
|
||||
|
||||
/*
|
||||
* When using the XBox Wireless Controller Elite 2 over Bluetooth,
|
||||
* the device exports the paddle on the back of the device as a single
|
||||
* bitfield value of usage "Assign Selection".
|
||||
*
|
||||
* The kernel doesn't process those usages properly and report KEY_UNKNOWN
|
||||
* for it.
|
||||
*
|
||||
* SDL doesn't know how to interprete that KEY_UNKNOWN and thus ignores the paddles.
|
||||
*
|
||||
* Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
|
||||
* can tweak the report descriptor to make the kernel interprete it properly:
|
||||
* - we need an application collection of gamepad (so we have to close the current
|
||||
* Consumer Control one)
|
||||
* - we need to change the usage to be buttons from 0x15 to 0x18
|
||||
*/
|
||||
|
||||
#define OFFSET_ASSIGN_SELECTION 211
|
||||
#define ORIGINAL_RDESC_SIZE 464
|
||||
|
||||
const __u8 rdesc_assign_selection[] = {
|
||||
0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
|
||||
0x15, 0x00, // Logical Minimum (0) 214
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255) 216
|
||||
0x95, 0x01, // Report Count (1) 219
|
||||
0x75, 0x04, // Report Size (4) 221
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 223
|
||||
0x15, 0x00, // Logical Minimum (0) 225
|
||||
0x25, 0x00, // Logical Maximum (0) 227
|
||||
0x95, 0x01, // Report Count (1) 229
|
||||
0x75, 0x04, // Report Size (4) 231
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 233
|
||||
0x0a, 0x81, 0x00, // Usage (Assign Selection) 235
|
||||
0x15, 0x00, // Logical Minimum (0) 238
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255) 240
|
||||
0x95, 0x01, // Report Count (1) 243
|
||||
0x75, 0x04, // Report Size (4) 245
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 247
|
||||
};
|
||||
|
||||
/*
|
||||
* we replace the above report descriptor extract
|
||||
* with the one below.
|
||||
* To make things equal in size, we take out a larger
|
||||
* portion than just the "Assign Selection" range, because
|
||||
* we need to insert a new application collection to force
|
||||
* the kernel to use BTN_TRIGGER_HAPPY[4-7].
|
||||
*/
|
||||
const __u8 fixed_rdesc_assign_selection[] = {
|
||||
0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
|
||||
0x15, 0x00, // Logical Minimum (0) 214
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255) 216
|
||||
0x95, 0x01, // Report Count (1) 219
|
||||
0x75, 0x04, // Report Size (4) 221
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 223
|
||||
/* 0x15, 0x00, */ // Logical Minimum (0) ignored
|
||||
0x25, 0x01, // Logical Maximum (1) 225
|
||||
0x95, 0x04, // Report Count (4) 227
|
||||
0x75, 0x01, // Report Size (1) 229
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 231
|
||||
0xc0, // End Collection 233
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 234
|
||||
0x0a, 0x05, 0x00, // Usage (Game Pad) 236
|
||||
0xa1, 0x01, // Collection (Application) 239
|
||||
0x05, 0x09, // Usage Page (Button) 241
|
||||
0x19, 0x15, // Usage Minimum (21) 243
|
||||
0x29, 0x18, // Usage Maximum (24) 245
|
||||
/* 0x15, 0x00, */ // Logical Minimum (0) ignored
|
||||
/* 0x25, 0x01, */ // Logical Maximum (1) ignored
|
||||
/* 0x95, 0x01, */ // Report Size (1) ignored
|
||||
/* 0x75, 0x04, */ // Report Count (4) ignored
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 247
|
||||
};
|
||||
|
||||
_Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
|
||||
"Rdesc and fixed rdesc of different size");
|
||||
_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
|
||||
"Rdesc at given offset is too big");
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Check that the device is compatible */
|
||||
if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
|
||||
rdesc_assign_selection,
|
||||
sizeof(rdesc_assign_selection)))
|
||||
return 0;
|
||||
|
||||
__builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
|
||||
fixed_rdesc_assign_selection,
|
||||
sizeof(fixed_rdesc_assign_selection));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/* only bind to the keyboard interface */
|
||||
ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
|
||||
rdesc_assign_selection,
|
||||
sizeof(rdesc_assign_selection)))
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,102 @@
|
|||
# HID-BPF programs
|
||||
|
||||
This directory contains various fixes for devices. They add new features or
|
||||
fix some behaviors without being entirely mandatory. It is better to load them
|
||||
when you have such a device, but they should not be a requirement for a device
|
||||
to be working during the boot stage.
|
||||
|
||||
The .bpf.c files provided here are not automatically compiled in the kernel.
|
||||
They should be loaded in the kernel by `udev-hid-bpf`:
|
||||
|
||||
https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
|
||||
|
||||
The main reasons for these fixes to be here is to have a central place to
|
||||
"upstream" them, but also this way we can test them thanks to the HID
|
||||
selftests.
|
||||
|
||||
Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
|
||||
in the `src/bpf/stable` directory, and distributions are encouraged to
|
||||
only ship those bpf objects. So adding a file here should eventually
|
||||
land in distributions when they update `udev-hid-bpf`
|
||||
|
||||
## Compilation
|
||||
|
||||
Just run `make`
|
||||
|
||||
## Installation
|
||||
|
||||
### Automated way
|
||||
|
||||
Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
|
||||
|
||||
### Manual way
|
||||
|
||||
- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
|
||||
- create a new udev rule to automatically load it
|
||||
|
||||
The following should do the trick (assuming udev-hid-bpf is available in
|
||||
/usr/bin):
|
||||
|
||||
```
|
||||
$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
|
||||
$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
|
||||
[
|
||||
{
|
||||
"name": "xppen-ArtistPro16Gen2.bpf.o",
|
||||
"devices": [
|
||||
{
|
||||
"bus": "0x0003",
|
||||
"group": "0x0001",
|
||||
"vid": "0x28BD",
|
||||
"pid": "0x095A"
|
||||
},
|
||||
{
|
||||
"bus": "0x0003",
|
||||
"group": "0x0001",
|
||||
"vid": "0x28BD",
|
||||
"pid": "0x095B"
|
||||
}
|
||||
],
|
||||
...
|
||||
$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
|
||||
ACTION!="add|remove", GOTO="hid_bpf_end"
|
||||
SUBSYSTEM!="hid", GOTO="hid_bpf_end"
|
||||
|
||||
# xppen-ArtistPro16Gen2.bpf.o
|
||||
ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
|
||||
ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
|
||||
# xppen-ArtistPro16Gen2.bpf.o
|
||||
ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
|
||||
ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
|
||||
|
||||
LABEL="hid_bpf_end"
|
||||
EOF
|
||||
$> udevadm control --reload
|
||||
```
|
||||
|
||||
Then unplug and replug the device.
|
||||
|
||||
## Checks
|
||||
|
||||
### udev rule
|
||||
|
||||
You can check that the udev rule is correctly working by issuing
|
||||
|
||||
```
|
||||
$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
|
||||
...
|
||||
run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
|
||||
```
|
||||
|
||||
### program loaded
|
||||
|
||||
You can check that the program has been properly loaded with `bpftool`
|
||||
|
||||
```
|
||||
$> bpftool prog
|
||||
...
|
||||
247: tracing name xppen_16_fix_eraser tag 18d389353ed2ef07 gpl
|
||||
loaded_at 2024-03-28T16:02:28+0100 uid 0
|
||||
xlated 120B jited 77B memlock 4096B
|
||||
btf_id 487
|
||||
```
|
|
@ -0,0 +1,173 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_WACOM 0x056a
|
||||
#define ART_PEN_ID 0x0804
|
||||
#define PID_INTUOS_PRO_2_M 0x0357
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
|
||||
);
|
||||
|
||||
/*
|
||||
* This filter is here for the Art Pen stylus only:
|
||||
* - when used on some Wacom devices (see the list of attached PIDs), this pen
|
||||
* reports pressure every other events.
|
||||
* - to solve that, given that we know that the next event will be the same as
|
||||
* the current one, we can emulate a smoother pressure reporting by reporting
|
||||
* the mean of the previous value and the current one.
|
||||
*
|
||||
* We are effectively delaying the pressure by one event every other event, but
|
||||
* that's less of an annoyance compared to the chunkiness of the reported data.
|
||||
*
|
||||
* For example, let's assume the following set of events:
|
||||
* <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 1> <Y 1> <Pressure 100 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 3> <Y 3> <Pressure 200 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
|
||||
* <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
|
||||
*
|
||||
* The filter will report:
|
||||
* <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
|
||||
* <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
|
||||
* <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
|
||||
*
|
||||
*/
|
||||
|
||||
struct wacom_params {
|
||||
__u16 pid;
|
||||
__u16 rdesc_len;
|
||||
__u8 report_id;
|
||||
__u8 report_len;
|
||||
struct {
|
||||
__u8 tip_switch;
|
||||
__u8 pressure;
|
||||
__u8 tool_type;
|
||||
} offsets;
|
||||
};
|
||||
|
||||
/*
|
||||
* Multiple device can support the same stylus, so
|
||||
* we need to know which device has which offsets
|
||||
*/
|
||||
static const struct wacom_params devices[] = {
|
||||
{
|
||||
.pid = PID_INTUOS_PRO_2_M,
|
||||
.rdesc_len = 949,
|
||||
.report_id = 16,
|
||||
.report_len = 27,
|
||||
.offsets = {
|
||||
.tip_switch = 1,
|
||||
.pressure = 8,
|
||||
.tool_type = 25,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static struct wacom_params params = { 0 };
|
||||
|
||||
/* HID-BPF reports a 64 bytes chunk anyway, so this ensures
|
||||
* the verifier to know we are addressing the memory correctly
|
||||
*/
|
||||
#define PEN_REPORT_LEN 64
|
||||
|
||||
/* only odd frames are modified */
|
||||
static bool odd;
|
||||
|
||||
static __u16 prev_pressure;
|
||||
|
||||
static inline void *get_bits(__u8 *data, unsigned int byte_offset)
|
||||
{
|
||||
return data + byte_offset;
|
||||
}
|
||||
|
||||
static inline __u16 *get_u16(__u8 *data, unsigned int offset)
|
||||
{
|
||||
return (__u16 *)get_bits(data, offset);
|
||||
}
|
||||
|
||||
static inline __u8 *get_u8(__u8 *data, unsigned int offset)
|
||||
{
|
||||
return (__u8 *)get_bits(data, offset);
|
||||
}
|
||||
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
|
||||
__u16 *pressure, *tool_type;
|
||||
__u8 *tip_switch;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (data[0] != params.report_id ||
|
||||
params.offsets.tip_switch >= PEN_REPORT_LEN ||
|
||||
params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
|
||||
params.offsets.tool_type >= PEN_REPORT_LEN - 1)
|
||||
return 0; /* invalid report or parameters */
|
||||
|
||||
tool_type = get_u16(data, params.offsets.tool_type);
|
||||
if (*tool_type != ART_PEN_ID)
|
||||
return 0;
|
||||
|
||||
tip_switch = get_u8(data, params.offsets.tip_switch);
|
||||
if ((*tip_switch & 0x01) == 0) {
|
||||
prev_pressure = 0;
|
||||
odd = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pressure = get_u16(data, params.offsets.pressure);
|
||||
|
||||
if (odd)
|
||||
*pressure = (*pressure + prev_pressure) / 2;
|
||||
|
||||
prev_pressure = *pressure;
|
||||
odd = !odd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
struct hid_bpf_ctx *hid_ctx;
|
||||
__u16 pid;
|
||||
int i;
|
||||
|
||||
/* get a struct hid_device to access the actual pid of the device */
|
||||
hid_ctx = hid_bpf_allocate_context(ctx->hid);
|
||||
if (!hid_ctx) {
|
||||
ctx->retval = -ENODEV;
|
||||
return -1; /* EPERM check */
|
||||
}
|
||||
pid = hid_ctx->hid->product;
|
||||
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
/* Match the given device with the list of known devices */
|
||||
for (i = 0; i < ARRAY_SIZE(devices); i++) {
|
||||
const struct wacom_params *device = &devices[i];
|
||||
|
||||
if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
|
||||
params = *device;
|
||||
ctx->retval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
hid_bpf_release_context(hid_ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,229 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
|
||||
#define PID_ARTIST_24 0x093A
|
||||
#define PID_ARTIST_24_PRO 0x092D
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
|
||||
);
|
||||
|
||||
/*
|
||||
* We need to amend the report descriptor for the following:
|
||||
* - the device reports Eraser instead of using Secondary Barrel Switch
|
||||
* - the pen doesn't have a rubber tail, so basically we are removing any
|
||||
* eraser/invert bits
|
||||
*/
|
||||
static const __u8 fixed_rdesc[] = {
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 0
|
||||
0x09, 0x02, // Usage (Pen) 2
|
||||
0xa1, 0x01, // Collection (Application) 4
|
||||
0x85, 0x07, // Report ID (7) 6
|
||||
0x09, 0x20, // Usage (Stylus) 8
|
||||
0xa1, 0x00, // Collection (Physical) 10
|
||||
0x09, 0x42, // Usage (Tip Switch) 12
|
||||
0x09, 0x44, // Usage (Barrel Switch) 14
|
||||
0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
|
||||
0x15, 0x00, // Logical Minimum (0) 18
|
||||
0x25, 0x01, // Logical Maximum (1) 20
|
||||
0x75, 0x01, // Report Size (1) 22
|
||||
0x95, 0x03, // Report Count (3) 24
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 26
|
||||
0x95, 0x02, // Report Count (2) 28
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 30
|
||||
0x09, 0x32, // Usage (In Range) 32
|
||||
0x95, 0x01, // Report Count (1) 34
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 36
|
||||
0x95, 0x02, // Report Count (2) 38
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 40
|
||||
0x75, 0x10, // Report Size (16) 42
|
||||
0x95, 0x01, // Report Count (1) 44
|
||||
0x35, 0x00, // Physical Minimum (0) 46
|
||||
0xa4, // Push 48
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 49
|
||||
0x09, 0x30, // Usage (X) 51
|
||||
0x65, 0x13, // Unit (EnglishLinear: in) 53
|
||||
0x55, 0x0d, // Unit Exponent (-3) 55
|
||||
0x46, 0xf0, 0x50, // Physical Maximum (20720) 57
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 60
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 63
|
||||
0x09, 0x31, // Usage (Y) 65
|
||||
0x46, 0x91, 0x2d, // Physical Maximum (11665) 67
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 70
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 73
|
||||
0xb4, // Pop 75
|
||||
0x09, 0x30, // Usage (Tip Pressure) 76
|
||||
0x45, 0x00, // Physical Maximum (0) 78
|
||||
0x26, 0xff, 0x1f, // Logical Maximum (8191) 80
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 83
|
||||
0x09, 0x3d, // Usage (X Tilt) 85
|
||||
0x15, 0x81, // Logical Minimum (-127) 87
|
||||
0x25, 0x7f, // Logical Maximum (127) 89
|
||||
0x75, 0x08, // Report Size (8) 91
|
||||
0x95, 0x01, // Report Count (1) 93
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 95
|
||||
0x09, 0x3e, // Usage (Y Tilt) 97
|
||||
0x15, 0x81, // Logical Minimum (-127) 99
|
||||
0x25, 0x7f, // Logical Maximum (127) 101
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 103
|
||||
0xc0, // End Collection 105
|
||||
0xc0, // End Collection 106
|
||||
};
|
||||
|
||||
#define BIT(n) (1UL << n)
|
||||
|
||||
#define TIP_SWITCH BIT(0)
|
||||
#define BARREL_SWITCH BIT(1)
|
||||
#define ERASER BIT(2)
|
||||
/* padding BIT(3) */
|
||||
/* padding BIT(4) */
|
||||
#define IN_RANGE BIT(5)
|
||||
/* padding BIT(6) */
|
||||
/* padding BIT(7) */
|
||||
|
||||
#define U16(index) (data[index] | (data[index + 1] << 8))
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
|
||||
|
||||
return sizeof(fixed_rdesc);
|
||||
}
|
||||
|
||||
static __u8 prev_state = 0;
|
||||
|
||||
/*
|
||||
* There are a few cases where the device is sending wrong event
|
||||
* sequences, all related to the second button (the pen doesn't
|
||||
* have an eraser switch on the tail end):
|
||||
*
|
||||
* whenever the second button gets pressed or released, an
|
||||
* out-of-proximity event is generated and then the firmware
|
||||
* compensate for the missing state (and the firmware uses
|
||||
* eraser for that button):
|
||||
*
|
||||
* - if the pen is in range, an extra out-of-range is sent
|
||||
* when the second button is pressed/released:
|
||||
* // Pen is in range
|
||||
* E: InRange
|
||||
*
|
||||
* // Second button is pressed
|
||||
* E:
|
||||
* E: Eraser InRange
|
||||
*
|
||||
* // Second button is released
|
||||
* E:
|
||||
* E: InRange
|
||||
*
|
||||
* This case is ignored by this filter, it's "valid"
|
||||
* and userspace knows how to deal with it, there are just
|
||||
* a few out-of-prox events generated, but the user doesn´t
|
||||
* see them.
|
||||
*
|
||||
* - if the pen is in contact, 2 extra events are added when
|
||||
* the second button is pressed/released: an out of range
|
||||
* and an in range:
|
||||
*
|
||||
* // Pen is in contact
|
||||
* E: TipSwitch InRange
|
||||
*
|
||||
* // Second button is pressed
|
||||
* E: <- false release, needs to be filtered out
|
||||
* E: Eraser InRange <- false release, needs to be filtered out
|
||||
* E: TipSwitch Eraser InRange
|
||||
*
|
||||
* // Second button is released
|
||||
* E: <- false release, needs to be filtered out
|
||||
* E: InRange <- false release, needs to be filtered out
|
||||
* E: TipSwitch InRange
|
||||
*
|
||||
*/
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
__u8 current_state, changed_state;
|
||||
bool prev_tip;
|
||||
__u16 tilt;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
current_state = data[1];
|
||||
|
||||
/* if the state is identical to previously, early return */
|
||||
if (current_state == prev_state)
|
||||
return 0;
|
||||
|
||||
prev_tip = !!(prev_state & TIP_SWITCH);
|
||||
|
||||
/*
|
||||
* Illegal transition: pen is in range with the tip pressed, and
|
||||
* it goes into out of proximity.
|
||||
*
|
||||
* Ideally we should hold the event, start a timer and deliver it
|
||||
* only if the timer ends, but we are not capable of that now.
|
||||
*
|
||||
* And it doesn't matter because when we are in such cases, this
|
||||
* means we are detecting a false release.
|
||||
*/
|
||||
if ((current_state & IN_RANGE) == 0) {
|
||||
if (prev_tip)
|
||||
return HID_IGNORE_EVENT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* XOR to only set the bits that have changed between
|
||||
* previous and current state
|
||||
*/
|
||||
changed_state = prev_state ^ current_state;
|
||||
|
||||
/* Store the new state for future processing */
|
||||
prev_state = current_state;
|
||||
|
||||
/*
|
||||
* We get both a tipswitch and eraser change in the same HID report:
|
||||
* this is not an authorized transition and is unlikely to happen
|
||||
* in real life.
|
||||
* This is likely to be added by the firmware to emulate the
|
||||
* eraser mode so we can skip the event.
|
||||
*/
|
||||
if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
|
||||
return HID_IGNORE_EVENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/*
|
||||
* The device exports 3 interfaces.
|
||||
*/
|
||||
ctx->retval = ctx->rdesc_size != 107;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
/* ensure the kernel isn't fixed already */
|
||||
if (ctx->rdesc[17] != 0x45) /* Eraser */
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,274 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2023 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
|
||||
#define PID_ARTIST_PRO14_GEN2 0x095A
|
||||
#define PID_ARTIST_PRO16_GEN2 0x095B
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO14_GEN2),
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_PRO16_GEN2)
|
||||
);
|
||||
|
||||
/*
|
||||
* We need to amend the report descriptor for the following:
|
||||
* - the device reports Eraser instead of using Secondary Barrel Switch
|
||||
* - when the eraser button is pressed and the stylus is touching the tablet,
|
||||
* the device sends Tip Switch instead of sending Eraser
|
||||
*
|
||||
* This descriptor uses physical dimensions of the 16" device.
|
||||
*/
|
||||
static const __u8 fixed_rdesc[] = {
|
||||
0x05, 0x0d, // Usage Page (Digitizers) 0
|
||||
0x09, 0x02, // Usage (Pen) 2
|
||||
0xa1, 0x01, // Collection (Application) 4
|
||||
0x85, 0x07, // Report ID (7) 6
|
||||
0x09, 0x20, // Usage (Stylus) 8
|
||||
0xa1, 0x00, // Collection (Physical) 10
|
||||
0x09, 0x42, // Usage (Tip Switch) 12
|
||||
0x09, 0x44, // Usage (Barrel Switch) 14
|
||||
0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
|
||||
0x09, 0x3c, // Usage (Invert) 18
|
||||
0x09, 0x45, // Usage (Eraser) 16 /* created over a padding bit at offset 29-33 */
|
||||
0x15, 0x00, // Logical Minimum (0) 20
|
||||
0x25, 0x01, // Logical Maximum (1) 22
|
||||
0x75, 0x01, // Report Size (1) 24
|
||||
0x95, 0x05, // Report Count (5) 26 /* changed from 4 to 5 */
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 28
|
||||
0x09, 0x32, // Usage (In Range) 34
|
||||
0x15, 0x00, // Logical Minimum (0) 36
|
||||
0x25, 0x01, // Logical Maximum (1) 38
|
||||
0x95, 0x01, // Report Count (1) 40
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 42
|
||||
0x95, 0x02, // Report Count (2) 44
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs) 46
|
||||
0x75, 0x10, // Report Size (16) 48
|
||||
0x95, 0x01, // Report Count (1) 50
|
||||
0x35, 0x00, // Physical Minimum (0) 52
|
||||
0xa4, // Push 54
|
||||
0x05, 0x01, // Usage Page (Generic Desktop) 55
|
||||
0x09, 0x30, // Usage (X) 57
|
||||
0x65, 0x13, // Unit (EnglishLinear: in) 59
|
||||
0x55, 0x0d, // Unit Exponent (-3) 61
|
||||
0x46, 0xff, 0x34, // Physical Maximum (13567) 63
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 66
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 69
|
||||
0x09, 0x31, // Usage (Y) 71
|
||||
0x46, 0x20, 0x21, // Physical Maximum (8480) 73
|
||||
0x26, 0xff, 0x7f, // Logical Maximum (32767) 76
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 79
|
||||
0xb4, // Pop 81
|
||||
0x09, 0x30, // Usage (Tip Pressure) 82
|
||||
0x45, 0x00, // Physical Maximum (0) 84
|
||||
0x26, 0xff, 0x3f, // Logical Maximum (16383) 86
|
||||
0x81, 0x42, // Input (Data,Var,Abs,Null) 89
|
||||
0x09, 0x3d, // Usage (X Tilt) 91
|
||||
0x15, 0x81, // Logical Minimum (-127) 93
|
||||
0x25, 0x7f, // Logical Maximum (127) 95
|
||||
0x75, 0x08, // Report Size (8) 97
|
||||
0x95, 0x01, // Report Count (1) 99
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 101
|
||||
0x09, 0x3e, // Usage (Y Tilt) 103
|
||||
0x15, 0x81, // Logical Minimum (-127) 105
|
||||
0x25, 0x7f, // Logical Maximum (127) 107
|
||||
0x81, 0x02, // Input (Data,Var,Abs) 109
|
||||
0xc0, // End Collection 111
|
||||
0xc0, // End Collection 112
|
||||
};
|
||||
|
||||
SEC("fmod_ret/hid_bpf_rdesc_fixup")
|
||||
int BPF_PROG(hid_fix_rdesc_xppen_artistpro16gen2, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
|
||||
|
||||
/* Fix the Physical maximum values for different sizes of the device
|
||||
* The 14" screen device descriptor size is 11.874" x 7.421"
|
||||
*/
|
||||
if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
|
||||
data[63] = 0x2e;
|
||||
data[62] = 0x62;
|
||||
data[73] = 0x1c;
|
||||
data[72] = 0xfd;
|
||||
}
|
||||
|
||||
return sizeof(fixed_rdesc);
|
||||
}
|
||||
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(xppen_16_fix_eraser, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if ((data[1] & 0x29) != 0x29) /* tip switch=1 invert=1 inrange=1 */
|
||||
return 0;
|
||||
|
||||
/* xor bits 0,3 and 4: convert Tip Switch + Invert into Eraser only */
|
||||
data[1] ^= 0x19;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Static coordinate offset table based on positive only angles
|
||||
* Two tables are needed, because the logical coordinates are scaled
|
||||
*
|
||||
* The table can be generated by Python like this:
|
||||
* >>> full_scale = 11.874 # the display width/height in inches
|
||||
* >>> tip_height = 0.055677699 # the center of the pen coil distance from screen in inch (empirical)
|
||||
* >>> h = tip_height * (32767 / full_scale) # height of the coil in logical coordinates
|
||||
* >>> [round(h*math.sin(math.radians(d))) for d in range(0, 128)]
|
||||
* [0, 13, 26, ....]
|
||||
*/
|
||||
|
||||
/* 14" inch screen 11.874" x 7.421" */
|
||||
static const __u16 angle_offsets_horizontal_14[128] = {
|
||||
0, 3, 5, 8, 11, 13, 16, 19, 21, 24, 27, 29, 32, 35, 37, 40, 42, 45, 47, 50, 53,
|
||||
55, 58, 60, 62, 65, 67, 70, 72, 74, 77, 79, 81, 84, 86, 88, 90, 92, 95, 97, 99,
|
||||
101, 103, 105, 107, 109, 111, 112, 114, 116, 118, 119, 121, 123, 124, 126, 127,
|
||||
129, 130, 132, 133, 134, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
|
||||
147, 148, 148, 149, 150, 150, 151, 151, 152, 152, 153, 153, 153, 153, 153, 154,
|
||||
154, 154, 154, 154, 153, 153, 153, 153, 153, 152, 152, 151, 151, 150, 150, 149,
|
||||
148, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 134, 133,
|
||||
132, 130, 129, 127, 126, 124, 123
|
||||
};
|
||||
static const __u16 angle_offsets_vertical_14[128] = {
|
||||
0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 59, 64, 68, 72, 76, 80, 84,
|
||||
88, 92, 96, 100, 104, 108, 112, 115, 119, 123, 127, 130, 134, 137, 141, 145, 148,
|
||||
151, 155, 158, 161, 165, 168, 171, 174, 177, 180, 183, 186, 188, 191, 194, 196,
|
||||
199, 201, 204, 206, 208, 211, 213, 215, 217, 219, 221, 223, 225, 226, 228, 230,
|
||||
231, 232, 234, 235, 236, 237, 239, 240, 240, 241, 242, 243, 243, 244, 244, 245,
|
||||
245, 246, 246, 246, 246, 246, 246, 246, 245, 245, 244, 244, 243, 243, 242, 241,
|
||||
240, 240, 239, 237, 236, 235, 234, 232, 231, 230, 228, 226, 225, 223, 221, 219,
|
||||
217, 215, 213, 211, 208, 206, 204, 201, 199, 196
|
||||
};
|
||||
|
||||
/* 16" inch screen 13.567" x 8.480" */
|
||||
static const __u16 angle_offsets_horizontal_16[128] = {
|
||||
0, 2, 5, 7, 9, 12, 14, 16, 19, 21, 23, 26, 28, 30, 33, 35, 37, 39, 42, 44, 46, 48,
|
||||
50, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 86, 88, 90,
|
||||
92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 107, 109, 110, 111, 113, 114, 115,
|
||||
116, 118, 119, 120, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 129, 130,
|
||||
130, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 134,
|
||||
134, 134, 134, 134, 134, 133, 133, 133, 132, 132, 132, 131, 130, 130, 129, 129,
|
||||
128, 127, 126, 126, 125, 124, 123, 122, 121, 120, 119, 118, 116, 115, 114, 113,
|
||||
111, 110, 109, 107
|
||||
};
|
||||
static const __u16 angle_offsets_vertical_16[128] = {
|
||||
0, 4, 8, 11, 15, 19, 22, 26, 30, 34, 37, 41, 45, 48, 52, 56, 59, 63, 66, 70, 74,
|
||||
77, 81, 84, 88, 91, 94, 98, 101, 104, 108, 111, 114, 117, 120, 123, 126, 129, 132,
|
||||
135, 138, 141, 144, 147, 149, 152, 155, 157, 160, 162, 165, 167, 170, 172, 174,
|
||||
176, 178, 180, 182, 184, 186, 188, 190, 192, 193, 195, 197, 198, 199, 201, 202,
|
||||
203, 205, 206, 207, 208, 209, 210, 210, 211, 212, 212, 213, 214, 214, 214, 215,
|
||||
215, 215, 215, 215, 215, 215, 215, 215, 214, 214, 214, 213, 212, 212, 211, 210,
|
||||
210, 209, 208, 207, 206, 205, 203, 202, 201, 199, 198, 197, 195, 193, 192, 190,
|
||||
188, 186, 184, 182, 180, 178, 176, 174, 172
|
||||
};
|
||||
|
||||
static void compensate_coordinates_by_tilt(__u8 *data, const __u8 idx,
|
||||
const __s8 tilt, const __u16 (*compensation_table)[128])
|
||||
{
|
||||
__u16 coords = data[idx+1];
|
||||
|
||||
coords <<= 8;
|
||||
coords += data[idx];
|
||||
|
||||
__u8 direction = tilt > 0 ? 0 : 1; /* Positive tilt means we need to subtract the compensation (vs. negative angle where we need to add) */
|
||||
__u8 angle = tilt > 0 ? tilt : -tilt;
|
||||
|
||||
if (angle > 127)
|
||||
return;
|
||||
|
||||
__u16 compensation = (*compensation_table)[angle];
|
||||
|
||||
if (direction == 0) {
|
||||
coords = (coords > compensation) ? coords - compensation : 0;
|
||||
} else {
|
||||
const __u16 logical_maximum = 32767;
|
||||
__u16 max = logical_maximum - compensation;
|
||||
|
||||
coords = (coords < max) ? coords + compensation : logical_maximum;
|
||||
}
|
||||
|
||||
data[idx] = coords & 0xff;
|
||||
data[idx+1] = coords >> 8;
|
||||
}
|
||||
|
||||
SEC("fmod_ret/hid_bpf_device_event")
|
||||
int BPF_PROG(xppen_16_fix_angle_offset, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/*
|
||||
* Compensate X and Y offset caused by tilt.
|
||||
*
|
||||
* The magnetic center moves when the pen is tilted, because the coil
|
||||
* is not touching the screen.
|
||||
*
|
||||
* a (tilt angle)
|
||||
* | /... h (coil distance from tip)
|
||||
* | /
|
||||
* |/______
|
||||
* |x (position offset)
|
||||
*
|
||||
* x = sin a * h
|
||||
*
|
||||
* Subtract the offset from the coordinates. Use the precomputed table!
|
||||
*
|
||||
* bytes 0 - report id
|
||||
* 1 - buttons
|
||||
* 2-3 - X coords (logical)
|
||||
* 4-5 - Y coords
|
||||
* 6-7 - pressure (ignore)
|
||||
* 8 - tilt X
|
||||
* 9 - tilt Y
|
||||
*/
|
||||
|
||||
__s8 tilt_x = (__s8) data[8];
|
||||
__s8 tilt_y = (__s8) data[9];
|
||||
|
||||
if (hctx->hid->product == PID_ARTIST_PRO14_GEN2) {
|
||||
compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_14);
|
||||
compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_14);
|
||||
} else if (hctx->hid->product == PID_ARTIST_PRO16_GEN2) {
|
||||
compensate_coordinates_by_tilt(data, 2, tilt_x, &angle_offsets_horizontal_16);
|
||||
compensate_coordinates_by_tilt(data, 4, tilt_y, &angle_offsets_vertical_16);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/*
|
||||
* The device exports 3 interfaces.
|
||||
*/
|
||||
ctx->retval = ctx->rdesc_size != 113;
|
||||
if (ctx->retval)
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
/* ensure the kernel isn't fixed already */
|
||||
if (ctx->rdesc[17] != 0x45) /* Eraser */
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#ifndef ____HID_BPF__H
|
||||
#define ____HID_BPF__H
|
||||
|
||||
struct hid_bpf_probe_args {
|
||||
unsigned int hid;
|
||||
unsigned int rdesc_size;
|
||||
unsigned char rdesc[4096];
|
||||
int retval;
|
||||
};
|
||||
|
||||
#endif /* ____HID_BPF__H */
|
|
@ -0,0 +1,168 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#ifndef __HID_BPF_HELPERS_H
|
||||
#define __HID_BPF_HELPERS_H
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
|
||||
unsigned int offset,
|
||||
const size_t __sz) __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;
|
||||
|
||||
#define HID_MAX_DESCRIPTOR_SIZE 4096
|
||||
#define HID_IGNORE_EVENT -1
|
||||
|
||||
/* extracted from <linux/input.h> */
|
||||
#define BUS_ANY 0x00
|
||||
#define BUS_PCI 0x01
|
||||
#define BUS_ISAPNP 0x02
|
||||
#define BUS_USB 0x03
|
||||
#define BUS_HIL 0x04
|
||||
#define BUS_BLUETOOTH 0x05
|
||||
#define BUS_VIRTUAL 0x06
|
||||
#define BUS_ISA 0x10
|
||||
#define BUS_I8042 0x11
|
||||
#define BUS_XTKBD 0x12
|
||||
#define BUS_RS232 0x13
|
||||
#define BUS_GAMEPORT 0x14
|
||||
#define BUS_PARPORT 0x15
|
||||
#define BUS_AMIGA 0x16
|
||||
#define BUS_ADB 0x17
|
||||
#define BUS_I2C 0x18
|
||||
#define BUS_HOST 0x19
|
||||
#define BUS_GSC 0x1A
|
||||
#define BUS_ATARI 0x1B
|
||||
#define BUS_SPI 0x1C
|
||||
#define BUS_RMI 0x1D
|
||||
#define BUS_CEC 0x1E
|
||||
#define BUS_INTEL_ISHTP 0x1F
|
||||
#define BUS_AMD_SFH 0x20
|
||||
|
||||
/* extracted from <linux/hid.h> */
|
||||
#define HID_GROUP_ANY 0x0000
|
||||
#define HID_GROUP_GENERIC 0x0001
|
||||
#define HID_GROUP_MULTITOUCH 0x0002
|
||||
#define HID_GROUP_SENSOR_HUB 0x0003
|
||||
#define HID_GROUP_MULTITOUCH_WIN_8 0x0004
|
||||
#define HID_GROUP_RMI 0x0100
|
||||
#define HID_GROUP_WACOM 0x0101
|
||||
#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102
|
||||
#define HID_GROUP_STEAM 0x0103
|
||||
#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104
|
||||
#define HID_GROUP_VIVALDI 0x0105
|
||||
|
||||
/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
|
||||
#define HID_VID_ANY 0x0000
|
||||
#define HID_PID_ANY 0x0000
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for
|
||||
* field/variable names
|
||||
*/
|
||||
#define COMBINE1(X, Y) X ## Y
|
||||
#define COMBINE(X, Y) COMBINE1(X, Y)
|
||||
|
||||
/* Macro magic:
|
||||
* __uint(foo, 123) creates a int (*foo)[1234]
|
||||
*
|
||||
* We use that macro to declare an anonymous struct with several
|
||||
* fields, each is the declaration of an pointer to an array of size
|
||||
* bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
|
||||
* would be sizeof(pointer) rather than sizeof(array). Not that we ever
|
||||
* instantiate it anyway).
|
||||
*
|
||||
* This is only used for BTF introspection, we can later check "what size
|
||||
* is the bus array" in the introspection data and thus extract the bus ID
|
||||
* again.
|
||||
*
|
||||
* And we use the __LINE__ to give each of our structs a unique name so the
|
||||
* BPF program writer doesn't have to.
|
||||
*
|
||||
* $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
|
||||
* shows the inspection data, start by searching for .hid_bpf_config
|
||||
* and working backwards from that (each entry references the type_id of the
|
||||
* content).
|
||||
*/
|
||||
|
||||
#define HID_DEVICE(b, g, ven, prod) \
|
||||
struct { \
|
||||
__uint(name, 0); \
|
||||
__uint(bus, (b)); \
|
||||
__uint(group, (g)); \
|
||||
__uint(vid, (ven)); \
|
||||
__uint(pid, (prod)); \
|
||||
} COMBINE(_entry, __LINE__)
|
||||
|
||||
/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
|
||||
* we can pass multiple HID_DEVICE() invocations in.
|
||||
*
|
||||
* For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
|
||||
*
|
||||
* union {
|
||||
* HID_DEVICE(...);
|
||||
* HID_DEVICE(...);
|
||||
* } _device_ids SEC(".hid_bpf_config")
|
||||
*
|
||||
*/
|
||||
|
||||
/* Returns the number of macro arguments, this expands
|
||||
* NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
|
||||
* NTH_ARG always returns the 16th argument which in our case is 3.
|
||||
*
|
||||
* If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
|
||||
* updated.
|
||||
*/
|
||||
#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN)
|
||||
#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
|
||||
|
||||
/* Add to this if we need more than 16 args */
|
||||
#define _COUNTDOWN \
|
||||
15, 14, 13, 12, 11, 10, 9, 8, \
|
||||
7, 6, 5, 4, 3, 2, 1, 0
|
||||
|
||||
/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
|
||||
* 1-indexed.
|
||||
*/
|
||||
#define _NTH_ARG( \
|
||||
_1, _2, _3, _4, _5, _6, _7, _8, \
|
||||
_9, _10, _11, _12, _13, _14, _15,\
|
||||
N, ...) N
|
||||
|
||||
/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
|
||||
#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
|
||||
|
||||
/* And now define all the ARG macros for each number of args we want to accept */
|
||||
#define _ARG1(_1) _1;
|
||||
#define _ARG2(_1, _2) _1; _2;
|
||||
#define _ARG3(_1, _2, _3) _1; _2; _3;
|
||||
#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4;
|
||||
#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5;
|
||||
#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6;
|
||||
#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7;
|
||||
#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8;
|
||||
#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9;
|
||||
#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
|
||||
#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
|
||||
#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
|
||||
#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
|
||||
#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
|
||||
#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
|
||||
|
||||
|
||||
#define HID_BPF_CONFIG(...) union { \
|
||||
_EXPAND(_ARG, __VA_ARGS__) \
|
||||
} _device_ids SEC(".hid_bpf_config")
|
||||
|
||||
#endif /* __HID_BPF_HELPERS_H */
|
|
@ -335,36 +335,20 @@ static int asus_raw_event(struct hid_device *hdev,
|
|||
if (drvdata->quirks & QUIRK_MEDION_E1239T)
|
||||
return asus_e1239t_event(drvdata, data, size);
|
||||
|
||||
if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) {
|
||||
/*
|
||||
* Skip these report ID, the device emits a continuous stream associated
|
||||
* with the AURA mode it is in which looks like an 'echo'.
|
||||
*/
|
||||
if (report->id == FEATURE_KBD_LED_REPORT_ID1 || report->id == FEATURE_KBD_LED_REPORT_ID2)
|
||||
return -1;
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
/*
|
||||
* Skip these report ID, the device emits a continuous stream associated
|
||||
* with the AURA mode it is in which looks like an 'echo'.
|
||||
* G713 and G733 send these codes on some keypresses, depending on
|
||||
* the key pressed it can trigger a shutdown event if not caught.
|
||||
*/
|
||||
if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
|
||||
report->id == FEATURE_KBD_LED_REPORT_ID2) {
|
||||
if (data[0] == 0x02 && data[1] == 0x30) {
|
||||
return -1;
|
||||
/* Additional report filtering */
|
||||
} else if (report->id == FEATURE_KBD_REPORT_ID) {
|
||||
/*
|
||||
* G14 and G15 send these codes on some keypresses with no
|
||||
* discernable reason for doing so. We'll filter them out to avoid
|
||||
* unmapped warning messages later.
|
||||
*/
|
||||
if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
|
||||
data[1] == 0x8a || data[1] == 0x9e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
/*
|
||||
* G713 and G733 send these codes on some keypresses, depending on
|
||||
* the key pressed it can trigger a shutdown event if not caught.
|
||||
*/
|
||||
if(data[0] == 0x02 && data[1] == 0x30) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_CLAYMORE_II_KEYBOARD) {
|
||||
|
@ -402,9 +386,9 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int asus_kbd_init(struct hid_device *hdev)
|
||||
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
|
||||
{
|
||||
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
|
||||
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
|
||||
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
|
||||
int ret;
|
||||
|
||||
|
@ -416,9 +400,10 @@ static int asus_kbd_init(struct hid_device *hdev)
|
|||
}
|
||||
|
||||
static int asus_kbd_get_functions(struct hid_device *hdev,
|
||||
unsigned char *kbd_func)
|
||||
unsigned char *kbd_func,
|
||||
u8 report_id)
|
||||
{
|
||||
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 };
|
||||
const u8 buf[] = { report_id, 0x05, 0x20, 0x31, 0x00, 0x08 };
|
||||
u8 *readbuf;
|
||||
int ret;
|
||||
|
||||
|
@ -447,51 +432,6 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int rog_nkey_led_init(struct hid_device *hdev)
|
||||
{
|
||||
const u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
|
||||
u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
|
||||
0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
|
||||
u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
|
||||
0x05, 0x20, 0x31, 0x00, 0x08 };
|
||||
int ret;
|
||||
|
||||
hid_info(hdev, "Asus initialise N-KEY Device");
|
||||
/* The first message is an init start */
|
||||
ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Asus failed to send init start command: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* Followed by a string */
|
||||
ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Asus failed to send init command 1.0: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
/* Followed by a string */
|
||||
ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Asus failed to send init command 1.1: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* begin second report ID with same data */
|
||||
buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
|
||||
buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
|
||||
|
||||
ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Asus failed to send init command 2.0: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
|
||||
if (ret < 0)
|
||||
hid_warn(hdev, "Asus failed to send init command 2.1: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void asus_schedule_work(struct asus_kbd_leds *led)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
@ -574,17 +514,27 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
|
|||
int ret;
|
||||
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
ret = rog_nkey_led_init(hdev);
|
||||
/* Initialize keyboard */
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* The LED endpoint is initialised in two HID */
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
/* Initialize keyboard */
|
||||
ret = asus_kbd_init(hdev);
|
||||
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Get keyboard functions */
|
||||
ret = asus_kbd_get_functions(hdev, &kbd_func);
|
||||
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
@ -897,7 +847,10 @@ static int asus_input_mapping(struct hid_device *hdev,
|
|||
case 0xb3: asus_map_key_clear(KEY_PROG3); break; /* Fn+Left next aura */
|
||||
case 0x6a: asus_map_key_clear(KEY_F13); break; /* Screenpad toggle */
|
||||
case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */
|
||||
|
||||
case 0xa5: asus_map_key_clear(KEY_F15); break; /* ROG Ally left back */
|
||||
case 0xa6: asus_map_key_clear(KEY_F16); break; /* ROG Ally QAM button */
|
||||
case 0xa7: asus_map_key_clear(KEY_F17); break; /* ROG Ally ROG long-press */
|
||||
case 0xa8: asus_map_key_clear(KEY_F18); break; /* ROG Ally ROG long-press-release */
|
||||
|
||||
default:
|
||||
/* ASUS lazily declares 256 usages, ignore the rest,
|
||||
|
@ -1250,6 +1203,19 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
|||
rdesc[205] = 0x01;
|
||||
}
|
||||
|
||||
/* match many more n-key devices */
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
for (int i = 0; i < *rsize + 1; i++) {
|
||||
/* offset to the count from 0x5a report part always 14 */
|
||||
if (rdesc[i] == 0x85 && rdesc[i + 1] == 0x5a &&
|
||||
rdesc[i + 14] == 0x95 && rdesc[i + 15] == 0x05) {
|
||||
hid_info(hdev, "Fixing up Asus N-Key report descriptor\n");
|
||||
rdesc[i + 15] = 0x01;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
|
@ -1276,6 +1242,12 @@ static const struct hid_device_id asus_devices[] = {
|
|||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
|
||||
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
|
||||
USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
|
||||
QUIRK_ROG_CLAYMORE_II_KEYBOARD },
|
||||
|
@ -1319,4 +1291,4 @@ static struct hid_driver asus_driver = {
|
|||
};
|
||||
module_hid_driver(asus_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -2974,6 +2974,8 @@ EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
|
|||
static struct hid_bpf_ops hid_ops = {
|
||||
.hid_get_report = hid_get_report,
|
||||
.hid_hw_raw_request = hid_hw_raw_request,
|
||||
.hid_hw_output_report = hid_hw_output_report,
|
||||
.hid_input_report = hid_input_report,
|
||||
.owner = THIS_MODULE,
|
||||
.bus_type = &hid_bus_type,
|
||||
};
|
||||
|
|
|
@ -298,7 +298,7 @@ static ssize_t k90_show_macro_mode(struct device *dev,
|
|||
goto out;
|
||||
}
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
|
||||
ret = sysfs_emit(buf, "%s\n", macro_mode);
|
||||
out:
|
||||
kfree(data);
|
||||
|
||||
|
@ -367,7 +367,7 @@ static ssize_t k90_show_current_profile(struct device *dev,
|
|||
goto out;
|
||||
}
|
||||
|
||||
ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
|
||||
ret = sysfs_emit(buf, "%d\n", current_profile);
|
||||
out:
|
||||
kfree(data);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -255,7 +255,7 @@ out:
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int cbas_ec_remove(struct platform_device *pdev)
|
||||
static void cbas_ec_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
|
||||
|
||||
|
@ -266,7 +266,6 @@ static int cbas_ec_remove(struct platform_device *pdev)
|
|||
cbas_ec_set_input(NULL);
|
||||
|
||||
mutex_unlock(&cbas_ec_reglock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id cbas_ec_acpi_ids[] = {
|
||||
|
@ -285,7 +284,7 @@ MODULE_DEVICE_TABLE(of, cbas_ec_of_match);
|
|||
|
||||
static struct platform_driver cbas_ec_driver = {
|
||||
.probe = cbas_ec_probe,
|
||||
.remove = cbas_ec_remove,
|
||||
.remove_new = cbas_ec_remove,
|
||||
.driver = {
|
||||
.name = "cbas_ec",
|
||||
.acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids),
|
||||
|
|
|
@ -208,6 +208,8 @@
|
|||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
|
||||
#define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b
|
||||
#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
|
||||
|
||||
|
@ -823,6 +825,7 @@
|
|||
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
|
||||
#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
|
||||
#define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD 0xb309
|
||||
#define USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD 0xbb00
|
||||
#define USB_DEVICE_ID_LOGITECH_C007 0xc007
|
||||
#define USB_DEVICE_ID_LOGITECH_C077 0xc077
|
||||
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
|
||||
|
|
|
@ -209,7 +209,7 @@ static const __u8 pensketch_t609a_control_rdesc[] = {
|
|||
0xC0 /* End Collection */
|
||||
};
|
||||
|
||||
/* Fix indexes in kye_tablet_fixup if you change this */
|
||||
/* Fix indexes in kye_tablet_fixup() if you change this */
|
||||
static const __u8 kye_tablet_rdesc[] = {
|
||||
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
|
||||
0x09, 0x01, /* Usage (01h), */
|
||||
|
@ -262,12 +262,16 @@ static const __u8 kye_tablet_rdesc[] = {
|
|||
0x27, 0xFF, 0x07, 0x00, 0x00, /* Logical Maximum (2047), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0xC0, /* End Collection, */
|
||||
0xC0, /* End Collection, */
|
||||
0x05, 0x0D, /* Usage Page (Digitizer), */
|
||||
0x09, 0x21, /* Usage (Puck), */
|
||||
0xC0 /* End Collection, */
|
||||
};
|
||||
|
||||
/* Fix indexes in kye_tablet_fixup() if you change this */
|
||||
static const __u8 kye_tablet_mouse_rdesc[] = {
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
0x09, 0x02, /* Usage (Mouse), */
|
||||
0xA1, 0x01, /* Collection (Application), */
|
||||
0x85, 0x11, /* Report ID (17), */
|
||||
0x09, 0x21, /* Usage (Puck), */
|
||||
0x09, 0x01, /* Usage (Pointer), */
|
||||
0xA0, /* Collection (Physical), */
|
||||
0x05, 0x09, /* Usage Page (Button), */
|
||||
0x19, 0x01, /* Usage Minimum (01h), */
|
||||
|
@ -280,7 +284,7 @@ static const __u8 kye_tablet_rdesc[] = {
|
|||
0x95, 0x04, /* Report Count (4), */
|
||||
0x81, 0x01, /* Input (Constant), */
|
||||
0x05, 0x0D, /* Usage Page (Digitizer), */
|
||||
0x09, 0x32, /* Usage (In Range), */
|
||||
0x09, 0x37, /* Usage (Data Valid), */
|
||||
0x95, 0x01, /* Report Count (1), */
|
||||
0x81, 0x02, /* Input (Variable), */
|
||||
0x05, 0x01, /* Usage Page (Desktop), */
|
||||
|
@ -317,7 +321,7 @@ static const struct kye_tablet_info {
|
|||
__s32 y_physical_maximum;
|
||||
__s8 unit_exponent;
|
||||
__s8 unit;
|
||||
bool has_punk;
|
||||
bool has_mouse;
|
||||
unsigned int control_rsize;
|
||||
const __u8 *control_rdesc;
|
||||
} kye_tablets_info[] = {
|
||||
|
@ -402,7 +406,7 @@ static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
|
|||
static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
|
||||
{
|
||||
const struct kye_tablet_info *info;
|
||||
unsigned int newsize;
|
||||
__u8 *newdesc = rdesc;
|
||||
|
||||
if (*rsize < sizeof(kye_tablet_rdesc)) {
|
||||
hid_warn(hdev,
|
||||
|
@ -420,36 +424,45 @@ static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int
|
|||
return rdesc;
|
||||
}
|
||||
|
||||
newsize = info->has_punk ? sizeof(kye_tablet_rdesc) : 112;
|
||||
memcpy(rdesc, kye_tablet_rdesc, newsize);
|
||||
memcpy(newdesc, kye_tablet_rdesc, sizeof(kye_tablet_rdesc));
|
||||
|
||||
put_unaligned_le32(info->x_logical_maximum, rdesc + 66);
|
||||
put_unaligned_le32(info->x_physical_maximum, rdesc + 72);
|
||||
rdesc[77] = info->unit;
|
||||
rdesc[79] = info->unit_exponent;
|
||||
put_unaligned_le32(info->y_logical_maximum, rdesc + 87);
|
||||
put_unaligned_le32(info->y_physical_maximum, rdesc + 92);
|
||||
put_unaligned_le32(info->pressure_logical_maximum, rdesc + 104);
|
||||
put_unaligned_le32(info->x_logical_maximum, newdesc + 66);
|
||||
put_unaligned_le32(info->x_physical_maximum, newdesc + 72);
|
||||
newdesc[77] = info->unit;
|
||||
newdesc[79] = info->unit_exponent;
|
||||
put_unaligned_le32(info->y_logical_maximum, newdesc + 87);
|
||||
put_unaligned_le32(info->y_physical_maximum, newdesc + 92);
|
||||
put_unaligned_le32(info->pressure_logical_maximum, newdesc + 104);
|
||||
|
||||
if (info->has_punk) {
|
||||
put_unaligned_le32(info->x_logical_maximum, rdesc + 156);
|
||||
put_unaligned_le32(info->x_physical_maximum, rdesc + 162);
|
||||
rdesc[167] = info->unit;
|
||||
rdesc[169] = info->unit_exponent;
|
||||
put_unaligned_le32(info->y_logical_maximum, rdesc + 177);
|
||||
put_unaligned_le32(info->y_physical_maximum, rdesc + 182);
|
||||
}
|
||||
newdesc += sizeof(kye_tablet_rdesc);
|
||||
|
||||
if (info->control_rsize) {
|
||||
if (newsize + info->control_rsize > *rsize)
|
||||
hid_err(hdev, "control rdesc unexpectedly large");
|
||||
if (info->has_mouse) {
|
||||
if (newdesc + sizeof(kye_tablet_mouse_rdesc) > rdesc + *rsize)
|
||||
hid_err(hdev, "control desc unexpectedly large\n");
|
||||
else {
|
||||
memcpy(rdesc + newsize, info->control_rdesc, info->control_rsize);
|
||||
newsize += info->control_rsize;
|
||||
memcpy(newdesc, kye_tablet_mouse_rdesc, sizeof(kye_tablet_mouse_rdesc));
|
||||
|
||||
put_unaligned_le32(info->x_logical_maximum, newdesc + 44);
|
||||
put_unaligned_le32(info->x_physical_maximum, newdesc + 50);
|
||||
newdesc[55] = info->unit;
|
||||
newdesc[57] = info->unit_exponent;
|
||||
put_unaligned_le32(info->y_logical_maximum, newdesc + 65);
|
||||
put_unaligned_le32(info->y_physical_maximum, newdesc + 70);
|
||||
|
||||
newdesc += sizeof(kye_tablet_mouse_rdesc);
|
||||
}
|
||||
}
|
||||
|
||||
*rsize = newsize;
|
||||
if (info->control_rsize) {
|
||||
if (newdesc + info->control_rsize > rdesc + *rsize)
|
||||
hid_err(hdev, "control desc unexpectedly large\n");
|
||||
else {
|
||||
memcpy(newdesc, info->control_rdesc, info->control_rsize);
|
||||
newdesc += info->control_rsize;
|
||||
}
|
||||
}
|
||||
|
||||
*rsize = newdesc - rdesc;
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
|
|
|
@ -555,7 +555,7 @@ static ssize_t attr_fn_lock_show(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock);
|
||||
return sysfs_emit(buf, "%u\n", data->fn_lock);
|
||||
}
|
||||
|
||||
static ssize_t attr_fn_lock_store(struct device *dev,
|
||||
|
@ -599,8 +599,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
cptkbd_data->sensitivity);
|
||||
return sysfs_emit(buf, "%u\n", cptkbd_data->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
|
||||
|
@ -628,8 +627,8 @@ static ssize_t attr_middleclick_workaround_show_cptkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
cptkbd_data->middleclick_workaround_cptkbd);
|
||||
return sysfs_emit(buf, "%u\n",
|
||||
cptkbd_data->middleclick_workaround_cptkbd);
|
||||
}
|
||||
|
||||
static ssize_t attr_middleclick_workaround_store_cptkbd(struct device *dev,
|
||||
|
@ -809,7 +808,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->press_to_select);
|
||||
}
|
||||
|
||||
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
|
||||
|
@ -839,7 +838,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->dragging);
|
||||
}
|
||||
|
||||
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
|
||||
|
@ -869,7 +868,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->release_to_select);
|
||||
}
|
||||
|
||||
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
|
||||
|
@ -899,7 +898,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->select_right);
|
||||
}
|
||||
|
||||
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
|
||||
|
@ -929,8 +928,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->sensitivity);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->sensitivity);
|
||||
}
|
||||
|
||||
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
|
||||
|
@ -958,8 +956,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
|
|||
struct hid_device *hdev = to_hid_device(dev);
|
||||
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%u\n",
|
||||
data_pointer->press_speed);
|
||||
return sysfs_emit(buf, "%u\n", data_pointer->press_speed);
|
||||
}
|
||||
|
||||
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
|
||||
|
|
|
@ -4603,6 +4603,12 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) },
|
||||
{ /* Logitech G903 Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC086) },
|
||||
{ /* Logitech G Pro Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
|
||||
{ /* MX Vertical over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) },
|
||||
{ /* Logitech G703 Hero Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
|
||||
{ /* Logitech G903 Hero Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
|
||||
{ /* Logitech G915 TKL Keyboard over USB */
|
||||
|
@ -4613,8 +4619,6 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
{ /* 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) },
|
||||
{ /* Logitech G Pro X Superlight Gaming Mouse over USB */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) },
|
||||
{ /* Logitech G Pro X Superlight 2 Gaming Mouse over USB */
|
||||
|
@ -4641,9 +4645,13 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) },
|
||||
{ /* M720 Triathlon mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb015) },
|
||||
{ /* MX Master 2S mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb019) },
|
||||
{ /* MX Ergo trackball over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
|
||||
{ /* MX Vertical mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
|
||||
{ /* Signature M650 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
|
||||
{ /* MX Master 3 mouse over Bluetooth */
|
||||
|
@ -4652,6 +4660,8 @@ static const struct hid_device_id hidpp_devices[] = {
|
|||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) },
|
||||
{ /* MX Master 3S mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
|
||||
{ /* MX Anywhere 3SB mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -2081,6 +2081,12 @@ static const struct hid_device_id mt_devices[] = {
|
|||
USB_VENDOR_ID_LENOVO,
|
||||
USB_DEVICE_ID_LENOVO_X12_TAB) },
|
||||
|
||||
/* Logitech devices */
|
||||
{ .driver_data = MT_CLS_NSMU,
|
||||
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD) },
|
||||
|
||||
/* MosArt panels */
|
||||
{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_ASUS,
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/leds.h>
|
||||
|
@ -569,6 +570,7 @@ static const enum led_brightness joycon_player_led_patterns[JC_NUM_LED_PATTERNS]
|
|||
struct joycon_ctlr {
|
||||
struct hid_device *hdev;
|
||||
struct input_dev *input;
|
||||
u32 player_id;
|
||||
struct led_classdev leds[JC_NUM_LEDS]; /* player leds */
|
||||
struct led_classdev home_led;
|
||||
enum joycon_ctlr_state ctlr_state;
|
||||
|
@ -692,15 +694,6 @@ static inline bool joycon_device_is_n64con(struct joycon_ctlr *ctlr)
|
|||
return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_N64CON;
|
||||
}
|
||||
|
||||
static inline bool joycon_device_has_usb(struct joycon_ctlr *ctlr)
|
||||
{
|
||||
return joycon_device_is_procon(ctlr) ||
|
||||
joycon_device_is_chrggrip(ctlr) ||
|
||||
joycon_device_is_snescon(ctlr) ||
|
||||
joycon_device_is_gencon(ctlr) ||
|
||||
joycon_device_is_n64con(ctlr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Controller type helpers
|
||||
*
|
||||
|
@ -2261,7 +2254,8 @@ static int joycon_home_led_brightness_set(struct led_classdev *led,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static DEFINE_SPINLOCK(joycon_input_num_spinlock);
|
||||
static DEFINE_IDA(nintendo_player_id_allocator);
|
||||
|
||||
static int joycon_leds_create(struct joycon_ctlr *ctlr)
|
||||
{
|
||||
struct hid_device *hdev = ctlr->hdev;
|
||||
|
@ -2272,20 +2266,19 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr)
|
|||
char *name;
|
||||
int ret;
|
||||
int i;
|
||||
unsigned long flags;
|
||||
int player_led_pattern;
|
||||
static int input_num;
|
||||
|
||||
/*
|
||||
* Set the player leds based on controller number
|
||||
* Because there is no standard concept of "player number", the pattern
|
||||
* number will simply increase by 1 every time a controller is connected.
|
||||
*/
|
||||
spin_lock_irqsave(&joycon_input_num_spinlock, flags);
|
||||
player_led_pattern = input_num++ % JC_NUM_LED_PATTERNS;
|
||||
spin_unlock_irqrestore(&joycon_input_num_spinlock, flags);
|
||||
|
||||
/* configure the player LEDs */
|
||||
ctlr->player_id = U32_MAX;
|
||||
ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
|
||||
goto home_led;
|
||||
}
|
||||
ctlr->player_id = ret;
|
||||
player_led_pattern = ret % JC_NUM_LED_PATTERNS;
|
||||
hid_info(ctlr->hdev, "assigned player %d led pattern", player_led_pattern + 1);
|
||||
|
||||
for (i = 0; i < JC_NUM_LEDS; i++) {
|
||||
name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s:%s",
|
||||
d_name,
|
||||
|
@ -2501,8 +2494,11 @@ static int joycon_init(struct hid_device *hdev)
|
|||
/* set baudrate for improved latency */
|
||||
ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret);
|
||||
goto out_unlock;
|
||||
/*
|
||||
* We can function with the default baudrate.
|
||||
* Provide a warning, and continue on.
|
||||
*/
|
||||
hid_warn(hdev, "Failed to set baudrate (ret=%d), continuing anyway\n", ret);
|
||||
}
|
||||
/* handshake */
|
||||
ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ);
|
||||
|
@ -2767,6 +2763,7 @@ static void nintendo_hid_remove(struct hid_device *hdev)
|
|||
spin_unlock_irqrestore(&ctlr->lock, flags);
|
||||
|
||||
destroy_workqueue(ctlr->rumble_queue);
|
||||
ida_free(&nintendo_player_id_allocator, ctlr->player_id);
|
||||
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
|
@ -2824,7 +2821,19 @@ static struct hid_driver nintendo_hid_driver = {
|
|||
.resume = nintendo_hid_resume,
|
||||
#endif
|
||||
};
|
||||
module_hid_driver(nintendo_hid_driver);
|
||||
static int __init nintendo_init(void)
|
||||
{
|
||||
return hid_register_driver(&nintendo_hid_driver);
|
||||
}
|
||||
|
||||
static void __exit nintendo_exit(void)
|
||||
{
|
||||
hid_unregister_driver(&nintendo_hid_driver);
|
||||
ida_destroy(&nintendo_player_id_allocator);
|
||||
}
|
||||
|
||||
module_init(nintendo_init);
|
||||
module_exit(nintendo_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
|
||||
|
|
|
@ -256,9 +256,9 @@ static ssize_t picolcd_operation_mode_show(struct device *dev,
|
|||
struct picolcd_data *data = dev_get_drvdata(dev);
|
||||
|
||||
if (data->status & PICOLCD_BOOTLOADER)
|
||||
return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
|
||||
return sysfs_emit(buf, "[bootloader] lcd\n");
|
||||
else
|
||||
return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
|
||||
return sysfs_emit(buf, "bootloader [lcd]\n");
|
||||
}
|
||||
|
||||
static ssize_t picolcd_operation_mode_store(struct device *dev,
|
||||
|
@ -301,7 +301,7 @@ static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
|
|||
{
|
||||
struct picolcd_data *data = dev_get_drvdata(dev);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
|
||||
return sysfs_emit(buf, "%hu\n", data->opmode_delay);
|
||||
}
|
||||
|
||||
static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
|
||||
|
|
|
@ -421,12 +421,10 @@ static ssize_t picolcd_fb_update_rate_show(struct device *dev,
|
|||
size_t ret = 0;
|
||||
|
||||
for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
|
||||
if (ret >= PAGE_SIZE)
|
||||
break;
|
||||
else if (i == fb_update_rate)
|
||||
ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
|
||||
if (i == fb_update_rate)
|
||||
ret += sysfs_emit_at(buf, ret, "[%u] ", i);
|
||||
else
|
||||
ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
|
||||
ret += sysfs_emit_at(buf, ret, "%u ", i);
|
||||
if (ret > 0)
|
||||
buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
|
||||
return ret;
|
||||
|
|
|
@ -27,6 +27,11 @@ static DEFINE_IDA(ps_player_id_allocator);
|
|||
|
||||
#define HID_PLAYSTATION_VERSION_PATCH 0x8000
|
||||
|
||||
enum PS_TYPE {
|
||||
PS_TYPE_PS4_DUALSHOCK4,
|
||||
PS_TYPE_PS5_DUALSENSE,
|
||||
};
|
||||
|
||||
/* Base class for playstation devices. */
|
||||
struct ps_device {
|
||||
struct list_head list;
|
||||
|
@ -287,6 +292,8 @@ struct dualsense_output_report {
|
|||
|
||||
#define DS4_INPUT_REPORT_USB 0x01
|
||||
#define DS4_INPUT_REPORT_USB_SIZE 64
|
||||
#define DS4_INPUT_REPORT_BT_MINIMAL 0x01
|
||||
#define DS4_INPUT_REPORT_BT_MINIMAL_SIZE 10
|
||||
#define DS4_INPUT_REPORT_BT 0x11
|
||||
#define DS4_INPUT_REPORT_BT_SIZE 78
|
||||
#define DS4_OUTPUT_REPORT_USB 0x05
|
||||
|
@ -1778,8 +1785,10 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
int retries;
|
||||
|
||||
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto transfer_failed;
|
||||
}
|
||||
|
||||
/* We should normally receive the feature report data we asked
|
||||
* for, but hidraw applications such as Steam can issue feature
|
||||
|
@ -1796,26 +1805,30 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
continue;
|
||||
}
|
||||
|
||||
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
|
||||
hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
|
||||
ret = -EILSEQ;
|
||||
goto err_free;
|
||||
goto transfer_failed;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { /* Bluetooth */
|
||||
buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
if (!buf) {
|
||||
ret = -ENOMEM;
|
||||
goto transfer_failed;
|
||||
}
|
||||
|
||||
ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf,
|
||||
DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
|
||||
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
|
||||
goto err_free;
|
||||
hid_warn(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
|
||||
goto transfer_failed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Transfer succeeded - parse the calibration data received. */
|
||||
gyro_pitch_bias = get_unaligned_le16(&buf[1]);
|
||||
gyro_yaw_bias = get_unaligned_le16(&buf[3]);
|
||||
gyro_roll_bias = get_unaligned_le16(&buf[5]);
|
||||
|
@ -1844,6 +1857,9 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
acc_z_plus = get_unaligned_le16(&buf[31]);
|
||||
acc_z_minus = get_unaligned_le16(&buf[33]);
|
||||
|
||||
/* Done parsing the buffer, so let's free it. */
|
||||
kfree(buf);
|
||||
|
||||
/*
|
||||
* Set gyroscope calibration and normalization parameters.
|
||||
* Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
|
||||
|
@ -1867,21 +1883,6 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
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
|
||||
* during report handling of virtual, clone or broken devices not implementing
|
||||
* calibration data properly.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
|
||||
if (ds4->gyro_calib_data[i].sens_denom == 0) {
|
||||
hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
|
||||
ds4->gyro_calib_data[i].abs_code);
|
||||
ds4->gyro_calib_data[i].bias = 0;
|
||||
ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
|
||||
ds4->gyro_calib_data[i].sens_denom = S16_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set accelerometer calibration and normalization parameters.
|
||||
* Data values will be normalized to 1/DS4_ACC_RES_PER_G g.
|
||||
|
@ -1904,6 +1905,23 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G;
|
||||
ds4->accel_calib_data[2].sens_denom = range_2g;
|
||||
|
||||
transfer_failed:
|
||||
/*
|
||||
* Sanity check gyro calibration data. This is needed to prevent crashes
|
||||
* during report handling of virtual, clone or broken devices not implementing
|
||||
* calibration data properly.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
|
||||
if (ds4->gyro_calib_data[i].sens_denom == 0) {
|
||||
ds4->gyro_calib_data[i].abs_code = ABS_RX + i;
|
||||
hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
|
||||
ds4->gyro_calib_data[i].abs_code);
|
||||
ds4->gyro_calib_data[i].bias = 0;
|
||||
ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
|
||||
ds4->gyro_calib_data[i].sens_denom = S16_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check accelerometer calibration data. This is needed to prevent crashes
|
||||
* during report handling of virtual, clone or broken devices not implementing calibration
|
||||
|
@ -1911,6 +1929,7 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) {
|
||||
if (ds4->accel_calib_data[i].sens_denom == 0) {
|
||||
ds4->accel_calib_data[i].abs_code = ABS_X + i;
|
||||
hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
|
||||
ds4->accel_calib_data[i].abs_code);
|
||||
ds4->accel_calib_data[i].bias = 0;
|
||||
|
@ -1919,8 +1938,6 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
|
|||
}
|
||||
}
|
||||
|
||||
err_free:
|
||||
kfree(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -2037,8 +2054,9 @@ static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *del
|
|||
|
||||
dualshock4_schedule_work(ds4);
|
||||
|
||||
*delay_on = ds4->lightbar_blink_on;
|
||||
*delay_off = ds4->lightbar_blink_off;
|
||||
/* Report scaled values back to LED subsystem */
|
||||
*delay_on = ds4->lightbar_blink_on * 10;
|
||||
*delay_off = ds4->lightbar_blink_off * 10;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2065,6 +2083,13 @@ static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brig
|
|||
break;
|
||||
case 3:
|
||||
ds4->lightbar_enabled = !!value;
|
||||
|
||||
/* brightness = 0 also cancels blinking in Linux. */
|
||||
if (!ds4->lightbar_enabled) {
|
||||
ds4->lightbar_blink_off = 0;
|
||||
ds4->lightbar_blink_on = 0;
|
||||
ds4->update_lightbar_blink = true;
|
||||
}
|
||||
}
|
||||
|
||||
ds4->update_lightbar = true;
|
||||
|
@ -2182,6 +2207,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
|
|||
int battery_status, i, j;
|
||||
uint16_t sensor_timestamp;
|
||||
unsigned long flags;
|
||||
bool is_minimal = false;
|
||||
|
||||
/*
|
||||
* DualShock4 in USB uses the full HID report for reportID 1, but
|
||||
|
@ -2209,6 +2235,18 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
|
|||
ds4_report = &bt->common;
|
||||
num_touch_reports = bt->num_touch_reports;
|
||||
touch_reports = bt->touch_reports;
|
||||
} else if (hdev->bus == BUS_BLUETOOTH &&
|
||||
report->id == DS4_INPUT_REPORT_BT_MINIMAL &&
|
||||
size == DS4_INPUT_REPORT_BT_MINIMAL_SIZE) {
|
||||
/* Some third-party pads never switch to the full 0x11 report.
|
||||
* The short 0x01 report is 10 bytes long:
|
||||
* u8 report_id == 0x01
|
||||
* u8 first_bytes_of_full_report[9]
|
||||
* So let's reuse the full report parser, and stop it after
|
||||
* parsing the buttons.
|
||||
*/
|
||||
ds4_report = (struct dualshock4_input_report_common *)&data[1];
|
||||
is_minimal = true;
|
||||
} else {
|
||||
hid_err(hdev, "Unhandled reportID=%d\n", report->id);
|
||||
return -1;
|
||||
|
@ -2242,6 +2280,9 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
|
|||
input_report_key(ds4->gamepad, BTN_MODE, ds4_report->buttons[2] & DS_BUTTONS2_PS_HOME);
|
||||
input_sync(ds4->gamepad);
|
||||
|
||||
if (is_minimal)
|
||||
return 0;
|
||||
|
||||
/* Parse and calibrate gyroscope data. */
|
||||
for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
|
||||
int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
|
||||
|
@ -2550,8 +2591,8 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
|
|||
|
||||
ret = dualshock4_get_firmware_info(ds4);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to get firmware info from DualShock4\n");
|
||||
return ERR_PTR(ret);
|
||||
hid_warn(hdev, "Failed to get firmware info from DualShock4\n");
|
||||
hid_warn(hdev, "HW/FW version data in sysfs will be invalid.\n");
|
||||
}
|
||||
|
||||
ret = ps_devices_list_add(ps_dev);
|
||||
|
@ -2560,8 +2601,8 @@ static struct ps_device *dualshock4_create(struct hid_device *hdev)
|
|||
|
||||
ret = dualshock4_get_calibration_data(ds4);
|
||||
if (ret) {
|
||||
hid_err(hdev, "Failed to get calibration data from DualShock4\n");
|
||||
goto err;
|
||||
hid_warn(hdev, "Failed to get calibration data from DualShock4\n");
|
||||
hid_warn(hdev, "Gyroscope and accelerometer will be inaccurate.\n");
|
||||
}
|
||||
|
||||
ds4->gamepad = ps_gamepad_create(hdev, dualshock4_play_effect);
|
||||
|
@ -2655,17 +2696,14 @@ static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||
goto err_stop;
|
||||
}
|
||||
|
||||
if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER ||
|
||||
hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 ||
|
||||
hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
|
||||
if (id->driver_data == PS_TYPE_PS4_DUALSHOCK4) {
|
||||
dev = dualshock4_create(hdev);
|
||||
if (IS_ERR(dev)) {
|
||||
hid_err(hdev, "Failed to create dualshock4.\n");
|
||||
ret = PTR_ERR(dev);
|
||||
goto err_close;
|
||||
}
|
||||
} else if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER ||
|
||||
hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) {
|
||||
} else if (id->driver_data == PS_TYPE_PS5_DUALSENSE) {
|
||||
dev = dualsense_create(hdev);
|
||||
if (IS_ERR(dev)) {
|
||||
hid_err(hdev, "Failed to create dualsense.\n");
|
||||
|
@ -2699,16 +2737,26 @@ static void ps_remove(struct hid_device *hdev)
|
|||
|
||||
static const struct hid_device_id ps_devices[] = {
|
||||
/* Sony DualShock 4 controllers for PS4 */
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
|
||||
.driver_data = PS_TYPE_PS4_DUALSHOCK4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
|
||||
.driver_data = PS_TYPE_PS4_DUALSHOCK4 },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
|
||||
.driver_data = PS_TYPE_PS4_DUALSHOCK4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
|
||||
.driver_data = PS_TYPE_PS4_DUALSHOCK4 },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE),
|
||||
.driver_data = PS_TYPE_PS4_DUALSHOCK4 },
|
||||
|
||||
/* Sony DualSense controllers for PS5 */
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER),
|
||||
.driver_data = PS_TYPE_PS5_DUALSENSE },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER),
|
||||
.driver_data = PS_TYPE_PS5_DUALSENSE },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2),
|
||||
.driver_data = PS_TYPE_PS5_DUALSENSE },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2),
|
||||
.driver_data = PS_TYPE_PS5_DUALSENSE },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, ps_devices);
|
||||
|
|
|
@ -61,7 +61,7 @@ static ssize_t isku_sysfs_show_actual_profile(struct device *dev,
|
|||
{
|
||||
struct isku_device *isku =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile);
|
||||
return sysfs_emit(buf, "%d\n", isku->actual_profile);
|
||||
}
|
||||
|
||||
static ssize_t isku_sysfs_set_actual_profile(struct device *dev,
|
||||
|
|
|
@ -400,7 +400,7 @@ static ssize_t kone_sysfs_show_actual_profile(struct device *dev,
|
|||
{
|
||||
struct kone_device *kone =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
|
||||
return sysfs_emit(buf, "%d\n", kone->actual_profile);
|
||||
}
|
||||
static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
|
||||
|
||||
|
@ -409,7 +409,7 @@ static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
|
|||
{
|
||||
struct kone_device *kone =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
|
||||
return sysfs_emit(buf, "%d\n", kone->actual_dpi);
|
||||
}
|
||||
static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
|
||||
|
||||
|
@ -432,7 +432,7 @@ static ssize_t kone_sysfs_show_weight(struct device *dev,
|
|||
|
||||
if (retval)
|
||||
return retval;
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", weight);
|
||||
return sysfs_emit(buf, "%d\n", weight);
|
||||
}
|
||||
static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
|
||||
|
||||
|
@ -441,7 +441,7 @@ static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
|
|||
{
|
||||
struct kone_device *kone =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
|
||||
return sysfs_emit(buf, "%d\n", kone->firmware_version);
|
||||
}
|
||||
static DEVICE_ATTR(firmware_version, 0440, kone_sysfs_show_firmware_version,
|
||||
NULL);
|
||||
|
@ -451,7 +451,7 @@ static ssize_t kone_sysfs_show_tcu(struct device *dev,
|
|||
{
|
||||
struct kone_device *kone =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
|
||||
return sysfs_emit(buf, "%d\n", kone->settings.tcu);
|
||||
}
|
||||
|
||||
static int kone_tcu_command(struct usb_device *usb_dev, int number)
|
||||
|
@ -553,7 +553,7 @@ static ssize_t kone_sysfs_show_startup_profile(struct device *dev,
|
|||
{
|
||||
struct kone_device *kone =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile);
|
||||
return sysfs_emit(buf, "%d\n", kone->settings.startup_profile);
|
||||
}
|
||||
|
||||
static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
|
||||
|
|
|
@ -242,7 +242,7 @@ static ssize_t koneplus_sysfs_show_actual_profile(struct device *dev,
|
|||
{
|
||||
struct koneplus_device *koneplus =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", koneplus->actual_profile);
|
||||
return sysfs_emit(buf, "%d\n", koneplus->actual_profile);
|
||||
}
|
||||
|
||||
static ssize_t koneplus_sysfs_set_actual_profile(struct device *dev,
|
||||
|
@ -309,7 +309,7 @@ static ssize_t koneplus_sysfs_show_firmware_version(struct device *dev,
|
|||
&info, KONEPLUS_SIZE_INFO);
|
||||
mutex_unlock(&koneplus->koneplus_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
|
||||
return sysfs_emit(buf, "%d\n", info.firmware_version);
|
||||
}
|
||||
static DEVICE_ATTR(firmware_version, 0440,
|
||||
koneplus_sysfs_show_firmware_version, NULL);
|
||||
|
|
|
@ -272,7 +272,7 @@ static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,
|
|||
{
|
||||
struct kovaplus_device *kovaplus =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);
|
||||
return sysfs_emit(buf, "%d\n", kovaplus->actual_profile);
|
||||
}
|
||||
|
||||
static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
|
||||
|
@ -325,7 +325,7 @@ static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,
|
|||
{
|
||||
struct kovaplus_device *kovaplus =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);
|
||||
return sysfs_emit(buf, "%d\n", kovaplus->actual_cpi);
|
||||
}
|
||||
static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);
|
||||
|
||||
|
@ -334,7 +334,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,
|
|||
{
|
||||
struct kovaplus_device *kovaplus =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);
|
||||
return sysfs_emit(buf, "%d\n", kovaplus->actual_x_sensitivity);
|
||||
}
|
||||
static DEVICE_ATTR(actual_sensitivity_x, 0440,
|
||||
kovaplus_sysfs_show_actual_sensitivity_x, NULL);
|
||||
|
@ -344,7 +344,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,
|
|||
{
|
||||
struct kovaplus_device *kovaplus =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);
|
||||
return sysfs_emit(buf, "%d\n", kovaplus->actual_y_sensitivity);
|
||||
}
|
||||
static DEVICE_ATTR(actual_sensitivity_y, 0440,
|
||||
kovaplus_sysfs_show_actual_sensitivity_y, NULL);
|
||||
|
@ -365,7 +365,7 @@ static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,
|
|||
&info, KOVAPLUS_SIZE_INFO);
|
||||
mutex_unlock(&kovaplus->kovaplus_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
|
||||
return sysfs_emit(buf, "%d\n", info.firmware_version);
|
||||
}
|
||||
static DEVICE_ATTR(firmware_version, 0440,
|
||||
kovaplus_sysfs_show_firmware_version, NULL);
|
||||
|
|
|
@ -283,7 +283,7 @@ static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev,
|
|||
{
|
||||
struct pyra_device *pyra =
|
||||
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi);
|
||||
return sysfs_emit(buf, "%d\n", pyra->actual_cpi);
|
||||
}
|
||||
static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL);
|
||||
|
||||
|
@ -300,7 +300,7 @@ static ssize_t pyra_sysfs_show_actual_profile(struct device *dev,
|
|||
&settings, PYRA_SIZE_SETTINGS);
|
||||
mutex_unlock(&pyra->pyra_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", settings.startup_profile);
|
||||
return sysfs_emit(buf, "%d\n", settings.startup_profile);
|
||||
}
|
||||
static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
|
||||
static DEVICE_ATTR(startup_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
|
||||
|
@ -321,7 +321,7 @@ static ssize_t pyra_sysfs_show_firmware_version(struct device *dev,
|
|||
&info, PYRA_SIZE_INFO);
|
||||
mutex_unlock(&pyra->pyra_lock);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
|
||||
return sysfs_emit(buf, "%d\n", info.firmware_version);
|
||||
}
|
||||
static DEVICE_ATTR(firmware_version, 0440, pyra_sysfs_show_firmware_version,
|
||||
NULL);
|
||||
|
|
|
@ -155,7 +155,7 @@ static ssize_t enable_sensor_show(struct device *dev,
|
|||
{
|
||||
struct hid_sensor_custom *sensor_inst = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", sensor_inst->enable);
|
||||
return sysfs_emit(buf, "%d\n", sensor_inst->enable);
|
||||
}
|
||||
|
||||
static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
|
||||
|
@ -372,14 +372,13 @@ static ssize_t show_value(struct device *dev, struct device_attribute *attr,
|
|||
sizeof(struct hid_custom_usage_desc),
|
||||
usage_id_cmp);
|
||||
if (usage_desc)
|
||||
return snprintf(buf, PAGE_SIZE, "%s\n",
|
||||
usage_desc->desc);
|
||||
return sysfs_emit(buf, "%s\n", usage_desc->desc);
|
||||
else
|
||||
return sprintf(buf, "not-specified\n");
|
||||
return sysfs_emit(buf, "not-specified\n");
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
return sprintf(buf, "%d\n", value);
|
||||
return sysfs_emit(buf, "%d\n", value);
|
||||
}
|
||||
|
||||
static ssize_t store_value(struct device *dev, struct device_attribute *attr,
|
||||
|
@ -1032,14 +1031,14 @@ err_remove_callback:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int hid_sensor_custom_remove(struct platform_device *pdev)
|
||||
static void hid_sensor_custom_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
|
||||
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
|
||||
|
||||
if (sensor_inst->custom_pdev) {
|
||||
platform_device_unregister(sensor_inst->custom_pdev);
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
hid_sensor_custom_dev_if_remove(sensor_inst);
|
||||
|
@ -1047,8 +1046,6 @@ static int hid_sensor_custom_remove(struct platform_device *pdev)
|
|||
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
|
||||
&enable_sensor_attr_group);
|
||||
sensor_hub_remove_callback(hsdev, hsdev->usage);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id hid_sensor_custom_ids[] = {
|
||||
|
@ -1068,7 +1065,7 @@ static struct platform_driver hid_sensor_custom_platform_driver = {
|
|||
.name = KBUILD_MODNAME,
|
||||
},
|
||||
.probe = hid_sensor_custom_probe,
|
||||
.remove = hid_sensor_custom_remove,
|
||||
.remove_new = hid_sensor_custom_remove,
|
||||
};
|
||||
module_platform_driver(hid_sensor_custom_platform_driver);
|
||||
|
||||
|
|
|
@ -1844,8 +1844,7 @@ static int sony_set_device_id(struct sony_sc *sc)
|
|||
* All others are set to -1.
|
||||
*/
|
||||
if (sc->quirks & SIXAXIS_CONTROLLER) {
|
||||
ret = ida_simple_get(&sony_device_id_allocator, 0, 0,
|
||||
GFP_KERNEL);
|
||||
ret = ida_alloc(&sony_device_id_allocator, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
sc->device_id = -1;
|
||||
return ret;
|
||||
|
@ -1861,7 +1860,7 @@ static int sony_set_device_id(struct sony_sc *sc)
|
|||
static void sony_release_device_id(struct sony_sc *sc)
|
||||
{
|
||||
if (sc->device_id >= 0) {
|
||||
ida_simple_remove(&sony_device_id_allocator, sc->device_id);
|
||||
ida_free(&sony_device_id_allocator, sc->device_id);
|
||||
sc->device_id = -1;
|
||||
}
|
||||
}
|
||||
|
@ -2016,8 +2015,6 @@ static int sony_input_configured(struct hid_device *hdev,
|
|||
|
||||
} else if (sc->quirks & MOTION_CONTROLLER) {
|
||||
sony_init_output_report(sc, motion_send_output_report);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
if (sc->quirks & SONY_LED_SUPPORT) {
|
||||
|
|
|
@ -66,6 +66,14 @@ static LIST_HEAD(steam_devices);
|
|||
#define STEAM_DECK_TRIGGER_RESOLUTION 5461
|
||||
/* Joystick runs are about 5 mm and 32768 units */
|
||||
#define STEAM_DECK_JOYSTICK_RESOLUTION 6553
|
||||
/* Accelerometer has 16 bit resolution and a range of +/- 2g */
|
||||
#define STEAM_DECK_ACCEL_RES_PER_G 16384
|
||||
#define STEAM_DECK_ACCEL_RANGE 32768
|
||||
#define STEAM_DECK_ACCEL_FUZZ 32
|
||||
/* Gyroscope has 16 bit resolution and a range of +/- 2000 dps */
|
||||
#define STEAM_DECK_GYRO_RES_PER_DPS 16
|
||||
#define STEAM_DECK_GYRO_RANGE 32768
|
||||
#define STEAM_DECK_GYRO_FUZZ 1
|
||||
|
||||
#define STEAM_PAD_FUZZ 256
|
||||
|
||||
|
@ -288,6 +296,7 @@ struct steam_device {
|
|||
struct mutex report_mutex;
|
||||
unsigned long client_opened;
|
||||
struct input_dev __rcu *input;
|
||||
struct input_dev __rcu *sensors;
|
||||
unsigned long quirks;
|
||||
struct work_struct work_connect;
|
||||
bool connected;
|
||||
|
@ -302,6 +311,7 @@ struct steam_device {
|
|||
struct work_struct rumble_work;
|
||||
u16 rumble_left;
|
||||
u16 rumble_right;
|
||||
unsigned int sensor_timestamp_us;
|
||||
};
|
||||
|
||||
static int steam_recv_report(struct steam_device *steam,
|
||||
|
@ -825,6 +835,74 @@ input_register_fail:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int steam_sensors_register(struct steam_device *steam)
|
||||
{
|
||||
struct hid_device *hdev = steam->hdev;
|
||||
struct input_dev *sensors;
|
||||
int ret;
|
||||
|
||||
if (!(steam->quirks & STEAM_QUIRK_DECK))
|
||||
return 0;
|
||||
|
||||
rcu_read_lock();
|
||||
sensors = rcu_dereference(steam->sensors);
|
||||
rcu_read_unlock();
|
||||
if (sensors) {
|
||||
dbg_hid("%s: already connected\n", __func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sensors = input_allocate_device();
|
||||
if (!sensors)
|
||||
return -ENOMEM;
|
||||
|
||||
input_set_drvdata(sensors, steam);
|
||||
sensors->dev.parent = &hdev->dev;
|
||||
|
||||
sensors->name = "Steam Deck Motion Sensors";
|
||||
sensors->phys = hdev->phys;
|
||||
sensors->uniq = steam->serial_no;
|
||||
sensors->id.bustype = hdev->bus;
|
||||
sensors->id.vendor = hdev->vendor;
|
||||
sensors->id.product = hdev->product;
|
||||
sensors->id.version = hdev->version;
|
||||
|
||||
__set_bit(INPUT_PROP_ACCELEROMETER, sensors->propbit);
|
||||
__set_bit(EV_MSC, sensors->evbit);
|
||||
__set_bit(MSC_TIMESTAMP, sensors->mscbit);
|
||||
|
||||
input_set_abs_params(sensors, ABS_X, -STEAM_DECK_ACCEL_RANGE,
|
||||
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
|
||||
input_set_abs_params(sensors, ABS_Y, -STEAM_DECK_ACCEL_RANGE,
|
||||
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
|
||||
input_set_abs_params(sensors, ABS_Z, -STEAM_DECK_ACCEL_RANGE,
|
||||
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
|
||||
input_abs_set_res(sensors, ABS_X, STEAM_DECK_ACCEL_RES_PER_G);
|
||||
input_abs_set_res(sensors, ABS_Y, STEAM_DECK_ACCEL_RES_PER_G);
|
||||
input_abs_set_res(sensors, ABS_Z, STEAM_DECK_ACCEL_RES_PER_G);
|
||||
|
||||
input_set_abs_params(sensors, ABS_RX, -STEAM_DECK_GYRO_RANGE,
|
||||
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
|
||||
input_set_abs_params(sensors, ABS_RY, -STEAM_DECK_GYRO_RANGE,
|
||||
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
|
||||
input_set_abs_params(sensors, ABS_RZ, -STEAM_DECK_GYRO_RANGE,
|
||||
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
|
||||
input_abs_set_res(sensors, ABS_RX, STEAM_DECK_GYRO_RES_PER_DPS);
|
||||
input_abs_set_res(sensors, ABS_RY, STEAM_DECK_GYRO_RES_PER_DPS);
|
||||
input_abs_set_res(sensors, ABS_RZ, STEAM_DECK_GYRO_RES_PER_DPS);
|
||||
|
||||
ret = input_register_device(sensors);
|
||||
if (ret)
|
||||
goto sensors_register_fail;
|
||||
|
||||
rcu_assign_pointer(steam->sensors, sensors);
|
||||
return 0;
|
||||
|
||||
sensors_register_fail:
|
||||
input_free_device(sensors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steam_input_unregister(struct steam_device *steam)
|
||||
{
|
||||
struct input_dev *input;
|
||||
|
@ -838,6 +916,24 @@ static void steam_input_unregister(struct steam_device *steam)
|
|||
input_unregister_device(input);
|
||||
}
|
||||
|
||||
static void steam_sensors_unregister(struct steam_device *steam)
|
||||
{
|
||||
struct input_dev *sensors;
|
||||
|
||||
if (!(steam->quirks & STEAM_QUIRK_DECK))
|
||||
return;
|
||||
|
||||
rcu_read_lock();
|
||||
sensors = rcu_dereference(steam->sensors);
|
||||
rcu_read_unlock();
|
||||
|
||||
if (!sensors)
|
||||
return;
|
||||
RCU_INIT_POINTER(steam->sensors, NULL);
|
||||
synchronize_rcu();
|
||||
input_unregister_device(sensors);
|
||||
}
|
||||
|
||||
static void steam_battery_unregister(struct steam_device *steam)
|
||||
{
|
||||
struct power_supply *battery;
|
||||
|
@ -890,18 +986,28 @@ static int steam_register(struct steam_device *steam)
|
|||
spin_lock_irqsave(&steam->lock, flags);
|
||||
client_opened = steam->client_opened;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
|
||||
if (!client_opened) {
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
ret = steam_input_register(steam);
|
||||
} else
|
||||
ret = 0;
|
||||
if (ret != 0)
|
||||
goto steam_register_input_fail;
|
||||
ret = steam_sensors_register(steam);
|
||||
if (ret != 0)
|
||||
goto steam_register_sensors_fail;
|
||||
}
|
||||
return 0;
|
||||
|
||||
steam_register_sensors_fail:
|
||||
steam_input_unregister(steam);
|
||||
steam_register_input_fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void steam_unregister(struct steam_device *steam)
|
||||
{
|
||||
steam_battery_unregister(steam);
|
||||
steam_sensors_unregister(steam);
|
||||
steam_input_unregister(steam);
|
||||
if (steam->serial_no[0]) {
|
||||
hid_info(steam->hdev, "Steam Controller '%s' disconnected",
|
||||
|
@ -1010,6 +1116,7 @@ static int steam_client_ll_open(struct hid_device *hdev)
|
|||
steam->client_opened++;
|
||||
spin_unlock_irqrestore(&steam->lock, flags);
|
||||
|
||||
steam_sensors_unregister(steam);
|
||||
steam_input_unregister(steam);
|
||||
|
||||
return 0;
|
||||
|
@ -1030,6 +1137,7 @@ static void steam_client_ll_close(struct hid_device *hdev)
|
|||
if (connected) {
|
||||
steam_set_lizard_mode(steam, lizard_mode);
|
||||
steam_input_register(steam);
|
||||
steam_sensors_register(steam);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1121,6 +1229,7 @@ static int steam_probe(struct hid_device *hdev,
|
|||
INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
|
||||
INIT_LIST_HEAD(&steam->list);
|
||||
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
|
||||
steam->sensor_timestamp_us = 0;
|
||||
|
||||
/*
|
||||
* With the real steam controller interface, do not connect hidraw.
|
||||
|
@ -1380,12 +1489,12 @@ static void steam_do_input_event(struct steam_device *steam,
|
|||
* 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
|
||||
* 24-25 | s16 | IMU ABS_X | accelerometer X value
|
||||
* 26-27 | s16 | IMU ABS_Z | accelerometer Y value
|
||||
* 28-29 | s16 | IMU ABS_Y | accelerometer Z value
|
||||
* 30-31 | s16 | IMU ABS_RX | gyro X value
|
||||
* 32-33 | s16 | IMU ABS_RZ | gyro Y value
|
||||
* 34-35 | s16 | IMU ABS_RY | gyro Z value
|
||||
* 36-37 | s16 | -- | quaternion W value
|
||||
* 38-39 | s16 | -- | quaternion X value
|
||||
* 40-41 | s16 | -- | quaternion Y value
|
||||
|
@ -1546,6 +1655,32 @@ static void steam_do_deck_input_event(struct steam_device *steam,
|
|||
input_sync(input);
|
||||
}
|
||||
|
||||
static void steam_do_deck_sensors_event(struct steam_device *steam,
|
||||
struct input_dev *sensors, u8 *data)
|
||||
{
|
||||
/*
|
||||
* The deck input report is received every 4 ms on average,
|
||||
* with a jitter of +/- 4 ms even though the USB descriptor claims
|
||||
* that it uses 1 kHz.
|
||||
* Since the HID report does not include a sensor timestamp,
|
||||
* use a fixed increment here.
|
||||
*/
|
||||
steam->sensor_timestamp_us += 4000;
|
||||
|
||||
if (!steam->gamepad_mode)
|
||||
return;
|
||||
|
||||
input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us);
|
||||
input_report_abs(sensors, ABS_X, steam_le16(data + 24));
|
||||
input_report_abs(sensors, ABS_Z, -steam_le16(data + 26));
|
||||
input_report_abs(sensors, ABS_Y, steam_le16(data + 28));
|
||||
input_report_abs(sensors, ABS_RX, steam_le16(data + 30));
|
||||
input_report_abs(sensors, ABS_RZ, -steam_le16(data + 32));
|
||||
input_report_abs(sensors, ABS_RY, steam_le16(data + 34));
|
||||
|
||||
input_sync(sensors);
|
||||
}
|
||||
|
||||
/*
|
||||
* The size for this message payload is 11.
|
||||
* The known values are:
|
||||
|
@ -1583,6 +1718,7 @@ static int steam_raw_event(struct hid_device *hdev,
|
|||
{
|
||||
struct steam_device *steam = hid_get_drvdata(hdev);
|
||||
struct input_dev *input;
|
||||
struct input_dev *sensors;
|
||||
struct power_supply *battery;
|
||||
|
||||
if (!steam)
|
||||
|
@ -1628,6 +1764,9 @@ static int steam_raw_event(struct hid_device *hdev,
|
|||
input = rcu_dereference(steam->input);
|
||||
if (likely(input))
|
||||
steam_do_deck_input_event(steam, input, data);
|
||||
sensors = rcu_dereference(steam->sensors);
|
||||
if (likely(sensors))
|
||||
steam_do_deck_sensors_event(steam, sensors, data);
|
||||
rcu_read_unlock();
|
||||
break;
|
||||
case ID_CONTROLLER_WIRELESS:
|
||||
|
|
|
@ -884,6 +884,9 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
/* The firmware is used in userspace as unique identifier */
|
||||
strscpy(hdev->uniq, ver_ptr, sizeof(hdev->uniq));
|
||||
|
||||
/* If this is a transition firmware */
|
||||
if (strcmp(ver_ptr, transition_ver) == 0) {
|
||||
hid_dbg(hdev,
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* HID driver for WinWing Orion 2 throttle
|
||||
*
|
||||
* Copyright (c) 2023 Ivan Gorinov
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/hidraw.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
#define MAX_REPORT 16
|
||||
|
||||
struct winwing_led {
|
||||
struct led_classdev cdev;
|
||||
struct hid_device *hdev;
|
||||
int number;
|
||||
};
|
||||
|
||||
struct winwing_led_info {
|
||||
int number;
|
||||
int max_brightness;
|
||||
const char *led_name;
|
||||
};
|
||||
|
||||
static struct winwing_led_info led_info[3] = {
|
||||
{ 0, 255, "backlight" },
|
||||
{ 1, 1, "a-a" },
|
||||
{ 2, 1, "a-g" },
|
||||
};
|
||||
|
||||
struct winwing_drv_data {
|
||||
struct hid_device *hdev;
|
||||
__u8 *report_buf;
|
||||
struct mutex lock;
|
||||
unsigned int num_leds;
|
||||
struct winwing_led leds[];
|
||||
};
|
||||
|
||||
static int winwing_led_write(struct led_classdev *cdev,
|
||||
enum led_brightness br)
|
||||
{
|
||||
struct winwing_led *led = (struct winwing_led *) cdev;
|
||||
struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
|
||||
__u8 *buf = data->report_buf;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
buf[0] = 0x02;
|
||||
buf[1] = 0x60;
|
||||
buf[2] = 0xbe;
|
||||
buf[3] = 0x00;
|
||||
buf[4] = 0x00;
|
||||
buf[5] = 0x03;
|
||||
buf[6] = 0x49;
|
||||
buf[7] = led->number;
|
||||
buf[8] = br;
|
||||
buf[9] = 0x00;
|
||||
buf[10] = 0;
|
||||
buf[11] = 0;
|
||||
buf[12] = 0;
|
||||
buf[13] = 0;
|
||||
|
||||
ret = hid_hw_output_report(led->hdev, buf, 14);
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int winwing_init_led(struct hid_device *hdev,
|
||||
struct input_dev *input)
|
||||
{
|
||||
struct winwing_drv_data *data;
|
||||
struct winwing_led *led;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
size_t data_size = struct_size(data, leds, 3);
|
||||
|
||||
data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL);
|
||||
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
|
||||
|
||||
if (!data->report_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < 3; i += 1) {
|
||||
struct winwing_led_info *info = &led_info[i];
|
||||
|
||||
led = &data->leds[i];
|
||||
led->hdev = hdev;
|
||||
led->number = info->number;
|
||||
led->cdev.max_brightness = info->max_brightness;
|
||||
led->cdev.brightness_set_blocking = winwing_led_write;
|
||||
led->cdev.flags = LED_HW_PLUGGABLE;
|
||||
led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
|
||||
"%s::%s",
|
||||
dev_name(&input->dev),
|
||||
info->led_name);
|
||||
|
||||
ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
hid_set_drvdata(hdev, data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int winwing_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_parse(hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "parse failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
if (ret) {
|
||||
hid_err(hdev, "hw start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int winwing_input_configured(struct hid_device *hdev,
|
||||
struct hid_input *hidinput)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = winwing_init_led(hdev, hidinput->input);
|
||||
|
||||
if (ret)
|
||||
hid_err(hdev, "led init failed\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static __u8 original_rdesc_buttons[] = {
|
||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x6F,
|
||||
0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
|
||||
0x45, 0x01, 0x75, 0x01, 0x95, 0x6F,
|
||||
0x81, 0x02, 0x75, 0x01, 0x95, 0x01,
|
||||
0x81, 0x01
|
||||
};
|
||||
|
||||
/*
|
||||
* HID report descriptor shows 111 buttons, which exceeds maximum
|
||||
* number of buttons (80) supported by Linux kernel HID subsystem.
|
||||
*
|
||||
* This module skips numbers 32-63, unused on some throttle grips.
|
||||
*/
|
||||
|
||||
static __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
int sig_length = sizeof(original_rdesc_buttons);
|
||||
int unused_button_numbers = 32;
|
||||
|
||||
if (*rsize < 34)
|
||||
return rdesc;
|
||||
|
||||
if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) {
|
||||
|
||||
/* Usage Maximum */
|
||||
rdesc[13] -= unused_button_numbers;
|
||||
|
||||
/* Report Count for buttons */
|
||||
rdesc[25] -= unused_button_numbers;
|
||||
|
||||
/* Report Count for padding [HID1_11, 6.2.2.9] */
|
||||
rdesc[31] += unused_button_numbers;
|
||||
|
||||
hid_info(hdev, "winwing descriptor fixed\n");
|
||||
}
|
||||
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static int winwing_raw_event(struct hid_device *hdev,
|
||||
struct hid_report *report, u8 *raw_data, int size)
|
||||
{
|
||||
if (size >= 15) {
|
||||
/* Skip buttons 32 .. 63 */
|
||||
memmove(raw_data + 5, raw_data + 9, 6);
|
||||
|
||||
/* Clear the padding */
|
||||
memset(raw_data + 11, 0, 4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id winwing_devices[] = {
|
||||
{ HID_USB_DEVICE(0x4098, 0xbe62) }, /* TGRIP-18 */
|
||||
{ HID_USB_DEVICE(0x4098, 0xbe68) }, /* TGRIP-16EX */
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(hid, winwing_devices);
|
||||
|
||||
static struct hid_driver winwing_driver = {
|
||||
.name = "winwing",
|
||||
.id_table = winwing_devices,
|
||||
.probe = winwing_probe,
|
||||
.input_configured = winwing_input_configured,
|
||||
.report_fixup = winwing_report_fixup,
|
||||
.raw_event = winwing_raw_event,
|
||||
};
|
||||
module_hid_driver(winwing_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
|
@ -163,6 +163,24 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
|
|||
return quirks;
|
||||
}
|
||||
|
||||
static int i2c_hid_probe_address(struct i2c_hid *ihid)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Some STM-based devices need 400µs after a rising clock edge to wake
|
||||
* from deep sleep, in which case the first read will fail. Try after a
|
||||
* short sleep to see if the device came alive on the bus. Certain
|
||||
* Weida Tech devices also need this.
|
||||
*/
|
||||
ret = i2c_smbus_read_byte(ihid->client);
|
||||
if (ret < 0) {
|
||||
usleep_range(400, 500);
|
||||
ret = i2c_smbus_read_byte(ihid->client);
|
||||
}
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
static int i2c_hid_xfer(struct i2c_hid *ihid,
|
||||
u8 *send_buf, int send_len, u8 *recv_buf, int recv_len)
|
||||
{
|
||||
|
@ -384,26 +402,11 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
|
|||
|
||||
i2c_hid_dbg(ihid, "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* Some devices require to send a command to wakeup before power on.
|
||||
* The call will get a return value (EREMOTEIO) but device will be
|
||||
* triggered and activated. After that, it goes like a normal device.
|
||||
*/
|
||||
if (power_state == I2C_HID_PWR_ON) {
|
||||
ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON);
|
||||
|
||||
/* Device was already activated */
|
||||
if (!ret)
|
||||
goto set_pwr_exit;
|
||||
}
|
||||
|
||||
ret = i2c_hid_set_power_command(ihid, power_state);
|
||||
if (ret)
|
||||
dev_err(&ihid->client->dev,
|
||||
"failed to change power setting.\n");
|
||||
|
||||
set_pwr_exit:
|
||||
|
||||
/*
|
||||
* The HID over I2C specification states that if a DEVICE needs time
|
||||
* after the PWR_ON request, it should utilise CLOCK stretching.
|
||||
|
@ -959,6 +962,14 @@ static int i2c_hid_core_resume(struct i2c_hid *ihid)
|
|||
|
||||
enable_irq(client->irq);
|
||||
|
||||
/* Make sure the device is awake on the bus */
|
||||
ret = i2c_hid_probe_address(ihid);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "nothing at address after resume: %d\n",
|
||||
ret);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* Instead of resetting device, simply powers the device on. This
|
||||
* solves "incomplete reports" on Raydium devices 2386:3118 and
|
||||
* 2386:4B33 and fixes various SIS touchscreens no longer sending
|
||||
|
@ -992,8 +1003,7 @@ static int __i2c_hid_core_probe(struct i2c_hid *ihid)
|
|||
struct hid_device *hid = ihid->hid;
|
||||
int ret;
|
||||
|
||||
/* Make sure there is something at this address */
|
||||
ret = i2c_smbus_read_byte(client);
|
||||
ret = i2c_hid_probe_address(ihid);
|
||||
if (ret < 0) {
|
||||
i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
|
||||
return -ENXIO;
|
||||
|
|
|
@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o
|
|||
intel-ishtp-objs += ishtp/bus.o
|
||||
intel-ishtp-objs += ishtp/dma-if.o
|
||||
intel-ishtp-objs += ishtp/client-buffers.o
|
||||
intel-ishtp-objs += ishtp/loader.o
|
||||
|
||||
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
|
||||
intel-ish-ipc-objs := ipc/ipc.o
|
||||
|
|
|
@ -13,28 +13,29 @@
|
|||
#include "hw-ish-regs.h"
|
||||
#include "ishtp-dev.h"
|
||||
|
||||
#define CHV_DEVICE_ID 0x22D8
|
||||
#define BXT_Ax_DEVICE_ID 0x0AA2
|
||||
#define BXT_Bx_DEVICE_ID 0x1AA2
|
||||
#define APL_Ax_DEVICE_ID 0x5AA2
|
||||
#define SPT_Ax_DEVICE_ID 0x9D35
|
||||
#define CNL_Ax_DEVICE_ID 0x9DFC
|
||||
#define GLK_Ax_DEVICE_ID 0x31A2
|
||||
#define CNL_H_DEVICE_ID 0xA37C
|
||||
#define ICL_MOBILE_DEVICE_ID 0x34FC
|
||||
#define SPT_H_DEVICE_ID 0xA135
|
||||
#define CML_LP_DEVICE_ID 0x02FC
|
||||
#define CMP_H_DEVICE_ID 0x06FC
|
||||
#define EHL_Ax_DEVICE_ID 0x4BB3
|
||||
#define TGL_LP_DEVICE_ID 0xA0FC
|
||||
#define TGL_H_DEVICE_ID 0x43FC
|
||||
#define ADL_S_DEVICE_ID 0x7AF8
|
||||
#define ADL_P_DEVICE_ID 0x51FC
|
||||
#define ADL_N_DEVICE_ID 0x54FC
|
||||
#define RPL_S_DEVICE_ID 0x7A78
|
||||
#define MTL_P_DEVICE_ID 0x7E45
|
||||
#define ARL_H_DEVICE_ID 0x7745
|
||||
#define ARL_S_DEVICE_ID 0x7F78
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_CHV 0x22D8
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Ax 0x0AA2
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Bx 0x1AA2
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_APL_Ax 0x5AA2
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_SPT_Ax 0x9D35
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_CNL_Ax 0x9DFC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_GLK_Ax 0x31A2
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_CNL_H 0xA37C
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE 0x34FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_SPT_H 0xA135
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_CML_LP 0x02FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_CMP_H 0x06FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_EHL_Ax 0x4BB3
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_TGL_LP 0xA0FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_TGL_H 0x43FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ADL_S 0x7AF8
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ADL_P 0x51FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ADL_N 0x54FC
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_RPL_S 0x7A78
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_MTL_P 0x7E45
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ARL_H 0x7745
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_ARL_S 0x7F78
|
||||
#define PCI_DEVICE_ID_INTEL_ISH_LNL_M 0xA845
|
||||
|
||||
#define REVISION_ID_CHT_A0 0x6
|
||||
#define REVISION_ID_CHT_Ax_SI 0x0
|
||||
|
|
|
@ -78,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev)
|
|||
bool interrupt_generated = true;
|
||||
uint32_t pisr_val = 0;
|
||||
|
||||
if (dev->pdev->device == CHV_DEVICE_ID) {
|
||||
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
|
||||
pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
|
||||
interrupt_generated =
|
||||
IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
|
||||
|
@ -117,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev)
|
|||
*/
|
||||
static void set_host_ready(struct ishtp_device *dev)
|
||||
{
|
||||
if (dev->pdev->device == CHV_DEVICE_ID) {
|
||||
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
|
||||
if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
|
||||
(dev->pdev->revision & REVISION_ID_SI_MASK) ==
|
||||
REVISION_ID_CHT_Ax_SI)
|
||||
|
@ -546,11 +546,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
|
|||
|
||||
/**
|
||||
* fw_reset_work_fn() - FW reset worker function
|
||||
* @unused: not used
|
||||
* @work: Work item
|
||||
*
|
||||
* Call ish_fw_reset_handler to complete FW reset
|
||||
*/
|
||||
static void fw_reset_work_fn(struct work_struct *unused)
|
||||
static void fw_reset_work_fn(struct work_struct *work)
|
||||
{
|
||||
int rv;
|
||||
|
||||
|
@ -562,7 +562,8 @@ static void fw_reset_work_fn(struct work_struct *unused)
|
|||
wake_up_interruptible(&ishtp_dev->wait_hw_ready);
|
||||
|
||||
/* ISHTP notification in IPC_RESET sequence completion */
|
||||
ishtp_reset_compl_handler(ishtp_dev);
|
||||
if (!work_pending(work))
|
||||
ishtp_reset_compl_handler(ishtp_dev);
|
||||
} else
|
||||
dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
|
||||
rv);
|
||||
|
@ -909,11 +910,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
|
|||
*/
|
||||
static bool _dma_no_cache_snooping(struct ishtp_device *dev)
|
||||
{
|
||||
return (dev->pdev->device == EHL_Ax_DEVICE_ID ||
|
||||
dev->pdev->device == TGL_LP_DEVICE_ID ||
|
||||
dev->pdev->device == TGL_H_DEVICE_ID ||
|
||||
dev->pdev->device == ADL_S_DEVICE_ID ||
|
||||
dev->pdev->device == ADL_P_DEVICE_ID);
|
||||
return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax ||
|
||||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP ||
|
||||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H ||
|
||||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S ||
|
||||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P);
|
||||
}
|
||||
|
||||
static const struct ishtp_hw_ops ish_hw_ops = {
|
||||
|
|
|
@ -23,30 +23,44 @@
|
|||
#include "ishtp-dev.h"
|
||||
#include "hw-ish.h"
|
||||
|
||||
enum ishtp_driver_data_index {
|
||||
ISHTP_DRIVER_DATA_NONE,
|
||||
ISHTP_DRIVER_DATA_LNL_M,
|
||||
};
|
||||
|
||||
#define ISH_FW_FILENAME_LNL_M "intel/ish/ish_lnlm.bin"
|
||||
|
||||
static struct ishtp_driver_data ishtp_driver_data[] = {
|
||||
[ISHTP_DRIVER_DATA_LNL_M] = {
|
||||
.fw_filename = ISH_FW_FILENAME_LNL_M,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct pci_device_id ish_pci_tbl[] = {
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MTL_P_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_H_DEVICE_ID)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_S_DEVICE_ID)},
|
||||
{0, }
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CHV)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Bx)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_APL_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_GLK_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_H)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_H)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CML_LP)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CMP_H)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_LP)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_H)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_S)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_P)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_N)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_RPL_S)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_MTL_P)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)},
|
||||
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_LNL_M), .driver_data = ISHTP_DRIVER_DATA_LNL_M},
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
|
||||
|
||||
|
@ -105,19 +119,19 @@ static int ish_init(struct ishtp_device *dev)
|
|||
|
||||
static const struct pci_device_id ish_invalid_pci_ids[] = {
|
||||
/* Mehlow platform special pci ids */
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
|
||||
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
|
||||
{PCI_VDEVICE(INTEL, 0xA309)},
|
||||
{PCI_VDEVICE(INTEL, 0xA30A)},
|
||||
{}
|
||||
};
|
||||
|
||||
static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
|
||||
{
|
||||
return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID;
|
||||
return !pm_suspend_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
|
||||
}
|
||||
|
||||
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
|
||||
{
|
||||
return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
|
||||
return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,6 +180,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
}
|
||||
hw = to_ish_hw(ishtp);
|
||||
ishtp->print_log = ish_event_tracer;
|
||||
ishtp->driver_data = &ishtp_driver_data[ent->driver_data];
|
||||
|
||||
/* mapping IO device memory */
|
||||
hw->mem_addr = pcim_iomap_table(pdev)[0];
|
||||
|
@ -173,6 +188,11 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
|
||||
/* request and enable interrupt */
|
||||
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "ISH: Failed to allocate IRQ vectors\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!pdev->msi_enabled && !pdev->msix_enabled)
|
||||
irq_flag = IRQF_SHARED;
|
||||
|
||||
|
@ -189,7 +209,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
init_waitqueue_head(&ishtp->resume_wait);
|
||||
|
||||
/* Enable PME for EHL */
|
||||
if (pdev->device == EHL_Ax_DEVICE_ID)
|
||||
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
ret = ish_init(ishtp);
|
||||
|
@ -222,7 +242,7 @@ static void ish_remove(struct pci_dev *pdev)
|
|||
*/
|
||||
static void ish_shutdown(struct pci_dev *pdev)
|
||||
{
|
||||
if (pdev->device == EHL_Ax_DEVICE_ID)
|
||||
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
|
||||
pci_prepare_to_sleep(pdev);
|
||||
}
|
||||
|
||||
|
@ -376,3 +396,5 @@ MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
|
|||
|
||||
MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
MODULE_FIRMWARE(ISH_FW_FILENAME_LNL_M);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "ishtp-dev.h"
|
||||
#include "hbm.h"
|
||||
#include "client.h"
|
||||
#include "loader.h"
|
||||
|
||||
/**
|
||||
* ishtp_hbm_fw_cl_allocate() - Allocate FW clients
|
||||
|
@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
|
|||
return;
|
||||
}
|
||||
|
||||
/* Start firmware loading process if it has loader capability */
|
||||
if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
|
||||
schedule_work(&dev->work_fw_loader);
|
||||
|
||||
dev->version.major_version = HBM_MAJOR_VERSION;
|
||||
dev->version.minor_version = HBM_MINOR_VERSION;
|
||||
if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
|
||||
|
@ -864,6 +869,20 @@ eoi:
|
|||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* ishtp_loader_recv_msg() - Receive a message from the ISHTP device
|
||||
* @dev: The ISHTP device
|
||||
* @buf: The buffer containing the message
|
||||
*/
|
||||
static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf)
|
||||
{
|
||||
if (dev->fw_loader_rx_buf)
|
||||
memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size);
|
||||
|
||||
dev->fw_loader_received = true;
|
||||
wake_up_interruptible(&dev->wait_loader_recvd_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* recv_fixed_cl_msg() - Receive fixed client message
|
||||
* @dev: ISHTP device instance
|
||||
|
@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev,
|
|||
else
|
||||
dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
|
||||
msg_hdr->cmd);
|
||||
} else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) {
|
||||
ishtp_loader_recv_msg(dev, rd_msg_buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
* Copyright (c) 2003-2016, Intel Corporation.
|
||||
*/
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
#include "ishtp-dev.h"
|
||||
#include "hbm.h"
|
||||
#include "client.h"
|
||||
#include "loader.h"
|
||||
|
||||
/**
|
||||
* ishtp_dev_state_str() -Convert to string format
|
||||
|
@ -51,6 +53,8 @@ const char *ishtp_dev_state_str(int state)
|
|||
*/
|
||||
void ishtp_device_init(struct ishtp_device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev->dev_state = ISHTP_DEV_INITIALIZING;
|
||||
INIT_LIST_HEAD(&dev->cl_list);
|
||||
INIT_LIST_HEAD(&dev->device_list);
|
||||
|
@ -59,6 +63,7 @@ void ishtp_device_init(struct ishtp_device *dev)
|
|||
spin_lock_init(&dev->rd_msg_spinlock);
|
||||
|
||||
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
|
||||
init_waitqueue_head(&dev->wait_loader_recvd_msg);
|
||||
spin_lock_init(&dev->read_list_spinlock);
|
||||
spin_lock_init(&dev->device_lock);
|
||||
spin_lock_init(&dev->device_list_lock);
|
||||
|
@ -76,6 +81,9 @@ void ishtp_device_init(struct ishtp_device *dev)
|
|||
|
||||
INIT_LIST_HEAD(&dev->read_list.list);
|
||||
|
||||
ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
|
||||
if (ret)
|
||||
dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
|
||||
}
|
||||
EXPORT_SYMBOL(ishtp_device_init);
|
||||
|
||||
|
|
|
@ -122,12 +122,29 @@ struct ishtp_hw_ops {
|
|||
bool (*dma_no_cache_snooping)(struct ishtp_device *dev);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ishtp_driver_data - Driver-specific data for ISHTP devices
|
||||
*
|
||||
* This structure holds driver-specific data that can be associated with each
|
||||
* ISHTP device instance. It allows for the storage of data that is unique to
|
||||
* a particular driver or hardware variant.
|
||||
*
|
||||
* @fw_filename: The firmware filename associated with a specific hardware
|
||||
* variant of the Intel Integrated Sensor Hub (ISH). This allows
|
||||
* the driver to load the correct firmware based on the device's
|
||||
* hardware variant.
|
||||
*/
|
||||
struct ishtp_driver_data {
|
||||
char *fw_filename;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ishtp_device - ISHTP private device struct
|
||||
*/
|
||||
struct ishtp_device {
|
||||
struct device *devc; /* pointer to lowest device */
|
||||
struct pci_dev *pdev; /* PCI device to get device ids */
|
||||
struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */
|
||||
|
||||
/* waitq for waiting for suspend response */
|
||||
wait_queue_head_t suspend_wait;
|
||||
|
@ -147,6 +164,17 @@ struct ishtp_device {
|
|||
struct hbm_version version;
|
||||
int transfer_path; /* Choice of transfer path: IPC or DMA */
|
||||
|
||||
/* work structure for scheduling firmware loading tasks */
|
||||
struct work_struct work_fw_loader;
|
||||
/* waitq for waiting for command response from the firmware loader */
|
||||
wait_queue_head_t wait_loader_recvd_msg;
|
||||
/* indicating whether a message from the firmware loader has been received */
|
||||
bool fw_loader_received;
|
||||
/* pointer to a buffer for receiving messages from the firmware loader */
|
||||
void *fw_loader_rx_buf;
|
||||
/* size of the buffer pointed to by fw_loader_rx_buf */
|
||||
int fw_loader_rx_size;
|
||||
|
||||
/* ishtp device states */
|
||||
enum ishtp_dev_state dev_state;
|
||||
enum ishtp_hbm_state hbm_state;
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* ISHTP firmware loader function
|
||||
*
|
||||
* Copyright (c) 2024, Intel Corporation.
|
||||
*
|
||||
* This module implements the functionality to load the main ISH firmware from the host, starting
|
||||
* with the Lunar Lake generation. It leverages a new method that enhances space optimization and
|
||||
* flexibility by dividing the ISH firmware into a bootloader and main firmware.
|
||||
*
|
||||
* Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on
|
||||
* flows.
|
||||
*
|
||||
* Additionally, address potential error scenarios to ensure graceful failure handling.
|
||||
* - Firmware Image Not Found:
|
||||
* Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will
|
||||
* remain in a state awaiting firmware loading from the host, with no further action from
|
||||
* the ISHTP driver.
|
||||
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
|
||||
*
|
||||
* - DMA Buffer Allocation Failure:
|
||||
* This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware
|
||||
* will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and
|
||||
* firmware without further actions.
|
||||
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
|
||||
*
|
||||
* - Incorrect Firmware Image:
|
||||
* Using an incorrect firmware image will initiate the firmware loading process but will
|
||||
* eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by
|
||||
* returning an error code. The ISHTP driver will stop attempting after three tries.
|
||||
* Recovery: A platform reset is required to retry firmware loading from the host.
|
||||
*/
|
||||
|
||||
#define dev_fmt(fmt) "ISH loader: " fmt
|
||||
|
||||
#include <linux/cacheflush.h>
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/dev_printk.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/gfp_types.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pfn.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "hbm.h"
|
||||
#include "loader.h"
|
||||
|
||||
/**
|
||||
* loader_write_message() - Write a message to the ISHTP device
|
||||
* @dev: The ISHTP device
|
||||
* @buf: The buffer containing the message
|
||||
* @len: The length of the message
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int loader_write_message(struct ishtp_device *dev, void *buf, int len)
|
||||
{
|
||||
struct ishtp_msg_hdr ishtp_hdr = {
|
||||
.fw_addr = ISHTP_LOADER_CLIENT_ADDR,
|
||||
.length = len,
|
||||
.msg_complete = 1,
|
||||
};
|
||||
|
||||
dev->fw_loader_received = false;
|
||||
|
||||
return ishtp_write_message(dev, &ishtp_hdr, buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* loader_xfer_cmd() - Transfer a command to the ISHTP device
|
||||
* @dev: The ISHTP device
|
||||
* @req: The request buffer
|
||||
* @req_len: The length of the request
|
||||
* @resp: The response buffer
|
||||
* @resp_len: The length of the response
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len,
|
||||
void *resp, int resp_len)
|
||||
{
|
||||
struct loader_msg_header *req_hdr = req;
|
||||
struct loader_msg_header *resp_hdr = resp;
|
||||
struct device *devc = dev->devc;
|
||||
int rv;
|
||||
|
||||
dev->fw_loader_rx_buf = resp;
|
||||
dev->fw_loader_rx_size = resp_len;
|
||||
|
||||
rv = loader_write_message(dev, req, req_len);
|
||||
if (rv < 0) {
|
||||
dev_err(devc, "write cmd %u failed:%d\n", req_hdr->command, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Wait the ACK */
|
||||
wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received,
|
||||
ISHTP_LOADER_TIMEOUT);
|
||||
dev->fw_loader_rx_size = 0;
|
||||
dev->fw_loader_rx_buf = NULL;
|
||||
if (!dev->fw_loader_received) {
|
||||
dev_err(devc, "wait response of cmd %u timeout\n", req_hdr->command);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
if (!resp_hdr->is_response) {
|
||||
dev_err(devc, "not a response for %u\n", req_hdr->command);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (req_hdr->command != resp_hdr->command) {
|
||||
dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr->command,
|
||||
resp_hdr->command);
|
||||
return -EBADMSG;
|
||||
}
|
||||
|
||||
if (resp_hdr->status) {
|
||||
dev_err(devc, "cmd %u failed %u\n", req_hdr->command, resp_hdr->status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* release_dma_bufs() - Release the DMA buffer for transferring firmware fragments
|
||||
* @dev: The ISHTP device
|
||||
* @fragment: The ISHTP firmware fragment descriptor
|
||||
* @dma_bufs: The array of DMA fragment buffers
|
||||
* @fragment_size: The size of a single DMA fragment
|
||||
*/
|
||||
static void release_dma_bufs(struct ishtp_device *dev,
|
||||
struct loader_xfer_dma_fragment *fragment,
|
||||
void **dma_bufs, u32 fragment_size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
|
||||
if (dma_bufs[i]) {
|
||||
dma_free_coherent(dev->devc, fragment_size, dma_bufs[i],
|
||||
fragment->fragment_tbl[i].ddr_adrs);
|
||||
dma_bufs[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments
|
||||
* @dev: The ISHTP device
|
||||
* @ish_fw: The ISH firmware
|
||||
* @fragment: The ISHTP firmware fragment descriptor
|
||||
* @dma_bufs: The array of DMA fragment buffers
|
||||
* @fragment_size: The size of a single DMA fragment
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int prepare_dma_bufs(struct ishtp_device *dev,
|
||||
const struct firmware *ish_fw,
|
||||
struct loader_xfer_dma_fragment *fragment,
|
||||
void **dma_bufs, u32 fragment_size)
|
||||
{
|
||||
u32 offset = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < fragment->fragment_cnt && offset < ish_fw->size; i++) {
|
||||
dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size,
|
||||
&fragment->fragment_tbl[i].ddr_adrs, GFP_KERNEL);
|
||||
if (!dma_bufs[i])
|
||||
return -ENOMEM;
|
||||
|
||||
fragment->fragment_tbl[i].length = clamp(ish_fw->size - offset, 0, fragment_size);
|
||||
fragment->fragment_tbl[i].fw_off = offset;
|
||||
memcpy(dma_bufs[i], ish_fw->data + offset, fragment->fragment_tbl[i].length);
|
||||
clflush_cache_range(dma_bufs[i], fragment_size);
|
||||
|
||||
offset += fragment->fragment_tbl[i].length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ishtp_loader_work() - Load the ISHTP firmware
|
||||
* @work: The work structure
|
||||
*
|
||||
* The ISH Loader attempts to load firmware by sending a series of commands
|
||||
* to the ISH device. If a command fails to be acknowledged by the ISH device,
|
||||
* the loader will retry sending the command, up to a maximum of
|
||||
* ISHTP_LOADER_RETRY_TIMES.
|
||||
*
|
||||
* After the maximum number of retries has been reached without success, the
|
||||
* ISH bootloader will return an error status code and will no longer respond
|
||||
* to the driver's commands. This behavior indicates that the ISH Loader has
|
||||
* encountered a critical error during the firmware loading process.
|
||||
*
|
||||
* In such a case, where the ISH bootloader is unresponsive after all retries
|
||||
* have been exhausted, a platform reset is required to restore communication
|
||||
* with the ISH device and to recover from this error state.
|
||||
*/
|
||||
void ishtp_loader_work(struct work_struct *work)
|
||||
{
|
||||
DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM);
|
||||
struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader);
|
||||
struct loader_xfer_query query = {
|
||||
.header.command = LOADER_CMD_XFER_QUERY,
|
||||
};
|
||||
struct loader_start start = {
|
||||
.header.command = LOADER_CMD_START,
|
||||
};
|
||||
union loader_recv_message recv_msg;
|
||||
char *filename = dev->driver_data->fw_filename;
|
||||
const struct firmware *ish_fw;
|
||||
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
|
||||
u32 fragment_size;
|
||||
int retry = ISHTP_LOADER_RETRY_TIMES;
|
||||
int rv;
|
||||
|
||||
rv = request_firmware(&ish_fw, filename, dev->devc);
|
||||
if (rv < 0) {
|
||||
dev_err(dev->devc, "request firmware %s failed:%d\n", filename, rv);
|
||||
return;
|
||||
}
|
||||
|
||||
fragment->fragment.header.command = LOADER_CMD_XFER_FRAGMENT;
|
||||
fragment->fragment.xfer_mode = LOADER_XFER_MODE_DMA;
|
||||
fragment->fragment.is_last = 1;
|
||||
fragment->fragment.size = ish_fw->size;
|
||||
/* Calculate the size of a single DMA fragment */
|
||||
fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM));
|
||||
/* Calculate the count of DMA fragments */
|
||||
fragment->fragment_cnt = DIV_ROUND_UP(ish_fw->size, fragment_size);
|
||||
|
||||
rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size);
|
||||
if (rv) {
|
||||
dev_err(dev->devc, "prepare DMA buffer failed.\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
do {
|
||||
query.image_size = ish_fw->size;
|
||||
rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data,
|
||||
sizeof(struct loader_xfer_query_ack));
|
||||
if (rv)
|
||||
continue; /* try again if failed */
|
||||
|
||||
dev_dbg(dev->devc, "ISH Version %u.%u.%u.%u\n",
|
||||
recv_msg.query_ack.version_major,
|
||||
recv_msg.query_ack.version_minor,
|
||||
recv_msg.query_ack.version_hotfix,
|
||||
recv_msg.query_ack.version_build);
|
||||
|
||||
rv = loader_xfer_cmd(dev, fragment,
|
||||
struct_size(fragment, fragment_tbl, fragment->fragment_cnt),
|
||||
recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack));
|
||||
if (rv)
|
||||
continue; /* try again if failed */
|
||||
|
||||
rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data,
|
||||
sizeof(struct loader_start_ack));
|
||||
if (rv)
|
||||
continue; /* try again if failed */
|
||||
|
||||
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
|
||||
break;
|
||||
} while (--retry);
|
||||
|
||||
out:
|
||||
release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
|
||||
release_firmware(ish_fw);
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* ISHTP firmware loader header
|
||||
*
|
||||
* Copyright (c) 2024, Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef _ISHTP_LOADER_H_
|
||||
#define _ISHTP_LOADER_H_
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "ishtp-dev.h"
|
||||
|
||||
struct work_struct;
|
||||
|
||||
#define LOADER_MSG_SIZE \
|
||||
(IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr))
|
||||
|
||||
/*
|
||||
* ISHTP firmware loader protocol definition
|
||||
*/
|
||||
#define LOADER_CMD_XFER_QUERY 0 /* SW -> FW */
|
||||
#define LOADER_CMD_XFER_FRAGMENT 1 /* SW -> FW */
|
||||
#define LOADER_CMD_START 2 /* SW -> FW */
|
||||
|
||||
/* Only support DMA mode */
|
||||
#define LOADER_XFER_MODE_DMA BIT(0)
|
||||
|
||||
/**
|
||||
* struct loader_msg_header - ISHTP firmware loader message header
|
||||
* @command: Command type
|
||||
* @is_response: Indicates if the message is a response
|
||||
* @has_next: Indicates if there is a next message
|
||||
* @reserved: Reserved for future use
|
||||
* @status: Status of the message
|
||||
*/
|
||||
struct loader_msg_header {
|
||||
__le32 command:7;
|
||||
__le32 is_response:1;
|
||||
__le32 has_next:1;
|
||||
__le32 reserved:15;
|
||||
__le32 status:8;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_xfer_query - ISHTP firmware loader transfer query packet
|
||||
* @header: Header of the message
|
||||
* @image_size: Size of the image
|
||||
*/
|
||||
struct loader_xfer_query {
|
||||
struct loader_msg_header header;
|
||||
__le32 image_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_version - ISHTP firmware loader version
|
||||
* @value: Value of the version
|
||||
* @major: Major version
|
||||
* @minor: Minor version
|
||||
* @hotfix: Hotfix version
|
||||
* @build: Build version
|
||||
*/
|
||||
struct loader_version {
|
||||
union {
|
||||
__le32 value;
|
||||
struct {
|
||||
__u8 major;
|
||||
__u8 minor;
|
||||
__u8 hotfix;
|
||||
__u8 build;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_capability - ISHTP firmware loader capability
|
||||
* @max_fw_image_size: Maximum firmware image size
|
||||
* @support_mode: Support mode
|
||||
* @reserved: Reserved for future use
|
||||
* @platform: Platform
|
||||
* @max_dma_buf_size: Maximum DMA buffer size, multiples of 4096
|
||||
*/
|
||||
struct loader_capability {
|
||||
__le32 max_fw_image_size;
|
||||
__le16 support_mode;
|
||||
__u8 reserved;
|
||||
__u8 platform;
|
||||
__le32 max_dma_buf_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_xfer_query_ack - ISHTP firmware loader transfer query acknowledgment
|
||||
* @header: Header of the message
|
||||
* @version_major: ISH Major version
|
||||
* @version_minor: ISH Minor version
|
||||
* @version_hotfix: ISH Hotfix version
|
||||
* @version_build: ISH Build version
|
||||
* @protocol_version: Protocol version
|
||||
* @loader_version: Loader version
|
||||
* @capability: Loader capability
|
||||
*/
|
||||
struct loader_xfer_query_ack {
|
||||
struct loader_msg_header header;
|
||||
__le16 version_major;
|
||||
__le16 version_minor;
|
||||
__le16 version_hotfix;
|
||||
__le16 version_build;
|
||||
__le32 protocol_version;
|
||||
struct loader_version loader_version;
|
||||
struct loader_capability capability;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_xfer_fragment - ISHTP firmware loader transfer fragment
|
||||
* @header: Header of the message
|
||||
* @xfer_mode: Transfer mode
|
||||
* @offset: Offset
|
||||
* @size: Size
|
||||
* @is_last: Is last
|
||||
*/
|
||||
struct loader_xfer_fragment {
|
||||
struct loader_msg_header header;
|
||||
__le32 xfer_mode;
|
||||
__le32 offset;
|
||||
__le32 size;
|
||||
__le32 is_last;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_xfer_fragment_ack - ISHTP firmware loader transfer fragment acknowledgment
|
||||
* @header: Header of the message
|
||||
*/
|
||||
struct loader_xfer_fragment_ack {
|
||||
struct loader_msg_header header;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct fragment_dscrpt - ISHTP firmware loader fragment descriptor
|
||||
* @ddr_adrs: The address in host DDR
|
||||
* @fw_off: The offset of the fragment in the fw image
|
||||
* @length: The length of the fragment
|
||||
*/
|
||||
struct fragment_dscrpt {
|
||||
__le64 ddr_adrs;
|
||||
__le32 fw_off;
|
||||
__le32 length;
|
||||
};
|
||||
|
||||
#define FRAGMENT_MAX_NUM \
|
||||
((LOADER_MSG_SIZE - sizeof(struct loader_xfer_dma_fragment)) / \
|
||||
sizeof(struct fragment_dscrpt))
|
||||
|
||||
/**
|
||||
* struct loader_xfer_dma_fragment - ISHTP firmware loader transfer DMA fragment
|
||||
* @fragment: Fragment
|
||||
* @fragment_cnt: How many descriptors in the fragment_tbl
|
||||
* @fragment_tbl: Fragment table
|
||||
*/
|
||||
struct loader_xfer_dma_fragment {
|
||||
struct loader_xfer_fragment fragment;
|
||||
__le32 fragment_cnt;
|
||||
struct fragment_dscrpt fragment_tbl[] __counted_by(fragment_cnt);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_start - ISHTP firmware loader start
|
||||
* @header: Header of the message
|
||||
*/
|
||||
struct loader_start {
|
||||
struct loader_msg_header header;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct loader_start_ack - ISHTP firmware loader start acknowledgment
|
||||
* @header: Header of the message
|
||||
*/
|
||||
struct loader_start_ack {
|
||||
struct loader_msg_header header;
|
||||
};
|
||||
|
||||
union loader_recv_message {
|
||||
struct loader_xfer_query_ack query_ack;
|
||||
struct loader_xfer_fragment_ack fragment_ack;
|
||||
struct loader_start_ack start_ack;
|
||||
__u8 raw_data[LOADER_MSG_SIZE];
|
||||
};
|
||||
|
||||
/*
|
||||
* ISHTP firmware loader internal use
|
||||
*/
|
||||
/* ISHTP firmware loader command timeout */
|
||||
#define ISHTP_LOADER_TIMEOUT msecs_to_jiffies(100)
|
||||
|
||||
/* ISHTP firmware loader retry times */
|
||||
#define ISHTP_LOADER_RETRY_TIMES 3
|
||||
|
||||
/**
|
||||
* struct ish_firmware_variant - ISH firmware variant
|
||||
* @device: PCI Device ID
|
||||
* @filename: The firmware file name
|
||||
*/
|
||||
struct ish_firmware_variant {
|
||||
unsigned short device;
|
||||
const char *filename;
|
||||
};
|
||||
|
||||
/*
|
||||
* ISHTP firmware loader API for ISHTP hbm
|
||||
*/
|
||||
|
||||
/* ISHTP capability bit for firmware loader */
|
||||
#define ISHTP_SUPPORT_CAP_LOADER BIT(4)
|
||||
|
||||
/* Firmware loader address */
|
||||
#define ISHTP_LOADER_CLIENT_ADDR 16
|
||||
|
||||
/**
|
||||
* ishtp_loader_work - The work function to start the firmware loading process
|
||||
* @work: The work structure
|
||||
*/
|
||||
void ishtp_loader_work(struct work_struct *work);
|
||||
|
||||
#endif /* _ISHTP_LOADER_H_ */
|
|
@ -271,10 +271,9 @@ static int surface_kbd_probe(struct platform_device *pdev)
|
|||
return surface_hid_device_add(shid);
|
||||
}
|
||||
|
||||
static int surface_kbd_remove(struct platform_device *pdev)
|
||||
static void surface_kbd_remove(struct platform_device *pdev)
|
||||
{
|
||||
surface_hid_device_destroy(platform_get_drvdata(pdev));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id surface_kbd_match[] = {
|
||||
|
@ -285,7 +284,7 @@ MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
|
|||
|
||||
static struct platform_driver surface_kbd_driver = {
|
||||
.probe = surface_kbd_probe,
|
||||
.remove = surface_kbd_remove,
|
||||
.remove_new = surface_kbd_remove,
|
||||
.driver = {
|
||||
.name = "surface_keyboard",
|
||||
.acpi_match_table = surface_kbd_match,
|
||||
|
|
|
@ -474,9 +474,9 @@ struct hid_usage {
|
|||
__s8 wheel_factor; /* 120/resolution_multiplier */
|
||||
__u16 code; /* input driver code */
|
||||
__u8 type; /* input driver type */
|
||||
__s8 hat_min; /* hat switch fun */
|
||||
__s8 hat_max; /* ditto */
|
||||
__s8 hat_dir; /* ditto */
|
||||
__s16 hat_min; /* hat switch fun */
|
||||
__s16 hat_max; /* ditto */
|
||||
__s16 hat_dir; /* ditto */
|
||||
__s16 wheel_accumulated; /* hi-res wheel */
|
||||
};
|
||||
|
||||
|
|
|
@ -103,6 +103,9 @@ struct hid_bpf_ops {
|
|||
unsigned char reportnum, __u8 *buf,
|
||||
size_t len, enum hid_report_type rtype,
|
||||
enum hid_class_request reqtype);
|
||||
int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len);
|
||||
int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type,
|
||||
u8 *data, u32 size, int interrupt);
|
||||
struct module *owner;
|
||||
const struct bus_type *bus_type;
|
||||
};
|
||||
|
|
|
@ -238,3 +238,4 @@ CONFIG_VLAN_8021Q=y
|
|||
CONFIG_XFRM_SUB_POLICY=y
|
||||
CONFIG_XFRM_USER=y
|
||||
CONFIG_ZEROPLUS_FF=y
|
||||
CONFIG_KASAN=y
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
|
||||
#define SHOW_UHID_DEBUG 0
|
||||
|
||||
#define min(a, b) \
|
||||
({ __typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a < _b ? _a : _b; })
|
||||
|
||||
static unsigned char rdesc[] = {
|
||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||
0x09, 0x21, /* Usage (Vendor Usage 0x21) */
|
||||
|
@ -111,6 +116,10 @@ struct hid_hw_request_syscall_args {
|
|||
static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
static pthread_mutex_t uhid_output_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t uhid_output_cond = PTHREAD_COND_INITIALIZER;
|
||||
static unsigned char output_report[10];
|
||||
|
||||
/* no need to protect uhid_stopped, only one thread accesses it */
|
||||
static bool uhid_stopped;
|
||||
|
||||
|
@ -205,6 +214,13 @@ static int uhid_event(struct __test_metadata *_metadata, int fd)
|
|||
break;
|
||||
case UHID_OUTPUT:
|
||||
UHID_LOG("UHID_OUTPUT from uhid-dev");
|
||||
|
||||
pthread_mutex_lock(&uhid_output_mtx);
|
||||
memcpy(output_report,
|
||||
ev.u.output.data,
|
||||
min(ev.u.output.size, sizeof(output_report)));
|
||||
pthread_cond_signal(&uhid_output_cond);
|
||||
pthread_mutex_unlock(&uhid_output_mtx);
|
||||
break;
|
||||
case UHID_GET_REPORT:
|
||||
UHID_LOG("UHID_GET_REPORT from uhid-dev");
|
||||
|
@ -734,8 +750,100 @@ TEST_F(hid_bpf, test_hid_change_report)
|
|||
}
|
||||
|
||||
/*
|
||||
* Attach hid_user_raw_request to the given uhid device,
|
||||
* call the bpf program from userspace
|
||||
* Call hid_bpf_input_report against the given uhid device,
|
||||
* check that the program is called and does the expected.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_hid_user_input_report_call)
|
||||
{
|
||||
struct hid_hw_request_syscall_args args = {
|
||||
.retval = -1,
|
||||
.size = 10,
|
||||
};
|
||||
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
|
||||
.ctx_in = &args,
|
||||
.ctx_size_in = sizeof(args),
|
||||
);
|
||||
__u8 buf[10] = {0};
|
||||
int err, prog_fd;
|
||||
|
||||
LOAD_BPF;
|
||||
|
||||
args.hid = self->hid_id;
|
||||
args.data[0] = 1; /* report ID */
|
||||
args.data[1] = 2; /* report ID */
|
||||
args.data[2] = 42; /* report ID */
|
||||
|
||||
prog_fd = bpf_program__fd(self->skel->progs.hid_user_input_report);
|
||||
|
||||
/* check that there is no data to read from hidraw */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
err = read(self->hidraw_fd, buf, sizeof(buf));
|
||||
ASSERT_EQ(err, -1) TH_LOG("read_hidraw");
|
||||
|
||||
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, 0);
|
||||
|
||||
/* read the data from hidraw */
|
||||
memset(buf, 0, sizeof(buf));
|
||||
err = read(self->hidraw_fd, buf, sizeof(buf));
|
||||
ASSERT_EQ(err, 6) TH_LOG("read_hidraw");
|
||||
ASSERT_EQ(buf[0], 1);
|
||||
ASSERT_EQ(buf[1], 2);
|
||||
ASSERT_EQ(buf[2], 42);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call hid_bpf_hw_output_report against the given uhid device,
|
||||
* check that the program is called and does the expected.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_hid_user_output_report_call)
|
||||
{
|
||||
struct hid_hw_request_syscall_args args = {
|
||||
.retval = -1,
|
||||
.size = 10,
|
||||
};
|
||||
DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs,
|
||||
.ctx_in = &args,
|
||||
.ctx_size_in = sizeof(args),
|
||||
);
|
||||
int err, cond_err, prog_fd;
|
||||
struct timespec time_to_wait;
|
||||
|
||||
LOAD_BPF;
|
||||
|
||||
args.hid = self->hid_id;
|
||||
args.data[0] = 1; /* report ID */
|
||||
args.data[1] = 2; /* report ID */
|
||||
args.data[2] = 42; /* report ID */
|
||||
|
||||
prog_fd = bpf_program__fd(self->skel->progs.hid_user_output_report);
|
||||
|
||||
pthread_mutex_lock(&uhid_output_mtx);
|
||||
|
||||
memset(output_report, 0, sizeof(output_report));
|
||||
clock_gettime(CLOCK_REALTIME, &time_to_wait);
|
||||
time_to_wait.tv_sec += 2;
|
||||
|
||||
err = bpf_prog_test_run_opts(prog_fd, &tattrs);
|
||||
cond_err = pthread_cond_timedwait(&uhid_output_cond, &uhid_output_mtx, &time_to_wait);
|
||||
|
||||
ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts");
|
||||
ASSERT_OK(cond_err) TH_LOG("error while calling waiting for the condition");
|
||||
|
||||
ASSERT_EQ(args.retval, 3);
|
||||
|
||||
ASSERT_EQ(output_report[0], 1);
|
||||
ASSERT_EQ(output_report[1], 2);
|
||||
ASSERT_EQ(output_report[2], 42);
|
||||
|
||||
pthread_mutex_unlock(&uhid_output_mtx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call hid_hw_raw_request against the given uhid device,
|
||||
* check that the program is called and does the expected.
|
||||
*/
|
||||
TEST_F(hid_bpf, test_hid_user_raw_request_call)
|
||||
|
|
|
@ -101,6 +101,52 @@ int hid_user_raw_request(struct hid_hw_request_syscall_args *args)
|
|||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int hid_user_output_report(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_output_report(ctx,
|
||||
args->data,
|
||||
size);
|
||||
args->retval = ret;
|
||||
|
||||
hid_bpf_release_context(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("syscall")
|
||||
int hid_user_input_report(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_input_report(ctx, HID_INPUT_REPORT, args->data, size);
|
||||
args->retval = ret;
|
||||
|
||||
hid_bpf_release_context(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const __u8 rdesc[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
0x09, 0x32, /* USAGE (Z) */
|
||||
|
|
|
@ -94,5 +94,11 @@ extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
|
|||
size_t buf__sz,
|
||||
enum hid_report_type type,
|
||||
enum hid_class_request reqtype) __ksym;
|
||||
extern int hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx,
|
||||
__u8 *buf, size_t buf__sz) __ksym;
|
||||
extern int hid_bpf_input_report(struct hid_bpf_ctx *ctx,
|
||||
enum hid_report_type type,
|
||||
__u8 *data,
|
||||
size_t buf__sz) __ksym;
|
||||
|
||||
#endif /* __HID_BPF_HELPERS_H */
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
import libevdev
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import logging
|
||||
|
||||
from hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
|
||||
from .base_device import BaseDevice, EvdevMatch, SysfsFile
|
||||
from pathlib import Path
|
||||
from typing import Final, List, Tuple
|
||||
|
||||
|
@ -157,6 +159,17 @@ class BaseTestCase:
|
|||
# for example ("playstation", "hid-playstation")
|
||||
kernel_modules: List[Tuple[str, str]] = []
|
||||
|
||||
# List of in kernel HID-BPF object files to load
|
||||
# before starting the test
|
||||
# Any existing pre-loaded HID-BPF module will be removed
|
||||
# before the ones in this list will be manually loaded.
|
||||
# Each Element is a tuple '(hid_bpf_object, rdesc_fixup_present)',
|
||||
# for example '("xppen-ArtistPro16Gen2.bpf.o", True)'
|
||||
# If 'rdesc_fixup_present' is True, the test needs to wait
|
||||
# for one unbind and rebind before it can be sure the kernel is
|
||||
# ready
|
||||
hid_bpfs: List[Tuple[str, bool]] = []
|
||||
|
||||
def assertInputEventsIn(self, expected_events, effective_events):
|
||||
effective_events = effective_events.copy()
|
||||
for ev in expected_events:
|
||||
|
@ -211,8 +224,6 @@ class BaseTestCase:
|
|||
# we don't know beforehand the name of the module from modinfo
|
||||
sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
|
||||
if not sysfs_path.exists():
|
||||
import subprocess
|
||||
|
||||
ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
|
||||
if ret.returncode != 0:
|
||||
pytest.skip(
|
||||
|
@ -225,6 +236,64 @@ class BaseTestCase:
|
|||
self._load_kernel_module(kernel_driver, kernel_module)
|
||||
yield
|
||||
|
||||
def load_hid_bpfs(self):
|
||||
script_dir = Path(os.path.dirname(os.path.realpath(__file__)))
|
||||
root_dir = (script_dir / "../../../../..").resolve()
|
||||
bpf_dir = root_dir / "drivers/hid/bpf/progs"
|
||||
|
||||
udev_hid_bpf = shutil.which("udev-hid-bpf")
|
||||
if not udev_hid_bpf:
|
||||
pytest.skip("udev-hid-bpf not found in $PATH, skipping")
|
||||
|
||||
wait = False
|
||||
for _, rdesc_fixup in self.hid_bpfs:
|
||||
if rdesc_fixup:
|
||||
wait = True
|
||||
|
||||
for hid_bpf, _ in self.hid_bpfs:
|
||||
# We need to start `udev-hid-bpf` in the background
|
||||
# and dispatch uhid events in case the kernel needs
|
||||
# to fetch features on the device
|
||||
process = subprocess.Popen(
|
||||
[
|
||||
"udev-hid-bpf",
|
||||
"--verbose",
|
||||
"add",
|
||||
str(self.uhdev.sys_path),
|
||||
str(bpf_dir / hid_bpf),
|
||||
],
|
||||
)
|
||||
while process.poll() is None:
|
||||
self.uhdev.dispatch(1)
|
||||
|
||||
if process.poll() != 0:
|
||||
pytest.fail(
|
||||
f"Couldn't insert hid-bpf program '{hid_bpf}', marking the test as failed"
|
||||
)
|
||||
|
||||
if wait:
|
||||
# the HID-BPF program exports a rdesc fixup, so it needs to be
|
||||
# unbound by the kernel and then rebound.
|
||||
# Ensure we get the bound event exactly 2 times (one for the normal
|
||||
# uhid loading, and then the reload from HID-BPF)
|
||||
now = time.time()
|
||||
while self.uhdev.kernel_ready_count < 2 and time.time() - now < 2:
|
||||
self.uhdev.dispatch(1)
|
||||
|
||||
if self.uhdev.kernel_ready_count < 2:
|
||||
pytest.fail(
|
||||
f"Couldn't insert hid-bpf programs, marking the test as failed"
|
||||
)
|
||||
|
||||
def unload_hid_bpfs(self):
|
||||
ret = subprocess.run(
|
||||
["udev-hid-bpf", "--verbose", "remove", str(self.uhdev.sys_path)],
|
||||
)
|
||||
if ret.returncode != 0:
|
||||
pytest.fail(
|
||||
f"Couldn't unload hid-bpf programs, marking the test as failed"
|
||||
)
|
||||
|
||||
@pytest.fixture()
|
||||
def new_uhdev(self, load_kernel_module):
|
||||
return self.create_device()
|
||||
|
@ -248,12 +317,18 @@ class BaseTestCase:
|
|||
now = time.time()
|
||||
while not self.uhdev.is_ready() and time.time() - now < 5:
|
||||
self.uhdev.dispatch(1)
|
||||
|
||||
if self.hid_bpfs:
|
||||
self.load_hid_bpfs()
|
||||
|
||||
if self.uhdev.get_evdev() is None:
|
||||
logger.warning(
|
||||
f"available list of input nodes: (default application is '{self.uhdev.application}')"
|
||||
)
|
||||
logger.warning(self.uhdev.input_nodes)
|
||||
yield
|
||||
if self.hid_bpfs:
|
||||
self.unload_hid_bpfs()
|
||||
self.uhdev = None
|
||||
except PermissionError:
|
||||
pytest.skip("Insufficient permissions, run me as root")
|
||||
|
@ -313,8 +388,6 @@ class HIDTestUdevRule(object):
|
|||
self.reload_udev_rules()
|
||||
|
||||
def reload_udev_rules(self):
|
||||
import subprocess
|
||||
|
||||
subprocess.run("udevadm control --reload-rules".split())
|
||||
subprocess.run("systemd-hwdb update".split())
|
||||
|
||||
|
@ -330,10 +403,11 @@ class HIDTestUdevRule(object):
|
|||
delete=False,
|
||||
) as f:
|
||||
f.write(
|
||||
'KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"\n'
|
||||
)
|
||||
f.write(
|
||||
'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n'
|
||||
"""
|
||||
KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"
|
||||
KERNELS=="*hid*", ENV{HID_NAME}=="*uhid test *", ENV{HID_BPF_IGNORE_DEVICE}="1"
|
||||
KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"
|
||||
"""
|
||||
)
|
||||
self.rulesfile = f
|
||||
|
||||
|
|
|
@ -0,0 +1,421 @@
|
|||
#!/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
|
||||
# Copyright (c) 2017 Red Hat, Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import fcntl
|
||||
import functools
|
||||
import libevdev
|
||||
import os
|
||||
|
||||
try:
|
||||
import pyudev
|
||||
except ImportError:
|
||||
raise ImportError("UHID is not supported due to missing pyudev dependency")
|
||||
|
||||
import logging
|
||||
|
||||
import hidtools.hid as hid
|
||||
from hidtools.uhid import UHIDDevice
|
||||
from hidtools.util import BusType
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
logger = logging.getLogger("hidtools.device.base_device")
|
||||
|
||||
|
||||
class SysfsFile(object):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def __set_value(self, value):
|
||||
with open(self.path, "w") as f:
|
||||
return f.write(f"{value}\n")
|
||||
|
||||
def __get_value(self):
|
||||
with open(self.path) as f:
|
||||
return f.read().strip()
|
||||
|
||||
@property
|
||||
def int_value(self) -> int:
|
||||
return int(self.__get_value())
|
||||
|
||||
@int_value.setter
|
||||
def int_value(self, v: int) -> None:
|
||||
self.__set_value(v)
|
||||
|
||||
@property
|
||||
def str_value(self) -> str:
|
||||
return self.__get_value()
|
||||
|
||||
@str_value.setter
|
||||
def str_value(self, v: str) -> None:
|
||||
self.__set_value(v)
|
||||
|
||||
|
||||
class LED(object):
|
||||
def __init__(self, sys_path):
|
||||
self.max_brightness = SysfsFile(sys_path / "max_brightness").int_value
|
||||
self.__brightness = SysfsFile(sys_path / "brightness")
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
return self.__brightness.int_value
|
||||
|
||||
@brightness.setter
|
||||
def brightness(self, value: int) -> None:
|
||||
self.__brightness.int_value = value
|
||||
|
||||
|
||||
class PowerSupply(object):
|
||||
"""Represents Linux power_supply_class sysfs nodes."""
|
||||
|
||||
def __init__(self, sys_path):
|
||||
self._capacity = SysfsFile(sys_path / "capacity")
|
||||
self._status = SysfsFile(sys_path / "status")
|
||||
self._type = SysfsFile(sys_path / "type")
|
||||
|
||||
@property
|
||||
def capacity(self) -> int:
|
||||
return self._capacity.int_value
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self._status.str_value
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self._type.str_value
|
||||
|
||||
|
||||
class HIDIsReady(object):
|
||||
"""
|
||||
Companion class that binds to a kernel mechanism
|
||||
and that allows to know when a uhid device is ready or not.
|
||||
|
||||
See :meth:`is_ready` for details.
|
||||
"""
|
||||
|
||||
def __init__(self: "HIDIsReady", uhid: UHIDDevice) -> None:
|
||||
self.uhid = uhid
|
||||
|
||||
def is_ready(self: "HIDIsReady") -> bool:
|
||||
"""
|
||||
Overwrite in subclasses: should return True or False whether
|
||||
the attached uhid device is ready or not.
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
class UdevHIDIsReady(HIDIsReady):
|
||||
_pyudev_context: ClassVar[Optional[pyudev.Context]] = None
|
||||
_pyudev_monitor: ClassVar[Optional[pyudev.Monitor]] = None
|
||||
_uhid_devices: ClassVar[Dict[int, Tuple[bool, int]]] = {}
|
||||
|
||||
def __init__(self: "UdevHIDIsReady", uhid: UHIDDevice) -> None:
|
||||
super().__init__(uhid)
|
||||
self._init_pyudev()
|
||||
|
||||
@classmethod
|
||||
def _init_pyudev(cls: Type["UdevHIDIsReady"]) -> None:
|
||||
if cls._pyudev_context is None:
|
||||
cls._pyudev_context = pyudev.Context()
|
||||
cls._pyudev_monitor = pyudev.Monitor.from_netlink(cls._pyudev_context)
|
||||
cls._pyudev_monitor.filter_by("hid")
|
||||
cls._pyudev_monitor.start()
|
||||
|
||||
UHIDDevice._append_fd_to_poll(
|
||||
cls._pyudev_monitor.fileno(), cls._cls_udev_event_callback
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _cls_udev_event_callback(cls: Type["UdevHIDIsReady"]) -> None:
|
||||
if cls._pyudev_monitor is None:
|
||||
return
|
||||
event: pyudev.Device
|
||||
for event in iter(functools.partial(cls._pyudev_monitor.poll, 0.02), None):
|
||||
if event.action not in ["bind", "remove", "unbind"]:
|
||||
return
|
||||
|
||||
logger.debug(f"udev event: {event.action} -> {event}")
|
||||
|
||||
id = int(event.sys_path.strip().split(".")[-1], 16)
|
||||
|
||||
device_ready, count = cls._uhid_devices.get(id, (False, 0))
|
||||
|
||||
ready = event.action == "bind"
|
||||
if not device_ready and ready:
|
||||
count += 1
|
||||
cls._uhid_devices[id] = (ready, count)
|
||||
|
||||
def is_ready(self: "UdevHIDIsReady") -> Tuple[bool, int]:
|
||||
try:
|
||||
return self._uhid_devices[self.uhid.hid_id]
|
||||
except KeyError:
|
||||
return (False, 0)
|
||||
|
||||
|
||||
class EvdevMatch(object):
|
||||
def __init__(
|
||||
self: "EvdevMatch",
|
||||
*,
|
||||
requires: List[Any] = [],
|
||||
excludes: List[Any] = [],
|
||||
req_properties: List[Any] = [],
|
||||
excl_properties: List[Any] = [],
|
||||
) -> None:
|
||||
self.requires = requires
|
||||
self.excludes = excludes
|
||||
self.req_properties = req_properties
|
||||
self.excl_properties = excl_properties
|
||||
|
||||
def is_a_match(self: "EvdevMatch", evdev: libevdev.Device) -> bool:
|
||||
for m in self.requires:
|
||||
if not evdev.has(m):
|
||||
return False
|
||||
for m in self.excludes:
|
||||
if evdev.has(m):
|
||||
return False
|
||||
for p in self.req_properties:
|
||||
if not evdev.has_property(p):
|
||||
return False
|
||||
for p in self.excl_properties:
|
||||
if evdev.has_property(p):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class EvdevDevice(object):
|
||||
"""
|
||||
Represents an Evdev node and its properties.
|
||||
This is a stub for the libevdev devices, as they are relying on
|
||||
uevent to get the data, saving us some ioctls to fetch the names
|
||||
and properties.
|
||||
"""
|
||||
|
||||
def __init__(self: "EvdevDevice", sysfs: Path) -> None:
|
||||
self.sysfs = sysfs
|
||||
self.event_node: Any = None
|
||||
self.libevdev: Optional[libevdev.Device] = None
|
||||
|
||||
self.uevents = {}
|
||||
# all of the interesting properties are stored in the input uevent, so in the parent
|
||||
# so convert the uevent file of the parent input node into a dict
|
||||
with open(sysfs.parent / "uevent") as f:
|
||||
for line in f.readlines():
|
||||
key, value = line.strip().split("=")
|
||||
self.uevents[key] = value.strip('"')
|
||||
|
||||
# we open all evdev nodes in order to not miss any event
|
||||
self.open()
|
||||
|
||||
@property
|
||||
def name(self: "EvdevDevice") -> str:
|
||||
assert "NAME" in self.uevents
|
||||
|
||||
return self.uevents["NAME"]
|
||||
|
||||
@property
|
||||
def evdev(self: "EvdevDevice") -> Path:
|
||||
return Path("/dev/input") / self.sysfs.name
|
||||
|
||||
def matches_application(
|
||||
self: "EvdevDevice", application: str, matches: Dict[str, EvdevMatch]
|
||||
) -> bool:
|
||||
if self.libevdev is None:
|
||||
return False
|
||||
|
||||
if application in matches:
|
||||
return matches[application].is_a_match(self.libevdev)
|
||||
|
||||
logger.error(
|
||||
f"application '{application}' is unknown, please update/fix hid-tools"
|
||||
)
|
||||
assert False # hid-tools likely needs an update
|
||||
|
||||
def open(self: "EvdevDevice") -> libevdev.Device:
|
||||
self.event_node = open(self.evdev, "rb")
|
||||
self.libevdev = libevdev.Device(self.event_node)
|
||||
|
||||
assert self.libevdev.fd is not None
|
||||
|
||||
fd = self.libevdev.fd.fileno()
|
||||
flag = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
|
||||
|
||||
return self.libevdev
|
||||
|
||||
def close(self: "EvdevDevice") -> None:
|
||||
if self.libevdev is not None and self.libevdev.fd is not None:
|
||||
self.libevdev.fd.close()
|
||||
self.libevdev = None
|
||||
if self.event_node is not None:
|
||||
self.event_node.close()
|
||||
self.event_node = None
|
||||
|
||||
|
||||
class BaseDevice(UHIDDevice):
|
||||
# default _application_matches that matches nothing. This needs
|
||||
# to be set in the subclasses to have get_evdev() working
|
||||
_application_matches: Dict[str, EvdevMatch] = {}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
application,
|
||||
rdesc_str: Optional[str] = None,
|
||||
rdesc: Optional[Union[hid.ReportDescriptor, str, bytes]] = None,
|
||||
input_info=None,
|
||||
) -> None:
|
||||
self._kernel_is_ready: HIDIsReady = UdevHIDIsReady(self)
|
||||
if rdesc_str is None and rdesc is None:
|
||||
raise Exception("Please provide at least a rdesc or rdesc_str")
|
||||
super().__init__()
|
||||
if name is None:
|
||||
name = f"uhid gamepad test {self.__class__.__name__}"
|
||||
if input_info is None:
|
||||
input_info = (BusType.USB, 1, 2)
|
||||
self.name = name
|
||||
self.info = input_info
|
||||
self.default_reportID = None
|
||||
self.opened = False
|
||||
self.started = False
|
||||
self.application = application
|
||||
self._input_nodes: Optional[list[EvdevDevice]] = None
|
||||
if rdesc is None:
|
||||
assert rdesc_str is not None
|
||||
self.rdesc = hid.ReportDescriptor.from_human_descr(rdesc_str) # type: ignore
|
||||
else:
|
||||
self.rdesc = rdesc # type: ignore
|
||||
|
||||
@property
|
||||
def power_supply_class(self: "BaseDevice") -> Optional[PowerSupply]:
|
||||
ps = self.walk_sysfs("power_supply", "power_supply/*")
|
||||
if ps is None or len(ps) < 1:
|
||||
return None
|
||||
|
||||
return PowerSupply(ps[0])
|
||||
|
||||
@property
|
||||
def led_classes(self: "BaseDevice") -> List[LED]:
|
||||
leds = self.walk_sysfs("led", "**/max_brightness")
|
||||
if leds is None:
|
||||
return []
|
||||
|
||||
return [LED(led.parent) for led in leds]
|
||||
|
||||
@property
|
||||
def kernel_is_ready(self: "BaseDevice") -> bool:
|
||||
return self._kernel_is_ready.is_ready()[0] and self.started
|
||||
|
||||
@property
|
||||
def kernel_ready_count(self: "BaseDevice") -> int:
|
||||
return self._kernel_is_ready.is_ready()[1]
|
||||
|
||||
@property
|
||||
def input_nodes(self: "BaseDevice") -> List[EvdevDevice]:
|
||||
if self._input_nodes is not None:
|
||||
return self._input_nodes
|
||||
|
||||
if not self.kernel_is_ready or not self.started:
|
||||
return []
|
||||
|
||||
self._input_nodes = [
|
||||
EvdevDevice(path)
|
||||
for path in self.walk_sysfs("input", "input/input*/event*")
|
||||
]
|
||||
return self._input_nodes
|
||||
|
||||
def match_evdev_rule(self, application, evdev):
|
||||
"""Replace this in subclasses if the device has multiple reports
|
||||
of the same type and we need to filter based on the actual evdev
|
||||
node.
|
||||
|
||||
returning True will append the corresponding report to
|
||||
`self.input_nodes[type]`
|
||||
returning False will ignore this report / type combination
|
||||
for the device.
|
||||
"""
|
||||
return True
|
||||
|
||||
def open(self):
|
||||
self.opened = True
|
||||
|
||||
def _close_all_opened_evdev(self):
|
||||
if self._input_nodes is not None:
|
||||
for e in self._input_nodes:
|
||||
e.close()
|
||||
|
||||
def __del__(self):
|
||||
self._close_all_opened_evdev()
|
||||
|
||||
def close(self):
|
||||
self.opened = False
|
||||
|
||||
def start(self, flags):
|
||||
self.started = True
|
||||
|
||||
def stop(self):
|
||||
self.started = False
|
||||
self._close_all_opened_evdev()
|
||||
|
||||
def next_sync_events(self, application=None):
|
||||
evdev = self.get_evdev(application)
|
||||
if evdev is not None:
|
||||
return list(evdev.events())
|
||||
return []
|
||||
|
||||
@property
|
||||
def application_matches(self: "BaseDevice") -> Dict[str, EvdevMatch]:
|
||||
return self._application_matches
|
||||
|
||||
@application_matches.setter
|
||||
def application_matches(self: "BaseDevice", data: Dict[str, EvdevMatch]) -> None:
|
||||
self._application_matches = data
|
||||
|
||||
def get_evdev(self, application=None):
|
||||
if application is None:
|
||||
application = self.application
|
||||
|
||||
if len(self.input_nodes) == 0:
|
||||
return None
|
||||
|
||||
assert self._input_nodes is not None
|
||||
|
||||
if len(self._input_nodes) == 1:
|
||||
evdev = self._input_nodes[0]
|
||||
if self.match_evdev_rule(application, evdev.libevdev):
|
||||
return evdev.libevdev
|
||||
else:
|
||||
for _evdev in self._input_nodes:
|
||||
if _evdev.matches_application(application, self.application_matches):
|
||||
if self.match_evdev_rule(application, _evdev.libevdev):
|
||||
return _evdev.libevdev
|
||||
|
||||
def is_ready(self):
|
||||
"""Returns whether a UHID device is ready. Can be overwritten in
|
||||
subclasses to add extra conditions on when to consider a UHID
|
||||
device ready. This can be:
|
||||
|
||||
- we need to wait on different types of input devices to be ready
|
||||
(Touch Screen and Pen for example)
|
||||
- we need to have at least 4 LEDs present
|
||||
(len(self.uhdev.leds_classes) == 4)
|
||||
- or any other combinations"""
|
||||
return self.kernel_is_ready
|
|
@ -0,0 +1,238 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
import libevdev
|
||||
|
||||
from .base_device import BaseDevice
|
||||
from hidtools.util import BusType
|
||||
|
||||
|
||||
class InvalidHIDCommunication(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class GamepadData(object):
|
||||
pass
|
||||
|
||||
|
||||
class AxisMapping(object):
|
||||
"""Represents a mapping between a HID type
|
||||
and an evdev event"""
|
||||
|
||||
def __init__(self, hid, evdev=None):
|
||||
self.hid = hid.lower()
|
||||
|
||||
if evdev is None:
|
||||
evdev = f"ABS_{hid.upper()}"
|
||||
|
||||
self.evdev = libevdev.evbit("EV_ABS", evdev)
|
||||
|
||||
|
||||
class BaseGamepad(BaseDevice):
|
||||
buttons_map = {
|
||||
1: "BTN_SOUTH",
|
||||
2: "BTN_EAST",
|
||||
3: "BTN_C",
|
||||
4: "BTN_NORTH",
|
||||
5: "BTN_WEST",
|
||||
6: "BTN_Z",
|
||||
7: "BTN_TL",
|
||||
8: "BTN_TR",
|
||||
9: "BTN_TL2",
|
||||
10: "BTN_TR2",
|
||||
11: "BTN_SELECT",
|
||||
12: "BTN_START",
|
||||
13: "BTN_MODE",
|
||||
14: "BTN_THUMBL",
|
||||
15: "BTN_THUMBR",
|
||||
}
|
||||
|
||||
axes_map = {
|
||||
"left_stick": {
|
||||
"x": AxisMapping("x"),
|
||||
"y": AxisMapping("y"),
|
||||
},
|
||||
"right_stick": {
|
||||
"x": AxisMapping("z"),
|
||||
"y": AxisMapping("Rz"),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, rdesc, application="Game Pad", name=None, input_info=None):
|
||||
assert rdesc is not None
|
||||
super().__init__(name, application, input_info=input_info, rdesc=rdesc)
|
||||
self.buttons = (1, 2, 3)
|
||||
self._buttons = {}
|
||||
self.left = (127, 127)
|
||||
self.right = (127, 127)
|
||||
self.hat_switch = 15
|
||||
assert self.parsed_rdesc is not None
|
||||
|
||||
self.fields = []
|
||||
for r in self.parsed_rdesc.input_reports.values():
|
||||
if r.application_name == self.application:
|
||||
self.fields.extend([f.usage_name for f in r])
|
||||
|
||||
def store_axes(self, which, gamepad, data):
|
||||
amap = self.axes_map[which]
|
||||
x, y = data
|
||||
setattr(gamepad, amap["x"].hid, x)
|
||||
setattr(gamepad, amap["y"].hid, y)
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
*,
|
||||
left=(None, None),
|
||||
right=(None, None),
|
||||
hat_switch=None,
|
||||
buttons=None,
|
||||
reportID=None,
|
||||
application="Game Pad",
|
||||
):
|
||||
"""
|
||||
Return an input report for this device.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
(expressed in 1/8 of circle, 0 being North, 2 East)
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
:param reportID: the numeric report ID for this report, if needed
|
||||
:param application: the application used to report the values
|
||||
"""
|
||||
if buttons is not None:
|
||||
for i, b in buttons.items():
|
||||
if i not in self.buttons:
|
||||
raise InvalidHIDCommunication(
|
||||
f"button {i} is not part of this {self.application}"
|
||||
)
|
||||
if b is not None:
|
||||
self._buttons[i] = b
|
||||
|
||||
def replace_none_in_tuple(item, default):
|
||||
if item is None:
|
||||
item = (None, None)
|
||||
|
||||
if None in item:
|
||||
if item[0] is None:
|
||||
item = (default[0], item[1])
|
||||
if item[1] is None:
|
||||
item = (item[0], default[1])
|
||||
|
||||
return item
|
||||
|
||||
right = replace_none_in_tuple(right, self.right)
|
||||
self.right = right
|
||||
left = replace_none_in_tuple(left, self.left)
|
||||
self.left = left
|
||||
|
||||
if hat_switch is None:
|
||||
hat_switch = self.hat_switch
|
||||
else:
|
||||
self.hat_switch = hat_switch
|
||||
|
||||
reportID = reportID or self.default_reportID
|
||||
|
||||
gamepad = GamepadData()
|
||||
for i, b in self._buttons.items():
|
||||
gamepad.__setattr__(f"b{i}", int(b) if b is not None else 0)
|
||||
|
||||
self.store_axes("left_stick", gamepad, left)
|
||||
self.store_axes("right_stick", gamepad, right)
|
||||
gamepad.hatswitch = hat_switch # type: ignore ### gamepad is by default empty
|
||||
return super().create_report(
|
||||
gamepad, reportID=reportID, application=application
|
||||
)
|
||||
|
||||
def event(
|
||||
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
|
||||
):
|
||||
"""
|
||||
Send an input event on the default report ID.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
"""
|
||||
r = self.create_report(
|
||||
left=left, right=right, hat_switch=hat_switch, buttons=buttons
|
||||
)
|
||||
self.call_input_event(r)
|
||||
return [r]
|
||||
|
||||
|
||||
class JoystickGamepad(BaseGamepad):
|
||||
buttons_map = {
|
||||
1: "BTN_TRIGGER",
|
||||
2: "BTN_THUMB",
|
||||
3: "BTN_THUMB2",
|
||||
4: "BTN_TOP",
|
||||
5: "BTN_TOP2",
|
||||
6: "BTN_PINKIE",
|
||||
7: "BTN_BASE",
|
||||
8: "BTN_BASE2",
|
||||
9: "BTN_BASE3",
|
||||
10: "BTN_BASE4",
|
||||
11: "BTN_BASE5",
|
||||
12: "BTN_BASE6",
|
||||
13: "BTN_DEAD",
|
||||
}
|
||||
|
||||
axes_map = {
|
||||
"left_stick": {
|
||||
"x": AxisMapping("x"),
|
||||
"y": AxisMapping("y"),
|
||||
},
|
||||
"right_stick": {
|
||||
"x": AxisMapping("rudder"),
|
||||
"y": AxisMapping("throttle"),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, rdesc, application="Joystick", name=None, input_info=None):
|
||||
super().__init__(rdesc, application, name, input_info)
|
||||
|
||||
def create_report(
|
||||
self,
|
||||
*,
|
||||
left=(None, None),
|
||||
right=(None, None),
|
||||
hat_switch=None,
|
||||
buttons=None,
|
||||
reportID=None,
|
||||
application=None,
|
||||
):
|
||||
"""
|
||||
Return an input report for this device.
|
||||
|
||||
:param left: a tuple of absolute (x, y) value of the left joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param right: a tuple of absolute (x, y) value of the right joypad
|
||||
where ``None`` is "leave unchanged"
|
||||
:param hat_switch: an absolute angular value of the hat switch
|
||||
where ``None`` is "leave unchanged"
|
||||
:param buttons: a dict of index/bool for the button states,
|
||||
where ``None`` is "leave unchanged"
|
||||
:param reportID: the numeric report ID for this report, if needed
|
||||
:param application: the application for this report, if needed
|
||||
"""
|
||||
if application is None:
|
||||
application = "Joystick"
|
||||
return super().create_report(
|
||||
left=left,
|
||||
right=right,
|
||||
hat_switch=hat_switch,
|
||||
buttons=buttons,
|
||||
reportID=reportID,
|
||||
application=application,
|
||||
)
|
||||
|
||||
def store_right_joystick(self, gamepad, data):
|
||||
gamepad.rudder, gamepad.throttle = data
|
|
@ -10,7 +10,8 @@ from . import base
|
|||
import libevdev
|
||||
import pytest
|
||||
|
||||
from hidtools.device.base_gamepad import AsusGamepad, SaitekGamepad
|
||||
from .base_gamepad import BaseGamepad, JoystickGamepad, AxisMapping
|
||||
from hidtools.util import BusType
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -199,6 +200,449 @@ class BaseTest:
|
|||
)
|
||||
|
||||
|
||||
class SaitekGamepad(JoystickGamepad):
|
||||
# fmt: off
|
||||
report_descriptor = [
|
||||
0x05, 0x01, # Usage Page (Generic Desktop) 0
|
||||
0x09, 0x04, # Usage (Joystick) 2
|
||||
0xa1, 0x01, # Collection (Application) 4
|
||||
0x09, 0x01, # .Usage (Pointer) 6
|
||||
0xa1, 0x00, # .Collection (Physical) 8
|
||||
0x85, 0x01, # ..Report ID (1) 10
|
||||
0x09, 0x30, # ..Usage (X) 12
|
||||
0x15, 0x00, # ..Logical Minimum (0) 14
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 16
|
||||
0x35, 0x00, # ..Physical Minimum (0) 19
|
||||
0x46, 0xff, 0x00, # ..Physical Maximum (255) 21
|
||||
0x75, 0x08, # ..Report Size (8) 24
|
||||
0x95, 0x01, # ..Report Count (1) 26
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 28
|
||||
0x09, 0x31, # ..Usage (Y) 30
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 32
|
||||
0x05, 0x02, # ..Usage Page (Simulation Controls) 34
|
||||
0x09, 0xba, # ..Usage (Rudder) 36
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 38
|
||||
0x09, 0xbb, # ..Usage (Throttle) 40
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 42
|
||||
0x05, 0x09, # ..Usage Page (Button) 44
|
||||
0x19, 0x01, # ..Usage Minimum (1) 46
|
||||
0x29, 0x0c, # ..Usage Maximum (12) 48
|
||||
0x25, 0x01, # ..Logical Maximum (1) 50
|
||||
0x45, 0x01, # ..Physical Maximum (1) 52
|
||||
0x75, 0x01, # ..Report Size (1) 54
|
||||
0x95, 0x0c, # ..Report Count (12) 56
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 58
|
||||
0x95, 0x01, # ..Report Count (1) 60
|
||||
0x75, 0x00, # ..Report Size (0) 62
|
||||
0x81, 0x03, # ..Input (Cnst,Var,Abs) 64
|
||||
0x05, 0x01, # ..Usage Page (Generic Desktop) 66
|
||||
0x09, 0x39, # ..Usage (Hat switch) 68
|
||||
0x25, 0x07, # ..Logical Maximum (7) 70
|
||||
0x46, 0x3b, 0x01, # ..Physical Maximum (315) 72
|
||||
0x55, 0x00, # ..Unit Exponent (0) 75
|
||||
0x65, 0x44, # ..Unit (Degrees^4,EngRotation) 77
|
||||
0x75, 0x04, # ..Report Size (4) 79
|
||||
0x81, 0x42, # ..Input (Data,Var,Abs,Null) 81
|
||||
0x65, 0x00, # ..Unit (None) 83
|
||||
0xc0, # .End Collection 85
|
||||
0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 86
|
||||
0x09, 0x92, # .Usage (Vendor Usage 0x92) 88
|
||||
0xa1, 0x02, # .Collection (Logical) 90
|
||||
0x85, 0x02, # ..Report ID (2) 92
|
||||
0x09, 0xa0, # ..Usage (Vendor Usage 0xa0) 94
|
||||
0x09, 0x9f, # ..Usage (Vendor Usage 0x9f) 96
|
||||
0x25, 0x01, # ..Logical Maximum (1) 98
|
||||
0x45, 0x00, # ..Physical Maximum (0) 100
|
||||
0x75, 0x01, # ..Report Size (1) 102
|
||||
0x95, 0x02, # ..Report Count (2) 104
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 106
|
||||
0x75, 0x06, # ..Report Size (6) 108
|
||||
0x95, 0x01, # ..Report Count (1) 110
|
||||
0x81, 0x03, # ..Input (Cnst,Var,Abs) 112
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 114
|
||||
0x75, 0x07, # ..Report Size (7) 116
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 118
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 120
|
||||
0x09, 0x94, # ..Usage (Vendor Usage 0x94) 122
|
||||
0x75, 0x01, # ..Report Size (1) 124
|
||||
0x25, 0x01, # ..Logical Maximum (1) 126
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 128
|
||||
0xc0, # .End Collection 130
|
||||
0x09, 0x21, # .Usage (Vendor Usage 0x21) 131
|
||||
0xa1, 0x02, # .Collection (Logical) 133
|
||||
0x85, 0x0b, # ..Report ID (11) 135
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 137
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 139
|
||||
0x75, 0x08, # ..Report Size (8) 142
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 144
|
||||
0x09, 0x53, # ..Usage (Vendor Usage 0x53) 146
|
||||
0x25, 0x0a, # ..Logical Maximum (10) 148
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 150
|
||||
0x09, 0x50, # ..Usage (Vendor Usage 0x50) 152
|
||||
0x27, 0xfe, 0xff, 0x00, 0x00, # ..Logical Maximum (65534) 154
|
||||
0x47, 0xfe, 0xff, 0x00, 0x00, # ..Physical Maximum (65534) 159
|
||||
0x75, 0x10, # ..Report Size (16) 164
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 166
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 168
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 171
|
||||
0x55, 0x00, # ..Unit Exponent (0) 173
|
||||
0x65, 0x00, # ..Unit (None) 175
|
||||
0x09, 0x54, # ..Usage (Vendor Usage 0x54) 177
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 179
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 181
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 184
|
||||
0x55, 0x00, # ..Unit Exponent (0) 186
|
||||
0x65, 0x00, # ..Unit (None) 188
|
||||
0x09, 0xa7, # ..Usage (Vendor Usage 0xa7) 190
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 192
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 194
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 197
|
||||
0x55, 0x00, # ..Unit Exponent (0) 199
|
||||
0x65, 0x00, # ..Unit (None) 201
|
||||
0xc0, # .End Collection 203
|
||||
0x09, 0x5a, # .Usage (Vendor Usage 0x5a) 204
|
||||
0xa1, 0x02, # .Collection (Logical) 206
|
||||
0x85, 0x0c, # ..Report ID (12) 208
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 210
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 212
|
||||
0x45, 0x00, # ..Physical Maximum (0) 215
|
||||
0x75, 0x08, # ..Report Size (8) 217
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 219
|
||||
0x09, 0x5c, # ..Usage (Vendor Usage 0x5c) 221
|
||||
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 223
|
||||
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 226
|
||||
0x75, 0x10, # ..Report Size (16) 229
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 231
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 233
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 236
|
||||
0x55, 0x00, # ..Unit Exponent (0) 238
|
||||
0x65, 0x00, # ..Unit (None) 240
|
||||
0x09, 0x5b, # ..Usage (Vendor Usage 0x5b) 242
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 244
|
||||
0x75, 0x08, # ..Report Size (8) 246
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 248
|
||||
0x09, 0x5e, # ..Usage (Vendor Usage 0x5e) 250
|
||||
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 252
|
||||
0x75, 0x10, # ..Report Size (16) 255
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 257
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 259
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 262
|
||||
0x55, 0x00, # ..Unit Exponent (0) 264
|
||||
0x65, 0x00, # ..Unit (None) 266
|
||||
0x09, 0x5d, # ..Usage (Vendor Usage 0x5d) 268
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 270
|
||||
0x75, 0x08, # ..Report Size (8) 272
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 274
|
||||
0xc0, # .End Collection 276
|
||||
0x09, 0x73, # .Usage (Vendor Usage 0x73) 277
|
||||
0xa1, 0x02, # .Collection (Logical) 279
|
||||
0x85, 0x0d, # ..Report ID (13) 281
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 283
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 285
|
||||
0x45, 0x00, # ..Physical Maximum (0) 288
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 290
|
||||
0x09, 0x70, # ..Usage (Vendor Usage 0x70) 292
|
||||
0x15, 0x81, # ..Logical Minimum (-127) 294
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 296
|
||||
0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 298
|
||||
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 301
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 304
|
||||
0xc0, # .End Collection 306
|
||||
0x09, 0x6e, # .Usage (Vendor Usage 0x6e) 307
|
||||
0xa1, 0x02, # .Collection (Logical) 309
|
||||
0x85, 0x0e, # ..Report ID (14) 311
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 313
|
||||
0x15, 0x00, # ..Logical Minimum (0) 315
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 317
|
||||
0x35, 0x00, # ..Physical Minimum (0) 320
|
||||
0x45, 0x00, # ..Physical Maximum (0) 322
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 324
|
||||
0x09, 0x70, # ..Usage (Vendor Usage 0x70) 326
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 328
|
||||
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 330
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 333
|
||||
0x09, 0x6f, # ..Usage (Vendor Usage 0x6f) 335
|
||||
0x15, 0x81, # ..Logical Minimum (-127) 337
|
||||
0x36, 0xf0, 0xd8, # ..Physical Minimum (-10000) 339
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 342
|
||||
0x09, 0x71, # ..Usage (Vendor Usage 0x71) 344
|
||||
0x15, 0x00, # ..Logical Minimum (0) 346
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 348
|
||||
0x35, 0x00, # ..Physical Minimum (0) 351
|
||||
0x46, 0x68, 0x01, # ..Physical Maximum (360) 353
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 356
|
||||
0x09, 0x72, # ..Usage (Vendor Usage 0x72) 358
|
||||
0x75, 0x10, # ..Report Size (16) 360
|
||||
0x26, 0x10, 0x27, # ..Logical Maximum (10000) 362
|
||||
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 365
|
||||
0x55, 0xfd, # ..Unit Exponent (237) 368
|
||||
0x66, 0x01, 0x10, # ..Unit (Seconds,SILinear) 370
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 373
|
||||
0x55, 0x00, # ..Unit Exponent (0) 375
|
||||
0x65, 0x00, # ..Unit (None) 377
|
||||
0xc0, # .End Collection 379
|
||||
0x09, 0x77, # .Usage (Vendor Usage 0x77) 380
|
||||
0xa1, 0x02, # .Collection (Logical) 382
|
||||
0x85, 0x51, # ..Report ID (81) 384
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 386
|
||||
0x25, 0x7f, # ..Logical Maximum (127) 388
|
||||
0x45, 0x00, # ..Physical Maximum (0) 390
|
||||
0x75, 0x08, # ..Report Size (8) 392
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 394
|
||||
0x09, 0x78, # ..Usage (Vendor Usage 0x78) 396
|
||||
0xa1, 0x02, # ..Collection (Logical) 398
|
||||
0x09, 0x7b, # ...Usage (Vendor Usage 0x7b) 400
|
||||
0x09, 0x79, # ...Usage (Vendor Usage 0x79) 402
|
||||
0x09, 0x7a, # ...Usage (Vendor Usage 0x7a) 404
|
||||
0x15, 0x01, # ...Logical Minimum (1) 406
|
||||
0x25, 0x03, # ...Logical Maximum (3) 408
|
||||
0x91, 0x00, # ...Output (Data,Arr,Abs) 410
|
||||
0xc0, # ..End Collection 412
|
||||
0x09, 0x7c, # ..Usage (Vendor Usage 0x7c) 413
|
||||
0x15, 0x00, # ..Logical Minimum (0) 415
|
||||
0x26, 0xfe, 0x00, # ..Logical Maximum (254) 417
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 420
|
||||
0xc0, # .End Collection 422
|
||||
0x09, 0x92, # .Usage (Vendor Usage 0x92) 423
|
||||
0xa1, 0x02, # .Collection (Logical) 425
|
||||
0x85, 0x52, # ..Report ID (82) 427
|
||||
0x09, 0x96, # ..Usage (Vendor Usage 0x96) 429
|
||||
0xa1, 0x02, # ..Collection (Logical) 431
|
||||
0x09, 0x9a, # ...Usage (Vendor Usage 0x9a) 433
|
||||
0x09, 0x99, # ...Usage (Vendor Usage 0x99) 435
|
||||
0x09, 0x97, # ...Usage (Vendor Usage 0x97) 437
|
||||
0x09, 0x98, # ...Usage (Vendor Usage 0x98) 439
|
||||
0x09, 0x9b, # ...Usage (Vendor Usage 0x9b) 441
|
||||
0x09, 0x9c, # ...Usage (Vendor Usage 0x9c) 443
|
||||
0x15, 0x01, # ...Logical Minimum (1) 445
|
||||
0x25, 0x06, # ...Logical Maximum (6) 447
|
||||
0x91, 0x00, # ...Output (Data,Arr,Abs) 449
|
||||
0xc0, # ..End Collection 451
|
||||
0xc0, # .End Collection 452
|
||||
0x05, 0xff, # .Usage Page (Vendor Usage Page 0xff) 453
|
||||
0x0a, 0x01, 0x03, # .Usage (Vendor Usage 0x301) 455
|
||||
0xa1, 0x02, # .Collection (Logical) 458
|
||||
0x85, 0x40, # ..Report ID (64) 460
|
||||
0x0a, 0x02, 0x03, # ..Usage (Vendor Usage 0x302) 462
|
||||
0xa1, 0x02, # ..Collection (Logical) 465
|
||||
0x1a, 0x11, 0x03, # ...Usage Minimum (785) 467
|
||||
0x2a, 0x20, 0x03, # ...Usage Maximum (800) 470
|
||||
0x25, 0x10, # ...Logical Maximum (16) 473
|
||||
0x91, 0x00, # ...Output (Data,Arr,Abs) 475
|
||||
0xc0, # ..End Collection 477
|
||||
0x0a, 0x03, 0x03, # ..Usage (Vendor Usage 0x303) 478
|
||||
0x15, 0x00, # ..Logical Minimum (0) 481
|
||||
0x27, 0xff, 0xff, 0x00, 0x00, # ..Logical Maximum (65535) 483
|
||||
0x75, 0x10, # ..Report Size (16) 488
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 490
|
||||
0xc0, # .End Collection 492
|
||||
0x05, 0x0f, # .Usage Page (Vendor Usage Page 0x0f) 493
|
||||
0x09, 0x7d, # .Usage (Vendor Usage 0x7d) 495
|
||||
0xa1, 0x02, # .Collection (Logical) 497
|
||||
0x85, 0x43, # ..Report ID (67) 499
|
||||
0x09, 0x7e, # ..Usage (Vendor Usage 0x7e) 501
|
||||
0x26, 0x80, 0x00, # ..Logical Maximum (128) 503
|
||||
0x46, 0x10, 0x27, # ..Physical Maximum (10000) 506
|
||||
0x75, 0x08, # ..Report Size (8) 509
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 511
|
||||
0xc0, # .End Collection 513
|
||||
0x09, 0x7f, # .Usage (Vendor Usage 0x7f) 514
|
||||
0xa1, 0x02, # .Collection (Logical) 516
|
||||
0x85, 0x0b, # ..Report ID (11) 518
|
||||
0x09, 0x80, # ..Usage (Vendor Usage 0x80) 520
|
||||
0x26, 0xff, 0x7f, # ..Logical Maximum (32767) 522
|
||||
0x45, 0x00, # ..Physical Maximum (0) 525
|
||||
0x75, 0x0f, # ..Report Size (15) 527
|
||||
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 529
|
||||
0x09, 0xa9, # ..Usage (Vendor Usage 0xa9) 531
|
||||
0x25, 0x01, # ..Logical Maximum (1) 533
|
||||
0x75, 0x01, # ..Report Size (1) 535
|
||||
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 537
|
||||
0x09, 0x83, # ..Usage (Vendor Usage 0x83) 539
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 541
|
||||
0x75, 0x08, # ..Report Size (8) 544
|
||||
0xb1, 0x03, # ..Feature (Cnst,Var,Abs) 546
|
||||
0xc0, # .End Collection 548
|
||||
0x09, 0xab, # .Usage (Vendor Usage 0xab) 549
|
||||
0xa1, 0x03, # .Collection (Report) 551
|
||||
0x85, 0x15, # ..Report ID (21) 553
|
||||
0x09, 0x25, # ..Usage (Vendor Usage 0x25) 555
|
||||
0xa1, 0x02, # ..Collection (Logical) 557
|
||||
0x09, 0x26, # ...Usage (Vendor Usage 0x26) 559
|
||||
0x09, 0x30, # ...Usage (Vendor Usage 0x30) 561
|
||||
0x09, 0x32, # ...Usage (Vendor Usage 0x32) 563
|
||||
0x09, 0x31, # ...Usage (Vendor Usage 0x31) 565
|
||||
0x09, 0x33, # ...Usage (Vendor Usage 0x33) 567
|
||||
0x09, 0x34, # ...Usage (Vendor Usage 0x34) 569
|
||||
0x15, 0x01, # ...Logical Minimum (1) 571
|
||||
0x25, 0x06, # ...Logical Maximum (6) 573
|
||||
0xb1, 0x00, # ...Feature (Data,Arr,Abs) 575
|
||||
0xc0, # ..End Collection 577
|
||||
0xc0, # .End Collection 578
|
||||
0x09, 0x89, # .Usage (Vendor Usage 0x89) 579
|
||||
0xa1, 0x03, # .Collection (Report) 581
|
||||
0x85, 0x16, # ..Report ID (22) 583
|
||||
0x09, 0x8b, # ..Usage (Vendor Usage 0x8b) 585
|
||||
0xa1, 0x02, # ..Collection (Logical) 587
|
||||
0x09, 0x8c, # ...Usage (Vendor Usage 0x8c) 589
|
||||
0x09, 0x8d, # ...Usage (Vendor Usage 0x8d) 591
|
||||
0x09, 0x8e, # ...Usage (Vendor Usage 0x8e) 593
|
||||
0x25, 0x03, # ...Logical Maximum (3) 595
|
||||
0xb1, 0x00, # ...Feature (Data,Arr,Abs) 597
|
||||
0xc0, # ..End Collection 599
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 600
|
||||
0x15, 0x00, # ..Logical Minimum (0) 602
|
||||
0x26, 0xfe, 0x00, # ..Logical Maximum (254) 604
|
||||
0xb1, 0x02, # ..Feature (Data,Var,Abs) 607
|
||||
0xc0, # .End Collection 609
|
||||
0x09, 0x90, # .Usage (Vendor Usage 0x90) 610
|
||||
0xa1, 0x03, # .Collection (Report) 612
|
||||
0x85, 0x50, # ..Report ID (80) 614
|
||||
0x09, 0x22, # ..Usage (Vendor Usage 0x22) 616
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 618
|
||||
0x91, 0x02, # ..Output (Data,Var,Abs) 621
|
||||
0xc0, # .End Collection 623
|
||||
0xc0, # End Collection 624
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
def __init__(self, rdesc=report_descriptor, name=None):
|
||||
super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x06A3, 0xFF0D))
|
||||
self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
||||
|
||||
|
||||
class AsusGamepad(BaseGamepad):
|
||||
# fmt: off
|
||||
report_descriptor = [
|
||||
0x05, 0x01, # Usage Page (Generic Desktop) 0
|
||||
0x09, 0x05, # Usage (Game Pad) 2
|
||||
0xa1, 0x01, # Collection (Application) 4
|
||||
0x85, 0x01, # .Report ID (1) 6
|
||||
0x05, 0x09, # .Usage Page (Button) 8
|
||||
0x0a, 0x01, 0x00, # .Usage (Vendor Usage 0x01) 10
|
||||
0x0a, 0x02, 0x00, # .Usage (Vendor Usage 0x02) 13
|
||||
0x0a, 0x04, 0x00, # .Usage (Vendor Usage 0x04) 16
|
||||
0x0a, 0x05, 0x00, # .Usage (Vendor Usage 0x05) 19
|
||||
0x0a, 0x07, 0x00, # .Usage (Vendor Usage 0x07) 22
|
||||
0x0a, 0x08, 0x00, # .Usage (Vendor Usage 0x08) 25
|
||||
0x0a, 0x0e, 0x00, # .Usage (Vendor Usage 0x0e) 28
|
||||
0x0a, 0x0f, 0x00, # .Usage (Vendor Usage 0x0f) 31
|
||||
0x0a, 0x0d, 0x00, # .Usage (Vendor Usage 0x0d) 34
|
||||
0x05, 0x0c, # .Usage Page (Consumer Devices) 37
|
||||
0x0a, 0x24, 0x02, # .Usage (AC Back) 39
|
||||
0x0a, 0x23, 0x02, # .Usage (AC Home) 42
|
||||
0x15, 0x00, # .Logical Minimum (0) 45
|
||||
0x25, 0x01, # .Logical Maximum (1) 47
|
||||
0x75, 0x01, # .Report Size (1) 49
|
||||
0x95, 0x0b, # .Report Count (11) 51
|
||||
0x81, 0x02, # .Input (Data,Var,Abs) 53
|
||||
0x75, 0x01, # .Report Size (1) 55
|
||||
0x95, 0x01, # .Report Count (1) 57
|
||||
0x81, 0x03, # .Input (Cnst,Var,Abs) 59
|
||||
0x05, 0x01, # .Usage Page (Generic Desktop) 61
|
||||
0x75, 0x04, # .Report Size (4) 63
|
||||
0x95, 0x01, # .Report Count (1) 65
|
||||
0x25, 0x07, # .Logical Maximum (7) 67
|
||||
0x46, 0x3b, 0x01, # .Physical Maximum (315) 69
|
||||
0x66, 0x14, 0x00, # .Unit (Degrees,EngRotation) 72
|
||||
0x09, 0x39, # .Usage (Hat switch) 75
|
||||
0x81, 0x42, # .Input (Data,Var,Abs,Null) 77
|
||||
0x66, 0x00, 0x00, # .Unit (None) 79
|
||||
0x09, 0x01, # .Usage (Pointer) 82
|
||||
0xa1, 0x00, # .Collection (Physical) 84
|
||||
0x09, 0x30, # ..Usage (X) 86
|
||||
0x09, 0x31, # ..Usage (Y) 88
|
||||
0x09, 0x32, # ..Usage (Z) 90
|
||||
0x09, 0x35, # ..Usage (Rz) 92
|
||||
0x05, 0x02, # ..Usage Page (Simulation Controls) 94
|
||||
0x09, 0xc5, # ..Usage (Brake) 96
|
||||
0x09, 0xc4, # ..Usage (Accelerator) 98
|
||||
0x15, 0x00, # ..Logical Minimum (0) 100
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 102
|
||||
0x35, 0x00, # ..Physical Minimum (0) 105
|
||||
0x46, 0xff, 0x00, # ..Physical Maximum (255) 107
|
||||
0x75, 0x08, # ..Report Size (8) 110
|
||||
0x95, 0x06, # ..Report Count (6) 112
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 114
|
||||
0xc0, # .End Collection 116
|
||||
0x85, 0x02, # .Report ID (2) 117
|
||||
0x05, 0x08, # .Usage Page (LEDs) 119
|
||||
0x0a, 0x01, 0x00, # .Usage (Num Lock) 121
|
||||
0x0a, 0x02, 0x00, # .Usage (Caps Lock) 124
|
||||
0x0a, 0x03, 0x00, # .Usage (Scroll Lock) 127
|
||||
0x0a, 0x04, 0x00, # .Usage (Compose) 130
|
||||
0x15, 0x00, # .Logical Minimum (0) 133
|
||||
0x25, 0x01, # .Logical Maximum (1) 135
|
||||
0x75, 0x01, # .Report Size (1) 137
|
||||
0x95, 0x04, # .Report Count (4) 139
|
||||
0x91, 0x02, # .Output (Data,Var,Abs) 141
|
||||
0x75, 0x04, # .Report Size (4) 143
|
||||
0x95, 0x01, # .Report Count (1) 145
|
||||
0x91, 0x03, # .Output (Cnst,Var,Abs) 147
|
||||
0xc0, # End Collection 149
|
||||
0x05, 0x0c, # Usage Page (Consumer Devices) 150
|
||||
0x09, 0x01, # Usage (Consumer Control) 152
|
||||
0xa1, 0x01, # Collection (Application) 154
|
||||
0x85, 0x03, # .Report ID (3) 156
|
||||
0x05, 0x01, # .Usage Page (Generic Desktop) 158
|
||||
0x09, 0x06, # .Usage (Keyboard) 160
|
||||
0xa1, 0x02, # .Collection (Logical) 162
|
||||
0x05, 0x06, # ..Usage Page (Generic Device Controls) 164
|
||||
0x09, 0x20, # ..Usage (Battery Strength) 166
|
||||
0x15, 0x00, # ..Logical Minimum (0) 168
|
||||
0x26, 0xff, 0x00, # ..Logical Maximum (255) 170
|
||||
0x75, 0x08, # ..Report Size (8) 173
|
||||
0x95, 0x01, # ..Report Count (1) 175
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 177
|
||||
0x06, 0xbc, 0xff, # ..Usage Page (Vendor Usage Page 0xffbc) 179
|
||||
0x0a, 0xad, 0xbd, # ..Usage (Vendor Usage 0xbdad) 182
|
||||
0x75, 0x08, # ..Report Size (8) 185
|
||||
0x95, 0x06, # ..Report Count (6) 187
|
||||
0x81, 0x02, # ..Input (Data,Var,Abs) 189
|
||||
0xc0, # .End Collection 191
|
||||
0xc0, # End Collection 192
|
||||
]
|
||||
# fmt: on
|
||||
|
||||
def __init__(self, rdesc=report_descriptor, name=None):
|
||||
super().__init__(rdesc, name=name, input_info=(BusType.USB, 0x18D1, 0x2C40))
|
||||
self.buttons = (1, 2, 4, 5, 7, 8, 14, 15, 13)
|
||||
|
||||
|
||||
class RaptorMach2Joystick(JoystickGamepad):
|
||||
axes_map = {
|
||||
"left_stick": {
|
||||
"x": AxisMapping("x"),
|
||||
"y": AxisMapping("y"),
|
||||
},
|
||||
"right_stick": {
|
||||
"x": AxisMapping("z"),
|
||||
"y": AxisMapping("Rz"),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
rdesc=None,
|
||||
application="Joystick",
|
||||
input_info=(BusType.USB, 0x11C0, 0x5606),
|
||||
):
|
||||
super().__init__(rdesc, application, name, input_info)
|
||||
self.buttons = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
|
||||
self.hat_switch = 240 # null value is 240 as max is 239
|
||||
|
||||
def event(
|
||||
self, *, left=(None, None), right=(None, None), hat_switch=None, buttons=None
|
||||
):
|
||||
if hat_switch is not None:
|
||||
hat_switch *= 30
|
||||
|
||||
return super().event(
|
||||
left=left, right=right, hat_switch=hat_switch, buttons=buttons
|
||||
)
|
||||
|
||||
|
||||
class TestSaitekGamepad(BaseTest.TestGamepad):
|
||||
def create_device(self):
|
||||
return SaitekGamepad()
|
||||
|
@ -207,3 +651,14 @@ class TestSaitekGamepad(BaseTest.TestGamepad):
|
|||
class TestAsusGamepad(BaseTest.TestGamepad):
|
||||
def create_device(self):
|
||||
return AsusGamepad()
|
||||
|
||||
|
||||
class TestRaptorMach2Joystick(BaseTest.TestGamepad):
|
||||
hid_bpfs = [("FR-TEC__Raptor-Mach-2.bpf.o", True)]
|
||||
|
||||
def create_device(self):
|
||||
return RaptorMach2Joystick(
|
||||
"uhid test Sanmos Group FR-TEC Raptor MACH 2",
|
||||
rdesc="05 01 09 04 a1 01 05 01 85 01 05 01 09 30 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 31 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 33 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 00 09 00 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 32 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 35 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 01 09 34 75 10 95 01 15 00 26 ff 07 46 ff 07 81 02 05 01 09 36 75 10 95 01 15 00 26 ff 03 46 ff 03 81 02 05 09 19 01 2a 1d 00 15 00 25 01 75 01 96 80 00 81 02 05 01 09 39 26 ef 00 46 68 01 65 14 75 10 95 01 81 42 05 01 09 00 75 08 95 1d 81 01 15 00 26 ef 00 85 58 26 ff 00 46 ff 00 75 08 95 3f 09 00 91 02 85 59 75 08 95 80 09 00 b1 02 c0",
|
||||
input_info=(BusType.USB, 0x11C0, 0x5606),
|
||||
)
|
||||
|
|
|
@ -35,6 +35,7 @@ class BtnPressed(Enum):
|
|||
|
||||
PRIMARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS
|
||||
SECONDARY_PRESSED = libevdev.EV_KEY.BTN_STYLUS2
|
||||
THIRD_PRESSED = libevdev.EV_KEY.BTN_STYLUS3
|
||||
|
||||
|
||||
class PenState(Enum):
|
||||
|
@ -44,58 +45,28 @@ class PenState(Enum):
|
|||
We extend it with the various buttons when we need to check them.
|
||||
"""
|
||||
|
||||
PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, None
|
||||
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, None
|
||||
PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, BtnPressed.PRIMARY_PRESSED
|
||||
PEN_IS_IN_RANGE_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.PEN,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, None
|
||||
PEN_IS_IN_CONTACT_WITH_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.PEN,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.PEN,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, None
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.UP,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, None
|
||||
PEN_IS_ERASING_WITH_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
PEN_IS_ERASING_WITH_SECOND_BUTTON = (
|
||||
BtnTouch.DOWN,
|
||||
ToolType.RUBBER,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
PEN_IS_OUT_OF_RANGE = BtnTouch.UP, None, False
|
||||
PEN_IS_IN_RANGE = BtnTouch.UP, ToolType.PEN, False
|
||||
PEN_IS_IN_RANGE_WITH_BUTTON = BtnTouch.UP, ToolType.PEN, True
|
||||
PEN_IS_IN_CONTACT = BtnTouch.DOWN, ToolType.PEN, False
|
||||
PEN_IS_IN_CONTACT_WITH_BUTTON = BtnTouch.DOWN, ToolType.PEN, True
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT = BtnTouch.UP, ToolType.RUBBER, False
|
||||
PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON = BtnTouch.UP, ToolType.RUBBER, True
|
||||
PEN_IS_ERASING = BtnTouch.DOWN, ToolType.RUBBER, False
|
||||
PEN_IS_ERASING_WITH_BUTTON = BtnTouch.DOWN, ToolType.RUBBER, True
|
||||
|
||||
def __init__(self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[BtnPressed]):
|
||||
def __init__(
|
||||
self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[bool]
|
||||
):
|
||||
self.touch = touch # type: ignore
|
||||
self.tool = tool # type: ignore
|
||||
self.button = button # type: ignore
|
||||
|
||||
@classmethod
|
||||
def from_evdev(cls, evdev) -> "PenState":
|
||||
def from_evdev(cls, evdev, test_button) -> "PenState":
|
||||
touch = BtnTouch(evdev.value[libevdev.EV_KEY.BTN_TOUCH])
|
||||
tool = None
|
||||
button = None
|
||||
button = False
|
||||
if (
|
||||
evdev.value[libevdev.EV_KEY.BTN_TOOL_RUBBER]
|
||||
and not evdev.value[libevdev.EV_KEY.BTN_TOOL_PEN]
|
||||
|
@ -112,19 +83,20 @@ class PenState(Enum):
|
|||
):
|
||||
raise ValueError("2 tools are not allowed")
|
||||
|
||||
# we take only the highest button in account
|
||||
for b in [libevdev.EV_KEY.BTN_STYLUS, libevdev.EV_KEY.BTN_STYLUS2]:
|
||||
if bool(evdev.value[b]):
|
||||
button = BtnPressed(b)
|
||||
# we take only the provided button into account
|
||||
if test_button is not None:
|
||||
button = bool(evdev.value[test_button.value])
|
||||
|
||||
# the kernel tends to insert an EV_SYN once removing the tool, so
|
||||
# the button will be released after
|
||||
if tool is None:
|
||||
button = None
|
||||
button = False
|
||||
|
||||
return cls((touch, tool, button)) # type: ignore
|
||||
|
||||
def apply(self, events: List[libevdev.InputEvent], strict: bool) -> "PenState":
|
||||
def apply(
|
||||
self, events: List[libevdev.InputEvent], strict: bool, test_button: BtnPressed
|
||||
) -> "PenState":
|
||||
if libevdev.EV_SYN.SYN_REPORT in events:
|
||||
raise ValueError("EV_SYN is in the event sequence")
|
||||
touch = self.touch
|
||||
|
@ -148,19 +120,16 @@ class PenState(Enum):
|
|||
raise ValueError(f"duplicated BTN_TOOL_* in {events}")
|
||||
tool_found = True
|
||||
tool = ToolType(ev.code) if ev.value else None
|
||||
elif ev in (
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS),
|
||||
libevdev.InputEvent(libevdev.EV_KEY.BTN_STYLUS2),
|
||||
):
|
||||
elif test_button is not None and ev in (test_button.value,):
|
||||
if button_found:
|
||||
raise ValueError(f"duplicated BTN_STYLUS* in {events}")
|
||||
button_found = True
|
||||
button = BtnPressed(ev.code) if ev.value else None
|
||||
button = bool(ev.value)
|
||||
|
||||
# the kernel tends to insert an EV_SYN once removing the tool, so
|
||||
# the button will be released after
|
||||
if tool is None:
|
||||
button = None
|
||||
button = False
|
||||
|
||||
new_state = PenState((touch, tool, button)) # type: ignore
|
||||
if strict:
|
||||
|
@ -183,11 +152,9 @@ class PenState(Enum):
|
|||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
)
|
||||
|
||||
|
@ -195,7 +162,6 @@ class PenState(Enum):
|
|||
return (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
)
|
||||
|
@ -204,7 +170,6 @@ class PenState(Enum):
|
|||
return (
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
)
|
||||
|
||||
|
@ -236,21 +201,6 @@ class PenState(Enum):
|
|||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
return tuple()
|
||||
|
||||
def historically_tolerated_transitions(self) -> Tuple["PenState", ...]:
|
||||
|
@ -263,11 +213,9 @@ class PenState(Enum):
|
|||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
)
|
||||
|
||||
|
@ -275,7 +223,6 @@ class PenState(Enum):
|
|||
return (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
)
|
||||
|
@ -284,7 +231,6 @@ class PenState(Enum):
|
|||
return (
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
@ -319,22 +265,6 @@ class PenState(Enum):
|
|||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
)
|
||||
|
||||
if self == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
return (
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
)
|
||||
|
||||
return tuple()
|
||||
|
||||
@staticmethod
|
||||
|
@ -402,9 +332,9 @@ class PenState(Enum):
|
|||
}
|
||||
|
||||
@staticmethod
|
||||
def legal_transitions_with_primary_button() -> Dict[str, Tuple["PenState", ...]]:
|
||||
def legal_transitions_with_button() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""We revisit the Windows Pen Implementation state machine:
|
||||
we now have a primary button.
|
||||
we now have a button.
|
||||
"""
|
||||
return {
|
||||
"hover-button": (PenState.PEN_IS_IN_RANGE_WITH_BUTTON,),
|
||||
|
@ -450,56 +380,6 @@ class PenState(Enum):
|
|||
),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def legal_transitions_with_secondary_button() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""We revisit the Windows Pen Implementation state machine:
|
||||
we now have a secondary button.
|
||||
Note: we don't looks for 2 buttons interactions.
|
||||
"""
|
||||
return {
|
||||
"hover-button": (PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,),
|
||||
"hover-button -> out-of-range": (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_OUT_OF_RANGE,
|
||||
),
|
||||
"in-range -> button-press": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
),
|
||||
"in-range -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> touch -> button-press -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
),
|
||||
"in-range -> touch -> button-press -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> release -> button-release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
"in-range -> button-press -> touch -> button-release -> release": (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def tolerated_transitions() -> Dict[str, Tuple["PenState", ...]]:
|
||||
"""This is not adhering to the Windows Pen Implementation state machine
|
||||
|
@ -616,10 +496,22 @@ class Pen(object):
|
|||
evdev.value[axis] == value
|
||||
), f"assert evdev.value[{axis}] ({evdev.value[axis]}) != {value}"
|
||||
|
||||
def assert_expected_input_events(self, evdev):
|
||||
def assert_expected_input_events(self, evdev, button):
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_X] == self.x
|
||||
assert evdev.value[libevdev.EV_ABS.ABS_Y] == self.y
|
||||
assert self.current_state == PenState.from_evdev(evdev)
|
||||
|
||||
# assert no other buttons than the tested ones are set
|
||||
buttons = [
|
||||
BtnPressed.PRIMARY_PRESSED,
|
||||
BtnPressed.SECONDARY_PRESSED,
|
||||
BtnPressed.THIRD_PRESSED,
|
||||
]
|
||||
if button is not None:
|
||||
buttons.remove(button)
|
||||
for b in buttons:
|
||||
assert evdev.value[b.value] is None or evdev.value[b.value] == False
|
||||
|
||||
assert self.current_state == PenState.from_evdev(evdev, button)
|
||||
|
||||
|
||||
class PenDigitizer(base.UHIDTestDevice):
|
||||
|
@ -647,7 +539,7 @@ class PenDigitizer(base.UHIDTestDevice):
|
|||
continue
|
||||
self.fields = [f.usage_name for f in r]
|
||||
|
||||
def move_to(self, pen, state):
|
||||
def move_to(self, pen, state, button):
|
||||
# fill in the previous values
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.restore()
|
||||
|
@ -690,29 +582,17 @@ class PenDigitizer(base.UHIDTestDevice):
|
|||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = True
|
||||
pen.secondarybarrelswitch = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = True
|
||||
pen.secondarybarrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_SECOND_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = True
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_SECOND_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarybarrelswitch = True
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.secondarybarrelswitch = button == BtnPressed.SECONDARY_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
|
@ -730,7 +610,7 @@ class PenDigitizer(base.UHIDTestDevice):
|
|||
|
||||
pen.current_state = state
|
||||
|
||||
def event(self, pen):
|
||||
def event(self, pen, button):
|
||||
rs = []
|
||||
r = self.create_report(application=self.cur_application, data=pen)
|
||||
self.call_input_event(r)
|
||||
|
@ -771,17 +651,17 @@ class BaseTest:
|
|||
def create_device(self):
|
||||
raise Exception("please reimplement me in subclasses")
|
||||
|
||||
def post(self, uhdev, pen):
|
||||
r = uhdev.event(pen)
|
||||
def post(self, uhdev, pen, test_button):
|
||||
r = uhdev.event(pen, test_button)
|
||||
events = uhdev.next_sync_events()
|
||||
self.debug_reports(r, uhdev, events)
|
||||
return events
|
||||
|
||||
def validate_transitions(
|
||||
self, from_state, pen, evdev, events, allow_intermediate_states
|
||||
self, from_state, pen, evdev, events, allow_intermediate_states, button
|
||||
):
|
||||
# check that the final state is correct
|
||||
pen.assert_expected_input_events(evdev)
|
||||
pen.assert_expected_input_events(evdev, button)
|
||||
|
||||
state = from_state
|
||||
|
||||
|
@ -794,12 +674,14 @@ class BaseTest:
|
|||
events = events[idx + 1 :]
|
||||
|
||||
# now check for a valid transition
|
||||
state = state.apply(sync_events, not allow_intermediate_states)
|
||||
state = state.apply(sync_events, not allow_intermediate_states, button)
|
||||
|
||||
if events:
|
||||
state = state.apply(sync_events, not allow_intermediate_states)
|
||||
state = state.apply(sync_events, not allow_intermediate_states, button)
|
||||
|
||||
def _test_states(self, state_list, scribble, allow_intermediate_states):
|
||||
def _test_states(
|
||||
self, state_list, scribble, allow_intermediate_states, button=None
|
||||
):
|
||||
"""Internal method to test against a list of
|
||||
transition between states.
|
||||
state_list is a list of PenState objects
|
||||
|
@ -812,10 +694,10 @@ class BaseTest:
|
|||
cur_state = PenState.PEN_IS_OUT_OF_RANGE
|
||||
|
||||
p = Pen(50, 60)
|
||||
uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE)
|
||||
events = self.post(uhdev, p)
|
||||
uhdev.move_to(p, PenState.PEN_IS_OUT_OF_RANGE, button)
|
||||
events = self.post(uhdev, p, button)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
cur_state, p, evdev, events, allow_intermediate_states, button
|
||||
)
|
||||
|
||||
cur_state = p.current_state
|
||||
|
@ -824,18 +706,18 @@ class BaseTest:
|
|||
if scribble and cur_state != PenState.PEN_IS_OUT_OF_RANGE:
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
events = self.post(uhdev, p, button)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
cur_state, p, evdev, events, allow_intermediate_states, button
|
||||
)
|
||||
assert len(events) >= 3 # X, Y, SYN
|
||||
uhdev.move_to(p, state)
|
||||
uhdev.move_to(p, state, button)
|
||||
if scribble and state != PenState.PEN_IS_OUT_OF_RANGE:
|
||||
p.x += 1
|
||||
p.y -= 1
|
||||
events = self.post(uhdev, p)
|
||||
events = self.post(uhdev, p, button)
|
||||
self.validate_transitions(
|
||||
cur_state, p, evdev, events, allow_intermediate_states
|
||||
cur_state, p, evdev, events, allow_intermediate_states, button
|
||||
)
|
||||
cur_state = p.current_state
|
||||
|
||||
|
@ -874,12 +756,17 @@ class BaseTest:
|
|||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.legal_transitions_with_primary_button().items()
|
||||
for k, v in PenState.legal_transitions_with_button().items()
|
||||
],
|
||||
)
|
||||
def test_valid_primary_button_pen_states(self, state_list, scribble):
|
||||
"""Rework the transition state machine by adding the primary button."""
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
self._test_states(
|
||||
state_list,
|
||||
scribble,
|
||||
allow_intermediate_states=False,
|
||||
button=BtnPressed.PRIMARY_PRESSED,
|
||||
)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Secondary Barrel Switch" not in uhdev.fields,
|
||||
|
@ -890,12 +777,38 @@ class BaseTest:
|
|||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.legal_transitions_with_secondary_button().items()
|
||||
for k, v in PenState.legal_transitions_with_button().items()
|
||||
],
|
||||
)
|
||||
def test_valid_secondary_button_pen_states(self, state_list, scribble):
|
||||
"""Rework the transition state machine by adding the secondary button."""
|
||||
self._test_states(state_list, scribble, allow_intermediate_states=False)
|
||||
self._test_states(
|
||||
state_list,
|
||||
scribble,
|
||||
allow_intermediate_states=False,
|
||||
button=BtnPressed.SECONDARY_PRESSED,
|
||||
)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Third Barrel Switch" not in uhdev.fields,
|
||||
"Device not compatible, missing Third Barrel Switch usage",
|
||||
)
|
||||
@pytest.mark.parametrize("scribble", [True, False], ids=["scribble", "static"])
|
||||
@pytest.mark.parametrize(
|
||||
"state_list",
|
||||
[
|
||||
pytest.param(v, id=k)
|
||||
for k, v in PenState.legal_transitions_with_button().items()
|
||||
],
|
||||
)
|
||||
def test_valid_third_button_pen_states(self, state_list, scribble):
|
||||
"""Rework the transition state machine by adding the secondary button."""
|
||||
self._test_states(
|
||||
state_list,
|
||||
scribble,
|
||||
allow_intermediate_states=False,
|
||||
button=BtnPressed.THIRD_PRESSED,
|
||||
)
|
||||
|
||||
@pytest.mark.skip_if_uhdev(
|
||||
lambda uhdev: "Invert" not in uhdev.fields,
|
||||
|
@ -956,7 +869,7 @@ class BaseTest:
|
|||
|
||||
|
||||
class GXTP_pen(PenDigitizer):
|
||||
def event(self, pen):
|
||||
def event(self, pen, test_button):
|
||||
if not hasattr(self, "prev_tip_state"):
|
||||
self.prev_tip_state = False
|
||||
|
||||
|
@ -977,13 +890,407 @@ class GXTP_pen(PenDigitizer):
|
|||
if pen.eraser:
|
||||
internal_pen.invert = False
|
||||
|
||||
return super().event(internal_pen)
|
||||
return super().event(internal_pen, test_button)
|
||||
|
||||
|
||||
class USIPen(PenDigitizer):
|
||||
pass
|
||||
|
||||
|
||||
class XPPen_ArtistPro16Gen2_28bd_095b(PenDigitizer):
|
||||
"""
|
||||
Pen with two buttons and a rubber end, but which reports
|
||||
the second button as an eraser
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
rdesc_str=None,
|
||||
rdesc=None,
|
||||
application="Pen",
|
||||
physical="Stylus",
|
||||
input_info=(BusType.USB, 0x28BD, 0x095B),
|
||||
evdev_name_suffix=None,
|
||||
):
|
||||
super().__init__(
|
||||
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
|
||||
)
|
||||
self.fields.append("Secondary Barrel Switch")
|
||||
|
||||
def move_to(self, pen, state, button):
|
||||
# fill in the previous values
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.restore()
|
||||
|
||||
print(f"\n *** pen is moving to {state} ***")
|
||||
|
||||
if state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.backup()
|
||||
pen.x = 0
|
||||
pen.y = 0
|
||||
pen.tipswitch = False
|
||||
pen.tippressure = 0
|
||||
pen.azimuth = 0
|
||||
pen.inrange = False
|
||||
pen.width = 0
|
||||
pen.height = 0
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.xtilt = 0
|
||||
pen.ytilt = 0
|
||||
pen.twist = 0
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = True
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_ERASING:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = True
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
|
||||
pen.current_state = state
|
||||
|
||||
def event(self, pen, test_button):
|
||||
import math
|
||||
|
||||
pen_copy = copy.copy(pen)
|
||||
width = 13.567
|
||||
height = 8.480
|
||||
tip_height = 0.055677699
|
||||
hx = tip_height * (32767 / width)
|
||||
hy = tip_height * (32767 / height)
|
||||
if pen_copy.xtilt != 0:
|
||||
pen_copy.x += round(hx * math.sin(math.radians(pen_copy.xtilt)))
|
||||
if pen_copy.ytilt != 0:
|
||||
pen_copy.y += round(hy * math.sin(math.radians(pen_copy.ytilt)))
|
||||
|
||||
return super().event(pen_copy, test_button)
|
||||
|
||||
|
||||
class XPPen_Artist24_28bd_093a(PenDigitizer):
|
||||
"""
|
||||
Pen that reports secondary barrel switch through eraser
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
rdesc_str=None,
|
||||
rdesc=None,
|
||||
application="Pen",
|
||||
physical="Stylus",
|
||||
input_info=(BusType.USB, 0x28BD, 0x093A),
|
||||
evdev_name_suffix=None,
|
||||
):
|
||||
super().__init__(
|
||||
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
|
||||
)
|
||||
self.fields.append("Secondary Barrel Switch")
|
||||
self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
|
||||
|
||||
def move_to(self, pen, state, button, debug=True):
|
||||
# fill in the previous values
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.restore()
|
||||
|
||||
if debug:
|
||||
print(f"\n *** pen is moving to {state} ***")
|
||||
|
||||
if state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.backup()
|
||||
pen.tipswitch = False
|
||||
pen.tippressure = 0
|
||||
pen.azimuth = 0
|
||||
pen.inrange = False
|
||||
pen.width = 0
|
||||
pen.height = 0
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.xtilt = 0
|
||||
pen.ytilt = 0
|
||||
pen.twist = 0
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.eraser = button == BtnPressed.SECONDARY_PRESSED
|
||||
|
||||
pen.current_state = state
|
||||
|
||||
def send_intermediate_state(self, pen, state, button):
|
||||
intermediate_pen = copy.copy(pen)
|
||||
self.move_to(intermediate_pen, state, button, debug=False)
|
||||
return super().event(intermediate_pen, button)
|
||||
|
||||
def event(self, pen, button):
|
||||
rs = []
|
||||
|
||||
# the pen reliably sends in-range events in a normal case (non emulation of eraser mode)
|
||||
if self.previous_state == PenState.PEN_IS_IN_CONTACT:
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
|
||||
)
|
||||
|
||||
if button == BtnPressed.SECONDARY_PRESSED:
|
||||
if self.previous_state == PenState.PEN_IS_IN_RANGE:
|
||||
if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_OUT_OF_RANGE, button
|
||||
)
|
||||
)
|
||||
|
||||
if self.previous_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
if pen.current_state == PenState.PEN_IS_IN_RANGE:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_OUT_OF_RANGE, button
|
||||
)
|
||||
)
|
||||
|
||||
if self.previous_state == PenState.PEN_IS_IN_CONTACT:
|
||||
if pen.current_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_OUT_OF_RANGE, button
|
||||
)
|
||||
)
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_IN_RANGE_WITH_BUTTON, button
|
||||
)
|
||||
)
|
||||
|
||||
if self.previous_state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
if pen.current_state == PenState.PEN_IS_IN_CONTACT:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_OUT_OF_RANGE, button
|
||||
)
|
||||
)
|
||||
rs.extend(
|
||||
self.send_intermediate_state(
|
||||
pen, PenState.PEN_IS_IN_RANGE, button
|
||||
)
|
||||
)
|
||||
|
||||
rs.extend(super().event(pen, button))
|
||||
self.previous_state = pen.current_state
|
||||
return rs
|
||||
|
||||
|
||||
class Huion_Kamvas_Pro_19_256c_006b(PenDigitizer):
|
||||
"""
|
||||
Pen that reports secondary barrel switch through secondary TipSwtich
|
||||
and 3rd button through Invert
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
rdesc_str=None,
|
||||
rdesc=None,
|
||||
application="Stylus",
|
||||
physical=None,
|
||||
input_info=(BusType.USB, 0x256C, 0x006B),
|
||||
evdev_name_suffix=None,
|
||||
):
|
||||
super().__init__(
|
||||
name, rdesc_str, rdesc, application, physical, input_info, evdev_name_suffix
|
||||
)
|
||||
self.fields.append("Secondary Barrel Switch")
|
||||
self.fields.append("Third Barrel Switch")
|
||||
self.previous_state = PenState.PEN_IS_OUT_OF_RANGE
|
||||
|
||||
def move_to(self, pen, state, button, debug=True):
|
||||
# fill in the previous values
|
||||
if pen.current_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.restore()
|
||||
|
||||
if debug:
|
||||
print(f"\n *** pen is moving to {state} ***")
|
||||
|
||||
if state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
pen.backup()
|
||||
pen.tipswitch = False
|
||||
pen.tippressure = 0
|
||||
pen.azimuth = 0
|
||||
pen.inrange = False
|
||||
pen.width = 0
|
||||
pen.height = 0
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.xtilt = 0
|
||||
pen.ytilt = 0
|
||||
pen.twist = 0
|
||||
pen.barrelswitch = False
|
||||
pen.secondarytipswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarytipswitch = False
|
||||
elif state == PenState.PEN_IS_IN_CONTACT:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarytipswitch = False
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.eraser = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
|
||||
pen.invert = button == BtnPressed.THIRD_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_CONTACT_WITH_BUTTON:
|
||||
pen.tipswitch = True
|
||||
pen.inrange = True
|
||||
pen.eraser = False
|
||||
assert button is not None
|
||||
pen.barrelswitch = button == BtnPressed.PRIMARY_PRESSED
|
||||
pen.secondarytipswitch = button == BtnPressed.SECONDARY_PRESSED
|
||||
pen.invert = button == BtnPressed.THIRD_PRESSED
|
||||
elif state == PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = True
|
||||
pen.eraser = False
|
||||
pen.barrelswitch = False
|
||||
pen.secondarytipswitch = False
|
||||
elif state == PenState.PEN_IS_ERASING:
|
||||
pen.tipswitch = False
|
||||
pen.inrange = True
|
||||
pen.invert = False
|
||||
pen.eraser = True
|
||||
pen.barrelswitch = False
|
||||
pen.secondarytipswitch = False
|
||||
|
||||
pen.current_state = state
|
||||
|
||||
def call_input_event(self, report):
|
||||
if report[0] == 0x0a:
|
||||
# ensures the original second Eraser usage is null
|
||||
report[1] &= 0xdf
|
||||
|
||||
# ensures the original last bit is equal to bit 6 (In Range)
|
||||
if report[1] & 0x40:
|
||||
report[1] |= 0x80
|
||||
|
||||
super().call_input_event(report)
|
||||
|
||||
def send_intermediate_state(self, pen, state, test_button):
|
||||
intermediate_pen = copy.copy(pen)
|
||||
self.move_to(intermediate_pen, state, test_button, debug=False)
|
||||
return super().event(intermediate_pen, test_button)
|
||||
|
||||
def event(self, pen, button):
|
||||
rs = []
|
||||
|
||||
# it's not possible to go between eraser mode or not without
|
||||
# going out-of-prox: the eraser mode is activated by presenting
|
||||
# the tail of the pen
|
||||
if self.previous_state in (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
) and pen.current_state in (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
PenState.PEN_IS_ERASING_WITH_BUTTON,
|
||||
):
|
||||
rs.extend(
|
||||
self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
|
||||
)
|
||||
|
||||
# same than above except from eraser to normal
|
||||
if self.previous_state in (
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_ERASING_INTENT_WITH_BUTTON,
|
||||
PenState.PEN_IS_ERASING,
|
||||
PenState.PEN_IS_ERASING_WITH_BUTTON,
|
||||
) and pen.current_state in (
|
||||
PenState.PEN_IS_IN_RANGE,
|
||||
PenState.PEN_IS_IN_RANGE_WITH_BUTTON,
|
||||
PenState.PEN_IS_IN_CONTACT,
|
||||
PenState.PEN_IS_IN_CONTACT_WITH_BUTTON,
|
||||
):
|
||||
rs.extend(
|
||||
self.send_intermediate_state(pen, PenState.PEN_IS_OUT_OF_RANGE, button)
|
||||
)
|
||||
|
||||
if self.previous_state == PenState.PEN_IS_OUT_OF_RANGE:
|
||||
if pen.current_state == PenState.PEN_IS_IN_RANGE_WITH_BUTTON:
|
||||
rs.extend(
|
||||
self.send_intermediate_state(pen, PenState.PEN_IS_IN_RANGE, button)
|
||||
)
|
||||
|
||||
rs.extend(super().event(pen, button))
|
||||
self.previous_state = pen.current_state
|
||||
return rs
|
||||
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# Windows 7 compatible devices
|
||||
|
@ -1162,3 +1469,37 @@ class TestGoodix_27c6_0e00(BaseTest.TestTablet):
|
|||
rdesc="05 0d 09 04 a1 01 85 01 09 22 a1 02 55 0e 65 11 35 00 15 00 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 95 01 75 08 09 51 81 02 75 10 05 01 26 04 20 46 e6 09 09 30 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 22 a1 02 09 42 15 00 25 01 75 01 95 01 81 02 25 7f 09 30 75 07 81 42 75 08 09 51 95 01 81 02 05 01 26 04 20 75 10 55 0e 65 11 09 30 35 00 46 e6 09 81 02 26 60 15 46 9a 06 09 31 81 02 05 0d 55 0f 75 08 25 ff 45 ff 09 48 81 42 09 49 81 42 55 0e c0 09 54 15 00 25 7f 75 08 95 01 81 02 85 02 09 55 95 01 25 0a b1 02 85 03 06 00 ff 09 c5 15 00 26 ff 00 75 08 96 00 01 b1 02 c0 05 0d 09 02 a1 01 09 20 a1 00 85 08 05 01 a4 09 30 35 00 46 e6 09 15 00 26 04 20 55 0d 65 13 75 10 95 01 81 02 09 31 46 9a 06 26 60 15 81 02 b4 05 0d 09 38 95 01 75 08 15 00 25 01 81 02 09 30 75 10 26 ff 0f 81 02 09 31 81 02 09 42 09 44 09 5a 09 3c 09 45 09 32 75 01 95 06 25 01 81 02 95 02 81 03 09 3d 55 0e 65 14 36 d8 dc 46 28 23 16 d8 dc 26 28 23 95 01 75 10 81 02 09 3e 81 02 09 41 15 00 27 a0 8c 00 00 35 00 47 a0 8c 00 00 81 02 05 20 0a 53 04 65 00 16 01 f8 26 ff 07 75 10 95 01 81 02 0a 54 04 81 02 0a 55 04 81 02 0a 57 04 81 02 0a 58 04 81 02 0a 59 04 81 02 0a 72 04 81 02 0a 73 04 81 02 0a 74 04 81 02 05 0d 09 3b 15 00 25 64 75 08 81 02 09 5b 25 ff 75 40 81 02 06 00 ff 09 5b 75 20 81 02 05 0d 09 5c 26 ff 00 75 08 81 02 09 5e 81 02 09 70 a1 02 15 01 25 06 09 72 09 73 09 74 09 75 09 76 09 77 81 20 c0 06 00 ff 09 01 15 00 27 ff ff 00 00 75 10 95 01 81 02 85 09 09 81 a1 02 09 81 15 01 25 04 09 82 09 83 09 84 09 85 81 20 c0 85 10 09 5c a1 02 15 00 25 01 75 08 95 01 09 38 b1 02 09 5c 26 ff 00 b1 02 09 5d 75 01 95 01 25 01 b1 02 95 07 b1 03 c0 85 11 09 5e a1 02 09 38 15 00 25 01 75 08 95 01 b1 02 09 5e 26 ff 00 b1 02 09 5f 75 01 25 01 b1 02 75 07 b1 03 c0 85 12 09 70 a1 02 75 08 95 01 15 00 25 01 09 38 b1 02 09 70 a1 02 25 06 09 72 09 73 09 74 09 75 09 76 09 77 b1 20 c0 09 71 75 01 25 01 b1 02 75 07 b1 03 c0 85 13 09 80 15 00 25 ff 75 40 95 01 b1 02 85 14 09 44 a1 02 09 38 75 08 95 01 25 01 b1 02 15 01 25 03 09 44 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 5a a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 09 45 a1 02 09 a4 09 44 09 5a 09 45 09 a3 b1 20 c0 c0 85 15 75 08 95 01 05 0d 09 90 a1 02 09 38 25 01 b1 02 09 91 75 10 26 ff 0f b1 02 09 92 75 40 25 ff b1 02 05 06 09 2a 75 08 26 ff 00 a1 02 09 2d b1 02 09 2e b1 02 c0 c0 85 16 05 06 09 2b a1 02 05 0d 25 01 09 38 b1 02 05 06 09 2b a1 02 09 2d 26 ff 00 b1 02 09 2e b1 02 c0 c0 85 17 06 00 ff 09 01 a1 02 05 0d 09 38 75 08 95 01 25 01 b1 02 06 00 ff 09 01 75 10 27 ff ff 00 00 b1 02 c0 85 18 05 0d 09 38 75 08 95 01 15 00 25 01 b1 02 c0 c0 06 f0 ff 09 01 a1 01 85 0e 09 01 15 00 25 ff 75 08 95 40 91 02 09 01 15 00 25 ff 75 08 95 40 81 02 c0",
|
||||
input_info=(BusType.I2C, 0x27C6, 0x0E00),
|
||||
)
|
||||
|
||||
|
||||
class TestXPPen_ArtistPro16Gen2_28bd_095b(BaseTest.TestTablet):
|
||||
hid_bpfs = [("XPPen__ArtistPro16Gen2.bpf.o", True)]
|
||||
|
||||
def create_device(self):
|
||||
dev = XPPen_ArtistPro16Gen2_28bd_095b(
|
||||
"uhid test XPPen Artist Pro 16 Gen2 28bd 095b",
|
||||
rdesc="05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 09 3c 15 00 25 01 75 01 95 04 81 02 95 01 81 03 09 32 15 00 25 01 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 ff 34 26 ff 7f 81 02 09 31 46 20 21 26 ff 7f 81 02 b4 09 30 45 00 26 ff 3f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0",
|
||||
input_info=(BusType.USB, 0x28BD, 0x095B),
|
||||
)
|
||||
return dev
|
||||
|
||||
|
||||
class TestXPPen_Artist24_28bd_093a(BaseTest.TestTablet):
|
||||
hid_bpfs = [("XPPen__Artist24.bpf.o", True)]
|
||||
|
||||
def create_device(self):
|
||||
return XPPen_Artist24_28bd_093a(
|
||||
"uhid test XPPen Artist 24 28bd 093a",
|
||||
rdesc="05 0d 09 02 a1 01 85 07 09 20 a1 00 09 42 09 44 09 45 15 00 25 01 75 01 95 03 81 02 95 02 81 03 09 32 95 01 81 02 95 02 81 03 75 10 95 01 35 00 a4 05 01 09 30 65 13 55 0d 46 f0 50 26 ff 7f 81 02 09 31 46 91 2d 26 ff 7f 81 02 b4 09 30 45 00 26 ff 1f 81 42 09 3d 15 81 25 7f 75 08 95 01 81 02 09 3e 15 81 25 7f 81 02 c0 c0",
|
||||
input_info=(BusType.USB, 0x28BD, 0x093A),
|
||||
)
|
||||
|
||||
|
||||
class TestHuion_Kamvas_Pro_19_256c_006b(BaseTest.TestTablet):
|
||||
hid_bpfs = [("Huion__Kamvas-Pro-19.bpf.o", True)]
|
||||
|
||||
def create_device(self):
|
||||
return Huion_Kamvas_Pro_19_256c_006b(
|
||||
"uhid test HUION Huion Tablet_GT1902",
|
||||
rdesc="05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 43 09 3c 09 45 15 00 25 01 75 01 95 06 81 02 09 32 75 01 95 01 81 02 81 03 05 01 09 30 09 31 55 0d 65 33 26 ff 7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 3f 75 10 95 01 81 02 09 3d 09 3e 15 a6 25 5a 75 08 95 02 81 02 c0 c0 05 0d 09 04 a1 01 85 04 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 22 a1 02 05 0d 95 01 75 06 09 51 15 00 25 3f 81 02 09 42 25 01 75 01 95 01 81 02 75 01 95 01 81 03 05 01 75 10 55 0e 65 11 09 30 26 ff 7f 35 00 46 15 0c 81 42 09 31 26 ff 7f 46 cb 06 81 42 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 c0 05 0d 09 56 55 00 65 00 27 ff ff ff 7f 95 01 75 20 81 02 09 54 25 7f 95 01 75 08 81 02 75 08 95 08 81 03 85 05 09 55 25 0a 75 08 95 01 b1 02 06 00 ff 09 c5 85 06 15 00 26 ff 00 75 08 96 00 01 b1 02 c0",
|
||||
input_info=(BusType.USB, 0x256C, 0x006B),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue