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:
Linus Torvalds 2024-05-14 15:12:46 -07:00
commit 614da38e2f
66 changed files with 8300 additions and 1092 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

226
drivers/hid/hid-winwing.c Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -238,3 +238,4 @@ CONFIG_VLAN_8021Q=y
CONFIG_XFRM_SUB_POLICY=y
CONFIG_XFRM_USER=y
CONFIG_ZEROPLUS_FF=y
CONFIG_KASAN=y

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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