diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-15 01:12:46 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2024-05-15 01:12:46 +0300 |
commit | 614da38e2f7afe9e01c6e359dfa09285f26fa381 (patch) | |
tree | bd5b66bade4842bb1a8b2c55771c1d23398a1213 | |
parent | ce952d8f0e9b58dc6a2bde7e47ca7fa7925583cc (diff) | |
parent | c9c92fc4c2ef4e2f11af0ba19cb18d9b5e3e6f08 (diff) | |
download | linux-614da38e2f7afe9e01c6e359dfa09285f26fa381.tar.xz |
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
...
66 files changed, 8301 insertions, 1093 deletions
diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst index 4fad83a6ebc3..0765b3298ecf 100644 --- a/Documentation/hid/hid-bpf.rst +++ b/Documentation/hid/hid-bpf.rst @@ -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 ===================================== diff --git a/Documentation/hid/intel-ish-hid.rst b/Documentation/hid/intel-ish-hid.rst index 42dc77b7b10f..55cbaa719a79 100644 --- a/Documentation/hid/intel-ish-hid.rst +++ b/Documentation/hid/intel-ish-hid.rst @@ -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 +------------------------------------------------- :: diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4c682c650704..08446c89eff6 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -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 diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 082a728eac60..ce71b53ea6c5 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -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 diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 9e97c26c4482..0c28ca349bcd 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -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; diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c index 5b24d5f63701..621793d92464 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c @@ -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; } diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 2de2668a0277..4676f060da26 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -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; diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c index e630caf644e8..10289f44d0cc 100644 --- a/drivers/hid/bpf/hid_bpf_dispatch.c +++ b/drivers/hid/bpf/hid_bpf_dispatch.c @@ -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); @@ -282,6 +240,31 @@ static int do_hid_bpf_attach_prog(struct hid_device *hdev, int prog_fd, struct b __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 * * @hid_id: the system unique identifier of the 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 = { diff --git a/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c new file mode 100644 index 000000000000..dc26a7677d36 --- /dev/null +++ b/drivers/hid/bpf/progs/FR-TEC__Raptor-Mach-2.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c new file mode 100644 index 000000000000..3d14bbb6f276 --- /dev/null +++ b/drivers/hid/bpf/progs/HP__Elite-Presenter.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c new file mode 100644 index 000000000000..ff759f2276f9 --- /dev/null +++ b/drivers/hid/bpf/progs/Huion__Kamvas-Pro-19.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c new file mode 100644 index 000000000000..225cbefdbf0e --- /dev/null +++ b/drivers/hid/bpf/progs/IOGEAR__Kaliber-MMOmentum.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/Makefile b/drivers/hid/bpf/progs/Makefile new file mode 100644 index 000000000000..63ed7e02adf1 --- /dev/null +++ b/drivers/hid/bpf/progs/Makefile @@ -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 diff --git a/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c b/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c new file mode 100644 index 000000000000..c04abecab8ee --- /dev/null +++ b/drivers/hid/bpf/progs/Microsoft__XBox-Elite-2.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/README b/drivers/hid/bpf/progs/README new file mode 100644 index 000000000000..20b0928f385b --- /dev/null +++ b/drivers/hid/bpf/progs/README @@ -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 +``` diff --git a/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c new file mode 100644 index 000000000000..dc05aa48faa7 --- /dev/null +++ b/drivers/hid/bpf/progs/Wacom__ArtPen.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c new file mode 100644 index 000000000000..e1be6a12bb75 --- /dev/null +++ b/drivers/hid/bpf/progs/XPPen__Artist24.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c new file mode 100644 index 000000000000..65ef10036126 --- /dev/null +++ b/drivers/hid/bpf/progs/XPPen__ArtistPro16Gen2.bpf.c @@ -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"; diff --git a/drivers/hid/bpf/progs/hid_bpf.h b/drivers/hid/bpf/progs/hid_bpf.h new file mode 100644 index 000000000000..7ee371cac2e1 --- /dev/null +++ b/drivers/hid/bpf/progs/hid_bpf.h @@ -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 */ diff --git a/drivers/hid/bpf/progs/hid_bpf_helpers.h b/drivers/hid/bpf/progs/hid_bpf_helpers.h new file mode 100644 index 000000000000..8f226f6e886b --- /dev/null +++ b/drivers/hid/bpf/progs/hid_bpf_helpers.h @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef __HID_BPF_HELPERS_H +#define __HID_BPF_HELPERS_H + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <linux/errno.h> + +extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, + unsigned int offset, + const size_t __sz) __ksym; +extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym; +extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym; +extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx, + __u8 *data, + size_t buf__sz, + enum hid_report_type type, + enum hid_class_request reqtype) __ksym; + +#define HID_MAX_DESCRIPTOR_SIZE 4096 +#define HID_IGNORE_EVENT -1 + +/* extracted from <linux/input.h> */ +#define BUS_ANY 0x00 +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B +#define BUS_SPI 0x1C +#define BUS_RMI 0x1D +#define BUS_CEC 0x1E +#define BUS_INTEL_ISHTP 0x1F +#define BUS_AMD_SFH 0x20 + +/* extracted from <linux/hid.h> */ +#define HID_GROUP_ANY 0x0000 +#define HID_GROUP_GENERIC 0x0001 +#define HID_GROUP_MULTITOUCH 0x0002 +#define HID_GROUP_SENSOR_HUB 0x0003 +#define HID_GROUP_MULTITOUCH_WIN_8 0x0004 +#define HID_GROUP_RMI 0x0100 +#define HID_GROUP_WACOM 0x0101 +#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102 +#define HID_GROUP_STEAM 0x0103 +#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104 +#define HID_GROUP_VIVALDI 0x0105 + +/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */ +#define HID_VID_ANY 0x0000 +#define HID_PID_ANY 0x0000 + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for + * field/variable names + */ +#define COMBINE1(X, Y) X ## Y +#define COMBINE(X, Y) COMBINE1(X, Y) + +/* Macro magic: + * __uint(foo, 123) creates a int (*foo)[1234] + * + * We use that macro to declare an anonymous struct with several + * fields, each is the declaration of an pointer to an array of size + * bus/group/vid/pid. (Because it's a pointer to such an array, actual storage + * would be sizeof(pointer) rather than sizeof(array). Not that we ever + * instantiate it anyway). + * + * This is only used for BTF introspection, we can later check "what size + * is the bus array" in the introspection data and thus extract the bus ID + * again. + * + * And we use the __LINE__ to give each of our structs a unique name so the + * BPF program writer doesn't have to. + * + * $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o + * shows the inspection data, start by searching for .hid_bpf_config + * and working backwards from that (each entry references the type_id of the + * content). + */ + +#define HID_DEVICE(b, g, ven, prod) \ + struct { \ + __uint(name, 0); \ + __uint(bus, (b)); \ + __uint(group, (g)); \ + __uint(vid, (ven)); \ + __uint(pid, (prod)); \ + } COMBINE(_entry, __LINE__) + +/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that + * we can pass multiple HID_DEVICE() invocations in. + * + * For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to + * + * union { + * HID_DEVICE(...); + * HID_DEVICE(...); + * } _device_ids SEC(".hid_bpf_config") + * + */ + +/* Returns the number of macro arguments, this expands + * NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1). + * NTH_ARG always returns the 16th argument which in our case is 3. + * + * If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be + * updated. + */ +#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN) +#define _NARGS1(...) _NTH_ARG(__VA_ARGS__) + +/* Add to this if we need more than 16 args */ +#define _COUNTDOWN \ + 15, 14, 13, 12, 11, 10, 9, 8, \ + 7, 6, 5, 4, 3, 2, 1, 0 + +/* Return the 16 argument passed in. See _NARGS above for usage. Note this is + * 1-indexed. + */ +#define _NTH_ARG( \ + _1, _2, _3, _4, _5, _6, _7, _8, \ + _9, _10, _11, _12, _13, _14, _15,\ + N, ...) N + +/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */ +#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__) + +/* And now define all the ARG macros for each number of args we want to accept */ +#define _ARG1(_1) _1; +#define _ARG2(_1, _2) _1; _2; +#define _ARG3(_1, _2, _3) _1; _2; _3; +#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4; +#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5; +#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6; +#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7; +#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8; +#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9; +#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; +#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; +#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; +#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; +#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; +#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f; + + +#define HID_BPF_CONFIG(...) union { \ + _EXPAND(_ARG, __VA_ARGS__) \ +} _device_ids SEC(".hid_bpf_config") + +#endif /* __HID_BPF_HELPERS_H */ diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 78cdfb8b9a7a..02de2bf4f790 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -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; } @@ -1277,6 +1243,12 @@ static const struct hid_device_id asus_devices[] = { 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 }, { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, @@ -1319,4 +1291,4 @@ static struct hid_driver asus_driver = { }; module_hid_driver(asus_driver); -MODULE_LICENSE("GPL");
\ No newline at end of file +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index de7a477d6665..b1fa0378e8f4 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -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, }; diff --git a/drivers/hid/hid-corsair.c b/drivers/hid/hid-corsair.c index 8c895c820b67..702f50e9841d 100644 --- a/drivers/hid/hid-corsair.c +++ b/drivers/hid/hid-corsair.c @@ -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); diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 7dd83ec74f8a..87a961cae775 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -37,437 +37,2808 @@ struct hid_usage_entry { }; static const struct hid_usage_entry hid_usage_table[] = { - { 0, 0, "Undefined" }, - { 1, 0, "GenericDesktop" }, - {0, 0x01, "Pointer"}, - {0, 0x02, "Mouse"}, - {0, 0x04, "Joystick"}, - {0, 0x05, "GamePad"}, - {0, 0x06, "Keyboard"}, - {0, 0x07, "Keypad"}, - {0, 0x08, "MultiAxis"}, - {0, 0x30, "X"}, - {0, 0x31, "Y"}, - {0, 0x32, "Z"}, - {0, 0x33, "Rx"}, - {0, 0x34, "Ry"}, - {0, 0x35, "Rz"}, - {0, 0x36, "Slider"}, - {0, 0x37, "Dial"}, - {0, 0x38, "Wheel"}, - {0, 0x39, "HatSwitch"}, - {0, 0x3a, "CountedBuffer"}, - {0, 0x3b, "ByteCount"}, - {0, 0x3c, "MotionWakeup"}, - {0, 0x3d, "Start"}, - {0, 0x3e, "Select"}, - {0, 0x40, "Vx"}, - {0, 0x41, "Vy"}, - {0, 0x42, "Vz"}, - {0, 0x43, "Vbrx"}, - {0, 0x44, "Vbry"}, - {0, 0x45, "Vbrz"}, - {0, 0x46, "Vno"}, - {0, 0x80, "SystemControl"}, - {0, 0x81, "SystemPowerDown"}, - {0, 0x82, "SystemSleep"}, - {0, 0x83, "SystemWakeUp"}, - {0, 0x84, "SystemContextMenu"}, - {0, 0x85, "SystemMainMenu"}, - {0, 0x86, "SystemAppMenu"}, - {0, 0x87, "SystemMenuHelp"}, - {0, 0x88, "SystemMenuExit"}, - {0, 0x89, "SystemMenuSelect"}, - {0, 0x8a, "SystemMenuRight"}, - {0, 0x8b, "SystemMenuLeft"}, - {0, 0x8c, "SystemMenuUp"}, - {0, 0x8d, "SystemMenuDown"}, - {0, 0x90, "D-PadUp"}, - {0, 0x91, "D-PadDown"}, - {0, 0x92, "D-PadRight"}, - {0, 0x93, "D-PadLeft"}, - { 2, 0, "Simulation" }, - {0, 0xb0, "Aileron"}, - {0, 0xb1, "AileronTrim"}, - {0, 0xb2, "Anti-Torque"}, - {0, 0xb3, "Autopilot"}, - {0, 0xb4, "Chaff"}, - {0, 0xb5, "Collective"}, - {0, 0xb6, "DiveBrake"}, - {0, 0xb7, "ElectronicCountermeasures"}, - {0, 0xb8, "Elevator"}, - {0, 0xb9, "ElevatorTrim"}, - {0, 0xba, "Rudder"}, - {0, 0xbb, "Throttle"}, - {0, 0xbc, "FlightCommunications"}, - {0, 0xbd, "FlareRelease"}, - {0, 0xbe, "LandingGear"}, - {0, 0xbf, "ToeBrake"}, - { 6, 0, "GenericDeviceControls" }, - {0, 0x20, "BatteryStrength" }, - {0, 0x21, "WirelessChannel" }, - {0, 0x22, "WirelessID" }, - {0, 0x23, "DiscoverWirelessControl" }, - {0, 0x24, "SecurityCodeCharacterEntered" }, - {0, 0x25, "SecurityCodeCharactedErased" }, - {0, 0x26, "SecurityCodeCleared" }, - { 7, 0, "Keyboard" }, - { 8, 0, "LED" }, - {0, 0x01, "NumLock"}, - {0, 0x02, "CapsLock"}, - {0, 0x03, "ScrollLock"}, - {0, 0x04, "Compose"}, - {0, 0x05, "Kana"}, - {0, 0x4b, "GenericIndicator"}, - { 9, 0, "Button" }, - { 10, 0, "Ordinal" }, - { 12, 0, "Consumer" }, - {0, 0x003, "ProgrammableButtons"}, - {0, 0x238, "HorizontalWheel"}, - { 13, 0, "Digitizers" }, - {0, 0x01, "Digitizer"}, - {0, 0x02, "Pen"}, - {0, 0x03, "LightPen"}, - {0, 0x04, "TouchScreen"}, - {0, 0x05, "TouchPad"}, - {0, 0x0e, "DeviceConfiguration"}, - {0, 0x20, "Stylus"}, - {0, 0x21, "Puck"}, - {0, 0x22, "Finger"}, - {0, 0x23, "DeviceSettings"}, - {0, 0x30, "TipPressure"}, - {0, 0x31, "BarrelPressure"}, - {0, 0x32, "InRange"}, - {0, 0x33, "Touch"}, - {0, 0x34, "UnTouch"}, - {0, 0x35, "Tap"}, - {0, 0x38, "Transducer Index"}, - {0, 0x39, "TabletFunctionKey"}, - {0, 0x3a, "ProgramChangeKey"}, - {0, 0x3B, "Battery Strength"}, - {0, 0x3c, "Invert"}, - {0, 0x42, "TipSwitch"}, - {0, 0x43, "SecondaryTipSwitch"}, - {0, 0x44, "BarrelSwitch"}, - {0, 0x45, "Eraser"}, - {0, 0x46, "TabletPick"}, - {0, 0x47, "Confidence"}, - {0, 0x48, "Width"}, - {0, 0x49, "Height"}, - {0, 0x51, "ContactID"}, - {0, 0x52, "InputMode"}, - {0, 0x53, "DeviceIndex"}, - {0, 0x54, "ContactCount"}, - {0, 0x55, "ContactMaximumNumber"}, - {0, 0x59, "ButtonType"}, - {0, 0x5A, "SecondaryBarrelSwitch"}, - {0, 0x5B, "TransducerSerialNumber"}, - {0, 0x5C, "Preferred Color"}, - {0, 0x5D, "Preferred Color is Locked"}, - {0, 0x5E, "Preferred Line Width"}, - {0, 0x5F, "Preferred Line Width is Locked"}, - {0, 0x6e, "TransducerSerialNumber2"}, - {0, 0x70, "Preferred Line Style"}, - {0, 0x71, "Preferred Line Style is Locked"}, - {0, 0x72, "Ink"}, - {0, 0x73, "Pencil"}, - {0, 0x74, "Highlighter"}, - {0, 0x75, "Chisel Marker"}, - {0, 0x76, "Brush"}, - {0, 0x77, "No Preference"}, - {0, 0x80, "Digitizer Diagnostic"}, - {0, 0x81, "Digitizer Error"}, - {0, 0x82, "Err Normal Status"}, - {0, 0x83, "Err Transducers Exceeded"}, - {0, 0x84, "Err Full Trans Features Unavailable"}, - {0, 0x85, "Err Charge Low"}, - {0, 0x90, "Transducer Software Info"}, - {0, 0x91, "Transducer Vendor Id"}, - {0, 0x92, "Transducer Product Id"}, - {0, 0x93, "Device Supported Protocols"}, - {0, 0x94, "Transducer Supported Protocols"}, - {0, 0x95, "No Protocol"}, - {0, 0x96, "Wacom AES Protocol"}, - {0, 0x97, "USI Protocol"}, - {0, 0x98, "Microsoft Pen Protocol"}, - {0, 0xA0, "Supported Report Rates"}, - {0, 0xA1, "Report Rate"}, - {0, 0xA2, "Transducer Connected"}, - {0, 0xA3, "Switch Disabled"}, - {0, 0xA4, "Switch Unimplemented"}, - {0, 0xA5, "Transducer Switches"}, - { 15, 0, "PhysicalInterfaceDevice" }, - {0, 0x00, "Undefined"}, - {0, 0x01, "Physical_Interface_Device"}, - {0, 0x20, "Normal"}, - {0, 0x21, "Set_Effect_Report"}, - {0, 0x22, "Effect_Block_Index"}, - {0, 0x23, "Parameter_Block_Offset"}, - {0, 0x24, "ROM_Flag"}, - {0, 0x25, "Effect_Type"}, - {0, 0x26, "ET_Constant_Force"}, - {0, 0x27, "ET_Ramp"}, - {0, 0x28, "ET_Custom_Force_Data"}, - {0, 0x30, "ET_Square"}, - {0, 0x31, "ET_Sine"}, - {0, 0x32, "ET_Triangle"}, - {0, 0x33, "ET_Sawtooth_Up"}, - {0, 0x34, "ET_Sawtooth_Down"}, - {0, 0x40, "ET_Spring"}, - {0, 0x41, "ET_Damper"}, - {0, 0x42, "ET_Inertia"}, - {0, 0x43, "ET_Friction"}, - {0, 0x50, "Duration"}, - {0, 0x51, "Sample_Period"}, - {0, 0x52, "Gain"}, - {0, 0x53, "Trigger_Button"}, - {0, 0x54, "Trigger_Repeat_Interval"}, - {0, 0x55, "Axes_Enable"}, - {0, 0x56, "Direction_Enable"}, - {0, 0x57, "Direction"}, - {0, 0x58, "Type_Specific_Block_Offset"}, - {0, 0x59, "Block_Type"}, - {0, 0x5A, "Set_Envelope_Report"}, - {0, 0x5B, "Attack_Level"}, - {0, 0x5C, "Attack_Time"}, - {0, 0x5D, "Fade_Level"}, - {0, 0x5E, "Fade_Time"}, - {0, 0x5F, "Set_Condition_Report"}, - {0, 0x60, "CP_Offset"}, - {0, 0x61, "Positive_Coefficient"}, - {0, 0x62, "Negative_Coefficient"}, - {0, 0x63, "Positive_Saturation"}, - {0, 0x64, "Negative_Saturation"}, - {0, 0x65, "Dead_Band"}, - {0, 0x66, "Download_Force_Sample"}, - {0, 0x67, "Isoch_Custom_Force_Enable"}, - {0, 0x68, "Custom_Force_Data_Report"}, - {0, 0x69, "Custom_Force_Data"}, - {0, 0x6A, "Custom_Force_Vendor_Defined_Data"}, - {0, 0x6B, "Set_Custom_Force_Report"}, - {0, 0x6C, "Custom_Force_Data_Offset"}, - {0, 0x6D, "Sample_Count"}, - {0, 0x6E, "Set_Periodic_Report"}, - {0, 0x6F, "Offset"}, - {0, 0x70, "Magnitude"}, - {0, 0x71, "Phase"}, - {0, 0x72, "Period"}, - {0, 0x73, "Set_Constant_Force_Report"}, - {0, 0x74, "Set_Ramp_Force_Report"}, - {0, 0x75, "Ramp_Start"}, - {0, 0x76, "Ramp_End"}, - {0, 0x77, "Effect_Operation_Report"}, - {0, 0x78, "Effect_Operation"}, - {0, 0x79, "Op_Effect_Start"}, - {0, 0x7A, "Op_Effect_Start_Solo"}, - {0, 0x7B, "Op_Effect_Stop"}, - {0, 0x7C, "Loop_Count"}, - {0, 0x7D, "Device_Gain_Report"}, - {0, 0x7E, "Device_Gain"}, - {0, 0x7F, "PID_Pool_Report"}, - {0, 0x80, "RAM_Pool_Size"}, - {0, 0x81, "ROM_Pool_Size"}, - {0, 0x82, "ROM_Effect_Block_Count"}, - {0, 0x83, "Simultaneous_Effects_Max"}, - {0, 0x84, "Pool_Alignment"}, - {0, 0x85, "PID_Pool_Move_Report"}, - {0, 0x86, "Move_Source"}, - {0, 0x87, "Move_Destination"}, - {0, 0x88, "Move_Length"}, - {0, 0x89, "PID_Block_Load_Report"}, - {0, 0x8B, "Block_Load_Status"}, - {0, 0x8C, "Block_Load_Success"}, - {0, 0x8D, "Block_Load_Full"}, - {0, 0x8E, "Block_Load_Error"}, - {0, 0x8F, "Block_Handle"}, - {0, 0x90, "PID_Block_Free_Report"}, - {0, 0x91, "Type_Specific_Block_Handle"}, - {0, 0x92, "PID_State_Report"}, - {0, 0x94, "Effect_Playing"}, - {0, 0x95, "PID_Device_Control_Report"}, - {0, 0x96, "PID_Device_Control"}, - {0, 0x97, "DC_Enable_Actuators"}, - {0, 0x98, "DC_Disable_Actuators"}, - {0, 0x99, "DC_Stop_All_Effects"}, - {0, 0x9A, "DC_Device_Reset"}, - {0, 0x9B, "DC_Device_Pause"}, - {0, 0x9C, "DC_Device_Continue"}, - {0, 0x9F, "Device_Paused"}, - {0, 0xA0, "Actuators_Enabled"}, - {0, 0xA4, "Safety_Switch"}, - {0, 0xA5, "Actuator_Override_Switch"}, - {0, 0xA6, "Actuator_Power"}, - {0, 0xA7, "Start_Delay"}, - {0, 0xA8, "Parameter_Block_Size"}, - {0, 0xA9, "Device_Managed_Pool"}, - {0, 0xAA, "Shared_Parameter_Blocks"}, - {0, 0xAB, "Create_New_Effect_Report"}, - {0, 0xAC, "RAM_Pool_Available"}, - { 0x20, 0, "Sensor" }, - { 0x20, 0x01, "Sensor" }, - { 0x20, 0x10, "Biometric" }, - { 0x20, 0x11, "BiometricHumanPresence" }, - { 0x20, 0x12, "BiometricHumanProximity" }, - { 0x20, 0x13, "BiometricHumanTouch" }, - { 0x20, 0x20, "Electrical" }, - { 0x20, 0x21, "ElectricalCapacitance" }, - { 0x20, 0x22, "ElectricalCurrent" }, - { 0x20, 0x23, "ElectricalPower" }, - { 0x20, 0x24, "ElectricalInductance" }, - { 0x20, 0x25, "ElectricalResistance" }, - { 0x20, 0x26, "ElectricalVoltage" }, - { 0x20, 0x27, "ElectricalPoteniometer" }, - { 0x20, 0x28, "ElectricalFrequency" }, - { 0x20, 0x29, "ElectricalPeriod" }, - { 0x20, 0x30, "Environmental" }, - { 0x20, 0x31, "EnvironmentalAtmosphericPressure" }, - { 0x20, 0x32, "EnvironmentalHumidity" }, - { 0x20, 0x33, "EnvironmentalTemperature" }, - { 0x20, 0x34, "EnvironmentalWindDirection" }, - { 0x20, 0x35, "EnvironmentalWindSpeed" }, - { 0x20, 0x40, "Light" }, - { 0x20, 0x41, "LightAmbientLight" }, - { 0x20, 0x42, "LightConsumerInfrared" }, - { 0x20, 0x50, "Location" }, - { 0x20, 0x51, "LocationBroadcast" }, - { 0x20, 0x52, "LocationDeadReckoning" }, - { 0x20, 0x53, "LocationGPS" }, - { 0x20, 0x54, "LocationLookup" }, - { 0x20, 0x55, "LocationOther" }, - { 0x20, 0x56, "LocationStatic" }, - { 0x20, 0x57, "LocationTriangulation" }, - { 0x20, 0x60, "Mechanical" }, - { 0x20, 0x61, "MechanicalBooleanSwitch" }, - { 0x20, 0x62, "MechanicalBooleanSwitchArray" }, - { 0x20, 0x63, "MechanicalMultivalueSwitch" }, - { 0x20, 0x64, "MechanicalForce" }, - { 0x20, 0x65, "MechanicalPressure" }, - { 0x20, 0x66, "MechanicalStrain" }, - { 0x20, 0x67, "MechanicalWeight" }, - { 0x20, 0x68, "MechanicalHapticVibrator" }, - { 0x20, 0x69, "MechanicalHallEffectSwitch" }, - { 0x20, 0x70, "Motion" }, - { 0x20, 0x71, "MotionAccelerometer1D" }, - { 0x20, 0x72, "MotionAccelerometer2D" }, - { 0x20, 0x73, "MotionAccelerometer3D" }, - { 0x20, 0x74, "MotionGyrometer1D" }, - { 0x20, 0x75, "MotionGyrometer2D" }, - { 0x20, 0x76, "MotionGyrometer3D" }, - { 0x20, 0x77, "MotionMotionDetector" }, - { 0x20, 0x78, "MotionSpeedometer" }, - { 0x20, 0x79, "MotionAccelerometer" }, - { 0x20, 0x7A, "MotionGyrometer" }, - { 0x20, 0x80, "Orientation" }, - { 0x20, 0x81, "OrientationCompass1D" }, - { 0x20, 0x82, "OrientationCompass2D" }, - { 0x20, 0x83, "OrientationCompass3D" }, - { 0x20, 0x84, "OrientationInclinometer1D" }, - { 0x20, 0x85, "OrientationInclinometer2D" }, - { 0x20, 0x86, "OrientationInclinometer3D" }, - { 0x20, 0x87, "OrientationDistance1D" }, - { 0x20, 0x88, "OrientationDistance2D" }, - { 0x20, 0x89, "OrientationDistance3D" }, - { 0x20, 0x8A, "OrientationDeviceOrientation" }, - { 0x20, 0x8B, "OrientationCompass" }, - { 0x20, 0x8C, "OrientationInclinometer" }, - { 0x20, 0x8D, "OrientationDistance" }, - { 0x20, 0x90, "Scanner" }, - { 0x20, 0x91, "ScannerBarcode" }, - { 0x20, 0x91, "ScannerRFID" }, - { 0x20, 0x91, "ScannerNFC" }, - { 0x20, 0xA0, "Time" }, - { 0x20, 0xA1, "TimeAlarmTimer" }, - { 0x20, 0xA2, "TimeRealTimeClock" }, - { 0x20, 0xE0, "Other" }, - { 0x20, 0xE1, "OtherCustom" }, - { 0x20, 0xE2, "OtherGeneric" }, - { 0x20, 0xE3, "OtherGenericEnumerator" }, - { 0x84, 0, "Power Device" }, - { 0x84, 0x02, "PresentStatus" }, - { 0x84, 0x03, "ChangeStatus" }, - { 0x84, 0x04, "UPS" }, - { 0x84, 0x05, "PowerSupply" }, - { 0x84, 0x10, "BatterySystem" }, - { 0x84, 0x11, "BatterySystemID" }, - { 0x84, 0x12, "Battery" }, - { 0x84, 0x13, "BatteryID" }, - { 0x84, 0x14, "Charger" }, - { 0x84, 0x15, "ChargerID" }, - { 0x84, 0x16, "PowerConverter" }, - { 0x84, 0x17, "PowerConverterID" }, - { 0x84, 0x18, "OutletSystem" }, - { 0x84, 0x19, "OutletSystemID" }, - { 0x84, 0x1a, "Input" }, - { 0x84, 0x1b, "InputID" }, - { 0x84, 0x1c, "Output" }, - { 0x84, 0x1d, "OutputID" }, - { 0x84, 0x1e, "Flow" }, - { 0x84, 0x1f, "FlowID" }, - { 0x84, 0x20, "Outlet" }, - { 0x84, 0x21, "OutletID" }, - { 0x84, 0x22, "Gang" }, - { 0x84, 0x24, "PowerSummary" }, - { 0x84, 0x25, "PowerSummaryID" }, - { 0x84, 0x30, "Voltage" }, - { 0x84, 0x31, "Current" }, - { 0x84, 0x32, "Frequency" }, - { 0x84, 0x33, "ApparentPower" }, - { 0x84, 0x35, "PercentLoad" }, - { 0x84, 0x40, "ConfigVoltage" }, - { 0x84, 0x41, "ConfigCurrent" }, - { 0x84, 0x43, "ConfigApparentPower" }, - { 0x84, 0x53, "LowVoltageTransfer" }, - { 0x84, 0x54, "HighVoltageTransfer" }, - { 0x84, 0x56, "DelayBeforeStartup" }, - { 0x84, 0x57, "DelayBeforeShutdown" }, - { 0x84, 0x58, "Test" }, - { 0x84, 0x5a, "AudibleAlarmControl" }, - { 0x84, 0x60, "Present" }, - { 0x84, 0x61, "Good" }, - { 0x84, 0x62, "InternalFailure" }, - { 0x84, 0x65, "Overload" }, - { 0x84, 0x66, "OverCharged" }, - { 0x84, 0x67, "OverTemperature" }, - { 0x84, 0x68, "ShutdownRequested" }, - { 0x84, 0x69, "ShutdownImminent" }, - { 0x84, 0x6b, "SwitchOn/Off" }, - { 0x84, 0x6c, "Switchable" }, - { 0x84, 0x6d, "Used" }, - { 0x84, 0x6e, "Boost" }, - { 0x84, 0x73, "CommunicationLost" }, - { 0x84, 0xfd, "iManufacturer" }, - { 0x84, 0xfe, "iProduct" }, - { 0x84, 0xff, "iSerialNumber" }, - { 0x85, 0, "Battery System" }, - { 0x85, 0x01, "SMBBatteryMode" }, - { 0x85, 0x02, "SMBBatteryStatus" }, - { 0x85, 0x03, "SMBAlarmWarning" }, - { 0x85, 0x04, "SMBChargerMode" }, - { 0x85, 0x05, "SMBChargerStatus" }, - { 0x85, 0x06, "SMBChargerSpecInfo" }, - { 0x85, 0x07, "SMBSelectorState" }, - { 0x85, 0x08, "SMBSelectorPresets" }, - { 0x85, 0x09, "SMBSelectorInfo" }, - { 0x85, 0x29, "RemainingCapacityLimit" }, - { 0x85, 0x2c, "CapacityMode" }, - { 0x85, 0x42, "BelowRemainingCapacityLimit" }, - { 0x85, 0x44, "Charging" }, - { 0x85, 0x45, "Discharging" }, - { 0x85, 0x4b, "NeedReplacement" }, - { 0x85, 0x65, "AbsoluteStateOfCharge" }, - { 0x85, 0x66, "RemainingCapacity" }, - { 0x85, 0x68, "RunTimeToEmpty" }, - { 0x85, 0x6a, "AverageTimeToFull" }, - { 0x85, 0x83, "DesignCapacity" }, - { 0x85, 0x85, "ManufacturerDate" }, - { 0x85, 0x89, "iDeviceChemistry" }, - { 0x85, 0x8b, "Rechargeable" }, - { 0x85, 0x8f, "iOEMInformation" }, - { 0x85, 0x8d, "CapacityGranularity1" }, - { 0x85, 0xd0, "ACPresent" }, - /* pages 0xff00 to 0xffff are vendor-specific */ - { 0xffff, 0, "Vendor-specific-FF" }, - { 0, 0, NULL } + { 0x00, 0, "Undefined" }, + { 0x01, 0, "GenericDesktop" }, + { 0x01, 0x0001, "Pointer" }, + { 0x01, 0x0002, "Mouse" }, + { 0x01, 0x0004, "Joystick" }, + { 0x01, 0x0005, "Gamepad" }, + { 0x01, 0x0006, "Keyboard" }, + { 0x01, 0x0007, "Keypad" }, + { 0x01, 0x0008, "MultiaxisController" }, + { 0x01, 0x0009, "TabletPCSystemControls" }, + { 0x01, 0x000a, "WaterCoolingDevice" }, + { 0x01, 0x000b, "ComputerChassisDevice" }, + { 0x01, 0x000c, "WirelessRadioControls" }, + { 0x01, 0x000d, "PortableDeviceControl" }, + { 0x01, 0x000e, "SystemMultiAxisController" }, + { 0x01, 0x000f, "SpatialController" }, + { 0x01, 0x0010, "AssistiveControl" }, + { 0x01, 0x0011, "DeviceDock" }, + { 0x01, 0x0012, "DockableDevice" }, + { 0x01, 0x0013, "CallStateManagementControl" }, + { 0x01, 0x0030, "X" }, + { 0x01, 0x0031, "Y" }, + { 0x01, 0x0032, "Z" }, + { 0x01, 0x0033, "Rx" }, + { 0x01, 0x0034, "Ry" }, + { 0x01, 0x0035, "Rz" }, + { 0x01, 0x0036, "Slider" }, + { 0x01, 0x0037, "Dial" }, + { 0x01, 0x0038, "Wheel" }, + { 0x01, 0x0039, "HatSwitch" }, + { 0x01, 0x003a, "CountedBuffer" }, + { 0x01, 0x003b, "ByteCount" }, + { 0x01, 0x003c, "MotionWakeup" }, + { 0x01, 0x003d, "Start" }, + { 0x01, 0x003e, "Select" }, + { 0x01, 0x0040, "Vx" }, + { 0x01, 0x0041, "Vy" }, + { 0x01, 0x0042, "Vz" }, + { 0x01, 0x0043, "Vbrx" }, + { 0x01, 0x0044, "Vbry" }, + { 0x01, 0x0045, "Vbrz" }, + { 0x01, 0x0046, "Vno" }, + { 0x01, 0x0047, "FeatureNotification" }, + { 0x01, 0x0048, "ResolutionMultiplier" }, + { 0x01, 0x0049, "Qx" }, + { 0x01, 0x004a, "Qy" }, + { 0x01, 0x004b, "Qz" }, + { 0x01, 0x004c, "Qw" }, + { 0x01, 0x0080, "SystemControl" }, + { 0x01, 0x0081, "SystemPowerDown" }, + { 0x01, 0x0082, "SystemSleep" }, + { 0x01, 0x0083, "SystemWakeUp" }, + { 0x01, 0x0084, "SystemContextMenu" }, + { 0x01, 0x0085, "SystemMainMenu" }, + { 0x01, 0x0086, "SystemAppMenu" }, + { 0x01, 0x0087, "SystemMenuHelp" }, + { 0x01, 0x0088, "SystemMenuExit" }, + { 0x01, 0x0089, "SystemMenuSelect" }, + { 0x01, 0x008a, "SystemMenuRight" }, + { 0x01, 0x008b, "SystemMenuLeft" }, + { 0x01, 0x008c, "SystemMenuUp" }, + { 0x01, 0x008d, "SystemMenuDown" }, + { 0x01, 0x008e, "SystemColdRestart" }, + { 0x01, 0x008f, "SystemWarmRestart" }, + { 0x01, 0x0090, "DpadUp" }, + { 0x01, 0x0091, "DpadDown" }, + { 0x01, 0x0092, "DpadRight" }, + { 0x01, 0x0093, "DpadLeft" }, + { 0x01, 0x0094, "IndexTrigger" }, + { 0x01, 0x0095, "PalmTrigger" }, + { 0x01, 0x0096, "Thumbstick" }, + { 0x01, 0x0097, "SystemFunctionShift" }, + { 0x01, 0x0098, "SystemFunctionShiftLock" }, + { 0x01, 0x0099, "SystemFunctionShiftLockIndicator" }, + { 0x01, 0x009a, "SystemDismissNotification" }, + { 0x01, 0x009b, "SystemDoNotDisturb" }, + { 0x01, 0x00a0, "SystemDock" }, + { 0x01, 0x00a1, "SystemUndock" }, + { 0x01, 0x00a2, "SystemSetup" }, + { 0x01, 0x00a3, "SystemBreak" }, + { 0x01, 0x00a4, "SystemDebuggerBreak" }, + { 0x01, 0x00a5, "ApplicationBreak" }, + { 0x01, 0x00a6, "ApplicationDebuggerBreak" }, + { 0x01, 0x00a7, "SystemSpeakerMute" }, + { 0x01, 0x00a8, "SystemHibernate" }, + { 0x01, 0x00a9, "SystemMicrophoneMute" }, + { 0x01, 0x00b0, "SystemDisplayInvert" }, + { 0x01, 0x00b1, "SystemDisplayInternal" }, + { 0x01, 0x00b2, "SystemDisplayExternal" }, + { 0x01, 0x00b3, "SystemDisplayBoth" }, + { 0x01, 0x00b4, "SystemDisplayDual" }, + { 0x01, 0x00b5, "SystemDisplayToggleIntExtMode" }, + { 0x01, 0x00b6, "SystemDisplaySwapPrimarySecondary" }, + { 0x01, 0x00b7, "SystemDisplayToggleLCDAutoscale" }, + { 0x01, 0x00c0, "SensorZone" }, + { 0x01, 0x00c1, "RPM" }, + { 0x01, 0x00c2, "CoolantLevel" }, + { 0x01, 0x00c3, "CoolantCriticalLevel" }, + { 0x01, 0x00c4, "CoolantPump" }, + { 0x01, 0x00c5, "ChassisEnclosure" }, + { 0x01, 0x00c6, "WirelessRadioButton" }, + { 0x01, 0x00c7, "WirelessRadioLED" }, + { 0x01, 0x00c8, "WirelessRadioSliderSwitch" }, + { 0x01, 0x00c9, "SystemDisplayRotationLockButton" }, + { 0x01, 0x00ca, "SystemDisplayRotationLockSliderSwitch" }, + { 0x01, 0x00cb, "ControlEnable" }, + { 0x01, 0x00d0, "DockableDeviceUniqueID" }, + { 0x01, 0x00d1, "DockableDeviceVendorID" }, + { 0x01, 0x00d2, "DockableDevicePrimaryUsagePage" }, + { 0x01, 0x00d3, "DockableDevicePrimaryUsageID" }, + { 0x01, 0x00d4, "DockableDeviceDockingState" }, + { 0x01, 0x00d5, "DockableDeviceDisplayOcclusion" }, + { 0x01, 0x00d6, "DockableDeviceObjectType" }, + { 0x01, 0x00e0, "CallActiveLED" }, + { 0x01, 0x00e1, "CallMuteToggle" }, + { 0x01, 0x00e2, "CallMuteLED" }, + { 0x02, 0, "SimulationControls" }, + { 0x02, 0x0001, "FlightSimulationDevice" }, + { 0x02, 0x0002, "AutomobileSimulationDevice" }, + { 0x02, 0x0003, "TankSimulationDevice" }, + { 0x02, 0x0004, "SpaceshipSimulationDevice" }, + { 0x02, 0x0005, "SubmarineSimulationDevice" }, + { 0x02, 0x0006, "SailingSimulationDevice" }, + { 0x02, 0x0007, "MotorcycleSimulationDevice" }, + { 0x02, 0x0008, "SportsSimulationDevice" }, + { 0x02, 0x0009, "AirplaneSimulationDevice" }, + { 0x02, 0x000a, "HelicopterSimulationDevice" }, + { 0x02, 0x000b, "MagicCarpetSimulationDevice" }, + { 0x02, 0x000c, "BicycleSimulationDevice" }, + { 0x02, 0x0020, "FlightControlStick" }, + { 0x02, 0x0021, "FlightStick" }, + { 0x02, 0x0022, "CyclicControl" }, + { 0x02, 0x0023, "CyclicTrim" }, + { 0x02, 0x0024, "FlightYoke" }, + { 0x02, 0x0025, "TrackControl" }, + { 0x02, 0x00b0, "Aileron" }, + { 0x02, 0x00b1, "AileronTrim" }, + { 0x02, 0x00b2, "AntiTorqueControl" }, + { 0x02, 0x00b3, "AutopilotEnable" }, + { 0x02, 0x00b4, "ChaffRelease" }, + { 0x02, 0x00b5, "CollectiveControl" }, + { 0x02, 0x00b6, "DiveBrake" }, + { 0x02, 0x00b7, "ElectronicCountermeasures" }, + { 0x02, 0x00b8, "Elevator" }, + { 0x02, 0x00b9, "ElevatorTrim" }, + { 0x02, 0x00ba, "Rudder" }, + { 0x02, 0x00bb, "Throttle" }, + { 0x02, 0x00bc, "FlightCommunications" }, + { 0x02, 0x00bd, "FlareRelease" }, + { 0x02, 0x00be, "LandingGear" }, + { 0x02, 0x00bf, "ToeBrake" }, + { 0x02, 0x00c0, "Trigger" }, + { 0x02, 0x00c1, "WeaponsArm" }, + { 0x02, 0x00c2, "WeaponsSelect" }, + { 0x02, 0x00c3, "WingFlaps" }, + { 0x02, 0x00c4, "Accelerator" }, + { 0x02, 0x00c5, "Brake" }, + { 0x02, 0x00c6, "Clutch" }, + { 0x02, 0x00c7, "Shifter" }, + { 0x02, 0x00c8, "Steering" }, + { 0x02, 0x00c9, "TurretDirection" }, + { 0x02, 0x00ca, "BarrelElevation" }, + { 0x02, 0x00cb, "DivePlane" }, + { 0x02, 0x00cc, "Ballast" }, + { 0x02, 0x00cd, "BicycleCrank" }, + { 0x02, 0x00ce, "HandleBars" }, + { 0x02, 0x00cf, "FrontBrake" }, + { 0x02, 0x00d0, "RearBrake" }, + { 0x03, 0, "VRControls" }, + { 0x03, 0x0001, "Belt" }, + { 0x03, 0x0002, "BodySuit" }, + { 0x03, 0x0003, "Flexor" }, + { 0x03, 0x0004, "Glove" }, + { 0x03, 0x0005, "HeadTracker" }, + { 0x03, 0x0006, "HeadMountedDisplay" }, + { 0x03, 0x0007, "HandTracker" }, + { 0x03, 0x0008, "Oculometer" }, + { 0x03, 0x0009, "Vest" }, + { 0x03, 0x000a, "AnimatronicDevice" }, + { 0x03, 0x0020, "StereoEnable" }, + { 0x03, 0x0021, "DisplayEnable" }, + { 0x04, 0, "SportControls" }, + { 0x04, 0x0001, "BaseballBat" }, + { 0x04, 0x0002, "GolfClub" }, + { 0x04, 0x0003, "RowingMachine" }, + { 0x04, 0x0004, "Treadmill" }, + { 0x04, 0x0030, "Oar" }, + { 0x04, 0x0031, "Slope" }, + { 0x04, 0x0032, "Rate" }, + { 0x04, 0x0033, "StickSpeed" }, + { 0x04, 0x0034, "StickFaceAngle" }, + { 0x04, 0x0035, "StickHeelToe" }, + { 0x04, 0x0036, "StickFollowThrough" }, + { 0x04, 0x0037, "StickTempo" }, + { 0x04, 0x0038, "StickType" }, + { 0x04, 0x0039, "StickHeight" }, + { 0x04, 0x0050, "Putter" }, + { 0x04, 0x0051, "1Iron" }, + { 0x04, 0x0052, "2Iron" }, + { 0x04, 0x0053, "3Iron" }, + { 0x04, 0x0054, "4Iron" }, + { 0x04, 0x0055, "5Iron" }, + { 0x04, 0x0056, "6Iron" }, + { 0x04, 0x0057, "7Iron" }, + { 0x04, 0x0058, "8Iron" }, + { 0x04, 0x0059, "9Iron" }, + { 0x04, 0x005a, "10Iron" }, + { 0x04, 0x005b, "11Iron" }, + { 0x04, 0x005c, "SandWedge" }, + { 0x04, 0x005d, "LoftWedge" }, + { 0x04, 0x005e, "PowerWedge" }, + { 0x04, 0x005f, "1Wood" }, + { 0x04, 0x0060, "3Wood" }, + { 0x04, 0x0061, "5Wood" }, + { 0x04, 0x0062, "7Wood" }, + { 0x04, 0x0063, "9Wood" }, + { 0x05, 0, "GameControls" }, + { 0x05, 0x0001, "3DGameController" }, + { 0x05, 0x0002, "PinballDevice" }, + { 0x05, 0x0003, "GunDevice" }, + { 0x05, 0x0020, "PointofView" }, + { 0x05, 0x0021, "TurnRightLeft" }, + { 0x05, 0x0022, "PitchForwardBackward" }, + { 0x05, 0x0023, "RollRightLeft" }, + { 0x05, 0x0024, "MoveRightLeft" }, + { 0x05, 0x0025, "MoveForwardBackward" }, + { 0x05, 0x0026, "MoveUpDown" }, + { 0x05, 0x0027, "LeanRightLeft" }, + { 0x05, 0x0028, "LeanForwardBackward" }, + { 0x05, 0x0029, "HeightofPOV" }, + { 0x05, 0x002a, "Flipper" }, + { 0x05, 0x002b, "SecondaryFlipper" }, + { 0x05, 0x002c, "Bump" }, + { 0x05, 0x002d, "NewGame" }, + { 0x05, 0x002e, "ShootBall" }, + { 0x05, 0x002f, "Player" }, + { 0x05, 0x0030, "GunBolt" }, + { 0x05, 0x0031, "GunClip" }, + { 0x05, 0x0032, "GunSelector" }, + { 0x05, 0x0033, "GunSingleShot" }, + { 0x05, 0x0034, "GunBurst" }, + { 0x05, 0x0035, "GunAutomatic" }, + { 0x05, 0x0036, "GunSafety" }, + { 0x05, 0x0037, "GamepadFireJump" }, + { 0x05, 0x0039, "GamepadTrigger" }, + { 0x05, 0x003a, "FormfittingGamepad" }, + { 0x06, 0, "GenericDeviceControls" }, + { 0x06, 0x0001, "BackgroundNonuserControls" }, + { 0x06, 0x0020, "BatteryStrength" }, + { 0x06, 0x0021, "WirelessChannel" }, + { 0x06, 0x0022, "WirelessID" }, + { 0x06, 0x0023, "DiscoverWirelessControl" }, + { 0x06, 0x0024, "SecurityCodeCharacterEntered" }, + { 0x06, 0x0025, "SecurityCodeCharacterErased" }, + { 0x06, 0x0026, "SecurityCodeCleared" }, + { 0x06, 0x0027, "SequenceID" }, + { 0x06, 0x0028, "SequenceIDReset" }, + { 0x06, 0x0029, "RFSignalStrength" }, + { 0x06, 0x002a, "SoftwareVersion" }, + { 0x06, 0x002b, "ProtocolVersion" }, + { 0x06, 0x002c, "HardwareVersion" }, + { 0x06, 0x002d, "Major" }, + { 0x06, 0x002e, "Minor" }, + { 0x06, 0x002f, "Revision" }, + { 0x06, 0x0030, "Handedness" }, + { 0x06, 0x0031, "EitherHand" }, + { 0x06, 0x0032, "LeftHand" }, + { 0x06, 0x0033, "RightHand" }, + { 0x06, 0x0034, "BothHands" }, + { 0x06, 0x0040, "GripPoseOffset" }, + { 0x06, 0x0041, "PointerPoseOffset" }, + { 0x07, 0, "KeyboardKeypad" }, + { 0x07, 0x0001, "ErrorRollOver" }, + { 0x07, 0x0002, "POSTFail" }, + { 0x07, 0x0003, "ErrorUndefined" }, + { 0x07, 0x0004, "KeyboardA" }, + { 0x07, 0x0005, "KeyboardB" }, + { 0x07, 0x0006, "KeyboardC" }, + { 0x07, 0x0007, "KeyboardD" }, + { 0x07, 0x0008, "KeyboardE" }, + { 0x07, 0x0009, "KeyboardF" }, + { 0x07, 0x000a, "KeyboardG" }, + { 0x07, 0x000b, "KeyboardH" }, + { 0x07, 0x000c, "KeyboardI" }, + { 0x07, 0x000d, "KeyboardJ" }, + { 0x07, 0x000e, "KeyboardK" }, + { 0x07, 0x000f, "KeyboardL" }, + { 0x07, 0x0010, "KeyboardM" }, + { 0x07, 0x0011, "KeyboardN" }, + { 0x07, 0x0012, "KeyboardO" }, + { 0x07, 0x0013, "KeyboardP" }, + { 0x07, 0x0014, "KeyboardQ" }, + { 0x07, 0x0015, "KeyboardR" }, + { 0x07, 0x0016, "KeyboardS" }, + { 0x07, 0x0017, "KeyboardT" }, + { 0x07, 0x0018, "KeyboardU" }, + { 0x07, 0x0019, "KeyboardV" }, + { 0x07, 0x001a, "KeyboardW" }, + { 0x07, 0x001b, "KeyboardX" }, + { 0x07, 0x001c, "KeyboardY" }, + { 0x07, 0x001d, "KeyboardZ" }, + { 0x07, 0x001e, "Keyboard1andBang" }, + { 0x07, 0x001f, "Keyboard2andAt" }, + { 0x07, 0x0020, "Keyboard3andHash" }, + { 0x07, 0x0021, "Keyboard4andDollar" }, + { 0x07, 0x0022, "Keyboard5andPercent" }, + { 0x07, 0x0023, "Keyboard6andCaret" }, + { 0x07, 0x0024, "Keyboard7andAmpersand" }, + { 0x07, 0x0025, "Keyboard8andStar" }, + { 0x07, 0x0026, "Keyboard9andLeftBracket" }, + { 0x07, 0x0027, "Keyboard0andRightBracket" }, + { 0x07, 0x0028, "KeyboardReturnEnter" }, + { 0x07, 0x0029, "KeyboardEscape" }, + { 0x07, 0x002a, "KeyboardDelete" }, + { 0x07, 0x002b, "KeyboardTab" }, + { 0x07, 0x002c, "KeyboardSpacebar" }, + { 0x07, 0x002d, "KeyboardDashandUnderscore" }, + { 0x07, 0x002e, "KeyboardEqualsandPlus" }, + { 0x07, 0x002f, "KeyboardLeftBrace" }, + { 0x07, 0x0030, "KeyboardRightBrace" }, + { 0x07, 0x0031, "KeyboardBackslashandPipe" }, + { 0x07, 0x0032, "KeyboardNonUSHashandTilde" }, + { 0x07, 0x0033, "KeyboardSemiColonandColon" }, + { 0x07, 0x0034, "KeyboardLeftAposandDouble" }, + { 0x07, 0x0035, "KeyboardGraveAccentandTilde" }, + { 0x07, 0x0036, "KeyboardCommaandLessThan" }, + { 0x07, 0x0037, "KeyboardPeriodandGreaterThan" }, + { 0x07, 0x0038, "KeyboardForwardSlashandQuestionMark" }, + { 0x07, 0x0039, "KeyboardCapsLock" }, + { 0x07, 0x003a, "KeyboardF1" }, + { 0x07, 0x003b, "KeyboardF2" }, + { 0x07, 0x003c, "KeyboardF3" }, + { 0x07, 0x003d, "KeyboardF4" }, + { 0x07, 0x003e, "KeyboardF5" }, + { 0x07, 0x003f, "KeyboardF6" }, + { 0x07, 0x0040, "KeyboardF7" }, + { 0x07, 0x0041, "KeyboardF8" }, + { 0x07, 0x0042, "KeyboardF9" }, + { 0x07, 0x0043, "KeyboardF10" }, + { 0x07, 0x0044, "KeyboardF11" }, + { 0x07, 0x0045, "KeyboardF12" }, + { 0x07, 0x0046, "KeyboardPrintScreen" }, + { 0x07, 0x0047, "KeyboardScrollLock" }, + { 0x07, 0x0048, "KeyboardPause" }, + { 0x07, 0x0049, "KeyboardInsert" }, + { 0x07, 0x004a, "KeyboardHome" }, + { 0x07, 0x004b, "KeyboardPageUp" }, + { 0x07, 0x004c, "KeyboardDeleteForward" }, + { 0x07, 0x004d, "KeyboardEnd" }, + { 0x07, 0x004e, "KeyboardPageDown" }, + { 0x07, 0x004f, "KeyboardRightArrow" }, + { 0x07, 0x0050, "KeyboardLeftArrow" }, + { 0x07, 0x0051, "KeyboardDownArrow" }, + { 0x07, 0x0052, "KeyboardUpArrow" }, + { 0x07, 0x0053, "KeypadNumLockandClear" }, + { 0x07, 0x0054, "KeypadForwardSlash" }, + { 0x07, 0x0055, "KeypadStar" }, + { 0x07, 0x0056, "KeypadDash" }, + { 0x07, 0x0057, "KeypadPlus" }, + { 0x07, 0x0058, "KeypadENTER" }, + { 0x07, 0x0059, "Keypad1andEnd" }, + { 0x07, 0x005a, "Keypad2andDownArrow" }, + { 0x07, 0x005b, "Keypad3andPageDn" }, + { 0x07, 0x005c, "Keypad4andLeftArrow" }, + { 0x07, 0x005d, "Keypad5" }, + { 0x07, 0x005e, "Keypad6andRightArrow" }, + { 0x07, 0x005f, "Keypad7andHome" }, + { 0x07, 0x0060, "Keypad8andUpArrow" }, + { 0x07, 0x0061, "Keypad9andPageUp" }, + { 0x07, 0x0062, "Keypad0andInsert" }, + { 0x07, 0x0063, "KeypadPeriodandDelete" }, + { 0x07, 0x0064, "KeyboardNonUSBackslashandPipe" }, + { 0x07, 0x0065, "KeyboardApplication" }, + { 0x07, 0x0066, "KeyboardPower" }, + { 0x07, 0x0067, "KeypadEquals" }, + { 0x07, 0x0068, "KeyboardF13" }, + { 0x07, 0x0069, "KeyboardF14" }, + { 0x07, 0x006a, "KeyboardF15" }, + { 0x07, 0x006b, "KeyboardF16" }, + { 0x07, 0x006c, "KeyboardF17" }, + { 0x07, 0x006d, "KeyboardF18" }, + { 0x07, 0x006e, "KeyboardF19" }, + { 0x07, 0x006f, "KeyboardF20" }, + { 0x07, 0x0070, "KeyboardF21" }, + { 0x07, 0x0071, "KeyboardF22" }, + { 0x07, 0x0072, "KeyboardF23" }, + { 0x07, 0x0073, "KeyboardF24" }, + { 0x07, 0x0074, "KeyboardExecute" }, + { 0x07, 0x0075, "KeyboardHelp" }, + { 0x07, 0x0076, "KeyboardMenu" }, + { 0x07, 0x0077, "KeyboardSelect" }, + { 0x07, 0x0078, "KeyboardStop" }, + { 0x07, 0x0079, "KeyboardAgain" }, + { 0x07, 0x007a, "KeyboardUndo" }, + { 0x07, 0x007b, "KeyboardCut" }, + { 0x07, 0x007c, "KeyboardCopy" }, + { 0x07, 0x007d, "KeyboardPaste" }, + { 0x07, 0x007e, "KeyboardFind" }, + { 0x07, 0x007f, "KeyboardMute" }, + { 0x07, 0x0080, "KeyboardVolumeUp" }, + { 0x07, 0x0081, "KeyboardVolumeDown" }, + { 0x07, 0x0082, "KeyboardLockingCapsLock" }, + { 0x07, 0x0083, "KeyboardLockingNumLock" }, + { 0x07, 0x0084, "KeyboardLockingScrollLock" }, + { 0x07, 0x0085, "KeypadComma" }, + { 0x07, 0x0086, "KeypadEqualSign" }, + { 0x07, 0x0087, "KeyboardInternational1" }, + { 0x07, 0x0088, "KeyboardInternational2" }, + { 0x07, 0x0089, "KeyboardInternational3" }, + { 0x07, 0x008a, "KeyboardInternational4" }, + { 0x07, 0x008b, "KeyboardInternational5" }, + { 0x07, 0x008c, "KeyboardInternational6" }, + { 0x07, 0x008d, "KeyboardInternational7" }, + { 0x07, 0x008e, "KeyboardInternational8" }, + { 0x07, 0x008f, "KeyboardInternational9" }, + { 0x07, 0x0090, "KeyboardLANG1" }, + { 0x07, 0x0091, "KeyboardLANG2" }, + { 0x07, 0x0092, "KeyboardLANG3" }, + { 0x07, 0x0093, "KeyboardLANG4" }, + { 0x07, 0x0094, "KeyboardLANG5" }, + { 0x07, 0x0095, "KeyboardLANG6" }, + { 0x07, 0x0096, "KeyboardLANG7" }, + { 0x07, 0x0097, "KeyboardLANG8" }, + { 0x07, 0x0098, "KeyboardLANG9" }, + { 0x07, 0x0099, "KeyboardAlternateErase" }, + { 0x07, 0x009a, "KeyboardSysReqAttention" }, + { 0x07, 0x009b, "KeyboardCancel" }, + { 0x07, 0x009c, "KeyboardClear" }, + { 0x07, 0x009d, "KeyboardPrior" }, + { 0x07, 0x009e, "KeyboardReturn" }, + { 0x07, 0x009f, "KeyboardSeparator" }, + { 0x07, 0x00a0, "KeyboardOut" }, + { 0x07, 0x00a1, "KeyboardOper" }, + { 0x07, 0x00a2, "KeyboardClearAgain" }, + { 0x07, 0x00a3, "KeyboardCrSelProps" }, + { 0x07, 0x00a4, "KeyboardExSel" }, + { 0x07, 0x00b0, "KeypadDouble0" }, + { 0x07, 0x00b1, "KeypadTriple0" }, + { 0x07, 0x00b2, "ThousandsSeparator" }, + { 0x07, 0x00b3, "DecimalSeparator" }, + { 0x07, 0x00b4, "CurrencyUnit" }, + { 0x07, 0x00b5, "CurrencySubunit" }, + { 0x07, 0x00b6, "KeypadLeftBracket" }, + { 0x07, 0x00b7, "KeypadRightBracket" }, + { 0x07, 0x00b8, "KeypadLeftBrace" }, + { 0x07, 0x00b9, "KeypadRightBrace" }, + { 0x07, 0x00ba, "KeypadTab" }, + { 0x07, 0x00bb, "KeypadBackspace" }, + { 0x07, 0x00bc, "KeypadA" }, + { 0x07, 0x00bd, "KeypadB" }, + { 0x07, 0x00be, "KeypadC" }, + { 0x07, 0x00bf, "KeypadD" }, + { 0x07, 0x00c0, "KeypadE" }, + { 0x07, 0x00c1, "KeypadF" }, + { 0x07, 0x00c2, "KeypadXOR" }, + { 0x07, 0x00c3, "KeypadCaret" }, + { 0x07, 0x00c4, "KeypadPercentage" }, + { 0x07, 0x00c5, "KeypadLess" }, + { 0x07, 0x00c6, "KeypadGreater" }, + { 0x07, 0x00c7, "KeypadAmpersand" }, + { 0x07, 0x00c8, "KeypadDoubleAmpersand" }, + { 0x07, 0x00c9, "KeypadBar" }, + { 0x07, 0x00ca, "KeypadDoubleBar" }, + { 0x07, 0x00cb, "KeypadColon" }, + { 0x07, 0x00cc, "KeypadHash" }, + { 0x07, 0x00cd, "KeypadSpace" }, + { 0x07, 0x00ce, "KeypadAt" }, + { 0x07, 0x00cf, "KeypadBang" }, + { 0x07, 0x00d0, "KeypadMemoryStore" }, + { 0x07, 0x00d1, "KeypadMemoryRecall" }, + { 0x07, 0x00d2, "KeypadMemoryClear" }, + { 0x07, 0x00d3, "KeypadMemoryAdd" }, + { 0x07, 0x00d4, "KeypadMemorySubtract" }, + { 0x07, 0x00d5, "KeypadMemoryMultiply" }, + { 0x07, 0x00d6, "KeypadMemoryDivide" }, + { 0x07, 0x00d7, "KeypadPlusMinus" }, + { 0x07, 0x00d8, "KeypadClear" }, + { 0x07, 0x00d9, "KeypadClearEntry" }, + { 0x07, 0x00da, "KeypadBinary" }, + { 0x07, 0x00db, "KeypadOctal" }, + { 0x07, 0x00dc, "KeypadDecimal" }, + { 0x07, 0x00dd, "KeypadHexadecimal" }, + { 0x07, 0x00e0, "KeyboardLeftControl" }, + { 0x07, 0x00e1, "KeyboardLeftShift" }, + { 0x07, 0x00e2, "KeyboardLeftAlt" }, + { 0x07, 0x00e3, "KeyboardLeftGUI" }, + { 0x07, 0x00e4, "KeyboardRightControl" }, + { 0x07, 0x00e5, "KeyboardRightShift" }, + { 0x07, 0x00e6, "KeyboardRightAlt" }, + { 0x07, 0x00e7, "KeyboardRightGUI" }, + { 0x08, 0, "LED" }, + { 0x08, 0x0001, "NumLock" }, + { 0x08, 0x0002, "CapsLock" }, + { 0x08, 0x0003, "ScrollLock" }, + { 0x08, 0x0004, "Compose" }, + { 0x08, 0x0005, "Kana" }, + { 0x08, 0x0006, "Power" }, + { 0x08, 0x0007, "Shift" }, + { 0x08, 0x0008, "DoNotDisturb" }, + { 0x08, 0x0009, "Mute" }, + { 0x08, 0x000a, "ToneEnable" }, + { 0x08, 0x000b, "HighCutFilter" }, + { 0x08, 0x000c, "LowCutFilter" }, + { 0x08, 0x000d, "EqualizerEnable" }, + { 0x08, 0x000e, "SoundFieldOn" }, + { 0x08, 0x000f, "SurroundOn" }, + { 0x08, 0x0010, "Repeat" }, + { 0x08, 0x0011, "Stereo" }, + { 0x08, 0x0012, "SamplingRateDetect" }, + { 0x08, 0x0013, "Spinning" }, + { 0x08, 0x0014, "CAV" }, + { 0x08, 0x0015, "CLV" }, + { 0x08, 0x0016, "RecordingFormatDetect" }, + { 0x08, 0x0017, "OffHook" }, + { 0x08, 0x0018, "Ring" }, + { 0x08, 0x0019, "MessageWaiting" }, + { 0x08, 0x001a, "DataMode" }, + { 0x08, 0x001b, "BatteryOperation" }, + { 0x08, 0x001c, "BatteryOK" }, + { 0x08, 0x001d, "BatteryLow" }, + { 0x08, 0x001e, "Speaker" }, + { 0x08, 0x001f, "Headset" }, + { 0x08, 0x0020, "Hold" }, + { 0x08, 0x0021, "Microphone" }, + { 0x08, 0x0022, "Coverage" }, + { 0x08, 0x0023, "NightMode" }, + { 0x08, 0x0024, "SendCalls" }, + { 0x08, 0x0025, "CallPickup" }, + { 0x08, 0x0026, "Conference" }, + { 0x08, 0x0027, "Standby" }, + { 0x08, 0x0028, "CameraOn" }, + { 0x08, 0x0029, "CameraOff" }, + { 0x08, 0x002a, "OnLine" }, + { 0x08, 0x002b, "OffLine" }, + { 0x08, 0x002c, "Busy" }, + { 0x08, 0x002d, "Ready" }, + { 0x08, 0x002e, "PaperOut" }, + { 0x08, 0x002f, "PaperJam" }, + { 0x08, 0x0030, "Remote" }, + { 0x08, 0x0031, "Forward" }, + { 0x08, 0x0032, "Reverse" }, + { 0x08, 0x0033, "Stop" }, + { 0x08, 0x0034, "Rewind" }, + { 0x08, 0x0035, "FastForward" }, + { 0x08, 0x0036, "Play" }, + { 0x08, 0x0037, "Pause" }, + { 0x08, 0x0038, "Record" }, + { 0x08, 0x0039, "Error" }, + { 0x08, 0x003a, "UsageSelectedIndicator" }, + { 0x08, 0x003b, "UsageInUseIndicator" }, + { 0x08, 0x003c, "UsageMultiModeIndicator" }, + { 0x08, 0x003d, "IndicatorOn" }, + { 0x08, 0x003e, "IndicatorFlash" }, + { 0x08, 0x003f, "IndicatorSlowBlink" }, + { 0x08, 0x0040, "IndicatorFastBlink" }, + { 0x08, 0x0041, "IndicatorOff" }, + { 0x08, 0x0042, "FlashOnTime" }, + { 0x08, 0x0043, "SlowBlinkOnTime" }, + { 0x08, 0x0044, "SlowBlinkOffTime" }, + { 0x08, 0x0045, "FastBlinkOnTime" }, + { 0x08, 0x0046, "FastBlinkOffTime" }, + { 0x08, 0x0047, "UsageIndicatorColor" }, + { 0x08, 0x0048, "IndicatorRed" }, + { 0x08, 0x0049, "IndicatorGreen" }, + { 0x08, 0x004a, "IndicatorAmber" }, + { 0x08, 0x004b, "GenericIndicator" }, + { 0x08, 0x004c, "SystemSuspend" }, + { 0x08, 0x004d, "ExternalPowerConnected" }, + { 0x08, 0x004e, "IndicatorBlue" }, + { 0x08, 0x004f, "IndicatorOrange" }, + { 0x08, 0x0050, "GoodStatus" }, + { 0x08, 0x0051, "WarningStatus" }, + { 0x08, 0x0052, "RGBLED" }, + { 0x08, 0x0053, "RedLEDChannel" }, + { 0x08, 0x0054, "BlueLEDChannel" }, + { 0x08, 0x0055, "GreenLEDChannel" }, + { 0x08, 0x0056, "LEDIntensity" }, + { 0x08, 0x0057, "SystemMicrophoneMute" }, + { 0x08, 0x0060, "PlayerIndicator" }, + { 0x08, 0x0061, "Player1" }, + { 0x08, 0x0062, "Player2" }, + { 0x08, 0x0063, "Player3" }, + { 0x08, 0x0064, "Player4" }, + { 0x08, 0x0065, "Player5" }, + { 0x08, 0x0066, "Player6" }, + { 0x08, 0x0067, "Player7" }, + { 0x08, 0x0068, "Player8" }, + { 0x09, 0, "Button" }, + { 0x0a, 0, "Ordinal" }, + { 0x0b, 0, "TelephonyDevice" }, + { 0x0b, 0x0001, "Phone" }, + { 0x0b, 0x0002, "AnsweringMachine" }, + { 0x0b, 0x0003, "MessageControls" }, + { 0x0b, 0x0004, "Handset" }, + { 0x0b, 0x0005, "Headset" }, + { 0x0b, 0x0006, "TelephonyKeyPad" }, + { 0x0b, 0x0007, "ProgrammableButton" }, + { 0x0b, 0x0020, "HookSwitch" }, + { 0x0b, 0x0021, "Flash" }, + { 0x0b, 0x0022, "Feature" }, + { 0x0b, 0x0023, "Hold" }, + { 0x0b, 0x0024, "Redial" }, + { 0x0b, 0x0025, "Transfer" }, + { 0x0b, 0x0026, "Drop" }, + { 0x0b, 0x0027, "Park" }, + { 0x0b, 0x0028, "ForwardCalls" }, + { 0x0b, 0x0029, "AlternateFunction" }, + { 0x0b, 0x002a, "Line" }, + { 0x0b, 0x002b, "SpeakerPhone" }, + { 0x0b, 0x002c, "Conference" }, + { 0x0b, 0x002d, "RingEnable" }, + { 0x0b, 0x002e, "RingSelect" }, + { 0x0b, 0x002f, "PhoneMute" }, + { 0x0b, 0x0030, "CallerID" }, + { 0x0b, 0x0031, "Send" }, + { 0x0b, 0x0050, "SpeedDial" }, + { 0x0b, 0x0051, "StoreNumber" }, + { 0x0b, 0x0052, "RecallNumber" }, + { 0x0b, 0x0053, "PhoneDirectory" }, + { 0x0b, 0x0070, "VoiceMail" }, + { 0x0b, 0x0071, "ScreenCalls" }, + { 0x0b, 0x0072, "DoNotDisturb" }, + { 0x0b, 0x0073, "Message" }, + { 0x0b, 0x0074, "AnswerOnOff" }, + { 0x0b, 0x0090, "InsideDialTone" }, + { 0x0b, 0x0091, "OutsideDialTone" }, + { 0x0b, 0x0092, "InsideRingTone" }, + { 0x0b, 0x0093, "OutsideRingTone" }, + { 0x0b, 0x0094, "PriorityRingTone" }, + { 0x0b, 0x0095, "InsideRingback" }, + { 0x0b, 0x0096, "PriorityRingback" }, + { 0x0b, 0x0097, "LineBusyTone" }, + { 0x0b, 0x0098, "ReorderTone" }, + { 0x0b, 0x0099, "CallWaitingTone" }, + { 0x0b, 0x009a, "ConfirmationTone1" }, + { 0x0b, 0x009b, "ConfirmationTone2" }, + { 0x0b, 0x009c, "TonesOff" }, + { 0x0b, 0x009d, "OutsideRingback" }, + { 0x0b, 0x009e, "Ringer" }, + { 0x0b, 0x00b0, "PhoneKey0" }, + { 0x0b, 0x00b1, "PhoneKey1" }, + { 0x0b, 0x00b2, "PhoneKey2" }, + { 0x0b, 0x00b3, "PhoneKey3" }, + { 0x0b, 0x00b4, "PhoneKey4" }, + { 0x0b, 0x00b5, "PhoneKey5" }, + { 0x0b, 0x00b6, "PhoneKey6" }, + { 0x0b, 0x00b7, "PhoneKey7" }, + { 0x0b, 0x00b8, "PhoneKey8" }, + { 0x0b, 0x00b9, "PhoneKey9" }, + { 0x0b, 0x00ba, "PhoneKeyStar" }, + { 0x0b, 0x00bb, "PhoneKeyPound" }, + { 0x0b, 0x00bc, "PhoneKeyA" }, + { 0x0b, 0x00bd, "PhoneKeyB" }, + { 0x0b, 0x00be, "PhoneKeyC" }, + { 0x0b, 0x00bf, "PhoneKeyD" }, + { 0x0b, 0x00c0, "PhoneCallHistoryKey" }, + { 0x0b, 0x00c1, "PhoneCallerIDKey" }, + { 0x0b, 0x00c2, "PhoneSettingsKey" }, + { 0x0b, 0x00f0, "HostControl" }, + { 0x0b, 0x00f1, "HostAvailable" }, + { 0x0b, 0x00f2, "HostCallActive" }, + { 0x0b, 0x00f3, "ActivateHandsetAudio" }, + { 0x0b, 0x00f4, "RingType" }, + { 0x0b, 0x00f5, "RedialablePhoneNumber" }, + { 0x0b, 0x00f8, "StopRingTone" }, + { 0x0b, 0x00f9, "PSTNRingTone" }, + { 0x0b, 0x00fa, "HostRingTone" }, + { 0x0b, 0x00fb, "AlertSoundError" }, + { 0x0b, 0x00fc, "AlertSoundConfirm" }, + { 0x0b, 0x00fd, "AlertSoundNotification" }, + { 0x0b, 0x00fe, "SilentRing" }, + { 0x0b, 0x0108, "EmailMessageWaiting" }, + { 0x0b, 0x0109, "VoicemailMessageWaiting" }, + { 0x0b, 0x010a, "HostHold" }, + { 0x0b, 0x0110, "IncomingCallHistoryCount" }, + { 0x0b, 0x0111, "OutgoingCallHistoryCount" }, + { 0x0b, 0x0112, "IncomingCallHistory" }, + { 0x0b, 0x0113, "OutgoingCallHistory" }, + { 0x0b, 0x0114, "PhoneLocale" }, + { 0x0b, 0x0140, "PhoneTimeSecond" }, + { 0x0b, 0x0141, "PhoneTimeMinute" }, + { 0x0b, 0x0142, "PhoneTimeHour" }, + { 0x0b, 0x0143, "PhoneDateDay" }, + { 0x0b, 0x0144, "PhoneDateMonth" }, + { 0x0b, 0x0145, "PhoneDateYear" }, + { 0x0b, 0x0146, "HandsetNickname" }, + { 0x0b, 0x0147, "AddressBookID" }, + { 0x0b, 0x014a, "CallDuration" }, + { 0x0b, 0x014b, "DualModePhone" }, + { 0x0c, 0, "Consumer" }, + { 0x0c, 0x0001, "ConsumerControl" }, + { 0x0c, 0x0002, "NumericKeyPad" }, + { 0x0c, 0x0003, "ProgrammableButtons" }, + { 0x0c, 0x0004, "Microphone" }, + { 0x0c, 0x0005, "Headphone" }, + { 0x0c, 0x0006, "GraphicEqualizer" }, + { 0x0c, 0x0020, "10" }, + { 0x0c, 0x0021, "100" }, + { 0x0c, 0x0022, "AMPM" }, + { 0x0c, 0x0030, "Power" }, + { 0x0c, 0x0031, "Reset" }, + { 0x0c, 0x0032, "Sleep" }, + { 0x0c, 0x0033, "SleepAfter" }, + { 0x0c, 0x0034, "SleepMode" }, + { 0x0c, 0x0035, "Illumination" }, + { 0x0c, 0x0036, "FunctionButtons" }, + { 0x0c, 0x0040, "Menu" }, + { 0x0c, 0x0041, "MenuPick" }, + { 0x0c, 0x0042, "MenuUp" }, + { 0x0c, 0x0043, "MenuDown" }, + { 0x0c, 0x0044, "MenuLeft" }, + { 0x0c, 0x0045, "MenuRight" }, + { 0x0c, 0x0046, "MenuEscape" }, + { 0x0c, 0x0047, "MenuValueIncrease" }, + { 0x0c, 0x0048, "MenuValueDecrease" }, + { 0x0c, 0x0060, "DataOnScreen" }, + { 0x0c, 0x0061, "ClosedCaption" }, + { 0x0c, 0x0062, "ClosedCaptionSelect" }, + { 0x0c, 0x0063, "VCRTV" }, + { 0x0c, 0x0064, "BroadcastMode" }, + { 0x0c, 0x0065, "Snapshot" }, + { 0x0c, 0x0066, "Still" }, + { 0x0c, 0x0067, "PictureinPictureToggle" }, + { 0x0c, 0x0068, "PictureinPictureSwap" }, + { 0x0c, 0x0069, "RedMenuButton" }, + { 0x0c, 0x006a, "GreenMenuButton" }, + { 0x0c, 0x006b, "BlueMenuButton" }, + { 0x0c, 0x006c, "YellowMenuButton" }, + { 0x0c, 0x006d, "Aspect" }, + { 0x0c, 0x006e, "3DModeSelect" }, + { 0x0c, 0x006f, "DisplayBrightnessIncrement" }, + { 0x0c, 0x0070, "DisplayBrightnessDecrement" }, + { 0x0c, 0x0071, "DisplayBrightness" }, + { 0x0c, 0x0072, "DisplayBacklightToggle" }, + { 0x0c, 0x0073, "DisplaySetBrightnesstoMinimum" }, + { 0x0c, 0x0074, "DisplaySetBrightnesstoMaximum" }, + { 0x0c, 0x0075, "DisplaySetAutoBrightness" }, + { 0x0c, 0x0076, "CameraAccessEnabled" }, + { 0x0c, 0x0077, "CameraAccessDisabled" }, + { 0x0c, 0x0078, "CameraAccessToggle" }, + { 0x0c, 0x0079, "KeyboardBrightnessIncrement" }, + { 0x0c, 0x007a, "KeyboardBrightnessDecrement" }, + { 0x0c, 0x007b, "KeyboardBacklightSetLevel" }, + { 0x0c, 0x007c, "KeyboardBacklightOOC" }, + { 0x0c, 0x007d, "KeyboardBacklightSetMinimum" }, + { 0x0c, 0x007e, "KeyboardBacklightSetMaximum" }, + { 0x0c, 0x007f, "KeyboardBacklightAuto" }, + { 0x0c, 0x0080, "Selection" }, + { 0x0c, 0x0081, "AssignSelection" }, + { 0x0c, 0x0082, "ModeStep" }, + { 0x0c, 0x0083, "RecallLast" }, + { 0x0c, 0x0084, "EnterChannel" }, + { 0x0c, 0x0085, "OrderMovie" }, + { 0x0c, 0x0086, "Channel" }, + { 0x0c, 0x0087, "MediaSelection" }, + { 0x0c, 0x0088, "MediaSelectComputer" }, + { 0x0c, 0x0089, "MediaSelectTV" }, + { 0x0c, 0x008a, "MediaSelectWWW" }, + { 0x0c, 0x008b, "MediaSelectDVD" }, + { 0x0c, 0x008c, "MediaSelectTelephone" }, + { 0x0c, 0x008d, "MediaSelectProgramGuide" }, + { 0x0c, 0x008e, "MediaSelectVideoPhone" }, + { 0x0c, 0x008f, "MediaSelectGames" }, + { 0x0c, 0x0090, "MediaSelectMessages" }, + { 0x0c, 0x0091, "MediaSelectCD" }, + { 0x0c, 0x0092, "MediaSelectVCR" }, + { 0x0c, 0x0093, "MediaSelectTuner" }, + { 0x0c, 0x0094, "Quit" }, + { 0x0c, 0x0095, "Help" }, + { 0x0c, 0x0096, "MediaSelectTape" }, + { 0x0c, 0x0097, "MediaSelectCable" }, + { 0x0c, 0x0098, "MediaSelectSatellite" }, + { 0x0c, 0x0099, "MediaSelectSecurity" }, + { 0x0c, 0x009a, "MediaSelectHome" }, + { 0x0c, 0x009b, "MediaSelectCall" }, + { 0x0c, 0x009c, "ChannelIncrement" }, + { 0x0c, 0x009d, "ChannelDecrement" }, + { 0x0c, 0x009e, "MediaSelectSAP" }, + { 0x0c, 0x00a0, "VCRPlus" }, + { 0x0c, 0x00a1, "Once" }, + { 0x0c, 0x00a2, "Daily" }, + { 0x0c, 0x00a3, "Weekly" }, + { 0x0c, 0x00a4, "Monthly" }, + { 0x0c, 0x00b0, "Play" }, + { 0x0c, 0x00b1, "Pause" }, + { 0x0c, 0x00b2, "Record" }, + { 0x0c, 0x00b3, "FastForward" }, + { 0x0c, 0x00b4, "Rewind" }, + { 0x0c, 0x00b5, "ScanNextTrack" }, + { 0x0c, 0x00b6, "ScanPreviousTrack" }, + { 0x0c, 0x00b7, "Stop" }, + { 0x0c, 0x00b8, "Eject" }, + { 0x0c, 0x00b9, "RandomPlay" }, + { 0x0c, 0x00ba, "SelectDisc" }, + { 0x0c, 0x00bb, "EnterDisc" }, + { 0x0c, 0x00bc, "Repeat" }, + { 0x0c, 0x00bd, "Tracking" }, + { 0x0c, 0x00be, "TrackNormal" }, + { 0x0c, 0x00bf, "SlowTracking" }, + { 0x0c, 0x00c0, "FrameForward" }, + { 0x0c, 0x00c1, "FrameBack" }, + { 0x0c, 0x00c2, "Mark" }, + { 0x0c, 0x00c3, "ClearMark" }, + { 0x0c, 0x00c4, "RepeatFromMark" }, + { 0x0c, 0x00c5, "ReturnToMark" }, + { 0x0c, 0x00c6, "SearchMarkForward" }, + { 0x0c, 0x00c7, "SearchMarkBackwards" }, + { 0x0c, 0x00c8, "CounterReset" }, + { 0x0c, 0x00c9, "ShowCounter" }, + { 0x0c, 0x00ca, "TrackingIncrement" }, + { 0x0c, 0x00cb, "TrackingDecrement" }, + { 0x0c, 0x00cc, "StopEject" }, + { 0x0c, 0x00cd, "PlayPause" }, + { 0x0c, 0x00ce, "PlaySkip" }, + { 0x0c, 0x00cf, "VoiceCommand" }, + { 0x0c, 0x00d0, "InvokeCaptureInterface" }, + { 0x0c, 0x00d1, "StartorStopGameRecording" }, + { 0x0c, 0x00d2, "HistoricalGameCapture" }, + { 0x0c, 0x00d3, "CaptureGameScreenshot" }, + { 0x0c, 0x00d4, "ShoworHideRecordingIndicator" }, + { 0x0c, 0x00d5, "StartorStopMicrophoneCapture" }, + { 0x0c, 0x00d6, "StartorStopCameraCapture" }, + { 0x0c, 0x00d7, "StartorStopGameBroadcast" }, + { 0x0c, 0x00d8, "StartorStopVoiceDictationSession" }, + { 0x0c, 0x00d9, "InvokeDismissEmojiPicker" }, + { 0x0c, 0x00e0, "Volume" }, + { 0x0c, 0x00e1, "Balance" }, + { 0x0c, 0x00e2, "Mute" }, + { 0x0c, 0x00e3, "Bass" }, + { 0x0c, 0x00e4, "Treble" }, + { 0x0c, 0x00e5, "BassBoost" }, + { 0x0c, 0x00e6, "SurroundMode" }, + { 0x0c, 0x00e7, "Loudness" }, + { 0x0c, 0x00e8, "MPX" }, + { 0x0c, 0x00e9, "VolumeIncrement" }, + { 0x0c, 0x00ea, "VolumeDecrement" }, + { 0x0c, 0x00f0, "SpeedSelect" }, + { 0x0c, 0x00f1, "PlaybackSpeed" }, + { 0x0c, 0x00f2, "StandardPlay" }, + { 0x0c, 0x00f3, "LongPlay" }, + { 0x0c, 0x00f4, "ExtendedPlay" }, + { 0x0c, 0x00f5, "Slow" }, + { 0x0c, 0x0100, "FanEnable" }, + { 0x0c, 0x0101, "FanSpeed" }, + { 0x0c, 0x0102, "LightEnable" }, + { 0x0c, 0x0103, "LightIlluminationLevel" }, + { 0x0c, 0x0104, "ClimateControlEnable" }, + { 0x0c, 0x0105, "RoomTemperature" }, + { 0x0c, 0x0106, "SecurityEnable" }, + { 0x0c, 0x0107, "FireAlarm" }, + { 0x0c, 0x0108, "PoliceAlarm" }, + { 0x0c, 0x0109, "Proximity" }, + { 0x0c, 0x010a, "Motion" }, + { 0x0c, 0x010b, "DuressAlarm" }, + { 0x0c, 0x010c, "HoldupAlarm" }, + { 0x0c, 0x010d, "MedicalAlarm" }, + { 0x0c, 0x0150, "BalanceRight" }, + { 0x0c, 0x0151, "BalanceLeft" }, + { 0x0c, 0x0152, "BassIncrement" }, + { 0x0c, 0x0153, "BassDecrement" }, + { 0x0c, 0x0154, "TrebleIncrement" }, + { 0x0c, 0x0155, "TrebleDecrement" }, + { 0x0c, 0x0160, "SpeakerSystem" }, + { 0x0c, 0x0161, "ChannelLeft" }, + { 0x0c, 0x0162, "ChannelRight" }, + { 0x0c, 0x0163, "ChannelCenter" }, + { 0x0c, 0x0164, "ChannelFront" }, + { 0x0c, 0x0165, "ChannelCenterFront" }, + { 0x0c, 0x0166, "ChannelSide" }, + { 0x0c, 0x0167, "ChannelSurround" }, + { 0x0c, 0x0168, "ChannelLowFrequencyEnhancement" }, + { 0x0c, 0x0169, "ChannelTop" }, + { 0x0c, 0x016a, "ChannelUnknown" }, + { 0x0c, 0x0170, "Subchannel" }, + { 0x0c, 0x0171, "SubchannelIncrement" }, + { 0x0c, 0x0172, "SubchannelDecrement" }, + { 0x0c, 0x0173, "AlternateAudioIncrement" }, + { 0x0c, 0x0174, "AlternateAudioDecrement" }, + { 0x0c, 0x0180, "ApplicationLaunchButtons" }, + { 0x0c, 0x0181, "ALLaunchButtonConfigurationTool" }, + { 0x0c, 0x0182, "ALProgrammableButtonConfiguration" }, + { 0x0c, 0x0183, "ALConsumerControlConfiguration" }, + { 0x0c, 0x0184, "ALWordProcessor" }, + { 0x0c, 0x0185, "ALTextEditor" }, + { 0x0c, 0x0186, "ALSpreadsheet" }, + { 0x0c, 0x0187, "ALGraphicsEditor" }, + { 0x0c, 0x0188, "ALPresentationApp" }, + { 0x0c, 0x0189, "ALDatabaseApp" }, + { 0x0c, 0x018a, "ALEmailReader" }, + { 0x0c, 0x018b, "ALNewsreader" }, + { 0x0c, 0x018c, "ALVoicemail" }, + { 0x0c, 0x018d, "ALContactsAddressBook" }, + { 0x0c, 0x018e, "ALCalendarSchedule" }, + { 0x0c, 0x018f, "ALTaskProjectManager" }, + { 0x0c, 0x0190, "ALLogJournalTimecard" }, + { 0x0c, 0x0191, "ALCheckbookFinance" }, + { 0x0c, 0x0192, "ALCalculator" }, + { 0x0c, 0x0193, "ALAVCapturePlayback" }, + { 0x0c, 0x0194, "ALLocalMachineBrowser" }, + { 0x0c, 0x0195, "ALLANWANBrowser" }, + { 0x0c, 0x0196, "ALInternetBrowser" }, + { 0x0c, 0x0197, "ALRemoteNetworkingISPConnect" }, + { 0x0c, 0x0198, "ALNetworkConference" }, + { 0x0c, 0x0199, "ALNetworkChat" }, + { 0x0c, 0x019a, "ALTelephonyDialer" }, + { 0x0c, 0x019b, "ALLogon" }, + { 0x0c, 0x019c, "ALLogoff" }, + { 0x0c, 0x019d, "ALLogonLogoff" }, + { 0x0c, 0x019e, "ALTerminalLockScreensaver" }, + { 0x0c, 0x019f, "ALControlPanel" }, + { 0x0c, 0x01a0, "ALCommandLineProcessorRun" }, + { 0x0c, 0x01a1, "ALProcessTaskManager" }, + { 0x0c, 0x01a2, "ALSelectTaskApplication" }, + { 0x0c, 0x01a3, "ALNextTaskApplication" }, + { 0x0c, 0x01a4, "ALPreviousTaskApplication" }, + { 0x0c, 0x01a5, "ALPreemptiveHaltTaskApplication" }, + { 0x0c, 0x01a6, "ALIntegratedHelpCenter" }, + { 0x0c, 0x01a7, "ALDocuments" }, + { 0x0c, 0x01a8, "ALThesaurus" }, + { 0x0c, 0x01a9, "ALDictionary" }, + { 0x0c, 0x01aa, "ALDesktop" }, + { 0x0c, 0x01ab, "ALSpellCheck" }, + { 0x0c, 0x01ac, "ALGrammarCheck" }, + { 0x0c, 0x01ad, "ALWirelessStatus" }, + { 0x0c, 0x01ae, "ALKeyboardLayout" }, + { 0x0c, 0x01af, "ALVirusProtection" }, + { 0x0c, 0x01b0, "ALEncryption" }, + { 0x0c, 0x01b1, "ALScreenSaver" }, + { 0x0c, 0x01b2, "ALAlarms" }, + { 0x0c, 0x01b3, "ALClock" }, + { 0x0c, 0x01b4, "ALFileBrowser" }, + { 0x0c, 0x01b5, "ALPowerStatus" }, + { 0x0c, 0x01b6, "ALImageBrowser" }, + { 0x0c, 0x01b7, "ALAudioBrowser" }, + { 0x0c, 0x01b8, "ALMovieBrowser" }, + { 0x0c, 0x01b9, "ALDigitalRightsManager" }, + { 0x0c, 0x01ba, "ALDigitalWallet" }, + { 0x0c, 0x01bc, "ALInstantMessaging" }, + { 0x0c, 0x01bd, "ALOEMFeaturesTipsTutorialBrowser" }, + { 0x0c, 0x01be, "ALOEMHelp" }, + { 0x0c, 0x01bf, "ALOnlineCommunity" }, + { 0x0c, 0x01c0, "ALEntertainmentContentBrowser" }, + { 0x0c, 0x01c1, "ALOnlineShoppingBrowser" }, + { 0x0c, 0x01c2, "ALSmartCardInformationHelp" }, + { 0x0c, 0x01c3, "ALMarketMonitorFinanceBrowser" }, + { 0x0c, 0x01c4, "ALCustomizedCorporateNewsBrowser" }, + { 0x0c, 0x01c5, "ALOnlineActivityBrowser" }, + { 0x0c, 0x01c6, "ALResearchSearchBrowser" }, + { 0x0c, 0x01c7, "ALAudioPlayer" }, + { 0x0c, 0x01c8, "ALMessageStatus" }, + { 0x0c, 0x01c9, "ALContactSync" }, + { 0x0c, 0x01ca, "ALNavigation" }, + { 0x0c, 0x01cb, "ALContextawareDesktopAssistant" }, + { 0x0c, 0x0200, "GenericGUIApplicationControls" }, + { 0x0c, 0x0201, "ACNew" }, + { 0x0c, 0x0202, "ACOpen" }, + { 0x0c, 0x0203, "ACClose" }, + { 0x0c, 0x0204, "ACExit" }, + { 0x0c, 0x0205, "ACMaximize" }, + { 0x0c, 0x0206, "ACMinimize" }, + { 0x0c, 0x0207, "ACSave" }, + { 0x0c, 0x0208, "ACPrint" }, + { 0x0c, 0x0209, "ACProperties" }, + { 0x0c, 0x021a, "ACUndo" }, + { 0x0c, 0x021b, "ACCopy" }, + { 0x0c, 0x021c, "ACCut" }, + { 0x0c, 0x021d, "ACPaste" }, + { 0x0c, 0x021e, "ACSelectAll" }, + { 0x0c, 0x021f, "ACFind" }, + { 0x0c, 0x0220, "ACFindandReplace" }, + { 0x0c, 0x0221, "ACSearch" }, + { 0x0c, 0x0222, "ACGoTo" }, + { 0x0c, 0x0223, "ACHome" }, + { 0x0c, 0x0224, "ACBack" }, + { 0x0c, 0x0225, "ACForward" }, + { 0x0c, 0x0226, "ACStop" }, + { 0x0c, 0x0227, "ACRefresh" }, + { 0x0c, 0x0228, "ACPreviousLink" }, + { 0x0c, 0x0229, "ACNextLink" }, + { 0x0c, 0x022a, "ACBookmarks" }, + { 0x0c, 0x022b, "ACHistory" }, + { 0x0c, 0x022c, "ACSubscriptions" }, + { 0x0c, 0x022d, "ACZoomIn" }, + { 0x0c, 0x022e, "ACZoomOut" }, + { 0x0c, 0x022f, "ACZoom" }, + { 0x0c, 0x0230, "ACFullScreenView" }, + { 0x0c, 0x0231, "ACNormalView" }, + { 0x0c, 0x0232, "ACViewToggle" }, + { 0x0c, 0x0233, "ACScrollUp" }, + { 0x0c, 0x0234, "ACScrollDown" }, + { 0x0c, 0x0235, "ACScroll" }, + { 0x0c, 0x0236, "ACPanLeft" }, + { 0x0c, 0x0237, "ACPanRight" }, + { 0x0c, 0x0238, "ACPan" }, + { 0x0c, 0x0239, "ACNewWindow" }, + { 0x0c, 0x023a, "ACTileHorizontally" }, + { 0x0c, 0x023b, "ACTileVertically" }, + { 0x0c, 0x023c, "ACFormat" }, + { 0x0c, 0x023d, "ACEdit" }, + { 0x0c, 0x023e, "ACBold" }, + { 0x0c, 0x023f, "ACItalics" }, + { 0x0c, 0x0240, "ACUnderline" }, + { 0x0c, 0x0241, "ACStrikethrough" }, + { 0x0c, 0x0242, "ACSubscript" }, + { 0x0c, 0x0243, "ACSuperscript" }, + { 0x0c, 0x0244, "ACAllCaps" }, + { 0x0c, 0x0245, "ACRotate" }, + { 0x0c, 0x0246, "ACResize" }, + { 0x0c, 0x0247, "ACFlipHorizontal" }, + { 0x0c, 0x0248, "ACFlipVertical" }, + { 0x0c, 0x0249, "ACMirrorHorizontal" }, + { 0x0c, 0x024a, "ACMirrorVertical" }, + { 0x0c, 0x024b, "ACFontSelect" }, + { 0x0c, 0x024c, "ACFontColor" }, + { 0x0c, 0x024d, "ACFontSize" }, + { 0x0c, 0x024e, "ACJustifyLeft" }, + { 0x0c, 0x024f, "ACJustifyCenterH" }, + { 0x0c, 0x0250, "ACJustifyRight" }, + { 0x0c, 0x0251, "ACJustifyBlockH" }, + { 0x0c, 0x0252, "ACJustifyTop" }, + { 0x0c, 0x0253, "ACJustifyCenterV" }, + { 0x0c, 0x0254, "ACJustifyBottom" }, + { 0x0c, 0x0255, "ACJustifyBlockV" }, + { 0x0c, 0x0256, "ACIndentDecrease" }, + { 0x0c, 0x0257, "ACIndentIncrease" }, + { 0x0c, 0x0258, "ACNumberedList" }, + { 0x0c, 0x0259, "ACRestartNumbering" }, + { 0x0c, 0x025a, "ACBulletedList" }, + { 0x0c, 0x025b, "ACPromote" }, + { 0x0c, 0x025c, "ACDemote" }, + { 0x0c, 0x025d, "ACYes" }, + { 0x0c, 0x025e, "ACNo" }, + { 0x0c, 0x025f, "ACCancel" }, + { 0x0c, 0x0260, "ACCatalog" }, + { 0x0c, 0x0261, "ACBuyCheckout" }, + { 0x0c, 0x0262, "ACAddtoCart" }, + { 0x0c, 0x0263, "ACExpand" }, + { 0x0c, 0x0264, "ACExpandAll" }, + { 0x0c, 0x0265, "ACCollapse" }, + { 0x0c, 0x0266, "ACCollapseAll" }, + { 0x0c, 0x0267, "ACPrintPreview" }, + { 0x0c, 0x0268, "ACPasteSpecial" }, + { 0x0c, 0x0269, "ACInsertMode" }, + { 0x0c, 0x026a, "ACDelete" }, + { 0x0c, 0x026b, "ACLock" }, + { 0x0c, 0x026c, "ACUnlock" }, + { 0x0c, 0x026d, "ACProtect" }, + { 0x0c, 0x026e, "ACUnprotect" }, + { 0x0c, 0x026f, "ACAttachComment" }, + { 0x0c, 0x0270, "ACDeleteComment" }, + { 0x0c, 0x0271, "ACViewComment" }, + { 0x0c, 0x0272, "ACSelectWord" }, + { 0x0c, 0x0273, "ACSelectSentence" }, + { 0x0c, 0x0274, "ACSelectParagraph" }, + { 0x0c, 0x0275, "ACSelectColumn" }, + { 0x0c, 0x0276, "ACSelectRow" }, + { 0x0c, 0x0277, "ACSelectTable" }, + { 0x0c, 0x0278, "ACSelectObject" }, + { 0x0c, 0x0279, "ACRedoRepeat" }, + { 0x0c, 0x027a, "ACSort" }, + { 0x0c, 0x027b, "ACSortAscending" }, + { 0x0c, 0x027c, "ACSortDescending" }, + { 0x0c, 0x027d, "ACFilter" }, + { 0x0c, 0x027e, "ACSetClock" }, + { 0x0c, 0x027f, "ACViewClock" }, + { 0x0c, 0x0280, "ACSelectTimeZone" }, + { 0x0c, 0x0281, "ACEditTimeZones" }, + { 0x0c, 0x0282, "ACSetAlarm" }, + { 0x0c, 0x0283, "ACClearAlarm" }, + { 0x0c, 0x0284, "ACSnoozeAlarm" }, + { 0x0c, 0x0285, "ACResetAlarm" }, + { 0x0c, 0x0286, "ACSynchronize" }, + { 0x0c, 0x0287, "ACSendReceive" }, + { 0x0c, 0x0288, "ACSendTo" }, + { 0x0c, 0x0289, "ACReply" }, + { 0x0c, 0x028a, "ACReplyAll" }, + { 0x0c, 0x028b, "ACForwardMsg" }, + { 0x0c, 0x028c, "ACSend" }, + { 0x0c, 0x028d, "ACAttachFile" }, + { 0x0c, 0x028e, "ACUpload" }, + { 0x0c, 0x028f, "ACDownloadSaveTargetAs" }, + { 0x0c, 0x0290, "ACSetBorders" }, + { 0x0c, 0x0291, "ACInsertRow" }, + { 0x0c, 0x0292, "ACInsertColumn" }, + { 0x0c, 0x0293, "ACInsertFile" }, + { 0x0c, 0x0294, "ACInsertPicture" }, + { 0x0c, 0x0295, "ACInsertObject" }, + { 0x0c, 0x0296, "ACInsertSymbol" }, + { 0x0c, 0x0297, "ACSaveandClose" }, + { 0x0c, 0x0298, "ACRename" }, + { 0x0c, 0x0299, "ACMerge" }, + { 0x0c, 0x029a, "ACSplit" }, + { 0x0c, 0x029b, "ACDisributeHorizontally" }, + { 0x0c, 0x029c, "ACDistributeVertically" }, + { 0x0c, 0x029d, "ACNextKeyboardLayoutSelect" }, + { 0x0c, 0x029e, "ACNavigationGuidance" }, + { 0x0c, 0x029f, "ACDesktopShowAllWindows" }, + { 0x0c, 0x02a0, "ACSoftKeyLeft" }, + { 0x0c, 0x02a1, "ACSoftKeyRight" }, + { 0x0c, 0x02a2, "ACDesktopShowAllApplications" }, + { 0x0c, 0x02b0, "ACIdleKeepAlive" }, + { 0x0c, 0x02c0, "ExtendedKeyboardAttributesCollection" }, + { 0x0c, 0x02c1, "KeyboardFormFactor" }, + { 0x0c, 0x02c2, "KeyboardKeyType" }, + { 0x0c, 0x02c3, "KeyboardPhysicalLayout" }, + { 0x0c, 0x02c4, "VendorSpecificKeyboardPhysicalLayout" }, + { 0x0c, 0x02c5, "KeyboardIETFLanguageTagIndex" }, + { 0x0c, 0x02c6, "ImplementedKeyboardInputAssistControls" }, + { 0x0c, 0x02c7, "KeyboardInputAssistPrevious" }, + { 0x0c, 0x02c8, "KeyboardInputAssistNext" }, + { 0x0c, 0x02c9, "KeyboardInputAssistPreviousGroup" }, + { 0x0c, 0x02ca, "KeyboardInputAssistNextGroup" }, + { 0x0c, 0x02cb, "KeyboardInputAssistAccept" }, + { 0x0c, 0x02cc, "KeyboardInputAssistCancel" }, + { 0x0c, 0x02d0, "PrivacyScreenToggle" }, + { 0x0c, 0x02d1, "PrivacyScreenLevelDecrement" }, + { 0x0c, 0x02d2, "PrivacyScreenLevelIncrement" }, + { 0x0c, 0x02d3, "PrivacyScreenLevelMinimum" }, + { 0x0c, 0x02d4, "PrivacyScreenLevelMaximum" }, + { 0x0c, 0x0500, "ContactEdited" }, + { 0x0c, 0x0501, "ContactAdded" }, + { 0x0c, 0x0502, "ContactRecordActive" }, + { 0x0c, 0x0503, "ContactIndex" }, + { 0x0c, 0x0504, "ContactNickname" }, + { 0x0c, 0x0505, "ContactFirstName" }, + { 0x0c, 0x0506, "ContactLastName" }, + { 0x0c, 0x0507, "ContactFullName" }, + { 0x0c, 0x0508, "ContactPhoneNumberPersonal" }, + { 0x0c, 0x0509, "ContactPhoneNumberBusiness" }, + { 0x0c, 0x050a, "ContactPhoneNumberMobile" }, + { 0x0c, 0x050b, "ContactPhoneNumberPager" }, + { 0x0c, 0x050c, "ContactPhoneNumberFax" }, + { 0x0c, 0x050d, "ContactPhoneNumberOther" }, + { 0x0c, 0x050e, "ContactEmailPersonal" }, + { 0x0c, 0x050f, "ContactEmailBusiness" }, + { 0x0c, 0x0510, "ContactEmailOther" }, + { 0x0c, 0x0511, "ContactEmailMain" }, + { 0x0c, 0x0512, "ContactSpeedDialNumber" }, + { 0x0c, 0x0513, "ContactStatusFlag" }, + { 0x0c, 0x0514, "ContactMisc" }, + { 0x0d, 0, "Digitizers" }, + { 0x0d, 0x0001, "Digitizer" }, + { 0x0d, 0x0002, "Pen" }, + { 0x0d, 0x0003, "LightPen" }, + { 0x0d, 0x0004, "TouchScreen" }, + { 0x0d, 0x0005, "TouchPad" }, + { 0x0d, 0x0006, "Whiteboard" }, + { 0x0d, 0x0007, "CoordinateMeasuringMachine" }, + { 0x0d, 0x0008, "3DDigitizer" }, + { 0x0d, 0x0009, "StereoPlotter" }, + { 0x0d, 0x000a, "ArticulatedArm" }, + { 0x0d, 0x000b, "Armature" }, + { 0x0d, 0x000c, "MultiplePointDigitizer" }, + { 0x0d, 0x000d, "FreeSpaceWand" }, + { 0x0d, 0x000e, "DeviceConfiguration" }, + { 0x0d, 0x000f, "CapacitiveHeatMapDigitizer" }, + { 0x0d, 0x0020, "Stylus" }, + { 0x0d, 0x0021, "Puck" }, + { 0x0d, 0x0022, "Finger" }, + { 0x0d, 0x0023, "Devicesettings" }, + { 0x0d, 0x0024, "CharacterGesture" }, + { 0x0d, 0x0030, "TipPressure" }, + { 0x0d, 0x0031, "BarrelPressure" }, + { 0x0d, 0x0032, "InRange" }, + { 0x0d, 0x0033, "Touch" }, + { 0x0d, 0x0034, "Untouch" }, + { 0x0d, 0x0035, "Tap" }, + { 0x0d, 0x0036, "Quality" }, + { 0x0d, 0x0037, "DataValid" }, + { 0x0d, 0x0038, "TransducerIndex" }, + { 0x0d, 0x0039, "TabletFunctionKeys" }, + { 0x0d, 0x003a, "ProgramChangeKeys" }, + { 0x0d, 0x003b, "BatteryStrength" }, + { 0x0d, 0x003c, "Invert" }, + { 0x0d, 0x003d, "XTilt" }, + { 0x0d, 0x003e, "YTilt" }, + { 0x0d, 0x003f, "Azimuth" }, + { 0x0d, 0x0040, "Altitude" }, + { 0x0d, 0x0041, "Twist" }, + { 0x0d, 0x0042, "TipSwitch" }, + { 0x0d, 0x0043, "SecondaryTipSwitch" }, + { 0x0d, 0x0044, "BarrelSwitch" }, + { 0x0d, 0x0045, "Eraser" }, + { 0x0d, 0x0046, "TabletPick" }, + { 0x0d, 0x0047, "TouchValid" }, + { 0x0d, 0x0048, "Width" }, + { 0x0d, 0x0049, "Height" }, + { 0x0d, 0x0051, "ContactIdentifier" }, + { 0x0d, 0x0052, "DeviceMode" }, + { 0x0d, 0x0053, "DeviceIdentifier" }, + { 0x0d, 0x0054, "ContactCount" }, + { 0x0d, 0x0055, "ContactCountMaximum" }, + { 0x0d, 0x0056, "ScanTime" }, + { 0x0d, 0x0057, "SurfaceSwitch" }, + { 0x0d, 0x0058, "ButtonSwitch" }, + { 0x0d, 0x0059, "PadType" }, + { 0x0d, 0x005a, "SecondaryBarrelSwitch" }, + { 0x0d, 0x005b, "TransducerSerialNumber" }, + { 0x0d, 0x005c, "PreferredColor" }, + { 0x0d, 0x005d, "PreferredColorisLocked" }, + { 0x0d, 0x005e, "PreferredLineWidth" }, + { 0x0d, 0x005f, "PreferredLineWidthisLocked" }, + { 0x0d, 0x0060, "LatencyMode" }, + { 0x0d, 0x0061, "GestureCharacterQuality" }, + { 0x0d, 0x0062, "CharacterGestureDataLength" }, + { 0x0d, 0x0063, "CharacterGestureData" }, + { 0x0d, 0x0064, "GestureCharacterEncoding" }, + { 0x0d, 0x0065, "UTF8CharacterGestureEncoding" }, + { 0x0d, 0x0066, "UTF16LittleEndianCharacterGestureEncoding" }, + { 0x0d, 0x0067, "UTF16BigEndianCharacterGestureEncoding" }, + { 0x0d, 0x0068, "UTF32LittleEndianCharacterGestureEncoding" }, + { 0x0d, 0x0069, "UTF32BigEndianCharacterGestureEncoding" }, + { 0x0d, 0x006a, "CapacitiveHeatMapProtocolVendorID" }, + { 0x0d, 0x006b, "CapacitiveHeatMapProtocolVersion" }, + { 0x0d, 0x006c, "CapacitiveHeatMapFrameData" }, + { 0x0d, 0x006d, "GestureCharacterEnable" }, + { 0x0d, 0x006e, "TransducerSerialNumberPart2" }, + { 0x0d, 0x006f, "NoPreferredColor" }, + { 0x0d, 0x0070, "PreferredLineStyle" }, + { 0x0d, 0x0071, "PreferredLineStyleisLocked" }, + { 0x0d, 0x0072, "Ink" }, + { 0x0d, 0x0073, "Pencil" }, + { 0x0d, 0x0074, "Highlighter" }, + { 0x0d, 0x0075, "ChiselMarker" }, + { 0x0d, 0x0076, "Brush" }, + { 0x0d, 0x0077, "NoPreference" }, + { 0x0d, 0x0080, "DigitizerDiagnostic" }, + { 0x0d, 0x0081, "DigitizerError" }, + { 0x0d, 0x0082, "ErrNormalStatus" }, + { 0x0d, 0x0083, "ErrTransducersExceeded" }, + { 0x0d, 0x0084, "ErrFullTransFeaturesUnavailable" }, + { 0x0d, 0x0085, "ErrChargeLow" }, + { 0x0d, 0x0090, "TransducerSoftwareInfo" }, + { 0x0d, 0x0091, "TransducerVendorId" }, + { 0x0d, 0x0092, "TransducerProductId" }, + { 0x0d, 0x0093, "DeviceSupportedProtocols" }, + { 0x0d, 0x0094, "TransducerSupportedProtocols" }, + { 0x0d, 0x0095, "NoProtocol" }, + { 0x0d, 0x0096, "WacomAESProtocol" }, + { 0x0d, 0x0097, "USIProtocol" }, + { 0x0d, 0x0098, "MicrosoftPenProtocol" }, + { 0x0d, 0x00a0, "SupportedReportRates" }, + { 0x0d, 0x00a1, "ReportRate" }, + { 0x0d, 0x00a2, "TransducerConnected" }, + { 0x0d, 0x00a3, "SwitchDisabled" }, + { 0x0d, 0x00a4, "SwitchUnimplemented" }, + { 0x0d, 0x00a5, "TransducerSwitches" }, + { 0x0d, 0x00a6, "TransducerIndexSelector" }, + { 0x0d, 0x00b0, "ButtonPressThreshold" }, + { 0x0e, 0, "Haptics" }, + { 0x0e, 0x0001, "SimpleHapticController" }, + { 0x0e, 0x0010, "WaveformList" }, + { 0x0e, 0x0011, "DurationList" }, + { 0x0e, 0x0020, "AutoTrigger" }, + { 0x0e, 0x0021, "ManualTrigger" }, + { 0x0e, 0x0022, "AutoTriggerAssociatedControl" }, + { 0x0e, 0x0023, "Intensity" }, + { 0x0e, 0x0024, "RepeatCount" }, + { 0x0e, 0x0025, "RetriggerPeriod" }, + { 0x0e, 0x0026, "WaveformVendorPage" }, + { 0x0e, 0x0027, "WaveformVendorID" }, + { 0x0e, 0x0028, "WaveformCutoffTime" }, + { 0x0e, 0x1001, "WaveformNone" }, + { 0x0e, 0x1002, "WaveformStop" }, + { 0x0e, 0x1003, "WaveformClick" }, + { 0x0e, 0x1004, "WaveformBuzzContinuous" }, + { 0x0e, 0x1005, "WaveformRumbleContinuous" }, + { 0x0e, 0x1006, "WaveformPress" }, + { 0x0e, 0x1007, "WaveformRelease" }, + { 0x0e, 0x1008, "WaveformHover" }, + { 0x0e, 0x1009, "WaveformSuccess" }, + { 0x0e, 0x100a, "WaveformError" }, + { 0x0e, 0x100b, "WaveformInkContinuous" }, + { 0x0e, 0x100c, "WaveformPencilContinuous" }, + { 0x0e, 0x100d, "WaveformMarkerContinuous" }, + { 0x0e, 0x100e, "WaveformChiselMarkerContinuous" }, + { 0x0e, 0x100f, "WaveformBrushContinuous" }, + { 0x0e, 0x1010, "WaveformEraserContinuous" }, + { 0x0e, 0x1011, "WaveformSparkleContinuous" }, + { 0x0f, 0, "PhysicalInputDevice" }, + { 0x0f, 0x0001, "PhysicalInputDevice" }, + { 0x0f, 0x0020, "Normal" }, + { 0x0f, 0x0021, "SetEffectReport" }, + { 0x0f, 0x0022, "EffectParameterBlockIndex" }, + { 0x0f, 0x0023, "ParameterBlockOffset" }, + { 0x0f, 0x0024, "ROMFlag" }, + { 0x0f, 0x0025, "EffectType" }, + { 0x0f, 0x0026, "ETConstantForce" }, + { 0x0f, 0x0027, "ETRamp" }, + { 0x0f, 0x0028, "ETCustomForce" }, + { 0x0f, 0x0030, "ETSquare" }, + { 0x0f, 0x0031, "ETSine" }, + { 0x0f, 0x0032, "ETTriangle" }, + { 0x0f, 0x0033, "ETSawtoothUp" }, + { 0x0f, 0x0034, "ETSawtoothDown" }, + { 0x0f, 0x0040, "ETSpring" }, + { 0x0f, 0x0041, "ETDamper" }, + { 0x0f, 0x0042, "ETInertia" }, + { 0x0f, 0x0043, "ETFriction" }, + { 0x0f, 0x0050, "Duration" }, + { 0x0f, 0x0051, "SamplePeriod" }, + { 0x0f, 0x0052, "Gain" }, + { 0x0f, 0x0053, "TriggerButton" }, + { 0x0f, 0x0054, "TriggerRepeatInterval" }, + { 0x0f, 0x0055, "AxesEnable" }, + { 0x0f, 0x0056, "DirectionEnable" }, + { 0x0f, 0x0057, "Direction" }, + { 0x0f, 0x0058, "TypeSpecificBlockOffset" }, + { 0x0f, 0x0059, "BlockType" }, + { 0x0f, 0x005a, "SetEnvelopeReport" }, + { 0x0f, 0x005b, "AttackLevel" }, + { 0x0f, 0x005c, "AttackTime" }, + { 0x0f, 0x005d, "FadeLevel" }, + { 0x0f, 0x005e, "FadeTime" }, + { 0x0f, 0x005f, "SetConditionReport" }, + { 0x0f, 0x0060, "CenterPointOffset" }, + { 0x0f, 0x0061, "PositiveCoefficient" }, + { 0x0f, 0x0062, "NegativeCoefficient" }, + { 0x0f, 0x0063, "PositiveSaturation" }, + { 0x0f, 0x0064, "NegativeSaturation" }, + { 0x0f, 0x0065, "DeadBand" }, + { 0x0f, 0x0066, "DownloadForceSample" }, + { 0x0f, 0x0067, "IsochCustomForceEnable" }, + { 0x0f, 0x0068, "CustomForceDataReport" }, + { 0x0f, 0x0069, "CustomForceData" }, + { 0x0f, 0x006a, "CustomForceVendorDefinedData" }, + { 0x0f, 0x006b, "SetCustomForceReport" }, + { 0x0f, 0x006c, "CustomForceDataOffset" }, + { 0x0f, 0x006d, "SampleCount" }, + { 0x0f, 0x006e, "SetPeriodicReport" }, + { 0x0f, 0x006f, "Offset" }, + { 0x0f, 0x0070, "Magnitude" }, + { 0x0f, 0x0071, "Phase" }, + { 0x0f, 0x0072, "Period" }, + { 0x0f, 0x0073, "SetConstantForceReport" }, + { 0x0f, 0x0074, "SetRampForceReport" }, + { 0x0f, 0x0075, "RampStart" }, + { 0x0f, 0x0076, "RampEnd" }, + { 0x0f, 0x0077, "EffectOperationReport" }, + { 0x0f, 0x0078, "EffectOperation" }, + { 0x0f, 0x0079, "OpEffectStart" }, + { 0x0f, 0x007a, "OpEffectStartSolo" }, + { 0x0f, 0x007b, "OpEffectStop" }, + { 0x0f, 0x007c, "LoopCount" }, + { 0x0f, 0x007d, "DeviceGainReport" }, + { 0x0f, 0x007e, "DeviceGain" }, + { 0x0f, 0x007f, "ParameterBlockPoolsReport" }, + { 0x0f, 0x0080, "RAMPoolSize" }, + { 0x0f, 0x0081, "ROMPoolSize" }, + { 0x0f, 0x0082, "ROMEffectBlockCount" }, + { 0x0f, 0x0083, "SimultaneousEffectsMax" }, + { 0x0f, 0x0084, "PoolAlignment" }, + { 0x0f, 0x0085, "ParameterBlockMoveReport" }, + { 0x0f, 0x0086, "MoveSource" }, + { 0x0f, 0x0087, "MoveDestination" }, + { 0x0f, 0x0088, "MoveLength" }, + { 0x0f, 0x0089, "EffectParameterBlockLoadReport" }, + { 0x0f, 0x008b, "EffectParameterBlockLoadStatus" }, + { 0x0f, 0x008c, "BlockLoadSuccess" }, + { 0x0f, 0x008d, "BlockLoadFull" }, + { 0x0f, 0x008e, "BlockLoadError" }, + { 0x0f, 0x008f, "BlockHandle" }, + { 0x0f, 0x0090, "EffectParameterBlockFreeReport" }, + { 0x0f, 0x0091, "TypeSpecificBlockHandle" }, + { 0x0f, 0x0092, "PIDStateReport" }, + { 0x0f, 0x0094, "EffectPlaying" }, + { 0x0f, 0x0095, "PIDDeviceControlReport" }, + { 0x0f, 0x0096, "PIDDeviceControl" }, + { 0x0f, 0x0097, "DCEnableActuators" }, + { 0x0f, 0x0098, "DCDisableActuators" }, + { 0x0f, 0x0099, "DCStopAllEffects" }, + { 0x0f, 0x009a, "DCReset" }, + { 0x0f, 0x009b, "DCPause" }, + { 0x0f, 0x009c, "DCContinue" }, + { 0x0f, 0x009f, "DevicePaused" }, + { 0x0f, 0x00a0, "ActuatorsEnabled" }, + { 0x0f, 0x00a4, "SafetySwitch" }, + { 0x0f, 0x00a5, "ActuatorOverrideSwitch" }, + { 0x0f, 0x00a6, "ActuatorPower" }, + { 0x0f, 0x00a7, "StartDelay" }, + { 0x0f, 0x00a8, "ParameterBlockSize" }, + { 0x0f, 0x00a9, "DeviceManagedPool" }, + { 0x0f, 0x00aa, "SharedParameterBlocks" }, + { 0x0f, 0x00ab, "CreateNewEffectParameterBlockReport" }, + { 0x0f, 0x00ac, "RAMPoolAvailable" }, + { 0x11, 0, "SoC" }, + { 0x11, 0x0001, "SocControl" }, + { 0x11, 0x0002, "FirmwareTransfer" }, + { 0x11, 0x0003, "FirmwareFileId" }, + { 0x11, 0x0004, "FileOffsetInBytes" }, + { 0x11, 0x0005, "FileTransferSizeMaxInBytes" }, + { 0x11, 0x0006, "FilePayload" }, + { 0x11, 0x0007, "FilePayloadSizeInBytes" }, + { 0x11, 0x0008, "FilePayloadContainsLastBytes" }, + { 0x11, 0x0009, "FileTransferStop" }, + { 0x11, 0x000a, "FileTransferTillEnd" }, + { 0x12, 0, "EyeandHeadTrackers" }, + { 0x12, 0x0001, "EyeTracker" }, + { 0x12, 0x0002, "HeadTracker" }, + { 0x12, 0x0010, "TrackingData" }, + { 0x12, 0x0011, "Capabilities" }, + { 0x12, 0x0012, "Configuration" }, + { 0x12, 0x0013, "Status" }, + { 0x12, 0x0014, "Control" }, + { 0x12, 0x0020, "SensorTimestamp" }, + { 0x12, 0x0021, "PositionX" }, + { 0x12, 0x0022, "PositionY" }, + { 0x12, 0x0023, "PositionZ" }, + { 0x12, 0x0024, "GazePoint" }, + { 0x12, 0x0025, "LeftEyePosition" }, + { 0x12, 0x0026, "RightEyePosition" }, + { 0x12, 0x0027, "HeadPosition" }, + { 0x12, 0x0028, "HeadDirectionPoint" }, + { 0x12, 0x0029, "RotationaboutXaxis" }, + { 0x12, 0x002a, "RotationaboutYaxis" }, + { 0x12, 0x002b, "RotationaboutZaxis" }, + { 0x12, 0x0100, "TrackerQuality" }, + { 0x12, 0x0101, "MinimumTrackingDistance" }, + { 0x12, 0x0102, "OptimumTrackingDistance" }, + { 0x12, 0x0103, "MaximumTrackingDistance" }, + { 0x12, 0x0104, "MaximumScreenPlaneWidth" }, + { 0x12, 0x0105, "MaximumScreenPlaneHeight" }, + { 0x12, 0x0200, "DisplayManufacturerID" }, + { 0x12, 0x0201, "DisplayProductID" }, + { 0x12, 0x0202, "DisplaySerialNumber" }, + { 0x12, 0x0203, "DisplayManufacturerDate" }, + { 0x12, 0x0204, "CalibratedScreenWidth" }, + { 0x12, 0x0205, "CalibratedScreenHeight" }, + { 0x12, 0x0300, "SamplingFrequency" }, + { 0x12, 0x0301, "ConfigurationStatus" }, + { 0x12, 0x0400, "DeviceModeRequest" }, + { 0x14, 0, "AuxiliaryDisplay" }, + { 0x14, 0x0001, "AlphanumericDisplay" }, + { 0x14, 0x0002, "AuxiliaryDisplay" }, + { 0x14, 0x0020, "DisplayAttributesReport" }, + { 0x14, 0x0021, "ASCIICharacterSet" }, + { 0x14, 0x0022, "DataReadBack" }, + { 0x14, 0x0023, "FontReadBack" }, + { 0x14, 0x0024, "DisplayControlReport" }, + { 0x14, 0x0025, "ClearDisplay" }, + { 0x14, 0x0026, "DisplayEnable" }, + { 0x14, 0x0027, "ScreenSaverDelay" }, + { 0x14, 0x0028, "ScreenSaverEnable" }, + { 0x14, 0x0029, "VerticalScroll" }, + { 0x14, 0x002a, "HorizontalScroll" }, + { 0x14, 0x002b, "CharacterReport" }, + { 0x14, 0x002c, "DisplayData" }, + { 0x14, 0x002d, "DisplayStatus" }, + { 0x14, 0x002e, "StatNotReady" }, + { 0x14, 0x002f, "StatReady" }, + { 0x14, 0x0030, "ErrNotaloadablecharacter" }, + { 0x14, 0x0031, "ErrFontdatacannotberead" }, + { 0x14, 0x0032, "CursorPositionReport" }, + { 0x14, 0x0033, "Row" }, + { 0x14, 0x0034, "Column" }, + { 0x14, 0x0035, "Rows" }, + { 0x14, 0x0036, "Columns" }, + { 0x14, 0x0037, "CursorPixelPositioning" }, + { 0x14, 0x0038, "CursorMode" }, + { 0x14, 0x0039, "CursorEnable" }, + { 0x14, 0x003a, "CursorBlink" }, + { 0x14, 0x003b, "FontReport" }, + { 0x14, 0x003c, "FontData" }, + { 0x14, 0x003d, "CharacterWidth" }, + { 0x14, 0x003e, "CharacterHeight" }, + { 0x14, 0x003f, "CharacterSpacingHorizontal" }, + { 0x14, 0x0040, "CharacterSpacingVertical" }, + { 0x14, 0x0041, "UnicodeCharacterSet" }, + { 0x14, 0x0042, "Font7Segment" }, + { 0x14, 0x0043, "7SegmentDirectMap" }, + { 0x14, 0x0044, "Font14Segment" }, + { 0x14, 0x0045, "14SegmentDirectMap" }, + { 0x14, 0x0046, "DisplayBrightness" }, + { 0x14, 0x0047, "DisplayContrast" }, + { 0x14, 0x0048, "CharacterAttribute" }, + { 0x14, 0x0049, "AttributeReadback" }, + { 0x14, 0x004a, "AttributeData" }, + { 0x14, 0x004b, "CharAttrEnhance" }, + { 0x14, 0x004c, "CharAttrUnderline" }, + { 0x14, 0x004d, "CharAttrBlink" }, + { 0x14, 0x0080, "BitmapSizeX" }, + { 0x14, 0x0081, "BitmapSizeY" }, + { 0x14, 0x0082, "MaxBlitSize" }, + { 0x14, 0x0083, "BitDepthFormat" }, + { 0x14, 0x0084, "DisplayOrientation" }, + { 0x14, 0x0085, "PaletteReport" }, + { 0x14, 0x0086, "PaletteDataSize" }, + { 0x14, 0x0087, "PaletteDataOffset" }, + { 0x14, 0x0088, "PaletteData" }, + { 0x14, 0x008a, "BlitReport" }, + { 0x14, 0x008b, "BlitRectangleX1" }, + { 0x14, 0x008c, "BlitRectangleY1" }, + { 0x14, 0x008d, "BlitRectangleX2" }, + { 0x14, 0x008e, "BlitRectangleY2" }, + { 0x14, 0x008f, "BlitData" }, + { 0x14, 0x0090, "SoftButton" }, + { 0x14, 0x0091, "SoftButtonID" }, + { 0x14, 0x0092, "SoftButtonSide" }, + { 0x14, 0x0093, "SoftButtonOffset1" }, + { 0x14, 0x0094, "SoftButtonOffset2" }, + { 0x14, 0x0095, "SoftButtonReport" }, + { 0x14, 0x00c2, "SoftKeys" }, + { 0x14, 0x00cc, "DisplayDataExtensions" }, + { 0x14, 0x00cf, "CharacterMapping" }, + { 0x14, 0x00dd, "UnicodeEquivalent" }, + { 0x14, 0x00df, "CharacterPageMapping" }, + { 0x14, 0x00ff, "RequestReport" }, + { 0x20, 0, "Sensors" }, + { 0x20, 0x0001, "Sensor" }, + { 0x20, 0x0010, "Biometric" }, + { 0x20, 0x0011, "BiometricHumanPresence" }, + { 0x20, 0x0012, "BiometricHumanProximity" }, + { 0x20, 0x0013, "BiometricHumanTouch" }, + { 0x20, 0x0014, "BiometricBloodPressure" }, + { 0x20, 0x0015, "BiometricBodyTemperature" }, + { 0x20, 0x0016, "BiometricHeartRate" }, + { 0x20, 0x0017, "BiometricHeartRateVariability" }, + { 0x20, 0x0018, "BiometricPeripheralOxygenSaturation" }, + { 0x20, 0x0019, "BiometricRespiratoryRate" }, + { 0x20, 0x0020, "Electrical" }, + { 0x20, 0x0021, "ElectricalCapacitance" }, + { 0x20, 0x0022, "ElectricalCurrent" }, + { 0x20, 0x0023, "ElectricalPower" }, + { 0x20, 0x0024, "ElectricalInductance" }, + { 0x20, 0x0025, "ElectricalResistance" }, + { 0x20, 0x0026, "ElectricalVoltage" }, + { 0x20, 0x0027, "ElectricalPotentiometer" }, + { 0x20, 0x0028, "ElectricalFrequency" }, + { 0x20, 0x0029, "ElectricalPeriod" }, + { 0x20, 0x0030, "Environmental" }, + { 0x20, 0x0031, "EnvironmentalAtmosphericPressure" }, + { 0x20, 0x0032, "EnvironmentalHumidity" }, + { 0x20, 0x0033, "EnvironmentalTemperature" }, + { 0x20, 0x0034, "EnvironmentalWindDirection" }, + { 0x20, 0x0035, "EnvironmentalWindSpeed" }, + { 0x20, 0x0036, "EnvironmentalAirQuality" }, + { 0x20, 0x0037, "EnvironmentalHeatIndex" }, + { 0x20, 0x0038, "EnvironmentalSurfaceTemperature" }, + { 0x20, 0x0039, "EnvironmentalVolatileOrganicCompounds" }, + { 0x20, 0x003a, "EnvironmentalObjectPresence" }, + { 0x20, 0x003b, "EnvironmentalObjectProximity" }, + { 0x20, 0x0040, "Light" }, + { 0x20, 0x0041, "LightAmbientLight" }, + { 0x20, 0x0042, "LightConsumerInfrared" }, + { 0x20, 0x0043, "LightInfraredLight" }, + { 0x20, 0x0044, "LightVisibleLight" }, + { 0x20, 0x0045, "LightUltravioletLight" }, + { 0x20, 0x0050, "Location" }, + { 0x20, 0x0051, "LocationBroadcast" }, + { 0x20, 0x0052, "LocationDeadReckoning" }, + { 0x20, 0x0053, "LocationGPSGlobalPositioningSystem" }, + { 0x20, 0x0054, "LocationLookup" }, + { 0x20, 0x0055, "LocationOther" }, + { 0x20, 0x0056, "LocationStatic" }, + { 0x20, 0x0057, "LocationTriangulation" }, + { 0x20, 0x0060, "Mechanical" }, + { 0x20, 0x0061, "MechanicalBooleanSwitch" }, + { 0x20, 0x0062, "MechanicalBooleanSwitchArray" }, + { 0x20, 0x0063, "MechanicalMultivalueSwitch" }, + { 0x20, 0x0064, "MechanicalForce" }, + { 0x20, 0x0065, "MechanicalPressure" }, + { 0x20, 0x0066, "MechanicalStrain" }, + { 0x20, 0x0067, "MechanicalWeight" }, + { 0x20, 0x0068, "MechanicalHapticVibrator" }, + { 0x20, 0x0069, "MechanicalHallEffectSwitch" }, + { 0x20, 0x0070, "Motion" }, + { 0x20, 0x0071, "MotionAccelerometer1D" }, + { 0x20, 0x0072, "MotionAccelerometer2D" }, + { 0x20, 0x0073, "MotionAccelerometer3D" }, + { 0x20, 0x0074, "MotionGyrometer1D" }, + { 0x20, 0x0075, "MotionGyrometer2D" }, + { 0x20, 0x0076, "MotionGyrometer3D" }, + { 0x20, 0x0077, "MotionMotionDetector" }, + { 0x20, 0x0078, "MotionSpeedometer" }, + { 0x20, 0x0079, "MotionAccelerometer" }, + { 0x20, 0x007a, "MotionGyrometer" }, + { 0x20, 0x007b, "MotionGravityVector" }, + { 0x20, 0x007c, "MotionLinearAccelerometer" }, + { 0x20, 0x0080, "Orientation" }, + { 0x20, 0x0081, "OrientationCompass1D" }, + { 0x20, 0x0082, "OrientationCompass2D" }, + { 0x20, 0x0083, "OrientationCompass3D" }, + { 0x20, 0x0084, "OrientationInclinometer1D" }, + { 0x20, 0x0085, "OrientationInclinometer2D" }, + { 0x20, 0x0086, "OrientationInclinometer3D" }, + { 0x20, 0x0087, "OrientationDistance1D" }, + { 0x20, 0x0088, "OrientationDistance2D" }, + { 0x20, 0x0089, "OrientationDistance3D" }, + { 0x20, 0x008a, "OrientationDeviceOrientation" }, + { 0x20, 0x008b, "OrientationCompass" }, + { 0x20, 0x008c, "OrientationInclinometer" }, + { 0x20, 0x008d, "OrientationDistance" }, + { 0x20, 0x008e, "OrientationRelativeOrientation" }, + { 0x20, 0x008f, "OrientationSimpleOrientation" }, + { 0x20, 0x0090, "Scanner" }, + { 0x20, 0x0091, "ScannerBarcode" }, + { 0x20, 0x0092, "ScannerRFID" }, + { 0x20, 0x0093, "ScannerNFC" }, + { 0x20, 0x00a0, "Time" }, + { 0x20, 0x00a1, "TimeAlarmTimer" }, + { 0x20, 0x00a2, "TimeRealTimeClock" }, + { 0x20, 0x00b0, "PersonalActivity" }, + { 0x20, 0x00b1, "PersonalActivityActivityDetection" }, + { 0x20, 0x00b2, "PersonalActivityDevicePosition" }, + { 0x20, 0x00b3, "PersonalActivityFloorTracker" }, + { 0x20, 0x00b4, "PersonalActivityPedometer" }, + { 0x20, 0x00b5, "PersonalActivityStepDetection" }, + { 0x20, 0x00c0, "OrientationExtended" }, + { 0x20, 0x00c1, "OrientationExtendedGeomagneticOrientation" }, + { 0x20, 0x00c2, "OrientationExtendedMagnetometer" }, + { 0x20, 0x00d0, "Gesture" }, + { 0x20, 0x00d1, "GestureChassisFlipGesture" }, + { 0x20, 0x00d2, "GestureHingeFoldGesture" }, + { 0x20, 0x00e0, "Other" }, + { 0x20, 0x00e1, "OtherCustom" }, + { 0x20, 0x00e2, "OtherGeneric" }, + { 0x20, 0x00e3, "OtherGenericEnumerator" }, + { 0x20, 0x00e4, "OtherHingeAngle" }, + { 0x20, 0x00f0, "VendorReserved1" }, + { 0x20, 0x00f1, "VendorReserved2" }, + { 0x20, 0x00f2, "VendorReserved3" }, + { 0x20, 0x00f3, "VendorReserved4" }, + { 0x20, 0x00f4, "VendorReserved5" }, + { 0x20, 0x00f5, "VendorReserved6" }, + { 0x20, 0x00f6, "VendorReserved7" }, + { 0x20, 0x00f7, "VendorReserved8" }, + { 0x20, 0x00f8, "VendorReserved9" }, + { 0x20, 0x00f9, "VendorReserved10" }, + { 0x20, 0x00fa, "VendorReserved11" }, + { 0x20, 0x00fb, "VendorReserved12" }, + { 0x20, 0x00fc, "VendorReserved13" }, + { 0x20, 0x00fd, "VendorReserved14" }, + { 0x20, 0x00fe, "VendorReserved15" }, + { 0x20, 0x00ff, "VendorReserved16" }, + { 0x20, 0x0200, "Event" }, + { 0x20, 0x0201, "EventSensorState" }, + { 0x20, 0x0202, "EventSensorEvent" }, + { 0x20, 0x0300, "Property" }, + { 0x20, 0x0301, "PropertyFriendlyName" }, + { 0x20, 0x0302, "PropertyPersistentUniqueID" }, + { 0x20, 0x0303, "PropertySensorStatus" }, + { 0x20, 0x0304, "PropertyMinimumReportInterval" }, + { 0x20, 0x0305, "PropertySensorManufacturer" }, + { 0x20, 0x0306, "PropertySensorModel" }, + { 0x20, 0x0307, "PropertySensorSerialNumber" }, + { 0x20, 0x0308, "PropertySensorDescription" }, + { 0x20, 0x0309, "PropertySensorConnectionType" }, + { 0x20, 0x030a, "PropertySensorDevicePath" }, + { 0x20, 0x030b, "PropertyHardwareRevision" }, + { 0x20, 0x030c, "PropertyFirmwareVersion" }, + { 0x20, 0x030d, "PropertyReleaseDate" }, + { 0x20, 0x030e, "PropertyReportInterval" }, + { 0x20, 0x030f, "PropertyChangeSensitivityAbsolute" }, + { 0x20, 0x0310, "PropertyChangeSensitivityPercentofRange" }, + { 0x20, 0x0311, "PropertyChangeSensitivityPercentRelative" }, + { 0x20, 0x0312, "PropertyAccuracy" }, + { 0x20, 0x0313, "PropertyResolution" }, + { 0x20, 0x0314, "PropertyMaximum" }, + { 0x20, 0x0315, "PropertyMinimum" }, + { 0x20, 0x0316, "PropertyReportingState" }, + { 0x20, 0x0317, "PropertySamplingRate" }, + { 0x20, 0x0318, "PropertyResponseCurve" }, + { 0x20, 0x0319, "PropertyPowerState" }, + { 0x20, 0x031a, "PropertyMaximumFIFOEvents" }, + { 0x20, 0x031b, "PropertyReportLatency" }, + { 0x20, 0x031c, "PropertyFlushFIFOEvents" }, + { 0x20, 0x031d, "PropertyMaximumPowerConsumption" }, + { 0x20, 0x031e, "PropertyIsPrimary" }, + { 0x20, 0x031f, "PropertyHumanPresenceDetectionType" }, + { 0x20, 0x0400, "DataFieldLocation" }, + { 0x20, 0x0402, "DataFieldAltitudeAntennaSeaLevel" }, + { 0x20, 0x0403, "DataFieldDifferentialReferenceStationID" }, + { 0x20, 0x0404, "DataFieldAltitudeEllipsoidError" }, + { 0x20, 0x0405, "DataFieldAltitudeEllipsoid" }, + { 0x20, 0x0406, "DataFieldAltitudeSeaLevelError" }, + { 0x20, 0x0407, "DataFieldAltitudeSeaLevel" }, + { 0x20, 0x0408, "DataFieldDifferentialGPSDataAge" }, + { 0x20, 0x0409, "DataFieldErrorRadius" }, + { 0x20, 0x040a, "DataFieldFixQuality" }, + { 0x20, 0x040b, "DataFieldFixType" }, + { 0x20, 0x040c, "DataFieldGeoidalSeparation" }, + { 0x20, 0x040d, "DataFieldGPSOperationMode" }, + { 0x20, 0x040e, "DataFieldGPSSelectionMode" }, + { 0x20, 0x040f, "DataFieldGPSStatus" }, + { 0x20, 0x0410, "DataFieldPositionDilutionofPrecision" }, + { 0x20, 0x0411, "DataFieldHorizontalDilutionofPrecision" }, + { 0x20, 0x0412, "DataFieldVerticalDilutionofPrecision" }, + { 0x20, 0x0413, "DataFieldLatitude" }, + { 0x20, 0x0414, "DataFieldLongitude" }, + { 0x20, 0x0415, "DataFieldTrueHeading" }, + { 0x20, 0x0416, "DataFieldMagneticHeading" }, + { 0x20, 0x0417, "DataFieldMagneticVariation" }, + { 0x20, 0x0418, "DataFieldSpeed" }, + { 0x20, 0x0419, "DataFieldSatellitesinView" }, + { 0x20, 0x041a, "DataFieldSatellitesinViewAzimuth" }, + { 0x20, 0x041b, "DataFieldSatellitesinViewElevation" }, + { 0x20, 0x041c, "DataFieldSatellitesinViewIDs" }, + { 0x20, 0x041d, "DataFieldSatellitesinViewPRNs" }, + { 0x20, 0x041e, "DataFieldSatellitesinViewSNRatios" }, + { 0x20, 0x041f, "DataFieldSatellitesUsedCount" }, + { 0x20, 0x0420, "DataFieldSatellitesUsedPRNs" }, + { 0x20, 0x0421, "DataFieldNMEASentence" }, + { 0x20, 0x0422, "DataFieldAddressLine1" }, + { 0x20, 0x0423, "DataFieldAddressLine2" }, + { 0x20, 0x0424, "DataFieldCity" }, + { 0x20, 0x0425, "DataFieldStateorProvince" }, + { 0x20, 0x0426, "DataFieldCountryorRegion" }, + { 0x20, 0x0427, "DataFieldPostalCode" }, + { 0x20, 0x042a, "PropertyLocation" }, + { 0x20, 0x042b, "PropertyLocationDesiredAccuracy" }, + { 0x20, 0x0430, "DataFieldEnvironmental" }, + { 0x20, 0x0431, "DataFieldAtmosphericPressure" }, + { 0x20, 0x0433, "DataFieldRelativeHumidity" }, + { 0x20, 0x0434, "DataFieldTemperature" }, + { 0x20, 0x0435, "DataFieldWindDirection" }, + { 0x20, 0x0436, "DataFieldWindSpeed" }, + { 0x20, 0x0437, "DataFieldAirQualityIndex" }, + { 0x20, 0x0438, "DataFieldEquivalentCO2" }, + { 0x20, 0x0439, "DataFieldVolatileOrganicCompoundConcentration" }, + { 0x20, 0x043a, "DataFieldObjectPresence" }, + { 0x20, 0x043b, "DataFieldObjectProximityRange" }, + { 0x20, 0x043c, "DataFieldObjectProximityOutofRange" }, + { 0x20, 0x0440, "PropertyEnvironmental" }, + { 0x20, 0x0441, "PropertyReferencePressure" }, + { 0x20, 0x0450, "DataFieldMotion" }, + { 0x20, 0x0451, "DataFieldMotionState" }, + { 0x20, 0x0452, "DataFieldAcceleration" }, + { 0x20, 0x0453, "DataFieldAccelerationAxisX" }, + { 0x20, 0x0454, "DataFieldAccelerationAxisY" }, + { 0x20, 0x0455, "DataFieldAccelerationAxisZ" }, + { 0x20, 0x0456, "DataFieldAngularVelocity" }, + { 0x20, 0x0457, "DataFieldAngularVelocityaboutXAxis" }, + { 0x20, 0x0458, "DataFieldAngularVelocityaboutYAxis" }, + { 0x20, 0x0459, "DataFieldAngularVelocityaboutZAxis" }, + { 0x20, 0x045a, "DataFieldAngularPosition" }, + { 0x20, 0x045b, "DataFieldAngularPositionaboutXAxis" }, + { 0x20, 0x045c, "DataFieldAngularPositionaboutYAxis" }, + { 0x20, 0x045d, "DataFieldAngularPositionaboutZAxis" }, + { 0x20, 0x045e, "DataFieldMotionSpeed" }, + { 0x20, 0x045f, "DataFieldMotionIntensity" }, + { 0x20, 0x0470, "DataFieldOrientation" }, + { 0x20, 0x0471, "DataFieldHeading" }, + { 0x20, 0x0472, "DataFieldHeadingXAxis" }, + { 0x20, 0x0473, "DataFieldHeadingYAxis" }, + { 0x20, 0x0474, "DataFieldHeadingZAxis" }, + { 0x20, 0x0475, "DataFieldHeadingCompensatedMagneticNorth" }, + { 0x20, 0x0476, "DataFieldHeadingCompensatedTrueNorth" }, + { 0x20, 0x0477, "DataFieldHeadingMagneticNorth" }, + { 0x20, 0x0478, "DataFieldHeadingTrueNorth" }, + { 0x20, 0x0479, "DataFieldDistance" }, + { 0x20, 0x047a, "DataFieldDistanceXAxis" }, + { 0x20, 0x047b, "DataFieldDistanceYAxis" }, + { 0x20, 0x047c, "DataFieldDistanceZAxis" }, + { 0x20, 0x047d, "DataFieldDistanceOutofRange" }, + { 0x20, 0x047e, "DataFieldTilt" }, + { 0x20, 0x047f, "DataFieldTiltXAxis" }, + { 0x20, 0x0480, "DataFieldTiltYAxis" }, + { 0x20, 0x0481, "DataFieldTiltZAxis" }, + { 0x20, 0x0482, "DataFieldRotationMatrix" }, + { 0x20, 0x0483, "DataFieldQuaternion" }, + { 0x20, 0x0484, "DataFieldMagneticFlux" }, + { 0x20, 0x0485, "DataFieldMagneticFluxXAxis" }, + { 0x20, 0x0486, "DataFieldMagneticFluxYAxis" }, + { 0x20, 0x0487, "DataFieldMagneticFluxZAxis" }, + { 0x20, 0x0488, "DataFieldMagnetometerAccuracy" }, + { 0x20, 0x0489, "DataFieldSimpleOrientationDirection" }, + { 0x20, 0x0490, "DataFieldMechanical" }, + { 0x20, 0x0491, "DataFieldBooleanSwitchState" }, + { 0x20, 0x0492, "DataFieldBooleanSwitchArrayStates" }, + { 0x20, 0x0493, "DataFieldMultivalueSwitchValue" }, + { 0x20, 0x0494, "DataFieldForce" }, + { 0x20, 0x0495, "DataFieldAbsolutePressure" }, + { 0x20, 0x0496, "DataFieldGaugePressure" }, + { 0x20, 0x0497, "DataFieldStrain" }, + { 0x20, 0x0498, "DataFieldWeight" }, + { 0x20, 0x04a0, "PropertyMechanical" }, + { 0x20, 0x04a1, "PropertyVibrationState" }, + { 0x20, 0x04a2, "PropertyForwardVibrationSpeed" }, + { 0x20, 0x04a3, "PropertyBackwardVibrationSpeed" }, + { 0x20, 0x04b0, "DataFieldBiometric" }, + { 0x20, 0x04b1, "DataFieldHumanPresence" }, + { 0x20, 0x04b2, "DataFieldHumanProximityRange" }, + { 0x20, 0x04b3, "DataFieldHumanProximityOutofRange" }, + { 0x20, 0x04b4, "DataFieldHumanTouchState" }, + { 0x20, 0x04b5, "DataFieldBloodPressure" }, + { 0x20, 0x04b6, "DataFieldBloodPressureDiastolic" }, + { 0x20, 0x04b7, "DataFieldBloodPressureSystolic" }, + { 0x20, 0x04b8, "DataFieldHeartRate" }, + { 0x20, 0x04b9, "DataFieldRestingHeartRate" }, + { 0x20, 0x04ba, "DataFieldHeartbeatInterval" }, + { 0x20, 0x04bb, "DataFieldRespiratoryRate" }, + { 0x20, 0x04bc, "DataFieldSpO2" }, + { 0x20, 0x04bd, "DataFieldHumanAttentionDetected" }, + { 0x20, 0x04be, "DataFieldHumanHeadAzimuth" }, + { 0x20, 0x04bf, "DataFieldHumanHeadAltitude" }, + { 0x20, 0x04c0, "DataFieldHumanHeadRoll" }, + { 0x20, 0x04c1, "DataFieldHumanHeadPitch" }, + { 0x20, 0x04c2, "DataFieldHumanHeadYaw" }, + { 0x20, 0x04c3, "DataFieldHumanCorrelationId" }, + { 0x20, 0x04d0, "DataFieldLight" }, + { 0x20, 0x04d1, "DataFieldIlluminance" }, + { 0x20, 0x04d2, "DataFieldColorTemperature" }, + { 0x20, 0x04d3, "DataFieldChromaticity" }, + { 0x20, 0x04d4, "DataFieldChromaticityX" }, + { 0x20, 0x04d5, "DataFieldChromaticityY" }, + { 0x20, 0x04d6, "DataFieldConsumerIRSentenceReceive" }, + { 0x20, 0x04d7, "DataFieldInfraredLight" }, + { 0x20, 0x04d8, "DataFieldRedLight" }, + { 0x20, 0x04d9, "DataFieldGreenLight" }, + { 0x20, 0x04da, "DataFieldBlueLight" }, + { 0x20, 0x04db, "DataFieldUltravioletALight" }, + { 0x20, 0x04dc, "DataFieldUltravioletBLight" }, + { 0x20, 0x04dd, "DataFieldUltravioletIndex" }, + { 0x20, 0x04de, "DataFieldNearInfraredLight" }, + { 0x20, 0x04df, "PropertyLight" }, + { 0x20, 0x04e0, "PropertyConsumerIRSentenceSend" }, + { 0x20, 0x04e2, "PropertyAutoBrightnessPreferred" }, + { 0x20, 0x04e3, "PropertyAutoColorPreferred" }, + { 0x20, 0x04f0, "DataFieldScanner" }, + { 0x20, 0x04f1, "DataFieldRFIDTag40Bit" }, + { 0x20, 0x04f2, "DataFieldNFCSentenceReceive" }, + { 0x20, 0x04f8, "PropertyScanner" }, + { 0x20, 0x04f9, "PropertyNFCSentenceSend" }, + { 0x20, 0x0500, "DataFieldElectrical" }, + { 0x20, 0x0501, "DataFieldCapacitance" }, + { 0x20, 0x0502, "DataFieldCurrent" }, + { 0x20, 0x0503, "DataFieldElectricalPower" }, + { 0x20, 0x0504, "DataFieldInductance" }, + { 0x20, 0x0505, "DataFieldResistance" }, + { 0x20, 0x0506, "DataFieldVoltage" }, + { 0x20, 0x0507, "DataFieldFrequency" }, + { 0x20, 0x0508, "DataFieldPeriod" }, + { 0x20, 0x0509, "DataFieldPercentofRange" }, + { 0x20, 0x0520, "DataFieldTime" }, + { 0x20, 0x0521, "DataFieldYear" }, + { 0x20, 0x0522, "DataFieldMonth" }, + { 0x20, 0x0523, "DataFieldDay" }, + { 0x20, 0x0524, "DataFieldDayofWeek" }, + { 0x20, 0x0525, "DataFieldHour" }, + { 0x20, 0x0526, "DataFieldMinute" }, + { 0x20, 0x0527, "DataFieldSecond" }, + { 0x20, 0x0528, "DataFieldMillisecond" }, + { 0x20, 0x0529, "DataFieldTimestamp" }, + { 0x20, 0x052a, "DataFieldJulianDayofYear" }, + { 0x20, 0x052b, "DataFieldTimeSinceSystemBoot" }, + { 0x20, 0x0530, "PropertyTime" }, + { 0x20, 0x0531, "PropertyTimeZoneOffsetfromUTC" }, + { 0x20, 0x0532, "PropertyTimeZoneName" }, + { 0x20, 0x0533, "PropertyDaylightSavingsTimeObserved" }, + { 0x20, 0x0534, "PropertyTimeTrimAdjustment" }, + { 0x20, 0x0535, "PropertyArmAlarm" }, + { 0x20, 0x0540, "DataFieldCustom" }, + { 0x20, 0x0541, "DataFieldCustomUsage" }, + { 0x20, 0x0542, "DataFieldCustomBooleanArray" }, + { 0x20, 0x0543, "DataFieldCustomValue" }, + { 0x20, 0x0544, "DataFieldCustomValue1" }, + { 0x20, 0x0545, "DataFieldCustomValue2" }, + { 0x20, 0x0546, "DataFieldCustomValue3" }, + { 0x20, 0x0547, "DataFieldCustomValue4" }, + { 0x20, 0x0548, "DataFieldCustomValue5" }, + { 0x20, 0x0549, "DataFieldCustomValue6" }, + { 0x20, 0x054a, "DataFieldCustomValue7" }, + { 0x20, 0x054b, "DataFieldCustomValue8" }, + { 0x20, 0x054c, "DataFieldCustomValue9" }, + { 0x20, 0x054d, "DataFieldCustomValue10" }, + { 0x20, 0x054e, "DataFieldCustomValue11" }, + { 0x20, 0x054f, "DataFieldCustomValue12" }, + { 0x20, 0x0550, "DataFieldCustomValue13" }, + { 0x20, 0x0551, "DataFieldCustomValue14" }, + { 0x20, 0x0552, "DataFieldCustomValue15" }, + { 0x20, 0x0553, "DataFieldCustomValue16" }, + { 0x20, 0x0554, "DataFieldCustomValue17" }, + { 0x20, 0x0555, "DataFieldCustomValue18" }, + { 0x20, 0x0556, "DataFieldCustomValue19" }, + { 0x20, 0x0557, "DataFieldCustomValue20" }, + { 0x20, 0x0558, "DataFieldCustomValue21" }, + { 0x20, 0x0559, "DataFieldCustomValue22" }, + { 0x20, 0x055a, "DataFieldCustomValue23" }, + { 0x20, 0x055b, "DataFieldCustomValue24" }, + { 0x20, 0x055c, "DataFieldCustomValue25" }, + { 0x20, 0x055d, "DataFieldCustomValue26" }, + { 0x20, 0x055e, "DataFieldCustomValue27" }, + { 0x20, 0x055f, "DataFieldCustomValue28" }, + { 0x20, 0x0560, "DataFieldGeneric" }, + { 0x20, 0x0561, "DataFieldGenericGUIDorPROPERTYKEY" }, + { 0x20, 0x0562, "DataFieldGenericCategoryGUID" }, + { 0x20, 0x0563, "DataFieldGenericTypeGUID" }, + { 0x20, 0x0564, "DataFieldGenericEventPROPERTYKEY" }, + { 0x20, 0x0565, "DataFieldGenericPropertyPROPERTYKEY" }, + { 0x20, 0x0566, "DataFieldGenericDataFieldPROPERTYKEY" }, + { 0x20, 0x0567, "DataFieldGenericEvent" }, + { 0x20, 0x0568, "DataFieldGenericProperty" }, + { 0x20, 0x0569, "DataFieldGenericDataField" }, + { 0x20, 0x056a, "DataFieldEnumeratorTableRowIndex" }, + { 0x20, 0x056b, "DataFieldEnumeratorTableRowCount" }, + { 0x20, 0x056c, "DataFieldGenericGUIDorPROPERTYKEYkind" }, + { 0x20, 0x056d, "DataFieldGenericGUID" }, + { 0x20, 0x056e, "DataFieldGenericPROPERTYKEY" }, + { 0x20, 0x056f, "DataFieldGenericTopLevelCollectionID" }, + { 0x20, 0x0570, "DataFieldGenericReportID" }, + { 0x20, 0x0571, "DataFieldGenericReportItemPositionIndex" }, + { 0x20, 0x0572, "DataFieldGenericFirmwareVARTYPE" }, + { 0x20, 0x0573, "DataFieldGenericUnitofMeasure" }, + { 0x20, 0x0574, "DataFieldGenericUnitExponent" }, + { 0x20, 0x0575, "DataFieldGenericReportSize" }, + { 0x20, 0x0576, "DataFieldGenericReportCount" }, + { 0x20, 0x0580, "PropertyGeneric" }, + { 0x20, 0x0581, "PropertyEnumeratorTableRowIndex" }, + { 0x20, 0x0582, "PropertyEnumeratorTableRowCount" }, + { 0x20, 0x0590, "DataFieldPersonalActivity" }, + { 0x20, 0x0591, "DataFieldActivityType" }, + { 0x20, 0x0592, "DataFieldActivityState" }, + { 0x20, 0x0593, "DataFieldDevicePosition" }, + { 0x20, 0x0594, "DataFieldStepCount" }, + { 0x20, 0x0595, "DataFieldStepCountReset" }, + { 0x20, 0x0596, "DataFieldStepDuration" }, + { 0x20, 0x0597, "DataFieldStepType" }, + { 0x20, 0x05a0, "PropertyMinimumActivityDetectionInterval" }, + { 0x20, 0x05a1, "PropertySupportedActivityTypes" }, + { 0x20, 0x05a2, "PropertySubscribedActivityTypes" }, + { 0x20, 0x05a3, "PropertySupportedStepTypes" }, + { 0x20, 0x05a4, "PropertySubscribedStepTypes" }, + { 0x20, 0x05a5, "PropertyFloorHeight" }, + { 0x20, 0x05b0, "DataFieldCustomTypeID" }, + { 0x20, 0x05c0, "PropertyCustom" }, + { 0x20, 0x05c1, "PropertyCustomValue1" }, + { 0x20, 0x05c2, "PropertyCustomValue2" }, + { 0x20, 0x05c3, "PropertyCustomValue3" }, + { 0x20, 0x05c4, "PropertyCustomValue4" }, + { 0x20, 0x05c5, "PropertyCustomValue5" }, + { 0x20, 0x05c6, "PropertyCustomValue6" }, + { 0x20, 0x05c7, "PropertyCustomValue7" }, + { 0x20, 0x05c8, "PropertyCustomValue8" }, + { 0x20, 0x05c9, "PropertyCustomValue9" }, + { 0x20, 0x05ca, "PropertyCustomValue10" }, + { 0x20, 0x05cb, "PropertyCustomValue11" }, + { 0x20, 0x05cc, "PropertyCustomValue12" }, + { 0x20, 0x05cd, "PropertyCustomValue13" }, + { 0x20, 0x05ce, "PropertyCustomValue14" }, + { 0x20, 0x05cf, "PropertyCustomValue15" }, + { 0x20, 0x05d0, "PropertyCustomValue16" }, + { 0x20, 0x05e0, "DataFieldHinge" }, + { 0x20, 0x05e1, "DataFieldHingeAngle" }, + { 0x20, 0x05f0, "DataFieldGestureSensor" }, + { 0x20, 0x05f1, "DataFieldGestureState" }, + { 0x20, 0x05f2, "DataFieldHingeFoldInitialAngle" }, + { 0x20, 0x05f3, "DataFieldHingeFoldFinalAngle" }, + { 0x20, 0x05f4, "DataFieldHingeFoldContributingPanel" }, + { 0x20, 0x05f5, "DataFieldHingeFoldType" }, + { 0x20, 0x0800, "SensorStateUndefined" }, + { 0x20, 0x0801, "SensorStateReady" }, + { 0x20, 0x0802, "SensorStateNotAvailable" }, + { 0x20, 0x0803, "SensorStateNoData" }, + { 0x20, 0x0804, "SensorStateInitializing" }, + { 0x20, 0x0805, "SensorStateAccessDenied" }, + { 0x20, 0x0806, "SensorStateError" }, + { 0x20, 0x0810, "SensorEventUnknown" }, + { 0x20, 0x0811, "SensorEventStateChanged" }, + { 0x20, 0x0812, "SensorEventPropertyChanged" }, + { 0x20, 0x0813, "SensorEventDataUpdated" }, + { 0x20, 0x0814, "SensorEventPollResponse" }, + { 0x20, 0x0815, "SensorEventChangeSensitivity" }, + { 0x20, 0x0816, "SensorEventRangeMaximumReached" }, + { 0x20, 0x0817, "SensorEventRangeMinimumReached" }, + { 0x20, 0x0818, "SensorEventHighThresholdCrossUpward" }, + { 0x20, 0x0819, "SensorEventHighThresholdCrossDownward" }, + { 0x20, 0x081a, "SensorEventLowThresholdCrossUpward" }, + { 0x20, 0x081b, "SensorEventLowThresholdCrossDownward" }, + { 0x20, 0x081c, "SensorEventZeroThresholdCrossUpward" }, + { 0x20, 0x081d, "SensorEventZeroThresholdCrossDownward" }, + { 0x20, 0x081e, "SensorEventPeriodExceeded" }, + { 0x20, 0x081f, "SensorEventFrequencyExceeded" }, + { 0x20, 0x0820, "SensorEventComplexTrigger" }, + { 0x20, 0x0830, "ConnectionTypePCIntegrated" }, + { 0x20, 0x0831, "ConnectionTypePCAttached" }, + { 0x20, 0x0832, "ConnectionTypePCExternal" }, + { 0x20, 0x0840, "ReportingStateReportNoEvents" }, + { 0x20, 0x0841, "ReportingStateReportAllEvents" }, + { 0x20, 0x0842, "ReportingStateReportThresholdEvents" }, + { 0x20, 0x0843, "ReportingStateWakeOnNoEvents" }, + { 0x20, 0x0844, "ReportingStateWakeOnAllEvents" }, + { 0x20, 0x0845, "ReportingStateWakeOnThresholdEvents" }, + { 0x20, 0x0846, "ReportingStateAnytime" }, + { 0x20, 0x0850, "PowerStateUndefined" }, + { 0x20, 0x0851, "PowerStateD0FullPower" }, + { 0x20, 0x0852, "PowerStateD1LowPower" }, + { 0x20, 0x0853, "PowerStateD2StandbyPowerwithWakeup" }, + { 0x20, 0x0854, "PowerStateD3SleepwithWakeup" }, + { 0x20, 0x0855, "PowerStateD4PowerOff" }, + { 0x20, 0x0860, "AccuracyDefault" }, + { 0x20, 0x0861, "AccuracyHigh" }, + { 0x20, 0x0862, "AccuracyMedium" }, + { 0x20, 0x0863, "AccuracyLow" }, + { 0x20, 0x0870, "FixQualityNoFix" }, + { 0x20, 0x0871, "FixQualityGPS" }, + { 0x20, 0x0872, "FixQualityDGPS" }, + { 0x20, 0x0880, "FixTypeNoFix" }, + { 0x20, 0x0881, "FixTypeGPSSPSModeFixValid" }, + { 0x20, 0x0882, "FixTypeDGPSSPSModeFixValid" }, + { 0x20, 0x0883, "FixTypeGPSPPSModeFixValid" }, + { 0x20, 0x0884, "FixTypeRealTimeKinematic" }, + { 0x20, 0x0885, "FixTypeFloatRTK" }, + { 0x20, 0x0886, "FixTypeEstimateddeadreckoned" }, + { 0x20, 0x0887, "FixTypeManualInputMode" }, + { 0x20, 0x0888, "FixTypeSimulatorMode" }, + { 0x20, 0x0890, "GPSOperationModeManual" }, + { 0x20, 0x0891, "GPSOperationModeAutomatic" }, + { 0x20, 0x08a0, "GPSSelectionModeAutonomous" }, + { 0x20, 0x08a1, "GPSSelectionModeDGPS" }, + { 0x20, 0x08a2, "GPSSelectionModeEstimateddeadreckoned" }, + { 0x20, 0x08a3, "GPSSelectionModeManualInput" }, + { 0x20, 0x08a4, "GPSSelectionModeSimulator" }, + { 0x20, 0x08a5, "GPSSelectionModeDataNotValid" }, + { 0x20, 0x08b0, "GPSStatusDataValid" }, + { 0x20, 0x08b1, "GPSStatusDataNotValid" }, + { 0x20, 0x08c0, "DayofWeekSunday" }, + { 0x20, 0x08c1, "DayofWeekMonday" }, + { 0x20, 0x08c2, "DayofWeekTuesday" }, + { 0x20, 0x08c3, "DayofWeekWednesday" }, + { 0x20, 0x08c4, "DayofWeekThursday" }, + { 0x20, 0x08c5, "DayofWeekFriday" }, + { 0x20, 0x08c6, "DayofWeekSaturday" }, + { 0x20, 0x08d0, "KindCategory" }, + { 0x20, 0x08d1, "KindType" }, + { 0x20, 0x08d2, "KindEvent" }, + { 0x20, 0x08d3, "KindProperty" }, + { 0x20, 0x08d4, "KindDataField" }, + { 0x20, 0x08e0, "MagnetometerAccuracyLow" }, + { 0x20, 0x08e1, "MagnetometerAccuracyMedium" }, + { 0x20, 0x08e2, "MagnetometerAccuracyHigh" }, + { 0x20, 0x08f0, "SimpleOrientationDirectionNotRotated" }, + { 0x20, 0x08f1, "SimpleOrientationDirectionRotated90DegreesCCW" }, + { 0x20, 0x08f2, "SimpleOrientationDirectionRotated180DegreesCCW" }, + { 0x20, 0x08f3, "SimpleOrientationDirectionRotated270DegreesCCW" }, + { 0x20, 0x08f4, "SimpleOrientationDirectionFaceUp" }, + { 0x20, 0x08f5, "SimpleOrientationDirectionFaceDown" }, + { 0x20, 0x0900, "VT_NULL" }, + { 0x20, 0x0901, "VT_BOOL" }, + { 0x20, 0x0902, "VT_UI1" }, + { 0x20, 0x0903, "VT_I1" }, + { 0x20, 0x0904, "VT_UI2" }, + { 0x20, 0x0905, "VT_I2" }, + { 0x20, 0x0906, "VT_UI4" }, + { 0x20, 0x0907, "VT_I4" }, + { 0x20, 0x0908, "VT_UI8" }, + { 0x20, 0x0909, "VT_I8" }, + { 0x20, 0x090a, "VT_R4" }, + { 0x20, 0x090b, "VT_R8" }, + { 0x20, 0x090c, "VT_WSTR" }, + { 0x20, 0x090d, "VT_STR" }, + { 0x20, 0x090e, "VT_CLSID" }, + { 0x20, 0x090f, "VT_VECTORVT_UI1" }, + { 0x20, 0x0910, "VT_F16E0" }, + { 0x20, 0x0911, "VT_F16E1" }, + { 0x20, 0x0912, "VT_F16E2" }, + { 0x20, 0x0913, "VT_F16E3" }, + { 0x20, 0x0914, "VT_F16E4" }, + { 0x20, 0x0915, "VT_F16E5" }, + { 0x20, 0x0916, "VT_F16E6" }, + { 0x20, 0x0917, "VT_F16E7" }, + { 0x20, 0x0918, "VT_F16E8" }, + { 0x20, 0x0919, "VT_F16E9" }, + { 0x20, 0x091a, "VT_F16EA" }, + { 0x20, 0x091b, "VT_F16EB" }, + { 0x20, 0x091c, "VT_F16EC" }, + { 0x20, 0x091d, "VT_F16ED" }, + { 0x20, 0x091e, "VT_F16EE" }, + { 0x20, 0x091f, "VT_F16EF" }, + { 0x20, 0x0920, "VT_F32E0" }, + { 0x20, 0x0921, "VT_F32E1" }, + { 0x20, 0x0922, "VT_F32E2" }, + { 0x20, 0x0923, "VT_F32E3" }, + { 0x20, 0x0924, "VT_F32E4" }, + { 0x20, 0x0925, "VT_F32E5" }, + { 0x20, 0x0926, "VT_F32E6" }, + { 0x20, 0x0927, "VT_F32E7" }, + { 0x20, 0x0928, "VT_F32E8" }, + { 0x20, 0x0929, "VT_F32E9" }, + { 0x20, 0x092a, "VT_F32EA" }, + { 0x20, 0x092b, "VT_F32EB" }, + { 0x20, 0x092c, "VT_F32EC" }, + { 0x20, 0x092d, "VT_F32ED" }, + { 0x20, 0x092e, "VT_F32EE" }, + { 0x20, 0x092f, "VT_F32EF" }, + { 0x20, 0x0930, "ActivityTypeUnknown" }, + { 0x20, 0x0931, "ActivityTypeStationary" }, + { 0x20, 0x0932, "ActivityTypeFidgeting" }, + { 0x20, 0x0933, "ActivityTypeWalking" }, + { 0x20, 0x0934, "ActivityTypeRunning" }, + { 0x20, 0x0935, "ActivityTypeInVehicle" }, + { 0x20, 0x0936, "ActivityTypeBiking" }, + { 0x20, 0x0937, "ActivityTypeIdle" }, + { 0x20, 0x0940, "UnitNotSpecified" }, + { 0x20, 0x0941, "UnitLux" }, + { 0x20, 0x0942, "UnitDegreesKelvin" }, + { 0x20, 0x0943, "UnitDegreesCelsius" }, + { 0x20, 0x0944, "UnitPascal" }, + { 0x20, 0x0945, "UnitNewton" }, + { 0x20, 0x0946, "UnitMetersSecond" }, + { 0x20, 0x0947, "UnitKilogram" }, + { 0x20, 0x0948, "UnitMeter" }, + { 0x20, 0x0949, "UnitMetersSecondSecond" }, + { 0x20, 0x094a, "UnitFarad" }, + { 0x20, 0x094b, "UnitAmpere" }, + { 0x20, 0x094c, "UnitWatt" }, + { 0x20, 0x094d, "UnitHenry" }, + { 0x20, 0x094e, "UnitOhm" }, + { 0x20, 0x094f, "UnitVolt" }, + { 0x20, 0x0950, "UnitHertz" }, + { 0x20, 0x0951, "UnitBar" }, + { 0x20, 0x0952, "UnitDegreesAnticlockwise" }, + { 0x20, 0x0953, "UnitDegreesClockwise" }, + { 0x20, 0x0954, "UnitDegrees" }, + { 0x20, 0x0955, "UnitDegreesSecond" }, + { 0x20, 0x0956, "UnitDegreesSecondSecond" }, + { 0x20, 0x0957, "UnitKnot" }, + { 0x20, 0x0958, "UnitPercent" }, + { 0x20, 0x0959, "UnitSecond" }, + { 0x20, 0x095a, "UnitMillisecond" }, + { 0x20, 0x095b, "UnitG" }, + { 0x20, 0x095c, "UnitBytes" }, + { 0x20, 0x095d, "UnitMilligauss" }, + { 0x20, 0x095e, "UnitBits" }, + { 0x20, 0x0960, "ActivityStateNoStateChange" }, + { 0x20, 0x0961, "ActivityStateStartActivity" }, + { 0x20, 0x0962, "ActivityStateEndActivity" }, + { 0x20, 0x0970, "Exponent0" }, + { 0x20, 0x0971, "Exponent1" }, + { 0x20, 0x0972, "Exponent2" }, + { 0x20, 0x0973, "Exponent3" }, + { 0x20, 0x0974, "Exponent4" }, + { 0x20, 0x0975, "Exponent5" }, + { 0x20, 0x0976, "Exponent6" }, + { 0x20, 0x0977, "Exponent7" }, + { 0x20, 0x0978, "Exponent8" }, + { 0x20, 0x0979, "Exponent9" }, + { 0x20, 0x097a, "ExponentA" }, + { 0x20, 0x097b, "ExponentB" }, + { 0x20, 0x097c, "ExponentC" }, + { 0x20, 0x097d, "ExponentD" }, + { 0x20, 0x097e, "ExponentE" }, + { 0x20, 0x097f, "ExponentF" }, + { 0x20, 0x0980, "DevicePositionUnknown" }, + { 0x20, 0x0981, "DevicePositionUnchanged" }, + { 0x20, 0x0982, "DevicePositionOnDesk" }, + { 0x20, 0x0983, "DevicePositionInHand" }, + { 0x20, 0x0984, "DevicePositionMovinginBag" }, + { 0x20, 0x0985, "DevicePositionStationaryinBag" }, + { 0x20, 0x0990, "StepTypeUnknown" }, + { 0x20, 0x0991, "StepTypeWalking" }, + { 0x20, 0x0992, "StepTypeRunning" }, + { 0x20, 0x09a0, "GestureStateUnknown" }, + { 0x20, 0x09a1, "GestureStateStarted" }, + { 0x20, 0x09a2, "GestureStateCompleted" }, + { 0x20, 0x09a3, "GestureStateCancelled" }, + { 0x20, 0x09b0, "HingeFoldContributingPanelUnknown" }, + { 0x20, 0x09b1, "HingeFoldContributingPanelPanel1" }, + { 0x20, 0x09b2, "HingeFoldContributingPanelPanel2" }, + { 0x20, 0x09b3, "HingeFoldContributingPanelBoth" }, + { 0x20, 0x09b4, "HingeFoldTypeUnknown" }, + { 0x20, 0x09b5, "HingeFoldTypeIncreasing" }, + { 0x20, 0x09b6, "HingeFoldTypeDecreasing" }, + { 0x20, 0x09c0, "HumanPresenceDetectionTypeVendorDefinedNonBiometric" }, + { 0x20, 0x09c1, "HumanPresenceDetectionTypeVendorDefinedBiometric" }, + { 0x20, 0x09c2, "HumanPresenceDetectionTypeFacialBiometric" }, + { 0x20, 0x09c3, "HumanPresenceDetectionTypeAudioBiometric" }, + { 0x20, 0x1000, "ModifierChangeSensitivityAbsolute" }, + { 0x20, 0x2000, "ModifierMaximum" }, + { 0x20, 0x3000, "ModifierMinimum" }, + { 0x20, 0x4000, "ModifierAccuracy" }, + { 0x20, 0x5000, "ModifierResolution" }, + { 0x20, 0x6000, "ModifierThresholdHigh" }, + { 0x20, 0x7000, "ModifierThresholdLow" }, + { 0x20, 0x8000, "ModifierCalibrationOffset" }, + { 0x20, 0x9000, "ModifierCalibrationMultiplier" }, + { 0x20, 0xa000, "ModifierReportInterval" }, + { 0x20, 0xb000, "ModifierFrequencyMax" }, + { 0x20, 0xc000, "ModifierPeriodMax" }, + { 0x20, 0xd000, "ModifierChangeSensitivityPercentofRange" }, + { 0x20, 0xe000, "ModifierChangeSensitivityPercentRelative" }, + { 0x20, 0xf000, "ModifierVendorReserved" }, + { 0x40, 0, "MedicalInstrument" }, + { 0x40, 0x0001, "MedicalUltrasound" }, + { 0x40, 0x0020, "VCRAcquisition" }, + { 0x40, 0x0021, "FreezeThaw" }, + { 0x40, 0x0022, "ClipStore" }, + { 0x40, 0x0023, "Update" }, + { 0x40, 0x0024, "Next" }, + { 0x40, 0x0025, "Save" }, + { 0x40, 0x0026, "Print" }, + { 0x40, 0x0027, "MicrophoneEnable" }, + { 0x40, 0x0040, "Cine" }, + { 0x40, 0x0041, "TransmitPower" }, + { 0x40, 0x0042, "Volume" }, + { 0x40, 0x0043, "Focus" }, + { 0x40, 0x0044, "Depth" }, + { 0x40, 0x0060, "SoftStepPrimary" }, + { 0x40, 0x0061, "SoftStepSecondary" }, + { 0x40, 0x0070, "DepthGainCompensation" }, + { 0x40, 0x0080, "ZoomSelect" }, + { 0x40, 0x0081, "ZoomAdjust" }, + { 0x40, 0x0082, "SpectralDopplerModeSelect" }, + { 0x40, 0x0083, "SpectralDopplerAdjust" }, + { 0x40, 0x0084, "ColorDopplerModeSelect" }, + { 0x40, 0x0085, "ColorDopplerAdjust" }, + { 0x40, 0x0086, "MotionModeSelect" }, + { 0x40, 0x0087, "MotionModeAdjust" }, + { 0x40, 0x0088, "2DModeSelect" }, + { 0x40, 0x0089, "2DModeAdjust" }, + { 0x40, 0x00a0, "SoftControlSelect" }, + { 0x40, 0x00a1, "SoftControlAdjust" }, + { 0x41, 0, "BrailleDisplay" }, + { 0x41, 0x0001, "BrailleDisplay" }, + { 0x41, 0x0002, "BrailleRow" }, + { 0x41, 0x0003, "8DotBrailleCell" }, + { 0x41, 0x0004, "6DotBrailleCell" }, + { 0x41, 0x0005, "NumberofBrailleCells" }, + { 0x41, 0x0006, "ScreenReaderControl" }, + { 0x41, 0x0007, "ScreenReaderIdentifier" }, + { 0x41, 0x00fa, "RouterSet1" }, + { 0x41, 0x00fb, "RouterSet2" }, + { 0x41, 0x00fc, "RouterSet3" }, + { 0x41, 0x0100, "RouterKey" }, + { 0x41, 0x0101, "RowRouterKey" }, + { 0x41, 0x0200, "BrailleButtons" }, + { 0x41, 0x0201, "BrailleKeyboardDot1" }, + { 0x41, 0x0202, "BrailleKeyboardDot2" }, + { 0x41, 0x0203, "BrailleKeyboardDot3" }, + { 0x41, 0x0204, "BrailleKeyboardDot4" }, + { 0x41, 0x0205, "BrailleKeyboardDot5" }, + { 0x41, 0x0206, "BrailleKeyboardDot6" }, + { 0x41, 0x0207, "BrailleKeyboardDot7" }, + { 0x41, 0x0208, "BrailleKeyboardDot8" }, + { 0x41, 0x0209, "BrailleKeyboardSpace" }, + { 0x41, 0x020a, "BrailleKeyboardLeftSpace" }, + { 0x41, 0x020b, "BrailleKeyboardRightSpace" }, + { 0x41, 0x020c, "BrailleFaceControls" }, + { 0x41, 0x020d, "BrailleLeftControls" }, + { 0x41, 0x020e, "BrailleRightControls" }, + { 0x41, 0x020f, "BrailleTopControls" }, + { 0x41, 0x0210, "BrailleJoystickCenter" }, + { 0x41, 0x0211, "BrailleJoystickUp" }, + { 0x41, 0x0212, "BrailleJoystickDown" }, + { 0x41, 0x0213, "BrailleJoystickLeft" }, + { 0x41, 0x0214, "BrailleJoystickRight" }, + { 0x41, 0x0215, "BrailleDPadCenter" }, + { 0x41, 0x0216, "BrailleDPadUp" }, + { 0x41, 0x0217, "BrailleDPadDown" }, + { 0x41, 0x0218, "BrailleDPadLeft" }, + { 0x41, 0x0219, "BrailleDPadRight" }, + { 0x41, 0x021a, "BraillePanLeft" }, + { 0x41, 0x021b, "BraillePanRight" }, + { 0x41, 0x021c, "BrailleRockerUp" }, + { 0x41, 0x021d, "BrailleRockerDown" }, + { 0x41, 0x021e, "BrailleRockerPress" }, + { 0x59, 0, "LightingAndIllumination" }, + { 0x59, 0x0001, "LampArray" }, + { 0x59, 0x0002, "LampArrayAttributesReport" }, + { 0x59, 0x0003, "LampCount" }, + { 0x59, 0x0004, "BoundingBoxWidthInMicrometers" }, + { 0x59, 0x0005, "BoundingBoxHeightInMicrometers" }, + { 0x59, 0x0006, "BoundingBoxDepthInMicrometers" }, + { 0x59, 0x0007, "LampArrayKind" }, + { 0x59, 0x0008, "MinUpdateIntervalInMicroseconds" }, + { 0x59, 0x0020, "LampAttributesRequestReport" }, + { 0x59, 0x0021, "LampId" }, + { 0x59, 0x0022, "LampAttributesResponseReport" }, + { 0x59, 0x0023, "PositionXInMicrometers" }, + { 0x59, 0x0024, "PositionYInMicrometers" }, + { 0x59, 0x0025, "PositionZInMicrometers" }, + { 0x59, 0x0026, "LampPurposes" }, + { 0x59, 0x0027, "UpdateLatencyInMicroseconds" }, + { 0x59, 0x0028, "RedLevelCount" }, + { 0x59, 0x0029, "GreenLevelCount" }, + { 0x59, 0x002a, "BlueLevelCount" }, + { 0x59, 0x002b, "IntensityLevelCount" }, + { 0x59, 0x002c, "IsProgrammable" }, + { 0x59, 0x002d, "InputBinding" }, + { 0x59, 0x0050, "LampMultiUpdateReport" }, + { 0x59, 0x0051, "RedUpdateChannel" }, + { 0x59, 0x0052, "GreenUpdateChannel" }, + { 0x59, 0x0053, "BlueUpdateChannel" }, + { 0x59, 0x0054, "IntensityUpdateChannel" }, + { 0x59, 0x0055, "LampUpdateFlags" }, + { 0x59, 0x0060, "LampRangeUpdateReport" }, + { 0x59, 0x0061, "LampIdStart" }, + { 0x59, 0x0062, "LampIdEnd" }, + { 0x59, 0x0070, "LampArrayControlReport" }, + { 0x59, 0x0071, "AutonomousMode" }, + { 0x80, 0, "Monitor" }, + { 0x80, 0x0001, "MonitorControl" }, + { 0x80, 0x0002, "EDIDInformation" }, + { 0x80, 0x0003, "VDIFInformation" }, + { 0x80, 0x0004, "VESAVersion" }, + { 0x81, 0, "MonitorEnumerated" }, + { 0x82, 0, "VESAVirtualControls" }, + { 0x82, 0x0001, "Degauss" }, + { 0x82, 0x0010, "Brightness" }, + { 0x82, 0x0012, "Contrast" }, + { 0x82, 0x0016, "RedVideoGain" }, + { 0x82, 0x0018, "GreenVideoGain" }, + { 0x82, 0x001a, "BlueVideoGain" }, + { 0x82, 0x001c, "Focus" }, + { 0x82, 0x0020, "HorizontalPosition" }, + { 0x82, 0x0022, "HorizontalSize" }, + { 0x82, 0x0024, "HorizontalPincushion" }, + { 0x82, 0x0026, "HorizontalPincushionBalance" }, + { 0x82, 0x0028, "HorizontalMisconvergence" }, + { 0x82, 0x002a, "HorizontalLinearity" }, + { 0x82, 0x002c, "HorizontalLinearityBalance" }, + { 0x82, 0x0030, "VerticalPosition" }, + { 0x82, 0x0032, "VerticalSize" }, + { 0x82, 0x0034, "VerticalPincushion" }, + { 0x82, 0x0036, "VerticalPincushionBalance" }, + { 0x82, 0x0038, "VerticalMisconvergence" }, + { 0x82, 0x003a, "VerticalLinearity" }, + { 0x82, 0x003c, "VerticalLinearityBalance" }, + { 0x82, 0x0040, "ParallelogramDistortionKeyBalance" }, + { 0x82, 0x0042, "TrapezoidalDistortionKey" }, + { 0x82, 0x0044, "TiltRotation" }, + { 0x82, 0x0046, "TopCornerDistortionControl" }, + { 0x82, 0x0048, "TopCornerDistortionBalance" }, + { 0x82, 0x004a, "BottomCornerDistortionControl" }, + { 0x82, 0x004c, "BottomCornerDistortionBalance" }, + { 0x82, 0x0056, "HorizontalMoire" }, + { 0x82, 0x0058, "VerticalMoire" }, + { 0x82, 0x005e, "InputLevelSelect" }, + { 0x82, 0x0060, "InputSourceSelect" }, + { 0x82, 0x006c, "RedVideoBlackLevel" }, + { 0x82, 0x006e, "GreenVideoBlackLevel" }, + { 0x82, 0x0070, "BlueVideoBlackLevel" }, + { 0x82, 0x00a2, "AutoSizeCenter" }, + { 0x82, 0x00a4, "PolarityHorizontalSynchronization" }, + { 0x82, 0x00a6, "PolarityVerticalSynchronization" }, + { 0x82, 0x00a8, "SynchronizationType" }, + { 0x82, 0x00aa, "ScreenOrientation" }, + { 0x82, 0x00ac, "HorizontalFrequency" }, + { 0x82, 0x00ae, "VerticalFrequency" }, + { 0x82, 0x00b0, "Settings" }, + { 0x82, 0x00ca, "OnScreenDisplay" }, + { 0x82, 0x00d4, "StereoMode" }, + { 0x84, 0, "Power" }, + { 0x84, 0x0001, "iName" }, + { 0x84, 0x0002, "PresentStatus" }, + { 0x84, 0x0003, "ChangedStatus" }, + { 0x84, 0x0004, "UPS" }, + { 0x84, 0x0005, "PowerSupply" }, + { 0x84, 0x0010, "BatterySystem" }, + { 0x84, 0x0011, "BatterySystemId" }, + { 0x84, 0x0012, "Battery" }, + { 0x84, 0x0013, "BatteryId" }, + { 0x84, 0x0014, "Charger" }, + { 0x84, 0x0015, "ChargerId" }, + { 0x84, 0x0016, "PowerConverter" }, + { 0x84, 0x0017, "PowerConverterId" }, + { 0x84, 0x0018, "OutletSystem" }, + { 0x84, 0x0019, "OutletSystemId" }, + { 0x84, 0x001a, "Input" }, + { 0x84, 0x001b, "InputId" }, + { 0x84, 0x001c, "Output" }, + { 0x84, 0x001d, "OutputId" }, + { 0x84, 0x001e, "Flow" }, + { 0x84, 0x001f, "FlowId" }, + { 0x84, 0x0020, "Outlet" }, + { 0x84, 0x0021, "OutletId" }, + { 0x84, 0x0022, "Gang" }, + { 0x84, 0x0023, "GangId" }, + { 0x84, 0x0024, "PowerSummary" }, + { 0x84, 0x0025, "PowerSummaryId" }, + { 0x84, 0x0030, "Voltage" }, + { 0x84, 0x0031, "Current" }, + { 0x84, 0x0032, "Frequency" }, + { 0x84, 0x0033, "ApparentPower" }, + { 0x84, 0x0034, "ActivePower" }, + { 0x84, 0x0035, "PercentLoad" }, + { 0x84, 0x0036, "Temperature" }, + { 0x84, 0x0037, "Humidity" }, + { 0x84, 0x0038, "BadCount" }, + { 0x84, 0x0040, "ConfigVoltage" }, + { 0x84, 0x0041, "ConfigCurrent" }, + { 0x84, 0x0042, "ConfigFrequency" }, + { 0x84, 0x0043, "ConfigApparentPower" }, + { 0x84, 0x0044, "ConfigActivePower" }, + { 0x84, 0x0045, "ConfigPercentLoad" }, + { 0x84, 0x0046, "ConfigTemperature" }, + { 0x84, 0x0047, "ConfigHumidity" }, + { 0x84, 0x0050, "SwitchOnControl" }, + { 0x84, 0x0051, "SwitchOffControl" }, + { 0x84, 0x0052, "ToggleControl" }, + { 0x84, 0x0053, "LowVoltageTransfer" }, + { 0x84, 0x0054, "HighVoltageTransfer" }, + { 0x84, 0x0055, "DelayBeforeReboot" }, + { 0x84, 0x0056, "DelayBeforeStartup" }, + { 0x84, 0x0057, "DelayBeforeShutdown" }, + { 0x84, 0x0058, "Test" }, + { 0x84, 0x0059, "ModuleReset" }, + { 0x84, 0x005a, "AudibleAlarmControl" }, + { 0x84, 0x0060, "Present" }, + { 0x84, 0x0061, "Good" }, + { 0x84, 0x0062, "InternalFailure" }, + { 0x84, 0x0063, "VoltagOutOfRange" }, + { 0x84, 0x0064, "FrequencyOutOfRange" }, + { 0x84, 0x0065, "Overload" }, + { 0x84, 0x0066, "OverCharged" }, + { 0x84, 0x0067, "OverTemperature" }, + { 0x84, 0x0068, "ShutdownRequested" }, + { 0x84, 0x0069, "ShutdownImminent" }, + { 0x84, 0x006b, "SwitchOnOff" }, + { 0x84, 0x006c, "Switchable" }, + { 0x84, 0x006d, "Used" }, + { 0x84, 0x006e, "Boost" }, + { 0x84, 0x006f, "Buck" }, + { 0x84, 0x0070, "Initialized" }, + { 0x84, 0x0071, "Tested" }, + { 0x84, 0x0072, "AwaitingPower" }, + { 0x84, 0x0073, "CommunicationLost" }, + { 0x84, 0x00fd, "iManufacturer" }, + { 0x84, 0x00fe, "iProduct" }, + { 0x84, 0x00ff, "iSerialNumber" }, + { 0x85, 0, "BatterySystem" }, + { 0x85, 0x0001, "SmartBatteryBatteryMode" }, + { 0x85, 0x0002, "SmartBatteryBatteryStatus" }, + { 0x85, 0x0003, "SmartBatteryAlarmWarning" }, + { 0x85, 0x0004, "SmartBatteryChargerMode" }, + { 0x85, 0x0005, "SmartBatteryChargerStatus" }, + { 0x85, 0x0006, "SmartBatteryChargerSpecInfo" }, + { 0x85, 0x0007, "SmartBatterySelectorState" }, + { 0x85, 0x0008, "SmartBatterySelectorPresets" }, + { 0x85, 0x0009, "SmartBatterySelectorInfo" }, + { 0x85, 0x0010, "OptionalMfgFunction1" }, + { 0x85, 0x0011, "OptionalMfgFunction2" }, + { 0x85, 0x0012, "OptionalMfgFunction3" }, + { 0x85, 0x0013, "OptionalMfgFunction4" }, + { 0x85, 0x0014, "OptionalMfgFunction5" }, + { 0x85, 0x0015, "ConnectionToSMBus" }, + { 0x85, 0x0016, "OutputConnection" }, + { 0x85, 0x0017, "ChargerConnection" }, + { 0x85, 0x0018, "BatteryInsertion" }, + { 0x85, 0x0019, "UseNext" }, + { 0x85, 0x001a, "OKToUse" }, + { 0x85, 0x001b, "BatterySupported" }, + { 0x85, 0x001c, "SelectorRevision" }, + { 0x85, 0x001d, "ChargingIndicator" }, + { 0x85, 0x0028, "ManufacturerAccess" }, + { 0x85, 0x0029, "RemainingCapacityLimit" }, + { 0x85, 0x002a, "RemainingTimeLimit" }, + { 0x85, 0x002b, "AtRate" }, + { 0x85, 0x002c, "CapacityMode" }, + { 0x85, 0x002d, "BroadcastToCharger" }, + { 0x85, 0x002e, "PrimaryBattery" }, + { 0x85, 0x002f, "ChargeController" }, + { 0x85, 0x0040, "TerminateCharge" }, + { 0x85, 0x0041, "TerminateDischarge" }, + { 0x85, 0x0042, "BelowRemainingCapacityLimit" }, + { 0x85, 0x0043, "RemainingTimeLimitExpired" }, + { 0x85, 0x0044, "Charging" }, + { 0x85, 0x0045, "Discharging" }, + { 0x85, 0x0046, "FullyCharged" }, + { 0x85, 0x0047, "FullyDischarged" }, + { 0x85, 0x0048, "ConditioningFlag" }, + { 0x85, 0x0049, "AtRateOK" }, + { 0x85, 0x004a, "SmartBatteryErrorCode" }, + { 0x85, 0x004b, "NeedReplacement" }, + { 0x85, 0x0060, "AtRateTimeToFull" }, + { 0x85, 0x0061, "AtRateTimeToEmpty" }, + { 0x85, 0x0062, "AverageCurrent" }, + { 0x85, 0x0063, "MaxError" }, + { 0x85, 0x0064, "RelativeStateOfCharge" }, + { 0x85, 0x0065, "AbsoluteStateOfCharge" }, + { 0x85, 0x0066, "RemainingCapacity" }, + { 0x85, 0x0067, "FullChargeCapacity" }, + { 0x85, 0x0068, "RunTimeToEmpty" }, + { 0x85, 0x0069, "AverageTimeToEmpty" }, + { 0x85, 0x006a, "AverageTimeToFull" }, + { 0x85, 0x006b, "CycleCount" }, + { 0x85, 0x0080, "BatteryPackModelLevel" }, + { 0x85, 0x0081, "InternalChargeController" }, + { 0x85, 0x0082, "PrimaryBatterySupport" }, + { 0x85, 0x0083, "DesignCapacity" }, + { 0x85, 0x0084, "SpecificationInfo" }, + { 0x85, 0x0085, "ManufactureDate" }, + { 0x85, 0x0086, "SerialNumber" }, + { 0x85, 0x0087, "iManufacturerName" }, + { 0x85, 0x0088, "iDeviceName" }, + { 0x85, 0x0089, "iDeviceChemistry" }, + { 0x85, 0x008a, "ManufacturerData" }, + { 0x85, 0x008b, "Rechargable" }, + { 0x85, 0x008c, "WarningCapacityLimit" }, + { 0x85, 0x008d, "CapacityGranularity1" }, + { 0x85, 0x008e, "CapacityGranularity2" }, + { 0x85, 0x008f, "iOEMInformation" }, + { 0x85, 0x00c0, "InhibitCharge" }, + { 0x85, 0x00c1, "EnablePolling" }, + { 0x85, 0x00c2, "ResetToZero" }, + { 0x85, 0x00d0, "ACPresent" }, + { 0x85, 0x00d1, "BatteryPresent" }, + { 0x85, 0x00d2, "PowerFail" }, + { 0x85, 0x00d3, "AlarmInhibited" }, + { 0x85, 0x00d4, "ThermistorUnderRange" }, + { 0x85, 0x00d5, "ThermistorHot" }, + { 0x85, 0x00d6, "ThermistorCold" }, + { 0x85, 0x00d7, "ThermistorOverRange" }, + { 0x85, 0x00d8, "VoltageOutOfRange" }, + { 0x85, 0x00d9, "CurrentOutOfRange" }, + { 0x85, 0x00da, "CurrentNotRegulated" }, + { 0x85, 0x00db, "VoltageNotRegulated" }, + { 0x85, 0x00dc, "MasterMode" }, + { 0x85, 0x00f0, "ChargerSelectorSupport" }, + { 0x85, 0x00f1, "ChargerSpec" }, + { 0x85, 0x00f2, "Level2" }, + { 0x85, 0x00f3, "Level3" }, + { 0x8c, 0, "BarcodeScanner" }, + { 0x8c, 0x0001, "BarcodeBadgeReader" }, + { 0x8c, 0x0002, "BarcodeScanner" }, + { 0x8c, 0x0003, "DumbBarCodeScanner" }, + { 0x8c, 0x0004, "CordlessScannerBase" }, + { 0x8c, 0x0005, "BarCodeScannerCradle" }, + { 0x8c, 0x0010, "AttributeReport" }, + { 0x8c, 0x0011, "SettingsReport" }, + { 0x8c, 0x0012, "ScannedDataReport" }, + { 0x8c, 0x0013, "RawScannedDataReport" }, + { 0x8c, 0x0014, "TriggerReport" }, + { 0x8c, 0x0015, "StatusReport" }, + { 0x8c, 0x0016, "UPCEANControlReport" }, + { 0x8c, 0x0017, "EAN23LabelControlReport" }, + { 0x8c, 0x0018, "Code39ControlReport" }, + { 0x8c, 0x0019, "Interleaved2of5ControlReport" }, + { 0x8c, 0x001a, "Standard2of5ControlReport" }, + { 0x8c, 0x001b, "MSIPlesseyControlReport" }, + { 0x8c, 0x001c, "CodabarControlReport" }, + { 0x8c, 0x001d, "Code128ControlReport" }, + { 0x8c, 0x001e, "Misc1DControlReport" }, + { 0x8c, 0x001f, "2DControlReport" }, + { 0x8c, 0x0030, "AimingPointerMode" }, + { 0x8c, 0x0031, "BarCodePresentSensor" }, + { 0x8c, 0x0032, "Class1ALaser" }, + { 0x8c, 0x0033, "Class2Laser" }, + { 0x8c, 0x0034, "HeaterPresent" }, + { 0x8c, 0x0035, "ContactScanner" }, + { 0x8c, 0x0036, "ElectronicArticleSurveillanceNotification" }, + { 0x8c, 0x0037, "ConstantElectronicArticleSurveillance" }, + { 0x8c, 0x0038, "ErrorIndication" }, + { 0x8c, 0x0039, "FixedBeeper" }, + { 0x8c, 0x003a, "GoodDecodeIndication" }, + { 0x8c, 0x003b, "HandsFreeScanning" }, + { 0x8c, 0x003c, "IntrinsicallySafe" }, + { 0x8c, 0x003d, "KlasseEinsLaser" }, + { 0x8c, 0x003e, "LongRangeScanner" }, + { 0x8c, 0x003f, "MirrorSpeedControl" }, + { 0x8c, 0x0040, "NotOnFileIndication" }, + { 0x8c, 0x0041, "ProgrammableBeeper" }, + { 0x8c, 0x0042, "Triggerless" }, + { 0x8c, 0x0043, "Wand" }, + { 0x8c, 0x0044, "WaterResistant" }, + { 0x8c, 0x0045, "MultiRangeScanner" }, + { 0x8c, 0x0046, "ProximitySensor" }, + { 0x8c, 0x004d, "FragmentDecoding" }, + { 0x8c, 0x004e, "ScannerReadConfidence" }, + { 0x8c, 0x004f, "DataPrefix" }, + { 0x8c, 0x0050, "PrefixAIMI" }, + { 0x8c, 0x0051, "PrefixNone" }, + { 0x8c, 0x0052, "PrefixProprietary" }, + { 0x8c, 0x0055, "ActiveTime" }, + { 0x8c, 0x0056, "AimingLaserPattern" }, + { 0x8c, 0x0057, "BarCodePresent" }, + { 0x8c, 0x0058, "BeeperState" }, + { 0x8c, 0x0059, "LaserOnTime" }, + { 0x8c, 0x005a, "LaserState" }, + { 0x8c, 0x005b, "LockoutTime" }, + { 0x8c, 0x005c, "MotorState" }, + { 0x8c, 0x005d, "MotorTimeout" }, + { 0x8c, 0x005e, "PowerOnResetScanner" }, + { 0x8c, 0x005f, "PreventReadofBarcodes" }, + { 0x8c, 0x0060, "InitiateBarcodeRead" }, + { 0x8c, 0x0061, "TriggerState" }, + { 0x8c, 0x0062, "TriggerMode" }, + { 0x8c, 0x0063, "TriggerModeBlinkingLaserOn" }, + { 0x8c, 0x0064, "TriggerModeContinuousLaserOn" }, + { 0x8c, 0x0065, "TriggerModeLaseronwhilePulled" }, + { 0x8c, 0x0066, "TriggerModeLaserstaysonafterrelease" }, + { 0x8c, 0x006d, "CommitParameterstoNVM" }, + { 0x8c, 0x006e, "ParameterScanning" }, + { 0x8c, 0x006f, "ParametersChanged" }, + { 0x8c, 0x0070, "Setparameterdefaultvalues" }, + { 0x8c, 0x0075, "ScannerInCradle" }, + { 0x8c, 0x0076, "ScannerInRange" }, + { 0x8c, 0x007a, "AimDuration" }, + { 0x8c, 0x007b, "GoodReadLampDuration" }, + { 0x8c, 0x007c, "GoodReadLampIntensity" }, + { 0x8c, 0x007d, "GoodReadLED" }, + { 0x8c, 0x007e, "GoodReadToneFrequency" }, + { 0x8c, 0x007f, "GoodReadToneLength" }, + { 0x8c, 0x0080, "GoodReadToneVolume" }, + { 0x8c, 0x0082, "NoReadMessage" }, + { 0x8c, 0x0083, "NotonFileVolume" }, + { 0x8c, 0x0084, "PowerupBeep" }, + { 0x8c, 0x0085, "SoundErrorBeep" }, + { 0x8c, 0x0086, "SoundGoodReadBeep" }, + { 0x8c, 0x0087, "SoundNotOnFileBeep" }, + { 0x8c, 0x0088, "GoodReadWhentoWrite" }, + { 0x8c, 0x0089, "GRWTIAfterDecode" }, + { 0x8c, 0x008a, "GRWTIBeepLampaftertransmit" }, + { 0x8c, 0x008b, "GRWTINoBeepLampuseatall" }, + { 0x8c, 0x0091, "BooklandEAN" }, + { 0x8c, 0x0092, "ConvertEAN8to13Type" }, + { 0x8c, 0x0093, "ConvertUPCAtoEAN13" }, + { 0x8c, 0x0094, "ConvertUPCEtoA" }, + { 0x8c, 0x0095, "EAN13" }, + { 0x8c, 0x0096, "EAN8" }, + { 0x8c, 0x0097, "EAN99128Mandatory" }, + { 0x8c, 0x0098, "EAN99P5128Optional" }, + { 0x8c, 0x0099, "EnableEANTwoLabel" }, + { 0x8c, 0x009a, "UPCEAN" }, + { 0x8c, 0x009b, "UPCEANCouponCode" }, + { 0x8c, 0x009c, "UPCEANPeriodicals" }, + { 0x8c, 0x009d, "UPCA" }, + { 0x8c, 0x009e, "UPCAwith128Mandatory" }, + { 0x8c, 0x009f, "UPCAwith128Optional" }, + { 0x8c, 0x00a0, "UPCAwithP5Optional" }, + { 0x8c, 0x00a1, "UPCE" }, + { 0x8c, 0x00a2, "UPCE1" }, + { 0x8c, 0x00a9, "Periodical" }, + { 0x8c, 0x00aa, "PeriodicalAutoDiscriminate2" }, + { 0x8c, 0x00ab, "PeriodicalOnlyDecodewith2" }, + { 0x8c, 0x00ac, "PeriodicalIgnore2" }, + { 0x8c, 0x00ad, "PeriodicalAutoDiscriminate5" }, + { 0x8c, 0x00ae, "PeriodicalOnlyDecodewith5" }, + { 0x8c, 0x00af, "PeriodicalIgnore5" }, + { 0x8c, 0x00b0, "Check" }, + { 0x8c, 0x00b1, "CheckDisablePrice" }, + { 0x8c, 0x00b2, "CheckEnable4digitPrice" }, + { 0x8c, 0x00b3, "CheckEnable5digitPrice" }, + { 0x8c, 0x00b4, "CheckEnableEuropean4digitPrice" }, + { 0x8c, 0x00b5, "CheckEnableEuropean5digitPrice" }, + { 0x8c, 0x00b7, "EANTwoLabel" }, + { 0x8c, 0x00b8, "EANThreeLabel" }, + { 0x8c, 0x00b9, "EAN8FlagDigit1" }, + { 0x8c, 0x00ba, "EAN8FlagDigit2" }, + { 0x8c, 0x00bb, "EAN8FlagDigit3" }, + { 0x8c, 0x00bc, "EAN13FlagDigit1" }, + { 0x8c, 0x00bd, "EAN13FlagDigit2" }, + { 0x8c, 0x00be, "EAN13FlagDigit3" }, + { 0x8c, 0x00bf, "AddEAN23LabelDefinition" }, + { 0x8c, 0x00c0, "ClearallEAN23LabelDefinitions" }, + { 0x8c, 0x00c3, "Codabar" }, + { 0x8c, 0x00c4, "Code128" }, + { 0x8c, 0x00c7, "Code39" }, + { 0x8c, 0x00c8, "Code93" }, + { 0x8c, 0x00c9, "FullASCIIConversion" }, + { 0x8c, 0x00ca, "Interleaved2of5" }, + { 0x8c, 0x00cb, "ItalianPharmacyCode" }, + { 0x8c, 0x00cc, "MSIPlessey" }, + { 0x8c, 0x00cd, "Standard2of5IATA" }, + { 0x8c, 0x00ce, "Standard2of5" }, + { 0x8c, 0x00d3, "TransmitStartStop" }, + { 0x8c, 0x00d4, "TriOptic" }, + { 0x8c, 0x00d5, "UCCEAN128" }, + { 0x8c, 0x00d6, "CheckDigit" }, + { 0x8c, 0x00d7, "CheckDigitDisable" }, + { 0x8c, 0x00d8, "CheckDigitEnableInterleaved2of5OPCC" }, + { 0x8c, 0x00d9, "CheckDigitEnableInterleaved2of5USS" }, + { 0x8c, 0x00da, "CheckDigitEnableStandard2of5OPCC" }, + { 0x8c, 0x00db, "CheckDigitEnableStandard2of5USS" }, + { 0x8c, 0x00dc, "CheckDigitEnableOneMSIPlessey" }, + { 0x8c, 0x00dd, "CheckDigitEnableTwoMSIPlessey" }, + { 0x8c, 0x00de, "CheckDigitCodabarEnable" }, + { 0x8c, 0x00df, "CheckDigitCode39Enable" }, + { 0x8c, 0x00f0, "TransmitCheckDigit" }, + { 0x8c, 0x00f1, "DisableCheckDigitTransmit" }, + { 0x8c, 0x00f2, "EnableCheckDigitTransmit" }, + { 0x8c, 0x00fb, "SymbologyIdentifier1" }, + { 0x8c, 0x00fc, "SymbologyIdentifier2" }, + { 0x8c, 0x00fd, "SymbologyIdentifier3" }, + { 0x8c, 0x00fe, "DecodedData" }, + { 0x8c, 0x00ff, "DecodeDataContinued" }, + { 0x8c, 0x0100, "BarSpaceData" }, + { 0x8c, 0x0101, "ScannerDataAccuracy" }, + { 0x8c, 0x0102, "RawDataPolarity" }, + { 0x8c, 0x0103, "PolarityInvertedBarCode" }, + { 0x8c, 0x0104, "PolarityNormalBarCode" }, + { 0x8c, 0x0106, "MinimumLengthtoDecode" }, + { 0x8c, 0x0107, "MaximumLengthtoDecode" }, + { 0x8c, 0x0108, "DiscreteLengthtoDecode1" }, + { 0x8c, 0x0109, "DiscreteLengthtoDecode2" }, + { 0x8c, 0x010a, "DataLengthMethod" }, + { 0x8c, 0x010b, "DLMethodReadany" }, + { 0x8c, 0x010c, "DLMethodCheckinRange" }, + { 0x8c, 0x010d, "DLMethodCheckforDiscrete" }, + { 0x8c, 0x0110, "AztecCode" }, + { 0x8c, 0x0111, "BC412" }, + { 0x8c, 0x0112, "ChannelCode" }, + { 0x8c, 0x0113, "Code16" }, + { 0x8c, 0x0114, "Code32" }, + { 0x8c, 0x0115, "Code49" }, + { 0x8c, 0x0116, "CodeOne" }, + { 0x8c, 0x0117, "Colorcode" }, + { 0x8c, 0x0118, "DataMatrix" }, + { 0x8c, 0x0119, "MaxiCode" }, + { 0x8c, 0x011a, "MicroPDF" }, + { 0x8c, 0x011b, "PDF417" }, + { 0x8c, 0x011c, "PosiCode" }, + { 0x8c, 0x011d, "QRCode" }, + { 0x8c, 0x011e, "SuperCode" }, + { 0x8c, 0x011f, "UltraCode" }, + { 0x8c, 0x0120, "USD5SlugCode" }, + { 0x8c, 0x0121, "VeriCode" }, + { 0x8d, 0, "Scales" }, + { 0x8d, 0x0001, "Scales" }, + { 0x8d, 0x0020, "ScaleDevice" }, + { 0x8d, 0x0021, "ScaleClass" }, + { 0x8d, 0x0022, "ScaleClassIMetric" }, + { 0x8d, 0x0023, "ScaleClassIIMetric" }, + { 0x8d, 0x0024, "ScaleClassIIIMetric" }, + { 0x8d, 0x0025, "ScaleClassIIILMetric" }, + { 0x8d, 0x0026, "ScaleClassIVMetric" }, + { 0x8d, 0x0027, "ScaleClassIIIEnglish" }, + { 0x8d, 0x0028, "ScaleClassIIILEnglish" }, + { 0x8d, 0x0029, "ScaleClassIVEnglish" }, + { 0x8d, 0x002a, "ScaleClassGeneric" }, + { 0x8d, 0x0030, "ScaleAttributeReport" }, + { 0x8d, 0x0031, "ScaleControlReport" }, + { 0x8d, 0x0032, "ScaleDataReport" }, + { 0x8d, 0x0033, "ScaleStatusReport" }, + { 0x8d, 0x0034, "ScaleWeightLimitReport" }, + { 0x8d, 0x0035, "ScaleStatisticsReport" }, + { 0x8d, 0x0040, "DataWeight" }, + { 0x8d, 0x0041, "DataScaling" }, + { 0x8d, 0x0050, "WeightUnit" }, + { 0x8d, 0x0051, "WeightUnitMilligram" }, + { 0x8d, 0x0052, "WeightUnitGram" }, + { 0x8d, 0x0053, "WeightUnitKilogram" }, + { 0x8d, 0x0054, "WeightUnitCarats" }, + { 0x8d, 0x0055, "WeightUnitTaels" }, + { 0x8d, 0x0056, "WeightUnitGrains" }, + { 0x8d, 0x0057, "WeightUnitPennyweights" }, + { 0x8d, 0x0058, "WeightUnitMetricTon" }, + { 0x8d, 0x0059, "WeightUnitAvoirTon" }, + { 0x8d, 0x005a, "WeightUnitTroyOunce" }, + { 0x8d, 0x005b, "WeightUnitOunce" }, + { 0x8d, 0x005c, "WeightUnitPound" }, + { 0x8d, 0x0060, "CalibrationCount" }, + { 0x8d, 0x0061, "ReZeroCount" }, + { 0x8d, 0x0070, "ScaleStatus" }, + { 0x8d, 0x0071, "ScaleStatusFault" }, + { 0x8d, 0x0072, "ScaleStatusStableatCenterofZero" }, + { 0x8d, 0x0073, "ScaleStatusInMotion" }, + { 0x8d, 0x0074, "ScaleStatusWeightStable" }, + { 0x8d, 0x0075, "ScaleStatusUnderZero" }, + { 0x8d, 0x0076, "ScaleStatusOverWeightLimit" }, + { 0x8d, 0x0077, "ScaleStatusRequiresCalibration" }, + { 0x8d, 0x0078, "ScaleStatusRequiresRezeroing" }, + { 0x8d, 0x0080, "ZeroScale" }, + { 0x8d, 0x0081, "EnforcedZeroReturn" }, + { 0x8e, 0, "MagneticStripeReader" }, + { 0x8e, 0x0001, "MSRDeviceReadOnly" }, + { 0x8e, 0x0011, "Track1Length" }, + { 0x8e, 0x0012, "Track2Length" }, + { 0x8e, 0x0013, "Track3Length" }, + { 0x8e, 0x0014, "TrackJISLength" }, + { 0x8e, 0x0020, "TrackData" }, + { 0x8e, 0x0021, "Track1Data" }, + { 0x8e, 0x0022, "Track2Data" }, + { 0x8e, 0x0023, "Track3Data" }, + { 0x8e, 0x0024, "TrackJISData" }, + { 0x90, 0, "CameraControl" }, + { 0x90, 0x0020, "CameraAutofocus" }, + { 0x90, 0x0021, "CameraShutter" }, + { 0x91, 0, "Arcade" }, + { 0x91, 0x0001, "GeneralPurposeIOCard" }, + { 0x91, 0x0002, "CoinDoor" }, + { 0x91, 0x0003, "WatchdogTimer" }, + { 0x91, 0x0030, "GeneralPurposeAnalogInputState" }, + { 0x91, 0x0031, "GeneralPurposeDigitalInputState" }, + { 0x91, 0x0032, "GeneralPurposeOpticalInputState" }, + { 0x91, 0x0033, "GeneralPurposeDigitalOutputState" }, + { 0x91, 0x0034, "NumberofCoinDoors" }, + { 0x91, 0x0035, "CoinDrawerDropCount" }, + { 0x91, 0x0036, "CoinDrawerStart" }, + { 0x91, 0x0037, "CoinDrawerService" }, + { 0x91, 0x0038, "CoinDrawerTilt" }, + { 0x91, 0x0039, "CoinDoorTest" }, + { 0x91, 0x0040, "CoinDoorLockout" }, + { 0x91, 0x0041, "WatchdogTimeout" }, + { 0x91, 0x0042, "WatchdogAction" }, + { 0x91, 0x0043, "WatchdogReboot" }, + { 0x91, 0x0044, "WatchdogRestart" }, + { 0x91, 0x0045, "AlarmInput" }, + { 0x91, 0x0046, "CoinDoorCounter" }, + { 0x91, 0x0047, "IODirectionMapping" }, + { 0x91, 0x0048, "SetIODirectionMapping" }, + { 0x91, 0x0049, "ExtendedOpticalInputState" }, + { 0x91, 0x004a, "PinPadInputState" }, + { 0x91, 0x004b, "PinPadStatus" }, + { 0x91, 0x004c, "PinPadOutput" }, + { 0x91, 0x004d, "PinPadCommand" }, + { 0xf1d0, 0, "FIDOAlliance" }, + { 0xf1d0, 0x0001, "U2FAuthenticatorDevice" }, + { 0xf1d0, 0x0020, "InputReportData" }, + { 0xf1d0, 0x0021, "OutputReportData" }, + /* pages 0xff00 to 0xffff are vendor-specific */ + { 0xffff, 0, "Vendor-specific-FF" }, + { 0, 0, NULL } }; /* Either output directly into simple seq_file, or (if f == NULL) @@ -510,8 +2881,12 @@ static char *resolv_usage_page(unsigned page, struct seq_file *f) { char *hid_resolv_usage(unsigned usage, struct seq_file *f) { const struct hid_usage_entry *p; + const struct hid_usage_entry *m; char *buf = NULL; int len = 0; + const char *modifier = NULL; + unsigned int usage_modifier = usage & 0xF000; + unsigned int usage_actual = usage & 0xFFFF; buf = resolv_usage_page(usage >> 16, f); if (IS_ERR(buf)) { @@ -529,16 +2904,33 @@ char *hid_resolv_usage(unsigned usage, struct seq_file *f) { } for (p = hid_usage_table; p->description; p++) if (p->page == (usage >> 16)) { + if (p->page == 0x20 && usage_modifier) { + for (m = p; m->description; m++) { + if (p->page == m->page && m->usage + == usage_modifier) { + modifier = m->description; + break; + } + } + if (modifier) + usage_actual = usage_actual & 0x0FFF; + } + + if (!modifier) + modifier = ""; + for(++p; p->description && p->usage != 0; p++) - if (p->usage == (usage & 0xffff)) { + if (p->usage == usage_actual) { if (!f) snprintf(buf + len, HID_DEBUG_BUFSIZE - len, - "%s", p->description); + "%s%s", p->description, + modifier); else seq_printf(f, - "%s", - p->description); + "%s%s", + p->description, + modifier); return buf; } break; @@ -753,12 +3145,12 @@ static const char *events[EV_MAX + 1] = { [EV_MSC] = "Misc", [EV_LED] = "LED", [EV_SND] = "Sound", [EV_REP] = "Repeat", [EV_FF] = "ForceFeedback", [EV_PWR] = "Power", - [EV_FF_STATUS] = "ForceFeedbackStatus", + [EV_FF_STATUS] = "ForceFeedbackStatus", [EV_SW] = "Software", }; -static const char *syncs[3] = { +static const char *syncs[SYN_CNT] = { [SYN_REPORT] = "Report", [SYN_CONFIG] = "Config", - [SYN_MT_REPORT] = "MT Report", + [SYN_MT_REPORT] = "MT Report", [SYN_DROPPED] = "Dropped", }; static const char *keys[KEY_MAX + 1] = { @@ -995,6 +3387,103 @@ static const char *keys[KEY_MAX + 1] = { [KEY_MACRO22] = "Macro22", [KEY_MACRO23] = "Macro23", [KEY_MACRO24] = "Macro24", [KEY_MACRO25] = "Macro25", [KEY_MACRO26] = "Macro26", [KEY_MACRO27] = "Macro27", [KEY_MACRO28] = "Macro28", [KEY_MACRO29] = "Macro29", [KEY_MACRO30] = "Macro30", + [BTN_TRIGGER_HAPPY1] = "TriggerHappy1", [BTN_TRIGGER_HAPPY2] = "TriggerHappy2", + [BTN_TRIGGER_HAPPY3] = "TriggerHappy3", [BTN_TRIGGER_HAPPY4] = "TriggerHappy4", + [BTN_TRIGGER_HAPPY5] = "TriggerHappy5", [BTN_TRIGGER_HAPPY6] = "TriggerHappy6", + [BTN_TRIGGER_HAPPY7] = "TriggerHappy7", [BTN_TRIGGER_HAPPY8] = "TriggerHappy8", + [BTN_TRIGGER_HAPPY9] = "TriggerHappy9", [BTN_TRIGGER_HAPPY10] = "TriggerHappy10", + [BTN_TRIGGER_HAPPY11] = "TriggerHappy11", [BTN_TRIGGER_HAPPY12] = "TriggerHappy12", + [BTN_TRIGGER_HAPPY13] = "TriggerHappy13", [BTN_TRIGGER_HAPPY14] = "TriggerHappy14", + [BTN_TRIGGER_HAPPY15] = "TriggerHappy15", [BTN_TRIGGER_HAPPY16] = "TriggerHappy16", + [BTN_TRIGGER_HAPPY17] = "TriggerHappy17", [BTN_TRIGGER_HAPPY18] = "TriggerHappy18", + [BTN_TRIGGER_HAPPY19] = "TriggerHappy19", [BTN_TRIGGER_HAPPY20] = "TriggerHappy20", + [BTN_TRIGGER_HAPPY21] = "TriggerHappy21", [BTN_TRIGGER_HAPPY22] = "TriggerHappy22", + [BTN_TRIGGER_HAPPY23] = "TriggerHappy23", [BTN_TRIGGER_HAPPY24] = "TriggerHappy24", + [BTN_TRIGGER_HAPPY25] = "TriggerHappy25", [BTN_TRIGGER_HAPPY26] = "TriggerHappy26", + [BTN_TRIGGER_HAPPY27] = "TriggerHappy27", [BTN_TRIGGER_HAPPY28] = "TriggerHappy28", + [BTN_TRIGGER_HAPPY29] = "TriggerHappy29", [BTN_TRIGGER_HAPPY30] = "TriggerHappy30", + [BTN_TRIGGER_HAPPY31] = "TriggerHappy31", [BTN_TRIGGER_HAPPY32] = "TriggerHappy32", + [BTN_TRIGGER_HAPPY33] = "TriggerHappy33", [BTN_TRIGGER_HAPPY34] = "TriggerHappy34", + [BTN_TRIGGER_HAPPY35] = "TriggerHappy35", [BTN_TRIGGER_HAPPY36] = "TriggerHappy36", + [BTN_TRIGGER_HAPPY37] = "TriggerHappy37", [BTN_TRIGGER_HAPPY38] = "TriggerHappy38", + [BTN_TRIGGER_HAPPY39] = "TriggerHappy39", [BTN_TRIGGER_HAPPY40] = "TriggerHappy40", + [BTN_DIGI] = "Digi", [BTN_STYLUS3] = "Stylus3", + [BTN_TOOL_QUINTTAP] = "ToolQuintTap", [BTN_WHEEL] = "Wheel", + [KEY_10CHANNELSDOWN] = "10ChannelsDown", + [KEY_10CHANNELSUP] = "10ChannelsUp", + [KEY_3D_MODE] = "3DMode", [KEY_ADDRESSBOOK] = "Addressbook", + [KEY_ALS_TOGGLE] = "ALSToggle", [KEY_ASPECT_RATIO] = "AspectRatio", + [KEY_ATTENDANT_OFF] = "AttendantOff", [KEY_ATTENDANT_ON] = "AttendantOn", + [KEY_ATTENDANT_TOGGLE] = "AttendantToggle", + [KEY_AUDIO_DESC] = "AudioDesc", + [KEY_AUTOPILOT_ENGAGE_TOGGLE] = "AutoPiloteEngage", + [KEY_BATTERY] = "Battery", [KEY_BLUETOOTH] = "BlueTooth", + [KEY_BRIGHTNESS_CYCLE] = "BrightnessCycle", + [KEY_BRIGHTNESS_MENU] = "BrightnessMenu", + [KEY_BRL_DOT1] = "BrlDot1", [KEY_BRL_DOT10] = "BrlDot10", + [KEY_BRL_DOT2] = "BrlDot2", [KEY_BRL_DOT3] = "BrlDot3", + [KEY_BRL_DOT4] = "BrlDot4", [KEY_BRL_DOT5] = "BrlDot5", + [KEY_BRL_DOT6] = "BrlDot6", [KEY_BRL_DOT7] = "BrlDot7", + [KEY_BRL_DOT8] = "BrlDot8", [KEY_BRL_DOT9] = "BrlDot9", + [KEY_CAMERA_DOWN] = "CameraDown", [KEY_CAMERA_FOCUS] = "CameraFocus", + [KEY_CAMERA_LEFT] = "CameraLeft", [KEY_CAMERA_RIGHT] = "CameraRight", + [KEY_CAMERA_UP] = "CameraUp", [KEY_CAMERA_ZOOMIN] = "CameraZoomIn", + [KEY_CAMERA_ZOOMOUT] = "CameraZoomOut", [KEY_CLEARVU_SONAR] = "ClearVUSonar", + [KEY_CONTEXT_MENU] = "ContextMenu", [KEY_DATA] = "Data", + [KEY_DATABASE] = "DataBase", [KEY_DISPLAY_OFF] = "DisplayOff", + [KEY_DISPLAYTOGGLE] = "DisplayToggle", [KEY_DOLLAR] = "Dollar", + [KEY_DUAL_RANGE_RADAR] = "DualRangeRadat", + [KEY_EDITOR] = "Editor", [KEY_EURO] = "Euro", + [KEY_FASTREVERSE] = "FastReverse", [KEY_FISHING_CHART] = "FishingChart", + [KEY_FN_RIGHT_SHIFT] = "FnRightShift", [KEY_FRAMEBACK] = "FrameBack", + [KEY_FRAMEFORWARD] = "FrameForward", [KEY_FULL_SCREEN] = "FullScreen", + [KEY_GAMES] = "Games", [KEY_GRAPHICSEDITOR] = "GraphicsEditor", + [KEY_HANGEUL] = "HanGeul", [KEY_HANGUP_PHONE] = "HangUpPhone", + [KEY_IMAGES] = "Images", [KEY_KBD_LCD_MENU1] = "KbdLcdMenu1", + [KEY_KBD_LCD_MENU2] = "KbdLcdMenu2", [KEY_KBD_LCD_MENU3] = "KbdLcdMenu3", + [KEY_KBD_LCD_MENU4] = "KbdLcdMenu4", [KEY_KBD_LCD_MENU5] = "KbdLcdMenu5", + [KEY_LEFT_DOWN] = "LeftDown", [KEY_LEFT_UP] = "LeftUp", + [KEY_LIGHTS_TOGGLE] = "LightToggle", [KEY_MACRO_PRESET1] = "MacroPreset1", + [KEY_MACRO_PRESET2] = "MacroPreset2", [KEY_MACRO_PRESET3] = "MacroPrest3", + [KEY_MACRO_PRESET_CYCLE] = "MacroPresetCycle", + [KEY_MACRO_RECORD_START] = "MacroRecordStart", + [KEY_MACRO_RECORD_STOP] = "MacroRecordStop", + [KEY_MARK_WAYPOINT] = "MarkWayPoint", [KEY_MEDIA_REPEAT] = "MediaRepeat", + [KEY_MEDIA_TOP_MENU] = "MediaTopMenu", [KEY_MESSENGER] = "Messanger", + [KEY_NAV_CHART] = "NavChar", [KEY_NAV_INFO] = "NavInfo", + [KEY_NEWS] = "News", [KEY_NEXT_ELEMENT] = "NextElement", + [KEY_NEXT_FAVORITE] = "NextFavorite", [KEY_NOTIFICATION_CENTER] = "NotificationCenter", + [KEY_NUMERIC_0] = "Numeric0", [KEY_NUMERIC_1] = "Numeric1", + [KEY_NUMERIC_11] = "Numceric11", [KEY_NUMERIC_12] = "Numeric12", + [KEY_NUMERIC_2] = "Numeric2", [KEY_NUMERIC_3] = "Numeric3", + [KEY_NUMERIC_4] = "Numeric4", [KEY_NUMERIC_5] = "Numeric5", + [KEY_NUMERIC_6] = "Numeric6", [KEY_NUMERIC_7] = "Numeric7", + [KEY_NUMERIC_8] = "Numeric8", [KEY_NUMERIC_9] = "Numeric9", + [KEY_NUMERIC_A] = "NumericA", [KEY_NUMERIC_B] = "NumericB", + [KEY_NUMERIC_C] = "NumericC", [KEY_NUMERIC_D] = "NumericD", + [KEY_NUMERIC_POUND] = "NumericPound", [KEY_NUMERIC_STAR] = "NumericStar", + [KEY_ONSCREEN_KEYBOARD] = "OnScreenKeyBoard", + [KEY_PAUSE_RECORD] = "PauseRecord", [KEY_PICKUP_PHONE] = "PickUpPhone", + [KEY_PRESENTATION] = "Presentation", [KEY_PREVIOUS_ELEMENT] = "PreviousElement", + [KEY_PRIVACY_SCREEN_TOGGLE] = "PrivacyScreenToggle", + [KEY_RADAR_OVERLAY] = "RadarOverLay", + [KEY_RFKILL] = "RFKill", [KEY_RIGHT_DOWN] = "RightDown", + [KEY_RIGHT_UP] = "RightUp", [KEY_ROOT_MENU] = "RootMenu", + [KEY_ROTATE_LOCK_TOGGLE] = "RotateLockToggle", + [KEY_SCALE] = "Scale", [KEY_SELECTIVE_SCREENSHOT] = "SelectiveScreenshot", + [KEY_SIDEVU_SONAR] = "SideVUSonar", [KEY_SINGLE_RANGE_RADAR] = "SingleRangeRadar", + [KEY_SLOWREVERSE] = "SlowReverse", [KEY_SOS] = "SOS", + [KEY_SPREADSHEET] = "SpreadSheet", [KEY_STOP_RECORD] = "StopRecord", + [KEY_TOUCHPAD_OFF] = "TouchPadOff", [KEY_TOUCHPAD_ON] = "TouchPadOn", + [KEY_TOUCHPAD_TOGGLE] = "TouchPadToggle", + [KEY_TRADITIONAL_SONAR] = "TraditionalSonar", + [KEY_UNMUTE] = "Unmute", [KEY_UWB] = "UWB", + [KEY_VIDEO_NEXT] = "VideoNext", [KEY_VIDEOPHONE] = "VideoPhone", + [KEY_VIDEO_PREV] = "VideoPrev", [KEY_VOD] = "VOD", + [KEY_VOICEMAIL] = "VoiceMail", [KEY_WLAN] = "WLAN", + [KEY_WORDPROCESSOR] = "WordProcessor", [KEY_WPS_BUTTON] = "WPSButton", + [KEY_WWAN] = "WWAN", [KEY_ZOOMIN] = "ZoomIn", + [KEY_ZOOMOUT] = "ZoomOut", [KEY_ZOOMRESET] = "ZoomReset", }; static const char *relatives[REL_MAX + 1] = { @@ -1003,6 +3492,8 @@ static const char *relatives[REL_MAX + 1] = { [REL_RY] = "Ry", [REL_RZ] = "Rz", [REL_HWHEEL] = "HWheel", [REL_DIAL] = "Dial", [REL_WHEEL] = "Wheel", [REL_MISC] = "Misc", + [REL_WHEEL_HI_RES] = "WheelHiRes", + [REL_HWHEEL_HI_RES] = "HWheelHiRes" }; static const char *absolutes[ABS_CNT] = { @@ -1020,6 +3511,7 @@ static const char *absolutes[ABS_CNT] = { [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth", [ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile", [ABS_MISC] = "Misc", + [ABS_MT_SLOT] = "MTSlot", [ABS_MT_TOUCH_MAJOR] = "MTMajor", [ABS_MT_TOUCH_MINOR] = "MTMinor", [ABS_MT_WIDTH_MAJOR] = "MTMajorW", @@ -1029,11 +3521,17 @@ static const char *absolutes[ABS_CNT] = { [ABS_MT_POSITION_Y] = "MTPositionY", [ABS_MT_TOOL_TYPE] = "MTToolType", [ABS_MT_BLOB_ID] = "MTBlobID", + [ABS_MT_TRACKING_ID] = "MTTrackingID", + [ABS_MT_PRESSURE] = "MTPressure", + [ABS_MT_DISTANCE] = "MTDistance", + [ABS_MT_TOOL_X] = "MTToolX", + [ABS_MT_TOOL_Y] = "MTToolY", }; static const char *misc[MSC_MAX + 1] = { [MSC_SERIAL] = "Serial", [MSC_PULSELED] = "Pulseled", - [MSC_GESTURE] = "Gesture", [MSC_RAW] = "RawData" + [MSC_GESTURE] = "Gesture", [MSC_RAW] = "RawData", + [MSC_SCAN] = "Scan", [MSC_TIMESTAMP] = "TimeStamp", }; static const char *leds[LED_MAX + 1] = { @@ -1041,7 +3539,8 @@ static const char *leds[LED_MAX + 1] = { [LED_SCROLLL] = "ScrollLock", [LED_COMPOSE] = "Compose", [LED_KANA] = "Kana", [LED_SLEEP] = "Sleep", [LED_SUSPEND] = "Suspend", [LED_MUTE] = "Mute", - [LED_MISC] = "Misc", + [LED_MISC] = "Misc", [LED_MAIL] = "Mail", + [LED_CHARGING] = "Charging", }; static const char *repeats[REP_MAX + 1] = { @@ -1053,17 +3552,71 @@ static const char *sounds[SND_MAX + 1] = { [SND_TONE] = "Tone" }; +static const char *software[SW_CNT] = { + [SW_LID] = "Lid", + [SW_TABLET_MODE] = "TabletMode", + [SW_HEADPHONE_INSERT] = "HeadPhoneInsert", + [SW_RFKILL_ALL] = "RFKillAll", + [SW_MICROPHONE_INSERT] = "MicrophoneInsert", + [SW_DOCK] = "Dock", + [SW_LINEOUT_INSERT] = "LineOutInsert", + [SW_JACK_PHYSICAL_INSERT] = "JackPhysicalInsert", + [SW_VIDEOOUT_INSERT] = "VideoOutInsert", + [SW_CAMERA_LENS_COVER] = "CameraLensCover", + [SW_KEYPAD_SLIDE] = "KeyPadSlide", + [SW_FRONT_PROXIMITY] = "FrontProximity", + [SW_ROTATE_LOCK] = "RotateLock", + [SW_LINEIN_INSERT] = "LineInInsert", + [SW_MUTE_DEVICE] = "MuteDevice", + [SW_PEN_INSERTED] = "PenInserted", + [SW_MACHINE_COVER] = "MachineCover", +}; + +static const char *force[FF_CNT] = { + [FF_RUMBLE] = "FF_RUMBLE", + [FF_PERIODIC] = "FF_PERIODIC", + [FF_CONSTANT] = "FF_CONSTANT", + [FF_SPRING] = "FF_SPRING", + [FF_FRICTION] = "FF_FRICTION", + [FF_DAMPER] = "FF_DAMPER", + [FF_INERTIA] = "FF_INERTIA", + [FF_RAMP] = "FF_RAMP", + [FF_SQUARE] = "FF_SQUARE", + [FF_TRIANGLE] = "FF_TRIANGLE", + [FF_SINE] = "FF_SINE", + [FF_SAW_UP] = "FF_SAW_UP", + [FF_SAW_DOWN] = "FF_SAW_DOWN", + [FF_CUSTOM] = "FF_CUSTOM", + [FF_GAIN] = "FF_GAIN", + [FF_AUTOCENTER] = "FF_AUTOCENTER", + [FF_MAX] = "FF_MAX", +}; + +static const char *force_status[FF_STATUS_MAX + 1] = { + [FF_STATUS_STOPPED] = "FF_STATUS_STOPPED", + [FF_STATUS_PLAYING] = "FF_STATUS_PLAYING", +}; + static const char **names[EV_MAX + 1] = { [EV_SYN] = syncs, [EV_KEY] = keys, [EV_REL] = relatives, [EV_ABS] = absolutes, [EV_MSC] = misc, [EV_LED] = leds, [EV_SND] = sounds, [EV_REP] = repeats, + [EV_SW] = software, [EV_FF] = force, + [EV_FF_STATUS] = force_status, }; static void hid_resolv_event(__u8 type, __u16 code, struct seq_file *f) { - seq_printf(f, "%s.%s", events[type] ? events[type] : "?", - names[type] ? (names[type][code] ? names[type][code] : "?") : "?"); + if (events[type]) + seq_printf(f, "%s.", events[type]); + else + seq_printf(f, "%02x.", type); + + if (names[type] && names[type][code]) + seq_printf(f, "%s", names[type][code]); + else + seq_printf(f, "%04x", code); } static void hid_dump_input_mapping(struct hid_device *hid, struct seq_file *f) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index c6bdb9c4ef3e..25331695ae32 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -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), diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 8376fb5e2d0b..61d2a21affa2 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -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 diff --git a/drivers/hid/hid-kye.c b/drivers/hid/hid-kye.c index eb9bf2829937..70ceb9437332 100644 --- a/drivers/hid/hid-kye.c +++ b/drivers/hid/hid-kye.c @@ -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); - - 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); - - 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); + memcpy(newdesc, kye_tablet_rdesc, sizeof(kye_tablet_rdesc)); + + 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); + + newdesc += sizeof(kye_tablet_rdesc); + + if (info->has_mouse) { + if (newdesc + sizeof(kye_tablet_mouse_rdesc) > rdesc + *rsize) + hid_err(hdev, "control desc unexpectedly large\n"); + else { + 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); + } } if (info->control_rsize) { - if (newsize + info->control_rsize > *rsize) - hid_err(hdev, "control rdesc unexpectedly large"); + if (newdesc + info->control_rsize > 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, info->control_rdesc, info->control_rsize); + newdesc += info->control_rsize; } } - *rsize = newsize; + *rsize = newdesc - rdesc; return rdesc; } diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index f86c1ea83a03..e6b2ae68b8fb 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -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, diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index d2f3f234f29d..b81d5bcc76a7 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -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) }, {} }; diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 04a014cd2a2f..56fc78841f24 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -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, diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c index 80e0f23c1c33..b4a97803eca3 100644 --- a/drivers/hid/hid-nintendo.c +++ b/drivers/hid/hid-nintendo.c @@ -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>"); diff --git a/drivers/hid/hid-picolcd_core.c b/drivers/hid/hid-picolcd_core.c index bbda231a7ce3..fa46fb6eab3f 100644 --- a/drivers/hid/hid-picolcd_core.c +++ b/drivers/hid/hid-picolcd_core.c @@ -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, diff --git a/drivers/hid/hid-picolcd_fb.c b/drivers/hid/hid-picolcd_fb.c index d7dddd99d325..063f9c01d2f7 100644 --- a/drivers/hid/hid-picolcd_fb.c +++ b/drivers/hid/hid-picolcd_fb.c @@ -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; diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c index 8ac8f7b8e317..e7c309cfe3a0 100644 --- a/drivers/hid/hid-playstation.c +++ b/drivers/hid/hid-playstation.c @@ -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. @@ -1868,21 +1884,6 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) 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); diff --git a/drivers/hid/hid-roccat-isku.c b/drivers/hid/hid-roccat-isku.c index 458060403397..0cd6208fb371 100644 --- a/drivers/hid/hid-roccat-isku.c +++ b/drivers/hid/hid-roccat-isku.c @@ -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, diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index 00a1abc7e839..3f8f459edcf3 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -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, diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index 22b895436a7c..8ccb3b14a1a9 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -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); diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c index 86af538c10d6..748d4d7cb2fc 100644 --- a/drivers/hid/hid-roccat-kovaplus.c +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -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); diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 5663b9cd9c69..eeb3d38cd805 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -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); diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c index d85398721659..bd400f6b472b 100644 --- a/drivers/hid/hid-sensor-custom.c +++ b/drivers/hid/hid-sensor-custom.c @@ -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); diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index ebc0aa4e4345..5a07a91a89ae 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -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) { diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index b08a5ab58528..f166188c21ec 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -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: diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 9859dad36495..5bab006ec165 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -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, diff --git a/drivers/hid/hid-winwing.c b/drivers/hid/hid-winwing.c new file mode 100644 index 000000000000..0e224d1a6466 --- /dev/null +++ b/drivers/hid/hid-winwing.c @@ -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"); diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index d965382196c6..632eaf9e11a6 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -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; diff --git a/drivers/hid/intel-ish-hid/Makefile b/drivers/hid/intel-ish-hid/Makefile index f0a82b1c7cb9..a927b224cd44 100644 --- a/drivers/hid/intel-ish-hid/Makefile +++ b/drivers/hid/intel-ish-hid/Makefile @@ -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 diff --git a/drivers/hid/intel-ish-hid/ipc/hw-ish.h b/drivers/hid/intel-ish-hid/ipc/hw-ish.h index f89b300417d7..cdd80c653918 100644 --- a/drivers/hid/intel-ish-hid/ipc/hw-ish.h +++ b/drivers/hid/intel-ish-hid/ipc/hw-ish.h @@ -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 diff --git a/drivers/hid/intel-ish-hid/ipc/ipc.c b/drivers/hid/intel-ish-hid/ipc/ipc.c index dd5fc60874ba..3cd53fc80634 100644 --- a/drivers/hid/intel-ish-hid/ipc/ipc.c +++ b/drivers/hid/intel-ish-hid/ipc/ipc.c @@ -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 = { diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index 56bd4f02f319..f82428d7f6c3 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -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); diff --git a/drivers/hid/intel-ish-hid/ishtp/hbm.c b/drivers/hid/intel-ish-hid/ishtp/hbm.c index 9c031a06e4c4..8ee5467127d8 100644 --- a/drivers/hid/intel-ish-hid/ishtp/hbm.c +++ b/drivers/hid/intel-ish-hid/ishtp/hbm.c @@ -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 && @@ -865,6 +870,20 @@ eoi: } /** + * 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 * @ishtp_hdr: received bus message @@ -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); } } diff --git a/drivers/hid/intel-ish-hid/ishtp/init.c b/drivers/hid/intel-ish-hid/ishtp/init.c index 02a00cc2dd11..07fdd52e4c5e 100644 --- a/drivers/hid/intel-ish-hid/ishtp/init.c +++ b/drivers/hid/intel-ish-hid/ishtp/init.c @@ -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); diff --git a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h index 32142c7d9a04..181838c3d7ac 100644 --- a/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h +++ b/drivers/hid/intel-ish-hid/ishtp/ishtp-dev.h @@ -123,11 +123,28 @@ struct ishtp_hw_ops { }; /** + * 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; diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.c b/drivers/hid/intel-ish-hid/ishtp/loader.c new file mode 100644 index 000000000000..993f8b390e57 --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/loader.c @@ -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); +} diff --git a/drivers/hid/intel-ish-hid/ishtp/loader.h b/drivers/hid/intel-ish-hid/ishtp/loader.h new file mode 100644 index 000000000000..7aa45ebc3f7b --- /dev/null +++ b/drivers/hid/intel-ish-hid/ishtp/loader.h @@ -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_ */ diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c index 4fbce201db6a..8c0cbb2deb11 100644 --- a/drivers/hid/surface-hid/surface_kbd.c +++ b/drivers/hid/surface-hid/surface_kbd.c @@ -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, diff --git a/include/linux/hid.h b/include/linux/hid.h index b12cb1c8e682..8e06d89698e6 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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 */ }; diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 7118ac28d468..17b08f500098 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -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; }; diff --git a/tools/testing/selftests/hid/config.common b/tools/testing/selftests/hid/config.common index 0f456dbab62f..45b5570441ce 100644 --- a/tools/testing/selftests/hid/config.common +++ b/tools/testing/selftests/hid/config.common @@ -238,3 +238,4 @@ CONFIG_VLAN_8021Q=y CONFIG_XFRM_SUB_POLICY=y CONFIG_XFRM_USER=y CONFIG_ZEROPLUS_FF=y +CONFIG_KASAN=y diff --git a/tools/testing/selftests/hid/hid_bpf.c b/tools/testing/selftests/hid/hid_bpf.c index 2cf96f818f25..f825623e3edc 100644 --- a/tools/testing/selftests/hid/hid_bpf.c +++ b/tools/testing/selftests/hid/hid_bpf.c @@ -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) diff --git a/tools/testing/selftests/hid/progs/hid.c b/tools/testing/selftests/hid/progs/hid.c index 1e558826b809..f67d35def142 100644 --- a/tools/testing/selftests/hid/progs/hid.c +++ b/tools/testing/selftests/hid/progs/hid.c @@ -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) */ diff --git a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h index 65e657ac1198..9cd56821d0f1 100644 --- a/tools/testing/selftests/hid/progs/hid_bpf_helpers.h +++ b/tools/testing/selftests/hid/progs/hid_bpf_helpers.h @@ -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 */ diff --git a/tools/testing/selftests/hid/tests/base.py b/tools/testing/selftests/hid/tests/base.py index 51433063b227..3a465768e507 100644 --- a/tools/testing/selftests/hid/tests/base.py +++ b/tools/testing/selftests/hid/tests/base.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/base_device.py b/tools/testing/selftests/hid/tests/base_device.py new file mode 100644 index 000000000000..e0515be97f83 --- /dev/null +++ b/tools/testing/selftests/hid/tests/base_device.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/base_gamepad.py b/tools/testing/selftests/hid/tests/base_gamepad.py new file mode 100644 index 000000000000..ec74d75767a2 --- /dev/null +++ b/tools/testing/selftests/hid/tests/base_gamepad.py @@ -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 diff --git a/tools/testing/selftests/hid/tests/test_gamepad.py b/tools/testing/selftests/hid/tests/test_gamepad.py index 26c74040b796..8d5b5ffdae49 100644 --- a/tools/testing/selftests/hid/tests/test_gamepad.py +++ b/tools/testing/selftests/hid/tests/test_gamepad.py @@ -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), + ) diff --git a/tools/testing/selftests/hid/tests/test_tablet.py b/tools/testing/selftests/hid/tests/test_tablet.py index 903f19f7cbe9..a9e2de1e8861 100644 --- a/tools/testing/selftests/hid/tests/test_tablet.py +++ b/tools/testing/selftests/hid/tests/test_tablet.py @@ -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, - ) - - def __init__(self, touch: BtnTouch, tool: Optional[ToolType], button: Optional[BtnPressed]): + 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[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,), @@ -451,56 +381,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 but we should expect the kernel to behave properly, mostly for historical @@ -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), + ) |