mirror of
https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10.git
synced 2026-09-09 00:07:04 +08:00
Merge: [RHEL 10.3]: rebase HID subsystem to 7.1
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10/-/merge_requests/2819 This is the usual rebase of the HID subsystem up to kernel v7.1 for 10.3. ``` JIRA: https://issues.redhat.com/browse/RHEL-170872 JIRA: https://redhat.atlassian.net/browse/RHEL-183865 Depends: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-10/-/merge_requests/2636 All following omitted-fix are not in drivers/hid/ and can thus safely be ignored: Omitted-fix: fd1d6b9d13f35dccbacbae25ed53593cd9086f84 # xz not part of this MR Omitted-fix: 96a7b71c4438d3b72d6c95e3efdc9e8e8aee6b78 # ubd not part of this MR Omitted-fix: 795469820c638b4449f3bb90ee5e98ebccfbc480 # kcsan not part of this MR Omitted-fix: 5548dd7fa84510f7bbce67c35cc3b388c86aeddf # testing/vma and testing/radix-tree not parts of this MR Omitted-fix: 405ca72dc589dd746e5ee5378bb9d9ee7f844010 # landlock not part of this MR Omitted-fix: 4c0134639694fcdc4ab041d7c53d6188a3e18040 # KVM not part of this MR Omitted-fix: 4c6d43db2a4d2cef3921e885cf34798f790d34ea # net: dst_metadata not part of this MR Omitted-fix: 01793374319cdb685bd487633bbd8bd57f416172 # m68k: defconfig not part of this MR Omitted-fix: 94ff7c59cdfde3a16ab830531acbcb3091b292eb # RDMA not part of this MR Omitted-fix: 2d2b5507e598984f5832f0c5193f35733c42995e # btrfs not part of this MR Omitted-fix: 94ff7c59cdfde3a16ab830531acbcb3091b292eb # RDMA not part of this MR Omitted-fix: 9f4ab0787e7bf6d2c709207317e9d4cd43909869 # btrfs not part of this MR Omitted-fix: 37f1f51fba1a4320149b1ea3b21d254d4b221b0a # btrfs not part of this MR Following one was silently dropped from Linus's tree during 7.2 pull request from Jiri Kosina, my HID co-maintainer: Omitted-fix: d0ff08d946c83b51359a8063c41e9f5af067e628 # not making any effect in 7.2-rc1, silently dropped in a merge commit ``` Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Approved-by: Jarod Wilson <jarod@redhat.com> Approved-by: Tony Camuso <tcamuso@redhat.com> Approved-by: Eric Chanudet <echanude@redhat.com> Approved-by: Andrea Arcangeli <aarcange@redhat.com> Approved-by: David Arcari <darcari@redhat.com> Approved-by: Barry Dunn <badunn@redhat.com> Approved-by: CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com> Merged-by: CKI GitLab Kmaint Pipeline Bot <26919896-cki-kmaint-pipeline-bot@users.noreply.gitlab.com>
This commit is contained in:
@@ -69,6 +69,7 @@ To read/write to RAM, need to send a command to the device.
|
||||
The command format is as below.
|
||||
|
||||
DataByte(SET_REPORT)
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
===== ======================
|
||||
Byte1 Command Byte
|
||||
@@ -89,6 +90,7 @@ Value Byte is writing data when you send the write commands.
|
||||
When you read RAM, there is no meaning.
|
||||
|
||||
DataByte(GET_REPORT)
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
===== ======================
|
||||
Byte1 Response Byte
|
||||
@@ -104,8 +106,10 @@ Read value is stored in Value Byte.
|
||||
|
||||
|
||||
Packet Format
|
||||
-------------
|
||||
|
||||
Touchpad data byte
|
||||
------------------
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
======= ======= ======= ======= ======= ======= ======= ======= =====
|
||||
@@ -156,7 +160,7 @@ Zsn_6-0(7bit):
|
||||
|
||||
|
||||
StickPointer data byte
|
||||
----------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
======= ======= ======= ======= ======= ======= ======= ======= =====
|
||||
- b7 b6 b5 b4 b3 b2 b1 b0
|
||||
|
||||
@@ -163,8 +163,8 @@ The transport layer is a bi-directional protocol, which defines:
|
||||
- A flow control mechanism to avoid buffer overflows
|
||||
|
||||
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"
|
||||
http://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/dcmi-hi-1-0-spec.pdf
|
||||
"Chapter 7: Bus Message Layer".
|
||||
|
||||
Connection and Flow Control Mechanism
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -413,6 +413,10 @@ Vendors who wish to upstream their custom firmware should follow these guideline
|
||||
|
||||
- The firmware filename should use one of the following patterns:
|
||||
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
- ``ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin``
|
||||
@@ -420,16 +424,21 @@ Vendors who wish to upstream their custom firmware should follow these guideline
|
||||
|
||||
- ``${intel_plat_gen}`` indicates the Intel platform generation (e.g., ``lnlm`` for Lunar Lake) and must not exceed 8 characters in length.
|
||||
- ``${SYS_VENDOR_CRC32}`` is the CRC32 checksum of the ``sys_vendor`` value from the DMI field ``DMI_SYS_VENDOR``.
|
||||
- ``${PRODUCT_FAMILY_CRC32}`` is the CRC32 checksum of the ``product_family`` value from the DMI field ``DMI_PRODUCT_FAMILY``.
|
||||
- ``${PRODUCT_NAME_CRC32}`` is the CRC32 checksum of the ``product_name`` value from the DMI field ``DMI_PRODUCT_NAME``.
|
||||
- ``${PRODUCT_SKU_CRC32}`` is the CRC32 checksum of the ``product_sku`` value from the DMI field ``DMI_PRODUCT_SKU``.
|
||||
|
||||
During system boot, the ISH Linux driver will attempt to load the firmware in the following order, prioritizing custom firmware with more precise matching patterns:
|
||||
|
||||
1. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
2. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
3. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin``
|
||||
4. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin``
|
||||
5. ``intel/ish/ish_${intel_plat_gen}.bin``
|
||||
1. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
2. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
3. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}_${PRODUCT_NAME_CRC32}.bin``
|
||||
4. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_FAMILY_CRC32}.bin``
|
||||
5. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
6. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_SKU_CRC32}.bin``
|
||||
7. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}_${PRODUCT_NAME_CRC32}.bin``
|
||||
8. ``intel/ish/ish_${intel_plat_gen}_${SYS_VENDOR_CRC32}.bin``
|
||||
9. ``intel/ish/ish_${intel_plat_gen}.bin``
|
||||
|
||||
The driver will load the first matching firmware and skip the rest. If no matching firmware is found, it will proceed to the next pattern in the specified order. If all searches fail, the default Intel firmware, listed last in the order above, will be loaded.
|
||||
|
||||
|
||||
@@ -13119,6 +13119,15 @@ S: Maintained
|
||||
W: http://legousb.sourceforge.net/
|
||||
F: drivers/usb/misc/legousbtower.c
|
||||
|
||||
LENOVO HID drivers
|
||||
M: Derek J. Clark <derekjohn.clark@gmail.com>
|
||||
M: Mark Pearson <mpearson-lenovo@squebb.ca>
|
||||
L: linux-input@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/hid/hid-lenovo-go-s.c
|
||||
F: drivers/hid/hid-lenovo-go.c
|
||||
F: drivers/hid/hid-lenovo.c
|
||||
|
||||
LETSKETCH HID TABLET DRIVER
|
||||
M: Hans de Goede <hdegoede@redhat.com>
|
||||
L: linux-input@vger.kernel.org
|
||||
|
||||
+61
-22
@@ -92,11 +92,21 @@ config HID_GENERIC
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config HID_HAPTIC
|
||||
bool "Haptic touchpad support"
|
||||
default n
|
||||
help
|
||||
Support for touchpads with force sensors and haptic actuators instead of a
|
||||
traditional button.
|
||||
Adds extra parsing and FF device for the hid multitouch driver.
|
||||
It can be used for Elan 2703 haptic touchpad.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
menu "Special HID drivers"
|
||||
|
||||
config HID_A4TECH
|
||||
tristate "A4TECH mice"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for some A4TECH mice with two scroll wheels.
|
||||
|
||||
@@ -129,7 +139,6 @@ config HID_APPLE
|
||||
tristate "Apple {i,Power,Mac}Books"
|
||||
depends on LEDS_CLASS
|
||||
depends on NEW_LEDS
|
||||
default !EXPERT
|
||||
help
|
||||
Support for some Apple devices which less or more break
|
||||
HID specification.
|
||||
@@ -198,7 +207,6 @@ config HID_AUREAL
|
||||
|
||||
config HID_BELKIN
|
||||
tristate "Belkin Flip KVM and Wireless keyboard"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Belkin Flip KVM and Wireless keyboard.
|
||||
|
||||
@@ -226,14 +234,12 @@ config HID_BIGBEN_FF
|
||||
|
||||
config HID_CHERRY
|
||||
tristate "Cherry Cymotion keyboard"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Cherry Cymotion keyboard.
|
||||
|
||||
config HID_CHICONY
|
||||
tristate "Chicony devices"
|
||||
depends on USB_HID
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Chicony Tactical pad and special keys on Chicony keyboards.
|
||||
|
||||
@@ -311,7 +317,6 @@ config HID_CREATIVE_SB0540
|
||||
|
||||
config HID_CYPRESS
|
||||
tristate "Cypress mouse and barcode readers"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for cypress mouse and barcode readers.
|
||||
|
||||
@@ -358,6 +363,7 @@ config HID_ELECOM
|
||||
- EX-G Trackballs (M-XT3DRBK, M-XT3URBK)
|
||||
- DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
|
||||
- HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
|
||||
- HUGE Plus Trackball (M-HT1MRBK)
|
||||
|
||||
config HID_ELO
|
||||
tristate "ELO USB 4000/4500 touchscreen"
|
||||
@@ -372,10 +378,10 @@ config HID_EVISION
|
||||
help
|
||||
Support for some EVision keyboards. Note that this is needed only when
|
||||
applying customization using userspace programs.
|
||||
Support for some EVision devices requiring report descriptor fixups.
|
||||
|
||||
config HID_EZKEY
|
||||
tristate "Ezkey BTC 8193 keyboard"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Ezkey BTC 8193 keyboard.
|
||||
|
||||
@@ -551,7 +557,6 @@ config HID_ICADE
|
||||
|
||||
config HID_ITE
|
||||
tristate "ITE devices"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for ITE devices not fully compliant with HID standard.
|
||||
|
||||
@@ -572,7 +577,6 @@ config HID_TWINHAN
|
||||
|
||||
config HID_KENSINGTON
|
||||
tristate "Kensington Slimblade Trackball"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Kensington Slimblade Trackball.
|
||||
|
||||
@@ -597,8 +601,7 @@ config HID_LED
|
||||
|
||||
config HID_LENOVO
|
||||
tristate "Lenovo / Thinkpad devices"
|
||||
select NEW_LEDS
|
||||
select LEDS_CLASS
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
Support for IBM/Lenovo devices that are not fully compliant with HID standard.
|
||||
|
||||
@@ -610,6 +613,28 @@ config HID_LENOVO
|
||||
- ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
|
||||
- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
|
||||
|
||||
config HID_LENOVO_GO
|
||||
tristate "HID Driver for Lenovo Legion Go Series Controllers"
|
||||
depends on USB_HID
|
||||
depends on LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
Support for Lenovo Legion Go devices with detachable controllers.
|
||||
|
||||
Say Y here to include configuration interface support for the Lenovo Legion Go
|
||||
and Legion Go 2 Handheld Console Controllers. Say M here to compile this
|
||||
driver as a module. The module will be called hid-lenovo-go.
|
||||
|
||||
config HID_LENOVO_GO_S
|
||||
tristate "HID Driver for Lenovo Legion Go S Controller"
|
||||
depends on USB_HID
|
||||
depends on LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
Support for Lenovo Legion Go S Handheld Console Controller.
|
||||
|
||||
Say Y here to include configuration interface support for the Lenovo Legion Go
|
||||
S. Say M here to compile this driver as a module. The module will be called
|
||||
hid-lenovo-go-s.
|
||||
|
||||
config HID_LETSKETCH
|
||||
tristate "Letsketch WP9620N tablets"
|
||||
depends on USB_HID
|
||||
@@ -628,7 +653,7 @@ config HID_LOGITECH
|
||||
tristate "Logitech devices"
|
||||
depends on USB_HID
|
||||
depends on LEDS_CLASS
|
||||
default !EXPERT
|
||||
depends on LEDS_CLASS_MULTICOLOR
|
||||
help
|
||||
Support for Logitech devices that are not fully compliant with HID standard.
|
||||
|
||||
@@ -743,20 +768,17 @@ config HID_MEGAWORLD_FF
|
||||
|
||||
config HID_REDRAGON
|
||||
tristate "Redragon keyboards"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Redragon keyboards that need fix-ups to work properly.
|
||||
|
||||
config HID_MICROSOFT
|
||||
tristate "Microsoft non-fully HID-compliant devices"
|
||||
default !EXPERT
|
||||
select INPUT_FF_MEMLESS
|
||||
help
|
||||
Support for Microsoft devices that are not fully compliant with HID standard.
|
||||
|
||||
config HID_MONTEREY
|
||||
tristate "Monterey Genius KB29E keyboard"
|
||||
default !EXPERT
|
||||
help
|
||||
Support for Monterey Genius KB29E.
|
||||
|
||||
@@ -1002,6 +1024,12 @@ config HID_PXRC
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called hid-pxrc.
|
||||
|
||||
config HID_RAPOO
|
||||
tristate "Rapoo non-fully HID-compliant devices"
|
||||
help
|
||||
Support for Rapoo devices that are not fully compliant with the
|
||||
HID standard.
|
||||
|
||||
config HID_RAZER
|
||||
tristate "Razer non-fully HID-compliant devices"
|
||||
help
|
||||
@@ -1071,7 +1099,7 @@ config HID_SIGMAMICRO
|
||||
- Rapoo V500
|
||||
|
||||
config HID_SONY
|
||||
tristate "Sony PS2/3/4 accessories"
|
||||
tristate "Sony PS2/3/4/5 accessories"
|
||||
depends on USB_HID
|
||||
depends on NEW_LEDS
|
||||
depends on LEDS_CLASS
|
||||
@@ -1080,13 +1108,15 @@ config HID_SONY
|
||||
help
|
||||
Support for
|
||||
|
||||
* Sony PS3 6-axis controllers
|
||||
* Sony PS4 DualShock 4 controllers
|
||||
* Sixaxis controllers for PS3
|
||||
* Buzz controllers
|
||||
* Sony PS3 Blue-ray Disk Remote Control (Bluetooth)
|
||||
* Logitech Harmony adapter for Sony Playstation 3 (Bluetooth)
|
||||
* Guitar Hero Live PS3, Wii U and PS4 guitar dongles
|
||||
* Guitar Hero PS3 and PC guitar dongles
|
||||
* Blu-ray Disc Remote Control for PS3
|
||||
* Logitech Harmony adapter for PS3
|
||||
* Guitar Hero Live PS3, Wii U and PS4 guitars
|
||||
* Guitar Hero PS3 and PC guitars
|
||||
* Rock Band 1, 2 and 3 PS3 and Wii instruments
|
||||
* Rock Band 4 PS4 and PS5 guitars
|
||||
* DJ Hero Turntable for PS3
|
||||
|
||||
config SONY_FF
|
||||
bool "Sony PS2/3/4 accessories force feedback support"
|
||||
@@ -1306,6 +1336,8 @@ config HID_WINWING
|
||||
help
|
||||
Support for WinWing Orion2 throttle base with the following grips:
|
||||
|
||||
* TGRIP-15E
|
||||
* TGRIP-15EX
|
||||
* TGRIP-16EX
|
||||
* TGRIP-18
|
||||
|
||||
@@ -1413,6 +1445,13 @@ config HID_KUNIT_TEST
|
||||
|
||||
If in doubt, say "N".
|
||||
|
||||
config HID_HUAWEI
|
||||
tristate "Huawei HID devices support"
|
||||
depends on USB_HID
|
||||
help
|
||||
Support for huawei cd30 keyboard or other hid devices
|
||||
that need fix-ups to work properly.
|
||||
|
||||
endmenu
|
||||
|
||||
source "drivers/hid/bpf/Kconfig"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#
|
||||
hid-y := hid-core.o hid-input.o hid-quirks.o
|
||||
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
|
||||
hid-$(CONFIG_HID_HAPTIC) += hid-haptic.o
|
||||
|
||||
obj-$(CONFIG_HID_BPF) += bpf/
|
||||
|
||||
@@ -75,6 +76,8 @@ obj-$(CONFIG_HID_KYE) += hid-kye.o
|
||||
obj-$(CONFIG_HID_KYSONA) += hid-kysona.o
|
||||
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
||||
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
|
||||
obj-$(CONFIG_HID_LENOVO_GO) += hid-lenovo-go.o
|
||||
obj-$(CONFIG_HID_LENOVO_GO_S) += hid-lenovo-go-s.o
|
||||
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
|
||||
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
|
||||
obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o
|
||||
@@ -112,6 +115,7 @@ obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o
|
||||
obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o
|
||||
obj-$(CONFIG_HID_PRIMAX) += hid-primax.o
|
||||
obj-$(CONFIG_HID_PXRC) += hid-pxrc.o
|
||||
obj-$(CONFIG_HID_RAPOO) += hid-rapoo.o
|
||||
obj-$(CONFIG_HID_RAZER) += hid-razer.o
|
||||
obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o
|
||||
obj-$(CONFIG_HID_RETRODE) += hid-retrode.o
|
||||
@@ -150,6 +154,7 @@ obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
|
||||
obj-$(CONFIG_HID_ZYDACRON) += hid-zydacron.o
|
||||
obj-$(CONFIG_HID_VIEWSONIC) += hid-viewsonic.o
|
||||
obj-$(CONFIG_HID_VRC2) += hid-vrc2.o
|
||||
obj-$(CONFIG_HID_HUAWEI) += hid-huawei.o
|
||||
|
||||
wacom-objs := wacom_wac.o wacom_sys.o
|
||||
obj-$(CONFIG_HID_WACOM) += wacom.o
|
||||
|
||||
@@ -47,7 +47,7 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type)
|
||||
guard(mutex)(&mp2->lock);
|
||||
for (i = 0; i < cli_data->num_hid_devices; i++) {
|
||||
if (cli_data->hid_sensor_hubs[i] == hid) {
|
||||
struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL);
|
||||
struct request_list *new = kzalloc_obj(*new);
|
||||
|
||||
if (!new)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -135,7 +135,7 @@ int amdtp_hid_probe(u32 cur_hid_dev, struct amdtp_cl_data *cli_data)
|
||||
if (IS_ERR(hid))
|
||||
return PTR_ERR(hid);
|
||||
|
||||
hid_data = kzalloc(sizeof(*hid_data), GFP_KERNEL);
|
||||
hid_data = kzalloc_obj(*hid_data);
|
||||
if (!hid_data) {
|
||||
rc = -ENOMEM;
|
||||
goto err_hid_data;
|
||||
|
||||
@@ -413,6 +413,7 @@ static void sfh_init_work(struct work_struct *work)
|
||||
rc = amd_sfh_hid_client_init(mp2);
|
||||
if (rc) {
|
||||
amd_sfh_clear_intr(mp2);
|
||||
if (rc != -EOPNOTSUPP)
|
||||
dev_err(&pdev->dev, "amd_sfh_hid_client_init failed err %d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,8 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
|
||||
if (rc)
|
||||
goto cleanup;
|
||||
|
||||
mp2_ops->stop(privdata, cl_data->sensor_idx[i]);
|
||||
amd_sfh_wait_for_response(privdata, cl_data->sensor_idx[i], DISABLE_SENSOR);
|
||||
writel(0, privdata->mmio + amd_get_p2c_val(privdata, 0));
|
||||
mp2_ops->start(privdata, info);
|
||||
status = amd_sfh_wait_for_response
|
||||
|
||||
@@ -24,7 +24,8 @@ EXPORT_SYMBOL(hid_ops);
|
||||
|
||||
u8 *
|
||||
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
|
||||
u32 *size, int interrupt, u64 source, bool from_bpf)
|
||||
size_t *buf_size, u32 *size, int interrupt, u64 source,
|
||||
bool from_bpf)
|
||||
{
|
||||
struct hid_bpf_ctx_kern ctx_kern = {
|
||||
.ctx = {
|
||||
@@ -74,6 +75,7 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
|
||||
*size = ret;
|
||||
}
|
||||
|
||||
*buf_size = ctx_kern.ctx.allocated_size;
|
||||
return ctx_kern.data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
|
||||
@@ -295,9 +297,6 @@ hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr
|
||||
{
|
||||
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)
|
||||
@@ -323,7 +322,7 @@ hid_bpf_allocate_context(unsigned int hid_id)
|
||||
if (IS_ERR(hdev))
|
||||
return NULL;
|
||||
|
||||
ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL);
|
||||
ctx_kern = kzalloc_obj(*ctx_kern);
|
||||
if (!ctx_kern) {
|
||||
hid_put_device(hdev);
|
||||
return NULL;
|
||||
@@ -364,7 +363,7 @@ __hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
|
||||
u32 report_len;
|
||||
|
||||
/* check arguments */
|
||||
if (!ctx || !hid_ops || !buf)
|
||||
if (!hid_ops)
|
||||
return -EINVAL;
|
||||
|
||||
switch (rtype) {
|
||||
@@ -447,6 +446,8 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
|
||||
(u64)(long)ctx,
|
||||
true); /* prevent infinite recursions */
|
||||
|
||||
if (ret > size)
|
||||
ret = size;
|
||||
if (ret > 0)
|
||||
memcpy(buf, dma_data, ret);
|
||||
|
||||
@@ -506,7 +507,7 @@ __hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *b
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return hid_ops->hid_input_report(ctx->hid, type, buf, size, 0, (u64)(long)ctx, true,
|
||||
return hid_ops->hid_input_report(ctx->hid, type, buf, size, size, 0, (u64)(long)ctx, true,
|
||||
lock_already_taken);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include "hid_usages.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8, HID_VID_ANY, HID_PID_ANY),
|
||||
);
|
||||
|
||||
EXPORT_UDEV_PROP(HID_DIGITIZER_PAD_TYPE, 32);
|
||||
|
||||
__u8 hw_req_buf[1024];
|
||||
|
||||
/* to be filled by udev-hid-bpf */
|
||||
struct hid_rdesc_descriptor HID_REPORT_DESCRIPTOR;
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
struct hid_rdesc_report *pad_type_feature = NULL;
|
||||
struct hid_rdesc_field *pad_type = NULL;
|
||||
struct hid_rdesc_report *feature;
|
||||
struct hid_bpf_ctx *hid_ctx;
|
||||
char *pad_type_str = "";
|
||||
int ret;
|
||||
|
||||
hid_bpf_for_each_feature_report(&HID_REPORT_DESCRIPTOR, feature) {
|
||||
struct hid_rdesc_field *field;
|
||||
|
||||
hid_bpf_for_each_field(feature, field) {
|
||||
if (field->usage_page == HidUsagePage_Digitizers &&
|
||||
field->usage_id == HidUsage_Dig_PadType) {
|
||||
pad_type = field;
|
||||
pad_type_feature = feature;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pad_type)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pad_type || !pad_type_feature) {
|
||||
ctx->retval = -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
hid_ctx = hid_bpf_allocate_context(ctx->hid);
|
||||
|
||||
if (!hid_ctx)
|
||||
return -1; /* EPERM check */
|
||||
|
||||
hw_req_buf[0] = pad_type_feature->report_id;
|
||||
|
||||
ret = hid_bpf_hw_request(hid_ctx, hw_req_buf, sizeof(hw_req_buf),
|
||||
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
||||
hid_bpf_release_context(hid_ctx);
|
||||
|
||||
if (ret < 0) {
|
||||
ctx->retval = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->retval = 0;
|
||||
|
||||
switch (EXTRACT_BITS(hw_req_buf, pad_type)) {
|
||||
case 0:
|
||||
pad_type_str = "Clickpad";
|
||||
break;
|
||||
case 1:
|
||||
pad_type_str = "Pressurepad";
|
||||
break;
|
||||
case 2:
|
||||
pad_type_str = "Discrete";
|
||||
break;
|
||||
default:
|
||||
pad_type_str = "Unknown";
|
||||
}
|
||||
|
||||
UDEV_PROP_SPRINTF(HID_DIGITIZER_PAD_TYPE, "%s", pad_type_str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -0,0 +1,563 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Red Hat, Inc
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_HUION 0x256C
|
||||
#define PID_INSPIROY_2_M 0x0067
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HUION, PID_INSPIROY_2_M),
|
||||
);
|
||||
|
||||
/* Filled in by udev-hid-bpf */
|
||||
char UDEV_PROP_HUION_FIRMWARE_ID[64];
|
||||
|
||||
/* The prefix of the firmware ID we expect for this device. The full firmware
|
||||
* string has a date suffix, e.g. HUION_T21j_221221
|
||||
*/
|
||||
char EXPECTED_FIRMWARE_ID[] = "HUION_T21k_";
|
||||
|
||||
/* How this BPF program works: the tablet has two modes, firmware mode and
|
||||
* tablet mode. In firmware mode (out of the box) the tablet sends button events
|
||||
* and the dial as keyboard combinations. In tablet mode it uses a vendor specific
|
||||
* hid report to report everything instead.
|
||||
* Depending on the mode some hid reports are never sent and the corresponding
|
||||
* devices are mute.
|
||||
*
|
||||
* To switch the tablet use e.g. https://github.com/whot/huion-switcher
|
||||
* or one of the tools from the digimend project
|
||||
*
|
||||
* This BPF works for both modes. The huion-switcher tool sets the
|
||||
* HUION_FIRMWARE_ID udev property - if that is set then we disable the firmware
|
||||
* pad and pen reports (by making them vendor collections that are ignored).
|
||||
* If that property is not set we fix all hidraw nodes so the tablet works in
|
||||
* either mode though the drawback is that the device will show up twice if
|
||||
* you bind it to all event nodes
|
||||
*
|
||||
* Default report descriptor for the first exposed hidraw node:
|
||||
*
|
||||
* # HUION Huion Tablet_H641P
|
||||
* # Report descriptor length: 18 bytes
|
||||
* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 0xFF00) 0
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 3
|
||||
* # 0xa1, 0x01, // Collection (Application) 5
|
||||
* # 0x85, 0x08, // Report ID (8) 7
|
||||
* # 0x75, 0x58, // Report Size (88) 9
|
||||
* # 0x95, 0x01, // Report Count (1) 11
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 13
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 15
|
||||
* # 0xc0, // End Collection 17
|
||||
* R: 18 06 00 ff 09 01 a1 01 85 08 75 58 95 01 09 01 81 02 c0
|
||||
*
|
||||
* This rdesc does nothing until the tablet is switched to raw mode, see
|
||||
* https://github.com/whot/huion-switcher
|
||||
*
|
||||
*
|
||||
* Second hidraw node is the Pen. This one sends events until the tablet is
|
||||
* switched to raw mode, then it's mute.
|
||||
*
|
||||
* # Report descriptor length: 93 bytes
|
||||
* # 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, 0x45, // Usage (Eraser) 16
|
||||
* # 0x09, 0x3c, // Usage (Invert) 18 <-- has no Invert eraser
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 20
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 22
|
||||
* # 0x75, 0x01, // Report Size (1) 24
|
||||
* # 0x95, 0x06, // Report Count (6) 26
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 28
|
||||
* # 0x09, 0x32, // Usage (In Range) 30
|
||||
* # 0x75, 0x01, // Report Size (1) 32
|
||||
* # 0x95, 0x01, // Report Count (1) 34
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 36
|
||||
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 40
|
||||
* # 0x09, 0x30, // Usage (X) 42
|
||||
* # 0x09, 0x31, // Usage (Y) 44
|
||||
* # 0x55, 0x0d, // Unit Exponent (-3) 46 <-- change to -2
|
||||
* # 0x65, 0x33, // Unit (EnglishLinear: in³) 48 <-- change in³ to in
|
||||
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 50
|
||||
* # 0x35, 0x00, // Physical Minimum (0) 53
|
||||
* # 0x46, 0x00, 0x08, // Physical Maximum (2048) 55 <-- invalid size
|
||||
* # 0x75, 0x10, // Report Size (16) 58
|
||||
* # 0x95, 0x02, // Report Count (2) 60
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 62
|
||||
* # 0x05, 0x0d, // Usage Page (Digitizers) 64
|
||||
* # 0x09, 0x30, // Usage (Tip Pressure) 66
|
||||
* # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 68
|
||||
* # 0x75, 0x10, // Report Size (16) 71
|
||||
* # 0x95, 0x01, // Report Count (1) 73
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 75
|
||||
* # 0x09, 0x3d, // Usage (X Tilt) 77 <-- No tilt reported
|
||||
* # 0x09, 0x3e, // Usage (Y Tilt) 79
|
||||
* # 0x15, 0x81, // Logical Minimum (-127) 81
|
||||
* # 0x25, 0x7f, // Logical Maximum (127) 83
|
||||
* # 0x75, 0x08, // Report Size (8) 85
|
||||
* # 0x95, 0x02, // Report Count (2) 87
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 89
|
||||
* # 0xc0, // End Collection 91
|
||||
* # 0xc0, // End Collection 92
|
||||
* R: 93 05 0d 09 02 a1 01 85 0a 09 20 a1 01 09 42 09 44 09 45 09 3c 15 00 25 01 7501 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 ff7f 35 00 46 00 08 75 10 95 02 81 02 05 0d 09 30 26 ff 1f 75 10 95 01 81 02 09 3d09 3e 15 81 25 7f 75 08 95 02 81 02 c0 c0
|
||||
*
|
||||
* Third hidraw node is the pad which sends a combination of keyboard shortcuts until
|
||||
* the tablet is switched to raw mode, then it's mute:
|
||||
*
|
||||
* # Report descriptor length: 65 bytes
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* # 0x09, 0x06, // Usage (Keyboard) 2
|
||||
* # 0xa1, 0x01, // Collection (Application) 4
|
||||
* # 0x85, 0x03, // Report ID (3) 6
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
|
||||
* # 0x19, 0xe0, // UsageMinimum (224) 10
|
||||
* # 0x29, 0xe7, // UsageMaximum (231) 12
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 14
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 16
|
||||
* # 0x75, 0x01, // Report Size (1) 18
|
||||
* # 0x95, 0x08, // Report Count (8) 20
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 22
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard/Keypad) 24
|
||||
* # 0x19, 0x00, // UsageMinimum (0) 26
|
||||
* # 0x29, 0xff, // UsageMaximum (255) 28
|
||||
* # 0x26, 0xff, 0x00, // Logical Maximum (255) 30
|
||||
* # 0x75, 0x08, // Report Size (8) 33
|
||||
* # 0x95, 0x06, // Report Count (6) 35
|
||||
* # 0x81, 0x00, // Input (Data,Arr,Abs) 37
|
||||
* # 0xc0, // End Collection 39
|
||||
* # 0x05, 0x0c, // Usage Page (Consumer) 40
|
||||
* # 0x09, 0x01, // Usage (Consumer Control) 42
|
||||
* # 0xa1, 0x01, // Collection (Application) 44
|
||||
* # 0x85, 0x04, // Report ID (4) 46
|
||||
* # 0x19, 0x00, // UsageMinimum (0) 48
|
||||
* # 0x2a, 0x3c, 0x02, // UsageMaximum (572) 50
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 53
|
||||
* # 0x26, 0x3c, 0x02, // Logical Maximum (572) 55
|
||||
* # 0x95, 0x01, // Report Count (1) 58
|
||||
* # 0x75, 0x10, // Report Size (16) 60
|
||||
* # 0x81, 0x00, // Input (Data,Arr,Abs) 62
|
||||
* # 0xc0, // End Collection 64
|
||||
* R: 65 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 0507 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 00 2a 3c02 15 00 26 3c 02 95 01 75 10 81 00 c0
|
||||
* N: HUION Huion Tablet_H641P
|
||||
*/
|
||||
|
||||
#define PAD_REPORT_DESCRIPTOR_LENGTH 133
|
||||
#define PEN_REPORT_DESCRIPTOR_LENGTH 93
|
||||
#define VENDOR_REPORT_DESCRIPTOR_LENGTH 36
|
||||
#define PAD_REPORT_ID 3
|
||||
#define PEN_REPORT_ID 10
|
||||
#define VENDOR_REPORT_ID 8
|
||||
#define PAD_REPORT_LENGTH 8
|
||||
#define PEN_REPORT_LENGTH 10
|
||||
#define VENDOR_REPORT_LENGTH 12
|
||||
|
||||
|
||||
__u16 last_button_state;
|
||||
|
||||
static const __u8 fixed_rdesc_pad[] = {
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
// -- Byte 0 in report
|
||||
ReportId(PAD_REPORT_ID)
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
// Byte 1 in report - just exists so we get to be a tablet pad
|
||||
Usage_Dig_BarrelSwitch // BTN_STYLUS
|
||||
ReportCount(1)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(7) // padding
|
||||
Input(Const)
|
||||
// Bytes 2/3 in report - just exists so we get to be a tablet pad
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Var|Abs)
|
||||
// Byte 4 in report is the wheel
|
||||
Usage_GD_Wheel
|
||||
LogicalMinimum_i8(-1)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportCount(1)
|
||||
ReportSize(8)
|
||||
Input(Var|Rel)
|
||||
// Byte 5 is the button state
|
||||
UsagePage_Button
|
||||
UsageMinimum_i8(0x1)
|
||||
UsageMaximum_i8(0x8)
|
||||
LogicalMinimum_i8(0x1)
|
||||
LogicalMaximum_i8(0x8)
|
||||
ReportCount(1)
|
||||
ReportSize(8)
|
||||
Input(Arr|Abs)
|
||||
)
|
||||
// Make sure we match our original report length
|
||||
FixedSizeVendorReport(PAD_REPORT_LENGTH)
|
||||
)
|
||||
};
|
||||
|
||||
static const __u8 fixed_rdesc_pen[] = {
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_Pen
|
||||
CollectionApplication(
|
||||
// -- Byte 0 in report
|
||||
ReportId(PEN_REPORT_ID)
|
||||
Usage_Dig_Pen
|
||||
CollectionPhysical(
|
||||
// -- Byte 1 in report
|
||||
Usage_Dig_TipSwitch
|
||||
Usage_Dig_BarrelSwitch
|
||||
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportSize(1)
|
||||
ReportCount(3)
|
||||
Input(Var|Abs)
|
||||
ReportCount(4) // Padding
|
||||
Input(Const)
|
||||
Usage_Dig_InRange
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
PushPop(
|
||||
UsagePage_GenericDesktop
|
||||
Unit(cm)
|
||||
UnitExponent(-1)
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(160)
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(32767)
|
||||
Usage_GD_X
|
||||
Input(Var|Abs) // Bytes 2+3
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(100)
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(32767)
|
||||
Usage_GD_Y
|
||||
Input(Var|Abs) // Bytes 4+5
|
||||
)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TipPressure
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(8191)
|
||||
Input(Var|Abs) // Byte 6+7
|
||||
// Two bytes padding so we don't need to change the report at all
|
||||
ReportSize(8)
|
||||
ReportCount(2)
|
||||
Input(Const) // Byte 6+7
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
static const __u8 fixed_rdesc_vendor[] = {
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_Pen
|
||||
CollectionApplication(
|
||||
// Byte 0
|
||||
// We leave the pen on the vendor report ID
|
||||
ReportId(VENDOR_REPORT_ID)
|
||||
Usage_Dig_Pen
|
||||
CollectionPhysical(
|
||||
// Byte 1 are the buttons
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportSize(1)
|
||||
Usage_Dig_TipSwitch
|
||||
Usage_Dig_BarrelSwitch
|
||||
Usage_Dig_SecondaryBarrelSwitch
|
||||
ReportCount(3)
|
||||
Input(Var|Abs)
|
||||
ReportCount(4) // Padding
|
||||
Input(Const)
|
||||
Usage_Dig_InRange
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
PushPop(
|
||||
UsagePage_GenericDesktop
|
||||
Unit(cm)
|
||||
UnitExponent(-1)
|
||||
// Note: reported logical range differs
|
||||
// from the pen report ID for x and y
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(32000)
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(160)
|
||||
// Bytes 2/3 in report
|
||||
Usage_GD_X
|
||||
Input(Var|Abs)
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(20000)
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(100)
|
||||
// Bytes 4/5 in report
|
||||
Usage_GD_Y
|
||||
Input(Var|Abs)
|
||||
)
|
||||
// Bytes 6/7 in report
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(8192)
|
||||
Usage_Dig_TipPressure
|
||||
Input(Var|Abs)
|
||||
)
|
||||
)
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
// Byte 0
|
||||
ReportId(PAD_REPORT_ID)
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
// Byte 1 are the buttons
|
||||
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
|
||||
ReportCount(1)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(7) // Padding
|
||||
Input(Const)
|
||||
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Var|Abs)
|
||||
// Bytes 4 and 5 are the button state
|
||||
UsagePage_Button
|
||||
UsageMinimum_i8(0x1)
|
||||
UsageMaximum_i8(0xa)
|
||||
LogicalMinimum_i8(0x0)
|
||||
LogicalMaximum_i8(0x1)
|
||||
ReportCount(10)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
Usage_i8(0x31) // maps to BTN_SOUTH
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(5)
|
||||
Input(Const)
|
||||
// Byte 6 is the wheel
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Wheel
|
||||
LogicalMinimum_i8(-1)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportCount(1)
|
||||
ReportSize(8)
|
||||
Input(Var|Rel)
|
||||
)
|
||||
// Make sure we match our original report length
|
||||
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
|
||||
)
|
||||
};
|
||||
|
||||
static const __u8 disabled_rdesc_pen[] = {
|
||||
FixedSizeVendorReport(PEN_REPORT_LENGTH)
|
||||
};
|
||||
|
||||
static const __u8 disabled_rdesc_pad[] = {
|
||||
FixedSizeVendorReport(PAD_REPORT_LENGTH)
|
||||
};
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
__s32 rdesc_size = hctx->size;
|
||||
__u8 have_fw_id;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* If we have a firmware ID and it matches our expected prefix, we
|
||||
* disable the default pad/pen nodes. They won't send events
|
||||
* but cause duplicate devices.
|
||||
*/
|
||||
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
|
||||
EXPECTED_FIRMWARE_ID,
|
||||
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
|
||||
if (rdesc_size == PAD_REPORT_DESCRIPTOR_LENGTH) {
|
||||
if (have_fw_id) {
|
||||
__builtin_memcpy(data, disabled_rdesc_pad, sizeof(disabled_rdesc_pad));
|
||||
return sizeof(disabled_rdesc_pad);
|
||||
}
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
|
||||
return sizeof(fixed_rdesc_pad);
|
||||
}
|
||||
if (rdesc_size == PEN_REPORT_DESCRIPTOR_LENGTH) {
|
||||
if (have_fw_id) {
|
||||
__builtin_memcpy(data, disabled_rdesc_pen, sizeof(disabled_rdesc_pen));
|
||||
return sizeof(disabled_rdesc_pen);
|
||||
}
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc_pen, sizeof(fixed_rdesc_pen));
|
||||
return sizeof(fixed_rdesc_pen);
|
||||
}
|
||||
/* Always fix the vendor mode so the tablet will work even if nothing sets
|
||||
* the udev property (e.g. huion-switcher run manually)
|
||||
*/
|
||||
if (rdesc_size == VENDOR_REPORT_DESCRIPTOR_LENGTH) {
|
||||
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
|
||||
return sizeof(fixed_rdesc_vendor);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Only sent if tablet is in default mode */
|
||||
if (data[0] == PAD_REPORT_ID) {
|
||||
/* Nicely enough, this device only supports one button down at a time so
|
||||
* the reports are easy to match. Buttons numbered from the top
|
||||
* Button released: 03 00 00 00 00 00 00 00
|
||||
* Button 1: 03 00 05 00 00 00 00 00 -> b
|
||||
* Button 2: 03 07 11 00 00 00 00 00 -> Ctrl Shift N
|
||||
* Button 3: 03 00 08 00 00 00 00 00 -> e
|
||||
* Button 4: 03 00 0c 00 00 00 00 00 -> i
|
||||
* Button 5: 03 00 2c 00 00 00 00 00 -> space
|
||||
* Button 6: 03 01 08 00 00 00 00 00 -> Ctrl E
|
||||
* Button 7: 03 01 16 00 00 00 00 00 -> Ctrl S
|
||||
* Button 8: 03 05 1d 00 00 00 00 00 -> Ctrl Alt Z
|
||||
*
|
||||
* Wheel down: 03 01 2d 00 00 00 00 00 -> Ctrl -
|
||||
* Wheel up: 03 01 2e 00 00 00 00 00 -> Ctrl =
|
||||
*/
|
||||
__u8 button = 0;
|
||||
__u8 wheel = 0;
|
||||
|
||||
switch (data[1] << 8 | data[2]) {
|
||||
case 0x0000:
|
||||
break;
|
||||
case 0x0005:
|
||||
button = 1;
|
||||
break;
|
||||
case 0x0711:
|
||||
button = 2;
|
||||
break;
|
||||
case 0x0008:
|
||||
button = 3;
|
||||
break;
|
||||
case 0x000c:
|
||||
button = 4;
|
||||
break;
|
||||
case 0x002c:
|
||||
button = 5;
|
||||
break;
|
||||
case 0x0108:
|
||||
button = 6;
|
||||
break;
|
||||
case 0x0116:
|
||||
button = 7;
|
||||
break;
|
||||
case 0x051d:
|
||||
button = 8;
|
||||
break;
|
||||
case 0x012d:
|
||||
wheel = -1;
|
||||
break;
|
||||
case 0x012e:
|
||||
wheel = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
__u8 report[6] = {PAD_REPORT_ID, 0x0, 0x0, 0x0, wheel, button};
|
||||
|
||||
__builtin_memcpy(data, report, sizeof(report));
|
||||
return sizeof(report);
|
||||
}
|
||||
|
||||
/* Nothing to do for the PEN_REPORT_ID, it's already mapped */
|
||||
|
||||
/* Only sent if tablet is in raw mode */
|
||||
if (data[0] == VENDOR_REPORT_ID) {
|
||||
/* Pad reports */
|
||||
if (data[1] & 0x20) {
|
||||
/* See fixed_rdesc_pad */
|
||||
struct pad_report {
|
||||
__u8 report_id;
|
||||
__u8 btn_stylus;
|
||||
__u8 x;
|
||||
__u8 y;
|
||||
__u16 buttons;
|
||||
__u8 wheel;
|
||||
} __attribute__((packed)) *pad_report;
|
||||
__u8 wheel = 0;
|
||||
|
||||
/* Wheel report */
|
||||
if (data[1] == 0xf1) {
|
||||
if (data[5] == 2)
|
||||
wheel = 0xff;
|
||||
else
|
||||
wheel = data[5];
|
||||
} else {
|
||||
/* data[4] and data[5] are the buttons, mapped correctly */
|
||||
last_button_state = data[4] | (data[5] << 8);
|
||||
wheel = 0; // wheel
|
||||
}
|
||||
|
||||
pad_report = (struct pad_report *)data;
|
||||
|
||||
pad_report->report_id = PAD_REPORT_ID;
|
||||
pad_report->btn_stylus = 0;
|
||||
pad_report->x = 0;
|
||||
pad_report->y = 0;
|
||||
pad_report->buttons = last_button_state;
|
||||
pad_report->wheel = wheel;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
}
|
||||
|
||||
/* Pen reports need nothing done */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(inspiroy_2) = {
|
||||
.hid_device_event = (void *)inspiroy_2_fix_events,
|
||||
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
switch (ctx->rdesc_size) {
|
||||
case PAD_REPORT_DESCRIPTOR_LENGTH:
|
||||
case PEN_REPORT_DESCRIPTOR_LENGTH:
|
||||
case VENDOR_REPORT_DESCRIPTOR_LENGTH:
|
||||
ctx->retval = 0;
|
||||
break;
|
||||
default:
|
||||
ctx->retval = -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -163,6 +163,9 @@ char EXPECTED_FIRMWARE_ID[] = "HUION_T21j_";
|
||||
|
||||
|
||||
__u8 last_button_state;
|
||||
__u8 last_tip_state;
|
||||
__u8 last_sec_barrel_state;
|
||||
__u8 force_tip_down_count;
|
||||
|
||||
static const __u8 fixed_rdesc_pad[] = {
|
||||
UsagePage_GenericDesktop
|
||||
@@ -522,9 +525,31 @@ int BPF_PROG(inspiroy_2_fix_events, struct hid_bpf_ctx *hctx)
|
||||
pad_report->wheel = wheel;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
} else if (data[1] & 0x80) { /* Pen reports with InRange 1 */
|
||||
__u8 tip_state = data[1] & 0x1;
|
||||
__u8 sec_barrel_state = data[1] & 0x4;
|
||||
|
||||
if (force_tip_down_count > 0) {
|
||||
data[1] |= 0x1;
|
||||
--force_tip_down_count;
|
||||
if (tip_state)
|
||||
force_tip_down_count = 0;
|
||||
}
|
||||
|
||||
/* Pen reports need nothing done */
|
||||
/* Tip was down and we just pressed or released the
|
||||
* secondary barrel switch (the physical eraser
|
||||
* button). The device will send up to 4
|
||||
* reports with Tip Switch 0 and sometimes
|
||||
* this report has Tip Switch 0.
|
||||
*/
|
||||
if (last_tip_state &&
|
||||
last_sec_barrel_state != sec_barrel_state) {
|
||||
force_tip_down_count = 4;
|
||||
data[1] |= 0x1;
|
||||
}
|
||||
last_tip_state = tip_state;
|
||||
last_sec_barrel_state = sec_barrel_state;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
|
||||
#define VID_HUION 0x256C
|
||||
#define PID_KAMVAS_PRO_19 0x006B
|
||||
#define PID_KAMVAS_PRO_27 0x006c
|
||||
#define NAME_KAMVAS_PRO_19 "HUION Huion Tablet_GT1902"
|
||||
#define NAME_KAMVAS_PRO_27 "HUION Huion Tablet_GT2701"
|
||||
|
||||
#define TEST_PREFIX "uhid test "
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_19),
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8, VID_HUION, PID_KAMVAS_PRO_27),
|
||||
);
|
||||
|
||||
bool prev_was_out_of_range;
|
||||
@@ -351,7 +354,8 @@ int probe(struct hid_bpf_probe_args *ctx)
|
||||
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)))
|
||||
if (__builtin_memcmp(name, NAME_KAMVAS_PRO_19, sizeof(NAME_KAMVAS_PRO_19)) &&
|
||||
__builtin_memcmp(name, NAME_KAMVAS_PRO_27, sizeof(NAME_KAMVAS_PRO_27)))
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
hid_bpf_release_context(hctx);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,724 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Nicholas LaPointe
|
||||
* Copyright (c) 2025 Higgins Dragon
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_HUION 0x256c
|
||||
#define PID_KAMVAS16_GEN3 0x2009
|
||||
|
||||
#define VENDOR_DESCRIPTOR_LENGTH 36
|
||||
#define TABLET_DESCRIPTOR_LENGTH 328
|
||||
#define WHEEL_DESCRIPTOR_LENGTH 200
|
||||
|
||||
#define VENDOR_REPORT_ID 8
|
||||
#define VENDOR_REPORT_LENGTH 14
|
||||
|
||||
#define VENDOR_REPORT_SUBTYPE_PEN 0x08
|
||||
#define VENDOR_REPORT_SUBTYPE_PEN_OUT 0x00
|
||||
#define VENDOR_REPORT_SUBTYPE_BUTTONS 0x0e
|
||||
#define VENDOR_REPORT_SUBTYPE_WHEELS 0x0f
|
||||
|
||||
/* For the reports that we create ourselves */
|
||||
#define CUSTOM_PAD_REPORT_ID 9
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_HUION, PID_KAMVAS16_GEN3),
|
||||
);
|
||||
|
||||
/*
|
||||
* This tablet can send reports using one of two different data formats,
|
||||
* depending on what "mode" the tablet is in.
|
||||
*
|
||||
* By default, the tablet will send reports that can be decoded using its
|
||||
* included HID descriptors (descriptors 1 and 2, shown below).
|
||||
* This mode will be called "firmware mode" throughout this file.
|
||||
*
|
||||
* The HID descriptor that describes pen events in firmware mode (descriptor 1)
|
||||
* has multiple bugs:
|
||||
* * "Secondary Tip Switch" instead of "Secondary Barrel Switch"
|
||||
* * "Invert" instead of (or potentially shared with) third barrel button
|
||||
* * Specified tablet area of 2048 in³ instead of 293.8 x 165.2mm
|
||||
* * Specified tilt range of -90 to +90 instead of -60 to +60
|
||||
*
|
||||
* While these can be easily patched up by editing the descriptor, a larger
|
||||
* problem with the firmware mode exists: it is impossible to tell which of the
|
||||
* two wheels are being rotated (or having their central button pressed).
|
||||
*
|
||||
*
|
||||
* By using a tool such as huion-switcher (https://github.com/whot/huion-switcher),
|
||||
* the tablet can be made to send reports using a proprietary format that is not
|
||||
* adequately described by its relevant descriptor (descriptor 0, shown below).
|
||||
* This mode will be called "vendor mode" throughout this file.
|
||||
*
|
||||
* The reports sent while in vendor mode allow for proper decoding of the wheels.
|
||||
*
|
||||
* For simplicity and maximum functionality, this BPF focuses strictly on
|
||||
* enabling one to make use of the vendor mode.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DESCRIPTORS
|
||||
* DESCRIPTOR 0
|
||||
* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 0
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 1) 3
|
||||
* # 0xa1, 0x01, // Collection (Application) 5
|
||||
* # 0x85, 0x08, // Report ID (8) 7
|
||||
* # 0x75, 0x68, // Report Size (104) 9
|
||||
* # 0x95, 0x01, // Report Count (1) 11
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 1) 13
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 15
|
||||
* # 0xc0, // End Collection 17
|
||||
* # 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 18
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 1) 21
|
||||
* # 0xa1, 0x01, // Collection (Application) 23
|
||||
* # 0x85, 0x16, // Report ID (22) 25
|
||||
* # 0x75, 0x08, // Report Size (8) 27
|
||||
* # 0x95, 0x07, // Report Count (7) 29
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 1) 31
|
||||
* # 0xb1, 0x02, // Feature (Data,Var,Abs) 33
|
||||
* # 0xc0, // End Collection 35
|
||||
* #
|
||||
* R: 36 06 00 ff 09 01 a1 01 85 08 75 68 95 01 09 01 81 02 c0 06 00 ff 09 01 a1 01 85 16 75 08 95 07 09 01 b1 02 c0
|
||||
* N: HUION Huion Tablet_GS1563
|
||||
* I: 3 256c 2009
|
||||
*
|
||||
*
|
||||
* DESCRIPTOR 1
|
||||
* # 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, 0x43, // Usage (Secondary Tip Switch) 16
|
||||
* # 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, 0x06, // Report Count (6) 28
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 30
|
||||
* # 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
|
||||
* #
|
||||
* R: 328 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
|
||||
* N: HUION Huion Tablet_GS1563
|
||||
* I: 3 256c 2009
|
||||
*
|
||||
* DESCRIPTOR 2
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* # 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
|
||||
* # 0xa1, 0x01, // Collection (Application) 4
|
||||
* # 0x85, 0x11, // Report ID (17) 6
|
||||
* # 0x05, 0x0d, // Usage Page (Digitizers) 8
|
||||
* # 0x09, 0x21, // Usage (Puck) 10
|
||||
* # 0xa1, 0x02, // Collection (Logical) 12
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 14
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 16
|
||||
* # 0x75, 0x01, // Report Size (1) 18
|
||||
* # 0x95, 0x01, // Report Count (1) 20
|
||||
* # 0xa1, 0x00, // Collection (Physical) 22
|
||||
* # 0x05, 0x09, // Usage Page (Button) 24
|
||||
* # 0x09, 0x01, // Usage (Vendor Usage 0x01) 26
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 28
|
||||
* # 0x05, 0x0d, // Usage Page (Digitizers) 30
|
||||
* # 0x09, 0x33, // Usage (Touch) 32
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 34
|
||||
* # 0x95, 0x06, // Report Count (6) 36
|
||||
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 38
|
||||
* # 0xa1, 0x02, // Collection (Logical) 40
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 42
|
||||
* # 0x09, 0x37, // Usage (Dial) 44
|
||||
* # 0x16, 0x00, 0x80, // Logical Minimum (-32768) 46
|
||||
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 49
|
||||
* # 0x75, 0x10, // Report Size (16) 52
|
||||
* # 0x95, 0x01, // Report Count (1) 54
|
||||
* # 0x81, 0x06, // Input (Data,Var,Rel) 56
|
||||
* # 0x35, 0x00, // Physical Minimum (0) 58
|
||||
* # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 60
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 63
|
||||
* # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 65
|
||||
* # 0x09, 0x48, // Usage (Resolution Multiplier) 68
|
||||
* # 0xb1, 0x02, // Feature (Data,Var,Abs) 70
|
||||
* # 0x45, 0x00, // Physical Maximum (0) 72
|
||||
* # 0xc0, // End Collection 74
|
||||
* # 0x75, 0x08, // Report Size (8) 75
|
||||
* # 0x95, 0x01, // Report Count (1) 77
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 79
|
||||
* # 0x75, 0x08, // Report Size (8) 81
|
||||
* # 0x95, 0x01, // Report Count (1) 83
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 85
|
||||
* # 0x75, 0x08, // Report Size (8) 87
|
||||
* # 0x95, 0x01, // Report Count (1) 89
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 91
|
||||
* # 0x75, 0x08, // Report Size (8) 93
|
||||
* # 0x95, 0x01, // Report Count (1) 95
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 97
|
||||
* # 0x75, 0x08, // Report Size (8) 99
|
||||
* # 0x95, 0x01, // Report Count (1) 101
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 103
|
||||
* # 0xc0, // End Collection 105
|
||||
* # 0xc0, // End Collection 106
|
||||
* # 0xc0, // End Collection 107
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 108
|
||||
* # 0x09, 0x06, // Usage (Keyboard) 110
|
||||
* # 0xa1, 0x01, // Collection (Application) 112
|
||||
* # 0x85, 0x03, // Report ID (3) 114
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard) 116
|
||||
* # 0x19, 0xe0, // Usage Minimum (224) 118
|
||||
* # 0x29, 0xe7, // Usage Maximum (231) 120
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 122
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 124
|
||||
* # 0x75, 0x01, // Report Size (1) 126
|
||||
* # 0x95, 0x08, // Report Count (8) 128
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 130
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard) 132
|
||||
* # 0x19, 0x00, // Usage Minimum (0) 134
|
||||
* # 0x29, 0xff, // Usage Maximum (255) 136
|
||||
* # 0x26, 0xff, 0x00, // Logical Maximum (255) 138
|
||||
* # 0x75, 0x08, // Report Size (8) 141
|
||||
* # 0x95, 0x06, // Report Count (6) 143
|
||||
* # 0x81, 0x00, // Input (Data,Arr,Abs) 145
|
||||
* # 0xc0, // End Collection 147
|
||||
* # 0x05, 0x0c, // Usage Page (Consumer Devices) 148
|
||||
* # 0x09, 0x01, // Usage (Consumer Control) 150
|
||||
* # 0xa1, 0x01, // Collection (Application) 152
|
||||
* # 0x85, 0x04, // Report ID (4) 154
|
||||
* # 0x19, 0x01, // Usage Minimum (1) 156
|
||||
* # 0x2a, 0x9c, 0x02, // Usage Maximum (668) 158
|
||||
* # 0x15, 0x01, // Logical Minimum (1) 161
|
||||
* # 0x26, 0x9c, 0x02, // Logical Maximum (668) 163
|
||||
* # 0x95, 0x01, // Report Count (1) 166
|
||||
* # 0x75, 0x10, // Report Size (16) 168
|
||||
* # 0x81, 0x00, // Input (Data,Arr,Abs) 170
|
||||
* # 0xc0, // End Collection 172
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 173
|
||||
* # 0x09, 0x80, // Usage (System Control) 175
|
||||
* # 0xa1, 0x01, // Collection (Application) 177
|
||||
* # 0x85, 0x05, // Report ID (5) 179
|
||||
* # 0x19, 0x81, // Usage Minimum (129) 181
|
||||
* # 0x29, 0x83, // Usage Maximum (131) 183
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 185
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 187
|
||||
* # 0x75, 0x01, // Report Size (1) 189
|
||||
* # 0x95, 0x03, // Report Count (3) 191
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 193
|
||||
* # 0x95, 0x05, // Report Count (5) 195
|
||||
* # 0x81, 0x01, // Input (Cnst,Arr,Abs) 197
|
||||
* # 0xc0, // End Collection 199
|
||||
* #
|
||||
* R: 200 05 01 09 0e a1 01 85 11 05 0d 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0 05 01 09 06 a1 01 85 03 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 05 07 19 00 29 ff 26 ff 00 75 08 95 06 81 00 c0 05 0c 09 01 a1 01 85 04 19 01 2a 9c 02 15 01 26 9c 02 95 01 75 10 81 00 c0 05 01 09 80 a1 01 85 05 19 81 29 83 15 00 25 01 75 01 95 03 81 02 95 05 81 01 c0
|
||||
* N: HUION Huion Tablet_GS1563
|
||||
* I: 3 256c 2009
|
||||
*
|
||||
*
|
||||
*
|
||||
* VENDOR MODE
|
||||
* HUION_FIRMWARE_ID="HUION_M22d_241101"
|
||||
* HUION_MAGIC_BYTES="1403201101ac9900ff3fd81305080080083c4010"
|
||||
*
|
||||
* MAGIC BYTES
|
||||
* [LogicalMaximum, X ] [LogicalMaximum, Y ] [LogicalMaximum, Pressure] [ LPI]
|
||||
* 14 03 [ 20 11 01] [ ac 99 00] [ ff 3f] [d8 13] 05 08 00 80 08 3c 40 10
|
||||
*
|
||||
* See Huion__Kamvas13Gen3.bpf.c for more details on detailed button/dial reports and caveats. It's very
|
||||
* similar to the Kamvas 16 Gen 3.
|
||||
*/
|
||||
|
||||
|
||||
/* Filled in by udev-hid-bpf */
|
||||
char UDEV_PROP_HUION_FIRMWARE_ID[64];
|
||||
|
||||
char EXPECTED_FIRMWARE_ID[] = "HUION_M22d_";
|
||||
|
||||
__u8 last_button_state;
|
||||
|
||||
static const __u8 disabled_rdesc_tablet[] = {
|
||||
FixedSizeVendorReport(28) /* Input report 4 */
|
||||
};
|
||||
|
||||
static const __u8 disabled_rdesc_wheel[] = {
|
||||
FixedSizeVendorReport(9) /* Input report 17 */
|
||||
};
|
||||
|
||||
static const __u8 fixed_rdesc_vendor[] = {
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_Pen
|
||||
CollectionApplication(
|
||||
ReportId(VENDOR_REPORT_ID)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_Pen
|
||||
CollectionPhysical(
|
||||
/*
|
||||
* I have only examined the tablet's behavior while using
|
||||
* the PW600L pen, which does not have an eraser.
|
||||
* Because of this, I don't know where the Eraser and Invert
|
||||
* bits will go, or if they work as one would expect.
|
||||
*
|
||||
* For the time being, there is no expectation that a pen
|
||||
* with an eraser will work without modifications here.
|
||||
*/
|
||||
ReportSize(1)
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportCount(3)
|
||||
Usage_Dig_TipSwitch
|
||||
Usage_Dig_BarrelSwitch
|
||||
Usage_Dig_SecondaryBarrelSwitch
|
||||
Input(Var|Abs)
|
||||
PushPop(
|
||||
ReportCount(1)
|
||||
UsagePage_Button
|
||||
Usage_i8(0x4a) /* (BTN_STYLUS3 + 1) & 0xff */
|
||||
Input(Var|Abs)
|
||||
)
|
||||
ReportCount(3)
|
||||
Input(Const)
|
||||
ReportCount(1)
|
||||
Usage_Dig_InRange
|
||||
Input(Var|Abs)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
PushPop(
|
||||
UsagePage_GenericDesktop
|
||||
Unit(cm)
|
||||
UnitExponent(-2)
|
||||
LogicalMinimum_i16(0)
|
||||
PhysicalMinimum_i16(0)
|
||||
/*
|
||||
* The tablet has a logical maximum of 69920 x 39340
|
||||
* and a claimed resolution of 5080 LPI (200 L/mm)
|
||||
* This works out to a physical maximum of
|
||||
* 349.6 x 196.7mm, which matches Huion's advertised
|
||||
* (rounded) active area dimensions from
|
||||
* https://www.huion.com/products/pen_display/Kamvas/kamvas-16-gen-3.html
|
||||
*
|
||||
* The Kamvas uses data[8] for the 3rd byte of the X-axis, and adding
|
||||
* that after data[2] and data[3] makes a contiguous little-endian
|
||||
* 24-bit value. (See BPF_PROG below)
|
||||
*/
|
||||
ReportSize(24)
|
||||
LogicalMaximum_i32(69920)
|
||||
PhysicalMaximum_i16(3496)
|
||||
Usage_GD_X
|
||||
Input(Var|Abs)
|
||||
ReportSize(16)
|
||||
LogicalMaximum_i16(39340)
|
||||
PhysicalMaximum_i16(1967)
|
||||
Usage_GD_Y
|
||||
Input(Var|Abs)
|
||||
)
|
||||
ReportSize(16)
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(16383)
|
||||
Usage_Dig_TipPressure
|
||||
Input(Var|Abs)
|
||||
ReportSize(8)
|
||||
ReportCount(1)
|
||||
Input(Const)
|
||||
ReportCount(2)
|
||||
PushPop(
|
||||
Unit(deg)
|
||||
UnitExponent(0)
|
||||
LogicalMinimum_i8(-60)
|
||||
PhysicalMinimum_i8(-60)
|
||||
LogicalMaximum_i8(60)
|
||||
PhysicalMaximum_i8(60)
|
||||
Usage_Dig_XTilt
|
||||
Usage_Dig_YTilt
|
||||
Input(Var|Abs)
|
||||
)
|
||||
)
|
||||
)
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
ReportId(CUSTOM_PAD_REPORT_ID)
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
/*
|
||||
* The first 3 bytes are somewhat vestigial and will
|
||||
* always be set to zero. Their presence here is needed
|
||||
* to ensure that this device will be detected as a
|
||||
* tablet pad by software that otherwise wouldn't know
|
||||
* any better.
|
||||
*/
|
||||
/* (data[1] & 0x01) barrel switch */
|
||||
ReportSize(1)
|
||||
ReportCount(1)
|
||||
Usage_Dig_BarrelSwitch
|
||||
Input(Var|Abs)
|
||||
ReportCount(7)
|
||||
Input(Const)
|
||||
/* data[2] X */
|
||||
/* data[3] Y */
|
||||
ReportSize(8)
|
||||
ReportCount(2)
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
Input(Var|Abs)
|
||||
/*
|
||||
* (data[4] & 0x01) button 1
|
||||
* (data[4] & 0x02) button 2
|
||||
* (data[4] & 0x04) button 3
|
||||
* (data[4] & 0x08) button 4
|
||||
* (data[4] & 0x10) button 5
|
||||
* (data[4] & 0x20) button 6
|
||||
* (data[4] & 0x40) button 7 (top wheel button)
|
||||
* (data[4] & 0x80) button 8 (bottom wheel button)
|
||||
*/
|
||||
ReportSize(1)
|
||||
ReportCount(8)
|
||||
UsagePage_Button
|
||||
UsageMinimum_i8(1)
|
||||
UsageMaximum_i8(8)
|
||||
Input(Var|Abs)
|
||||
/* data[5] top wheel (signed, positive clockwise) */
|
||||
ReportSize(8)
|
||||
ReportCount(1)
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Wheel
|
||||
LogicalMinimum_i8(-1)
|
||||
LogicalMaximum_i8(1)
|
||||
Input(Var|Rel)
|
||||
/* data[6] bottom wheel (signed, positive clockwise) */
|
||||
UsagePage_Consumer
|
||||
Usage_Con_ACPan
|
||||
Input(Var|Rel)
|
||||
)
|
||||
/*
|
||||
* The kernel will drop reports that are bigger than the
|
||||
* largest report specified in the HID descriptor.
|
||||
* Therefore, our modified descriptor needs to have at least one
|
||||
* HID report that is as long as, or longer than, the largest
|
||||
* report in the original descriptor.
|
||||
*
|
||||
* This macro expands to a no-op report that is padded to the
|
||||
* provided length.
|
||||
*/
|
||||
FixedSizeVendorReport(VENDOR_REPORT_LENGTH)
|
||||
)
|
||||
};
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(hid_fix_rdesc_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
__s32 rdesc_size = hid_ctx->size;
|
||||
__u8 have_fw_id;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
have_fw_id = __builtin_memcmp(UDEV_PROP_HUION_FIRMWARE_ID,
|
||||
EXPECTED_FIRMWARE_ID,
|
||||
sizeof(EXPECTED_FIRMWARE_ID) - 1) == 0;
|
||||
|
||||
if (have_fw_id) {
|
||||
/*
|
||||
* Tablet should be in vendor mode.
|
||||
* Disable the unused devices
|
||||
*/
|
||||
if (rdesc_size == TABLET_DESCRIPTOR_LENGTH) {
|
||||
__builtin_memcpy(data, disabled_rdesc_tablet,
|
||||
sizeof(disabled_rdesc_tablet));
|
||||
return sizeof(disabled_rdesc_tablet);
|
||||
}
|
||||
|
||||
if (rdesc_size == WHEEL_DESCRIPTOR_LENGTH) {
|
||||
__builtin_memcpy(data, disabled_rdesc_wheel,
|
||||
sizeof(disabled_rdesc_wheel));
|
||||
return sizeof(disabled_rdesc_wheel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Regardless of which mode the tablet is in, always fix the vendor
|
||||
* descriptor in case the udev property just happened to not be set
|
||||
*/
|
||||
if (rdesc_size == VENDOR_DESCRIPTOR_LENGTH) {
|
||||
__builtin_memcpy(data, fixed_rdesc_vendor, sizeof(fixed_rdesc_vendor));
|
||||
return sizeof(fixed_rdesc_vendor);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(hid_fix_event_huion_kamvas16_gen3, struct hid_bpf_ctx *hid_ctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, VENDOR_REPORT_LENGTH /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Handle vendor reports only */
|
||||
if (hid_ctx->size != VENDOR_REPORT_LENGTH)
|
||||
return 0;
|
||||
if (data[0] != VENDOR_REPORT_ID)
|
||||
return 0;
|
||||
|
||||
__u8 report_subtype = (data[1] >> 4) & 0x0f;
|
||||
|
||||
if (report_subtype == VENDOR_REPORT_SUBTYPE_PEN ||
|
||||
report_subtype == VENDOR_REPORT_SUBTYPE_PEN_OUT) {
|
||||
/* Invert Y tilt */
|
||||
data[11] = -data[11];
|
||||
|
||||
/*
|
||||
* Rearrange the bytes of the report so that
|
||||
* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
|
||||
* will be arranged as
|
||||
* [0, 1, 2, 3, 8, 4, 5, 6, 7, 9, 10, 11, 12, 13]
|
||||
*/
|
||||
__u8 x_24 = data[8];
|
||||
|
||||
data[8] = data[7];
|
||||
data[7] = data[6];
|
||||
data[6] = data[5];
|
||||
data[5] = data[4];
|
||||
|
||||
data[4] = x_24;
|
||||
|
||||
} else if (report_subtype == VENDOR_REPORT_SUBTYPE_BUTTONS ||
|
||||
report_subtype == VENDOR_REPORT_SUBTYPE_WHEELS) {
|
||||
struct pad_report {
|
||||
__u8 report_id;
|
||||
__u8 btn_stylus:1;
|
||||
__u8 padding:7;
|
||||
__u8 x;
|
||||
__u8 y;
|
||||
__u8 buttons;
|
||||
__s8 top_wheel;
|
||||
__s8 bottom_wheel;
|
||||
} __attribute__((packed)) *pad_report;
|
||||
|
||||
__s8 top_wheel = 0;
|
||||
__s8 bottom_wheel = 0;
|
||||
|
||||
switch (report_subtype) {
|
||||
case VENDOR_REPORT_SUBTYPE_WHEELS:
|
||||
/*
|
||||
* The wheel direction byte is 1 for clockwise rotation
|
||||
* and 2 for counter-clockwise.
|
||||
* Change it to 1 and -1, respectively.
|
||||
*/
|
||||
switch (data[3]) {
|
||||
case 1:
|
||||
top_wheel = (data[5] == 1) ? 1 : -1;
|
||||
break;
|
||||
case 2:
|
||||
bottom_wheel = (data[5] == 1) ? 1 : -1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case VENDOR_REPORT_SUBTYPE_BUTTONS:
|
||||
/*
|
||||
* If a button is already being held, ignore any new
|
||||
* button event unless it's a release.
|
||||
*
|
||||
* The tablet only cleanly handles one button being held
|
||||
* at a time, and trying to hold multiple buttons
|
||||
* (particularly wheel+pad buttons) can result in sequences
|
||||
* of reports that look like imaginary presses and releases.
|
||||
*
|
||||
* This is an imperfect way to filter out some of these
|
||||
* reports.
|
||||
*/
|
||||
if (last_button_state != 0x00 && data[4] != 0x00)
|
||||
break;
|
||||
|
||||
last_button_state = data[4];
|
||||
break;
|
||||
}
|
||||
|
||||
pad_report = (struct pad_report *)data;
|
||||
|
||||
pad_report->report_id = CUSTOM_PAD_REPORT_ID;
|
||||
pad_report->btn_stylus = 0;
|
||||
pad_report->x = 0;
|
||||
pad_report->y = 0;
|
||||
pad_report->buttons = last_button_state;
|
||||
pad_report->top_wheel = top_wheel;
|
||||
pad_report->bottom_wheel = bottom_wheel;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(huion_kamvas16_gen3) = {
|
||||
.hid_device_event = (void *)hid_fix_event_huion_kamvas16_gen3,
|
||||
.hid_rdesc_fixup = (void *)hid_fix_rdesc_huion_kamvas16_gen3,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
switch (ctx->rdesc_size) {
|
||||
case VENDOR_DESCRIPTOR_LENGTH:
|
||||
case TABLET_DESCRIPTOR_LENGTH:
|
||||
case WHEEL_DESCRIPTOR_LENGTH:
|
||||
ctx->retval = 0;
|
||||
break;
|
||||
default:
|
||||
ctx->retval = -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -0,0 +1,492 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2024 Red Hat, Inc
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_HUION 0x256C
|
||||
#define PID_KEYDIAL_K20_BLUETOOTH 0x8251
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HUION, PID_KEYDIAL_K20_BLUETOOTH),
|
||||
);
|
||||
|
||||
/* This is the same device as in 0010-Huion__KeydialK20 but connected via Bluetooth.
|
||||
* It does not need (to support?) switching to a vendor mode so we just modify the
|
||||
* existing mode.
|
||||
*
|
||||
* By default it exports two hidraw nodes, only the second one sends events.
|
||||
*
|
||||
* This is the first hidraw node which we disable:
|
||||
*
|
||||
* # Keydial mini-050
|
||||
* # Report descriptor length: 114 bytes
|
||||
* # Bytes // Field Name Offset
|
||||
* # ----------------------------------------------------------------------------------
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* # 🭬 0x09, 0x0e, // Usage (System Multi-Axis Controller) 2
|
||||
* # 0xa1, 0x01, // Collection (Application) 4
|
||||
* # ┅ 0x85, 0x03, // Report ID (3) 6
|
||||
* # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 8
|
||||
* # 0x75, 0x08, // Report Size (8) 10
|
||||
* # 0x95, 0x01, // Report Count (1) 12
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 14
|
||||
* # 🭬 0x09, 0x21, // Usage (Puck) 16
|
||||
* # 0xa1, 0x02, // Collection (Logical) 18
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 20
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 22
|
||||
* # 0x75, 0x01, // Report Size (1) 24
|
||||
* # 0x95, 0x01, // Report Count (1) 26
|
||||
* # 0xa1, 0x00, // Collection (Physical) 28
|
||||
* # 🮥 0x05, 0x09, // Usage Page (Button) 30
|
||||
* # 🭬 0x09, 0x01, // Usage (Button 1) 32
|
||||
* # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 34
|
||||
* # 🮥 0x05, 0x0d, // Usage Page (Digitizers) 36
|
||||
* # 🭬 0x09, 0x33, // Usage (Touch) 38
|
||||
* # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 40
|
||||
* # 0x95, 0x06, // Report Count (6) 42
|
||||
* # ┇ 0x81, 0x03, // Input (Cnst,Var,Abs) 44
|
||||
* # 0xa1, 0x02, // Collection (Logical) 46
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 48
|
||||
* # 🭬 0x09, 0x37, // Usage (Dial) 50
|
||||
* # 0x16, 0x00, 0x80, // Logical Minimum (32768) 52
|
||||
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 55
|
||||
* # 0x75, 0x10, // Report Size (16) 58
|
||||
* # 0x95, 0x01, // Report Count (1) 60
|
||||
* # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 62
|
||||
* # 0x35, 0x00, // Physical Minimum (0) 64
|
||||
* # 0x46, 0x10, 0x0e, // Physical Maximum (3600) 66
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 69
|
||||
* # 0x26, 0x10, 0x0e, // Logical Maximum (3600) 71
|
||||
* # 🭬 0x09, 0x48, // Usage (Resolution Multiplier) 74
|
||||
* # ║ 0xb1, 0x02, // Feature (Data,Var,Abs) 76
|
||||
* # 0x45, 0x00, // Physical Maximum (0) 78
|
||||
* # 0xc0, // End Collection 80
|
||||
* # 0x75, 0x08, // Report Size (8) 81
|
||||
* # 0x95, 0x01, // Report Count (1) 83
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 85
|
||||
* # 0x75, 0x08, // Report Size (8) 87
|
||||
* # 0x95, 0x01, // Report Count (1) 89
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 91
|
||||
* # 0x75, 0x08, // Report Size (8) 93
|
||||
* # 0x95, 0x01, // Report Count (1) 95
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 97
|
||||
* # 0x75, 0x08, // Report Size (8) 99
|
||||
* # 0x95, 0x01, // Report Count (1) 101
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 103
|
||||
* # 0x75, 0x08, // Report Size (8) 105
|
||||
* # 0x95, 0x01, // Report Count (1) 107
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 109
|
||||
* # 0xc0, // End Collection 111
|
||||
* # 0xc0, // End Collection 112
|
||||
* # 0xc0, // End Collection 113
|
||||
* R: 114 05 01 09 0e a1 01 85 03 05 0d 75 08 95 01 81 01 09 21 a1 02 15 00 25 01 75 01 95 01 a1 00 05 09 09 01 81 02 05 0d 09 33 81 02 95 06 81 03 a1 02 05 01 09 37 16 00 80 26 ff 7f 75 10 95 01 81 06 35 00 46 10 0e 15 00 26 10 0e 09 48 b1 02 45 00 c0 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 75 08 95 01 81 01 c0 c0 c0
|
||||
* N: Keydial mini-050
|
||||
* I: 5 256c 8251
|
||||
*
|
||||
* The second hidraw node is what sends events:
|
||||
*
|
||||
* # Keydial mini-050
|
||||
* # Report descriptor length: 160 bytes
|
||||
* # Bytes // Field Name Offset
|
||||
* # ----------------------------------------------------------------------------------
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* # 🭬 0x09, 0x06, // Usage (Keyboard) 2
|
||||
* # 0xa1, 0x01, // Collection (Application) 4
|
||||
* # ┅ 0x85, 0x01, // Report ID (1) 6
|
||||
* # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 8
|
||||
* # 🭬 0x19, 0xe0, // Usage Minimum (224) 10
|
||||
* # 🭬 0x29, 0xe7, // Usage Maximum (231) 12
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 14
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 16
|
||||
* # 0x75, 0x01, // Report Size (1) 18
|
||||
* # 0x95, 0x08, // Report Count (8) 20
|
||||
* # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 22
|
||||
* # 0x95, 0x01, // Report Count (1) 24
|
||||
* # 0x75, 0x08, // Report Size (8) 26
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 28
|
||||
* # 0x95, 0x05, // Report Count (5) 30
|
||||
* # 0x75, 0x01, // Report Size (1) 32
|
||||
* # 🮥 0x05, 0x08, // Usage Page (LED) 34
|
||||
* # 🭬 0x19, 0x01, // Usage Minimum (1) 36
|
||||
* # 🭬 0x29, 0x05, // Usage Maximum (5) 38
|
||||
* # ┊ 0x91, 0x02, // Output (Data,Var,Abs) 40
|
||||
* # 0x95, 0x01, // Report Count (1) 42
|
||||
* # 0x75, 0x03, // Report Size (3) 44
|
||||
* # ┊ 0x91, 0x01, // Output (Cnst,Arr,Abs) 46
|
||||
* # 0x95, 0x06, // Report Count (6) 48
|
||||
* # 0x75, 0x08, // Report Size (8) 50
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 52
|
||||
* # 0x25, 0xf1, // Logical Maximum (241) 54
|
||||
* # 🮥 0x05, 0x07, // Usage Page (Keyboard/Keypad) 56
|
||||
* # 🭬 0x19, 0x00, // Usage Minimum (0) 58
|
||||
* # 🭬 0x29, 0xf1, // Usage Maximum (241) 60
|
||||
* # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 62
|
||||
* # 0xc0, // End Collection 64
|
||||
* # 🮥 0x05, 0x0c, // Usage Page (Consumer) 65
|
||||
* # 🭬 0x09, 0x01, // Usage (Consumer Control) 67
|
||||
* # 0xa1, 0x01, // Collection (Application) 69
|
||||
* # ┅ 0x85, 0x02, // Report ID (2) 71
|
||||
* # 🮥 0x05, 0x0c, // Usage Page (Consumer) 73
|
||||
* # 🭬 0x19, 0x00, // Usage Minimum (0) 75
|
||||
* # 🭬 0x2a, 0x80, 0x03, // Usage Maximum (896) 77
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 80
|
||||
* # 0x26, 0x80, 0x03, // Logical Maximum (896) 82
|
||||
* # 0x75, 0x10, // Report Size (16) 85
|
||||
* # 0x95, 0x01, // Report Count (1) 87
|
||||
* # ┇ 0x81, 0x00, // Input (Data,Arr,Abs) 89
|
||||
* # 0xc0, // End Collection 91
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 92
|
||||
* # 🭬 0x09, 0x02, // Usage (Mouse) 94
|
||||
* # 0xa1, 0x01, // Collection (Application) 96
|
||||
* # 🭬 0x09, 0x01, // Usage (Pointer) 98
|
||||
* # ┅ 0x85, 0x05, // Report ID (5) 100
|
||||
* # 0xa1, 0x00, // Collection (Physical) 102
|
||||
* # 🮥 0x05, 0x09, // Usage Page (Button) 104
|
||||
* # 🭬 0x19, 0x01, // Usage Minimum (1) 106
|
||||
* # 🭬 0x29, 0x05, // Usage Maximum (5) 108
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 110
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 112
|
||||
* # 0x95, 0x05, // Report Count (5) 114
|
||||
* # 0x75, 0x01, // Report Size (1) 116
|
||||
* # ┇ 0x81, 0x02, // Input (Data,Var,Abs) 118
|
||||
* # 0x95, 0x01, // Report Count (1) 120
|
||||
* # 0x75, 0x03, // Report Size (3) 122
|
||||
* # ┇ 0x81, 0x01, // Input (Cnst,Arr,Abs) 124
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 126
|
||||
* # 🭬 0x09, 0x30, // Usage (X) 128
|
||||
* # 🭬 0x09, 0x31, // Usage (Y) 130
|
||||
* # 0x16, 0x01, 0x80, // Logical Minimum (32769) 132
|
||||
* # 0x26, 0xff, 0x7f, // Logical Maximum (32767) 135
|
||||
* # 0x75, 0x10, // Report Size (16) 138
|
||||
* # 0x95, 0x02, // Report Count (2) 140
|
||||
* # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 142
|
||||
* # 🮥 0x05, 0x01, // Usage Page (Generic Desktop) 144
|
||||
* # 🭬 0x09, 0x38, // Usage (Wheel) 146
|
||||
* # 0x15, 0x81, // Logical Minimum (129) 148
|
||||
* # 0x25, 0x7f, // Logical Maximum (127) 150
|
||||
* # 0x95, 0x01, // Report Count (1) 152
|
||||
* # 0x75, 0x08, // Report Size (8) 154
|
||||
* # ┇ 0x81, 0x06, // Input (Data,Var,Rel) 156
|
||||
* # 0xc0, // End Collection 158
|
||||
* # 0xc0, // End Collection 159
|
||||
* R: 160 05 01 09 06 a1 01 85 01 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 01 75 08 81 01 95 05 75 01 05 08 19 01 29 05 91 02 95 01 75 03 91 01 95 06 75 08 15 00 25 f1 05 07 19 00 29 f1 81 00 c0 05 0c 09 01 a1 01 85 02 05 0c 19 00 2a 80 03 15 00 26 80 03 75 10 95 01 81 00 c0 05 01 09 02 a1 01 09 01 85 05 a1 00 05 09 19 01 29 05 15 00 25 01 95 05 75 01 81 02 95 01 75 03 81 01 05 01 09 30 09 31 16 01 80 26 ff 7f 75 10 95 02 81 06 05 01 09 38 15 81 25 7f 95 01 75 08 81 06 c0 c0
|
||||
* N: Keydial mini-050
|
||||
* I: 5 256c 8251
|
||||
* # Report descriptor:
|
||||
* # ------- Input Report -------
|
||||
* # ░ Report ID: 1
|
||||
* # ░ | Report size: 72 bits
|
||||
* # ░ Bit: 8 Usage: 0007/00e0: Keyboard/Keypad / Keyboard LeftControl Logical Range: 0..=1
|
||||
* # ░ Bit: 9 Usage: 0007/00e1: Keyboard/Keypad / Keyboard LeftShift Logical Range: 0..=1
|
||||
* # ░ Bit: 10 Usage: 0007/00e2: Keyboard/Keypad / Keyboard LeftAlt Logical Range: 0..=1
|
||||
* # ░ Bit: 11 Usage: 0007/00e3: Keyboard/Keypad / Keyboard Left GUI Logical Range: 0..=1
|
||||
* # ░ Bit: 12 Usage: 0007/00e4: Keyboard/Keypad / Keyboard RightControl Logical Range: 0..=1
|
||||
* # ░ Bit: 13 Usage: 0007/00e5: Keyboard/Keypad / Keyboard RightShift Logical Range: 0..=1
|
||||
* # ░ Bit: 14 Usage: 0007/00e6: Keyboard/Keypad / Keyboard RightAlt Logical Range: 0..=1
|
||||
* # ░ Bit: 15 Usage: 0007/00e7: Keyboard/Keypad / Keyboard Right GUI Logical Range: 0..=1
|
||||
* # ░ Bits: 16..=23 ######### Padding
|
||||
* # ░ Bits: 24..=71 Usages: Logical Range: 0..=241
|
||||
* # ░ 0007/0000: <unknown>
|
||||
* # ░ 0007/0001: Keyboard/Keypad / ErrorRollOver
|
||||
* # ░ 0007/0002: Keyboard/Keypad / POSTFail
|
||||
* # ░ 0007/0003: Keyboard/Keypad / ErrorUndefined
|
||||
* # ░ 0007/0004: Keyboard/Keypad / Keyboard A
|
||||
* # ░ ... use --full to see all usages
|
||||
* # ------- Input Report -------
|
||||
* # ▒ Report ID: 2
|
||||
* # ▒ | Report size: 24 bits
|
||||
* # ▒ Bits: 8..=23 Usages: Logical Range: 0..=896
|
||||
* # ▒ 000c/0000: <unknown>
|
||||
* # ▒ 000c/0001: Consumer / Consumer Control
|
||||
* # ▒ 000c/0002: Consumer / Numeric Key Pad
|
||||
* # ▒ 000c/0003: Consumer / Programmable Buttons
|
||||
* # ▒ 000c/0004: Consumer / Microphone
|
||||
* # ▒ ... use --full to see all usages
|
||||
* # ------- Input Report -------
|
||||
* # ▞ Report ID: 5
|
||||
* # ▞ | Report size: 56 bits
|
||||
* # ▞ Bit: 8 Usage: 0009/0001: Button / Button 1 Logical Range: 0..=1
|
||||
* # ▞ Bit: 9 Usage: 0009/0002: Button / Button 2 Logical Range: 0..=1
|
||||
* # ▞ Bit: 10 Usage: 0009/0003: Button / Button 3 Logical Range: 0..=1
|
||||
* # ▞ Bit: 11 Usage: 0009/0004: Button / Button 4 Logical Range: 0..=1
|
||||
* # ▞ Bit: 12 Usage: 0009/0005: Button / Button 5 Logical Range: 0..=1
|
||||
* # ▞ Bits: 13..=15 ######### Padding
|
||||
* # ▞ Bits: 16..=31 Usage: 0001/0030: Generic Desktop / X Logical Range: 32769..=32767
|
||||
* # ▞ Bits: 32..=47 Usage: 0001/0031: Generic Desktop / Y Logical Range: 32769..=32767
|
||||
* # ▞ Bits: 48..=55 Usage: 0001/0038: Generic Desktop / Wheel Logical Range: 129..=127
|
||||
* # ------- Output Report -------
|
||||
* # ░ Report ID: 1
|
||||
* # ░ | Report size: 16 bits
|
||||
* # ░ Bit: 8 Usage: 0008/0001: LED / Num Lock Logical Range: 0..=1
|
||||
* # ░ Bit: 9 Usage: 0008/0002: LED / Caps Lock Logical Range: 0..=1
|
||||
* # ░ Bit: 10 Usage: 0008/0003: LED / Scroll Lock Logical Range: 0..=1
|
||||
* # ░ Bit: 11 Usage: 0008/0004: LED / Compose Logical Range: 0..=1
|
||||
* # ░ Bit: 12 Usage: 0008/0005: LED / Kana Logical Range: 0..=1
|
||||
* # ░ Bits: 13..=15 ######### Padding
|
||||
* ##############################################################################
|
||||
* # Event nodes:
|
||||
* # - /dev/input/event12: "Keydial mini-050 Keyboard"
|
||||
* # - /dev/input/event14: "Keydial mini-050 Mouse"
|
||||
* ##############################################################################
|
||||
* # Recorded events below in format:
|
||||
* # E: <seconds>.<microseconds> <length-in-bytes> [bytes ...]
|
||||
* #
|
||||
*
|
||||
* - Report ID 1 sends keyboard shortcuts when pressing the buttons, e.g.
|
||||
*
|
||||
* # ░ Report ID: 1 /
|
||||
* # ░ Keyboard LeftControl: 0 |Keyboard LeftShift: 0 |Keyboard LeftAlt: 0 |Keyboard Left GUI: 0 |Keyboard RightControl: 0 |Keyboard RightShift: 0 |Keyboard RightAlt: 0 |Keyboard Right GUI: 0 |<8 bits padding> |0007/0000: 0| Keyboard K: 14| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0| 0007/0000: 0
|
||||
* E: 000000.000292 9 01 00 00 00 0e 00 00 00 00
|
||||
*
|
||||
* - Report ID 2 sends the button inside the wheel/dial thing
|
||||
* # ▒ Report ID: 2 /
|
||||
* # ▒ Play/Pause: 205
|
||||
* E: 000134.347845 3 02 cd 00
|
||||
* # ▒ Report ID: 2 /
|
||||
* # ▒ 000c/0000: 0
|
||||
* E: 000134.444965 3 02 00 00
|
||||
*
|
||||
* - Report ID 5 sends the wheel relative events (always a double-event with the second as zero)
|
||||
* # ▞ Report ID: 5 /
|
||||
* # ▞ Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 255
|
||||
* E: 000064.859915 7 05 00 00 00 00 00 ff
|
||||
* # ▞ Report ID: 5 /
|
||||
* # ▞ Button 1: 0 |Button 2: 0 |Button 3: 0 |Button 4: 0 |Button 5: 0 |<3 bits padding> |X: 0 |Y: 0 |Wheel: 0
|
||||
* E: 000064.882009 7 05 00 00 00 00 00 00
|
||||
*/
|
||||
|
||||
#define BT_PAD_REPORT_DESCRIPTOR_LENGTH 160
|
||||
#define BT_PUCK_REPORT_DESCRIPTOR_LENGTH 114 // This one doesn't send events
|
||||
#define BT_PAD_KBD_REPORT_ID 1
|
||||
#define BT_PAD_CC_REPORT_ID 2
|
||||
#define BT_PAD_MOUSE_REPORT_ID 5
|
||||
#define BT_PAD_KBD_REPORT_LENGTH 9
|
||||
#define BT_PAD_CC_REPORT_LENGTH 3
|
||||
#define BT_PAD_MOUSE_REPORT_LENGTH 7
|
||||
#define OUR_REPORT_ID 11 /* "randomly" picked report ID for our reports */
|
||||
|
||||
__u32 last_button_state = 0;
|
||||
|
||||
static const __u8 disabled_rdesc_puck[] = {
|
||||
FixedSizeVendorReport(BT_PUCK_REPORT_DESCRIPTOR_LENGTH)
|
||||
};
|
||||
|
||||
static const __u8 fixed_rdesc_pad[] = {
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
// Byte 0
|
||||
ReportId(OUR_REPORT_ID)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
// Byte 1 is a button so we look like a tablet
|
||||
Usage_Dig_BarrelSwitch // BTN_STYLUS, needed so we get to be a tablet pad
|
||||
ReportCount(1)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(7) // Padding
|
||||
Input(Const)
|
||||
// Bytes 2/3 - x/y just exist so we get to be a tablet pad
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
LogicalMinimum_i8(0x0)
|
||||
LogicalMaximum_i8(0x1)
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Var|Abs)
|
||||
// Bytes 4-7 are the button state for 19 buttons + pad out to u32
|
||||
// We send the first 10 buttons as buttons 1-10 which is BTN_0 -> BTN_9
|
||||
UsagePage_Button
|
||||
UsageMinimum_i8(1)
|
||||
UsageMaximum_i8(10)
|
||||
LogicalMinimum_i8(0x0)
|
||||
LogicalMaximum_i8(0x1)
|
||||
ReportCount(10)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
// We send the other 9 buttons as buttons 0x31 and above -> BTN_A - BTN_TL2
|
||||
UsageMinimum_i8(0x31)
|
||||
UsageMaximum_i8(0x3a)
|
||||
ReportCount(9)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(13)
|
||||
ReportSize(1)
|
||||
Input(Const) // padding
|
||||
// Byte 8 is the wheel
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Wheel
|
||||
LogicalMinimum_i8(-1)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportCount(1)
|
||||
ReportSize(8)
|
||||
Input(Var|Rel)
|
||||
)
|
||||
// Make sure we match our original report length
|
||||
FixedSizeVendorReport(BT_PAD_KBD_REPORT_LENGTH)
|
||||
)
|
||||
};
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(k20_bt_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
__s32 rdesc_size = hctx->size;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (rdesc_size == BT_PAD_REPORT_DESCRIPTOR_LENGTH) {
|
||||
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
|
||||
return sizeof(fixed_rdesc_pad);
|
||||
}
|
||||
if (rdesc_size == BT_PUCK_REPORT_DESCRIPTOR_LENGTH) {
|
||||
// This hidraw node doesn't send anything and can be ignored
|
||||
__builtin_memcpy(data, disabled_rdesc_puck, sizeof(disabled_rdesc_puck));
|
||||
return sizeof(disabled_rdesc_puck);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(k20_bt_fix_events, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 12 /* size */);
|
||||
struct pad_report {
|
||||
__u8 report_id;
|
||||
__u8 btn_stylus:1;
|
||||
__u8 pad:7;
|
||||
__u8 x;
|
||||
__u8 y;
|
||||
__u32 buttons;
|
||||
__u8 wheel;
|
||||
} __packed * pad_report = (struct pad_report *)data;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
/* Report ID 1 - Keyboard events (button presses) */
|
||||
if (data[0] == BT_PAD_KBD_REPORT_ID) {
|
||||
const __u8 button_mapping[] = {
|
||||
0x0e, /* Button 1: K */
|
||||
0x0a, /* Button 2: G */
|
||||
0x0f, /* Button 3: L */
|
||||
0x4c, /* Button 4: Delete */
|
||||
0x0c, /* Button 5: I */
|
||||
0x07, /* Button 6: D */
|
||||
0x05, /* Button 7: B */
|
||||
0x08, /* Button 8: E */
|
||||
0x16, /* Button 9: S */
|
||||
0x1d, /* Button 10: Z */
|
||||
0x06, /* Button 11: C */
|
||||
0x19, /* Button 12: V */
|
||||
0xff, /* Button 13: LeftControl */
|
||||
0xff, /* Button 14: LeftAlt */
|
||||
0xff, /* Button 15: LeftShift */
|
||||
0x28, /* Button 16: Return Enter */
|
||||
0x2c, /* Button 17: Spacebar */
|
||||
0x11, /* Button 18: N */
|
||||
};
|
||||
|
||||
__u8 modifiers = data[1];
|
||||
__u32 buttons = 0;
|
||||
|
||||
if (modifiers & 0x01) { /* Control */
|
||||
buttons |= BIT(12);
|
||||
}
|
||||
if (modifiers & 0x02) { /* Shift */
|
||||
buttons |= BIT(14);
|
||||
}
|
||||
if (modifiers & 0x04) { /* Alt */
|
||||
buttons |= BIT(13);
|
||||
}
|
||||
|
||||
for (int i = 4; i < BT_PAD_KBD_REPORT_LENGTH; i++) {
|
||||
if (!data[i])
|
||||
break;
|
||||
|
||||
for (size_t b = 0; b < ARRAY_SIZE(button_mapping); b++) {
|
||||
if (data[i] != 0xff && data[i] == button_mapping[b]) {
|
||||
buttons |= BIT(b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
last_button_state = buttons;
|
||||
|
||||
pad_report->report_id = OUR_REPORT_ID;
|
||||
pad_report->btn_stylus = 0;
|
||||
pad_report->x = 0;
|
||||
pad_report->y = 0;
|
||||
pad_report->buttons = buttons;
|
||||
pad_report->wheel = 0;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
}
|
||||
|
||||
/* Report ID 2 - Consumer control events (the button inside the wheel) */
|
||||
if (data[0] == BT_PAD_CC_REPORT_ID) {
|
||||
const __u8 PlayPause = 0xcd;
|
||||
|
||||
if (data[1] == PlayPause)
|
||||
last_button_state |= BIT(18);
|
||||
else
|
||||
last_button_state &= ~BIT(18);
|
||||
|
||||
pad_report->report_id = OUR_REPORT_ID;
|
||||
pad_report->btn_stylus = 0;
|
||||
pad_report->x = 0;
|
||||
pad_report->y = 0;
|
||||
pad_report->buttons = last_button_state;
|
||||
pad_report->wheel = 0;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
}
|
||||
|
||||
/* Report ID 5 - Mouse events (wheel rotation) */
|
||||
if (data[0] == BT_PAD_MOUSE_REPORT_ID) {
|
||||
__u8 wheel_delta = data[6];
|
||||
|
||||
pad_report->report_id = OUR_REPORT_ID;
|
||||
pad_report->btn_stylus = 0;
|
||||
pad_report->x = 0;
|
||||
pad_report->y = 0;
|
||||
pad_report->buttons = last_button_state;
|
||||
pad_report->wheel = wheel_delta;
|
||||
|
||||
return sizeof(struct pad_report);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(keydial_k20_bluetooth) = {
|
||||
.hid_device_event = (void *)k20_bt_fix_events,
|
||||
.hid_rdesc_fixup = (void *)k20_bt_fix_rdesc,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
switch (ctx->rdesc_size) {
|
||||
case BT_PAD_REPORT_DESCRIPTOR_LENGTH:
|
||||
case BT_PUCK_REPORT_DESCRIPTOR_LENGTH:
|
||||
ctx->retval = 0;
|
||||
break;
|
||||
default:
|
||||
ctx->retval = -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -462,7 +462,8 @@ int BPF_PROG(k20_fix_events, struct hid_bpf_ctx *hctx)
|
||||
__u32 buttons;
|
||||
__u8 wheel;
|
||||
} __attribute__((packed)) *pad_report;
|
||||
int i, b;
|
||||
int i;
|
||||
size_t b;
|
||||
__u8 modifiers = data[1];
|
||||
__u32 buttons = 0;
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
if (data[3] != 0x06)
|
||||
return 0;
|
||||
|
||||
for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
|
||||
for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
|
||||
u8 offset = offsets[idx];
|
||||
|
||||
/* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Curran Muhlberger
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_LOGITECH 0x046D
|
||||
#define PID_SPACENAVIGATOR 0xC626
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_LOGITECH, PID_SPACENAVIGATOR)
|
||||
);
|
||||
|
||||
/*
|
||||
* The 3Dconnexion SpaceNavigator 3D Mouse is a multi-axis controller with 6
|
||||
* axes (grouped as X,Y,Z and Rx,Ry,Rz). Axis data is absolute, but the report
|
||||
* descriptor erroneously declares it to be relative. We fix the report
|
||||
* descriptor to mark both axis collections as absolute.
|
||||
*
|
||||
* The kernel attempted to fix this in commit 24985cf68612 (HID: support
|
||||
* Logitech/3DConnexion SpaceTraveler and SpaceNavigator), but the descriptor
|
||||
* data offsets are incorrect for at least some SpaceNavigator units.
|
||||
*/
|
||||
|
||||
SEC(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 */
|
||||
|
||||
/* Offset of Input item in X,Y,Z and Rx,Ry,Rz collections for all known
|
||||
* firmware variants.
|
||||
* - 2009 model: X,Y,Z @ 32-33, Rx,Ry,Rz @ 49-50 (fixup originally
|
||||
* applied in kernel)
|
||||
* - 2016 model (size==228): X,Y,Z @ 36-37, Rx,Ry,Rz @ 53-54
|
||||
*
|
||||
* The descriptor size of the 2009 model is not known, and there is evidence
|
||||
* for at least two other variants (with sizes 202 & 217) besides the 2016
|
||||
* model, so we try all known offsets regardless of descriptor size.
|
||||
*/
|
||||
const u8 offsets[] = {32, 36, 49, 53};
|
||||
|
||||
for (size_t idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
|
||||
u8 offset = offsets[idx];
|
||||
|
||||
/* if Input (Data,Var,Rel) , make it Input (Data,Var,Abs) */
|
||||
if (data[offset] == 0x81 && data[offset + 1] == 0x06)
|
||||
data[offset + 1] = 0x02;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(logitech_spacenavigator) = {
|
||||
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/* Ensure report descriptor size matches one of the known variants. */
|
||||
if (ctx->rdesc_size != 202 &&
|
||||
ctx->rdesc_size != 217 &&
|
||||
ctx->rdesc_size != 228) {
|
||||
ctx->retval = -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check whether the kernel has already applied the fix. */
|
||||
if ((ctx->rdesc[32] == 0x81 && ctx->rdesc[33] == 0x02 &&
|
||||
ctx->rdesc[49] == 0x81 && ctx->rdesc[50] == 0x02) ||
|
||||
(ctx->rdesc[36] == 0x81 && ctx->rdesc[37] == 0x02 &&
|
||||
ctx->rdesc[53] == 0x81 && ctx->rdesc[54] == 0x02))
|
||||
ctx->retval = -EINVAL;
|
||||
else
|
||||
ctx->retval = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -57,6 +57,8 @@ clean:
|
||||
%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
|
||||
$(call msg,BPF,$@)
|
||||
$(Q)$(CLANG) -g -O2 --target=bpf -Wall -Werror $(INCLUDES) \
|
||||
-Wno-microsoft-anon-tag \
|
||||
-fms-extensions \
|
||||
-c $(filter %.c,$^) -o $@ && \
|
||||
$(LLVM_STRIP) -g $@
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Fix for Trust Philips SPK6327 (145f:024b)
|
||||
* Modifier keys report as Array (0x00) instead of Variable (0x02)
|
||||
* causing LCtrl, LAlt, Super etc. to all act as LShift
|
||||
*/
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_TRUST 0x145F
|
||||
#define PID_SPK6327 0x024B
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_TRUST, PID_SPK6327)
|
||||
);
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0, 4096);
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
/* Fix modifier keys: Input Array (0x00) -> Input Variable (0x02) */
|
||||
if (data[101] == 0x00)
|
||||
data[101] = 0x02;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(trust_spk6327) = {
|
||||
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
/* Only apply to interface 1 (169 bytes) not interface 0 (62 bytes) */
|
||||
if (ctx->rdesc_size == 169)
|
||||
ctx->retval = 0;
|
||||
else
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -0,0 +1,321 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Red Hat
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_WALTOP 0x172F
|
||||
#define PID_BATTERYLESS_TABLET 0x0505
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_ANY, VID_WALTOP, PID_BATTERYLESS_TABLET)
|
||||
);
|
||||
|
||||
#define EXPECTED_RDESC_SIZE 335
|
||||
#define PEN_REPORT_ID 16
|
||||
|
||||
#define TIP_SWITCH BIT(0)
|
||||
#define BARREL_SWITCH BIT(1)
|
||||
#define SECONDARY_BARREL_SWITCH BIT(5)
|
||||
|
||||
static __u8 last_button_state;
|
||||
|
||||
static const __u8 fixed_rdesc[] = {
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x02, // Usage (Mouse)
|
||||
0xa1, 0x01, // Collection (Application)
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x09, 0x01, // Usage (Pointer)
|
||||
0xa1, 0x00, // Collection (Physical)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (1)
|
||||
0x29, 0x05, // Usage Maximum (5)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x75, 0x03, // Report Size (3)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x09, 0x38, // Usage (Wheel)
|
||||
0x15, 0x81, // Logical Minimum (-127)
|
||||
0x25, 0x7f, // Logical Maximum (127)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0x81, 0x06, // Input (Data,Var,Rel)
|
||||
0x05, 0x0c, // Usage Page (Consumer)
|
||||
0x15, 0x81, // Logical Minimum (-127)
|
||||
0x25, 0x7f, // Logical Maximum (127)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x0a, 0x38, 0x02, // Usage (AC Pan)
|
||||
0x81, 0x06, // Input (Data,Var,Rel)
|
||||
0xc0, // End Collection
|
||||
0xc0, // End Collection
|
||||
0x05, 0x0d, // Usage Page (Digitizers)
|
||||
0x09, 0x02, // Usage (Pen)
|
||||
0xa1, 0x01, // Collection (Application)
|
||||
0x85, 0x02, // Report ID (2)
|
||||
0x09, 0x20, // Usage (Stylus)
|
||||
0xa1, 0x00, // Collection (Physical)
|
||||
0x09, 0x00, // Usage (0x0000)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x09, // Report Count (9)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x09, 0x3f, // Usage (Azimuth)
|
||||
0x09, 0x40, // Usage (Altitude)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0xb1, 0x02, // Feature (Data,Var,Abs)
|
||||
0xc0, // End Collection
|
||||
0x85, 0x05, // Report ID (5)
|
||||
0x05, 0x0d, // Usage Page (Digitizers)
|
||||
0x09, 0x20, // Usage (Stylus)
|
||||
0xa1, 0x00, // Collection (Physical)
|
||||
0x09, 0x00, // Usage (0x0000)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x07, // Report Count (7)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0xc0, // End Collection
|
||||
0x85, 0x0a, // Report ID (10)
|
||||
0x05, 0x0d, // Usage Page (Digitizers)
|
||||
0x09, 0x20, // Usage (Stylus)
|
||||
0xa1, 0x00, // Collection (Physical)
|
||||
0x09, 0x00, // Usage (0x0000)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xff, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x07, // Report Count (7)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0xc0, // End Collection
|
||||
0x85, 0x10, // Report ID (16)
|
||||
0x09, 0x20, // Usage (Stylus)
|
||||
0xa1, 0x00, // Collection (Physical)
|
||||
0x09, 0x42, // Usage (Tip Switch)
|
||||
0x09, 0x44, // Usage (Barrel Switch)
|
||||
0x09, 0x3c, // Usage (Invert)
|
||||
0x09, 0x45, // Usage (Eraser)
|
||||
0x09, 0x32, // Usage (In Range)
|
||||
0x09, 0x5a, // Usage (Secondary Barrel Switch) <-- added
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x06, // Report Count (6) <--- changed from 5
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x95, 0x02, // Report Count (2) <--- changed from 3
|
||||
0x81, 0x03, // Input (Cnst,Var,Abs)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x75, 0x10, // Report Size (16)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0xa4, // Push
|
||||
0x55, 0x0d, // Unit Exponent (-3)
|
||||
0x65, 0x33, // Unit (EnglishLinear: in³)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0x00, 0x7d, // Logical Maximum (32000)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x00, 0x7d, // Physical Maximum (32000)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0x20, 0x4e, // Logical Maximum (20000)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x20, 0x4e, // Physical Maximum (20000)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x05, 0x0d, // Usage Page (Digitizers)
|
||||
0x09, 0x30, // Usage (Tip Pressure)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xff, 0x07, // Logical Maximum (2047)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0xff, 0x07, // Physical Maximum (2047)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x05, 0x0d, // Usage Page (Digitizers)
|
||||
0x09, 0x3d, // Usage (X Tilt)
|
||||
0x09, 0x3e, // Usage (Y Tilt)
|
||||
0x15, 0xc4, // Logical Minimum (-60) <- changed from -127
|
||||
0x25, 0x3c, // Logical Maximum (60) <- changed from 127
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0xc0, // End Collection
|
||||
0xc0, // End Collection
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
0x09, 0x06, // Usage (Keyboard)
|
||||
0xa1, 0x01, // Collection (Application)
|
||||
0x85, 0x0d, // Report ID (13)
|
||||
0x05, 0x07, // Usage Page (Keyboard/Keypad)
|
||||
0x19, 0xe0, // Usage Minimum (224)
|
||||
0x29, 0xe7, // Usage Maximum (231)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x08, // Report Count (8)
|
||||
0x81, 0x02, // Input (Data,Var,Abs)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x01, // Input (Cnst,Arr,Abs)
|
||||
0x05, 0x07, // Usage Page (Keyboard/Keypad)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0x65, // Usage Maximum (101)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x65, // Logical Maximum (101)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x05, // Report Count (5)
|
||||
0x81, 0x00, // Input (Data,Arr,Abs)
|
||||
0xc0, // End Collection
|
||||
0x05, 0x0c, // Usage Page (Consumer)
|
||||
0x09, 0x01, // Usage (Consumer Control)
|
||||
0xa1, 0x01, // Collection (Application)
|
||||
0x85, 0x0c, // Report ID (12)
|
||||
0x09, 0xe9, // Usage (Volume Increment)
|
||||
0x09, 0xea, // Usage (Volume Decrement)
|
||||
0x09, 0xe2, // Usage (Mute)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0x81, 0x06, // Input (Data,Var,Rel)
|
||||
0x75, 0x05, // Report Size (5)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x07, // Input (Cnst,Var,Rel)
|
||||
0xc0, // End Collection
|
||||
};
|
||||
|
||||
static inline unsigned int bitwidth32(__u32 x)
|
||||
{
|
||||
return 32 - __builtin_clzg(x, 32);
|
||||
}
|
||||
|
||||
static inline unsigned int floor_log2_32(__u32 x)
|
||||
{
|
||||
return bitwidth32(x) - 1;
|
||||
}
|
||||
|
||||
/* Maps the interval [0, 2047] to itself using a scaled
|
||||
* approximation of the function log2(x+1).
|
||||
*/
|
||||
static unsigned int scaled_log2(__u16 v)
|
||||
{
|
||||
const unsigned int XMAX = 2047;
|
||||
const unsigned int YMAX = 11; /* log2(2048) = 11 */
|
||||
|
||||
unsigned int x = v + 1;
|
||||
unsigned int n = floor_log2_32(x);
|
||||
unsigned int b = 1 << n;
|
||||
|
||||
/* Fixed-point fraction in [0, 1), linearly
|
||||
* interpolated using delta-y = 1 and
|
||||
* delta-x = (2b - b) = b.
|
||||
*/
|
||||
unsigned int frac = (x - b) << YMAX;
|
||||
unsigned int lerp = frac / b;
|
||||
unsigned int log2 = (n << YMAX) + lerp;
|
||||
|
||||
return ((log2 * XMAX) / YMAX) >> YMAX;
|
||||
}
|
||||
|
||||
SEC(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 */
|
||||
|
||||
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
|
||||
|
||||
return sizeof(fixed_rdesc);
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(waltop_fix_events, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
__u8 report_id = data[0];
|
||||
|
||||
if (report_id != PEN_REPORT_ID)
|
||||
return 0;
|
||||
|
||||
/* On this tablet if the secondary barrel switch is pressed,
|
||||
* the tablet sends tip down and barrel down. Change this to
|
||||
* just secondary barrel down when there is no ambiguity.
|
||||
*
|
||||
* It's possible that there is a bug in the firmware and the
|
||||
* device intends to set invert + eraser instead (i.e. the
|
||||
* pysical button is an eraser button) but since
|
||||
* the pressure is always zero, said eraser button
|
||||
* would be useless anyway.
|
||||
*
|
||||
* So let's just change the button to secondary barrel down.
|
||||
*/
|
||||
|
||||
__u8 tip_switch = data[1] & TIP_SWITCH;
|
||||
__u8 barrel_switch = data[1] & BARREL_SWITCH;
|
||||
|
||||
__u8 tip_held = last_button_state & TIP_SWITCH;
|
||||
__u8 barrel_held = last_button_state & BARREL_SWITCH;
|
||||
|
||||
if (tip_switch && barrel_switch && !tip_held && !barrel_held) {
|
||||
data[1] &= ~(TIP_SWITCH | BARREL_SWITCH); /* release tip and barrel */
|
||||
data[1] |= SECONDARY_BARREL_SWITCH; /* set secondary barrel switch */
|
||||
}
|
||||
|
||||
last_button_state = data[1];
|
||||
|
||||
/* The pressure sensor on this tablet maps around half of the
|
||||
* logical pressure range into the interval [0-100]. Further
|
||||
* pressure causes the sensor value to increase exponentially
|
||||
* up to a maximum value of 2047.
|
||||
*
|
||||
* The values 12 and 102 were chosen to have an integer slope
|
||||
* with smooth transition between the two curves around the
|
||||
* value 100.
|
||||
*/
|
||||
|
||||
__u16 pressure = (((__u16)data[6]) << 0) | (((__u16)data[7]) << 8);
|
||||
|
||||
if (pressure <= 102)
|
||||
pressure *= 12;
|
||||
else
|
||||
pressure = scaled_log2(pressure);
|
||||
|
||||
data[6] = pressure >> 0;
|
||||
data[7] = pressure >> 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(waltop_batteryless) = {
|
||||
.hid_device_event = (void *)waltop_fix_events,
|
||||
.hid_rdesc_fixup = (void *)hid_fix_rdesc,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
if (ctx->rdesc_size == EXPECTED_RDESC_SIZE)
|
||||
ctx->retval = 0;
|
||||
else
|
||||
ctx->retval = -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -148,7 +148,7 @@ int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
struct hid_bpf_ctx *hid_ctx;
|
||||
__u16 pid;
|
||||
int i;
|
||||
size_t i;
|
||||
|
||||
/* get a struct hid_device to access the actual pid of the device */
|
||||
hid_ctx = hid_bpf_allocate_context(ctx->hid);
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2025 Red Hat
|
||||
*/
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
|
||||
#define PID_DECO_01_V3 0x0947
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_01_V3),
|
||||
);
|
||||
|
||||
/*
|
||||
* Default report descriptor reports:
|
||||
* - a report descriptor for the pad buttons, reported as key sequences
|
||||
* - a report descriptor for the pen
|
||||
* - a vendor-specific report descriptor
|
||||
*
|
||||
* The Pad report descriptor, see
|
||||
* https://gitlab.freedesktop.org/libevdev/udev-hid-bpf/-/issues/54
|
||||
*
|
||||
* # Report descriptor length: 102 bytes
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||
* 0x09, 0x02, // Usage (Mouse) 2
|
||||
* 0xa1, 0x01, // Collection (Application) 4
|
||||
* 0x85, 0x09, // Report ID (9) 6
|
||||
* 0x09, 0x01, // Usage (Pointer) 8
|
||||
* 0xa1, 0x00, // Collection (Physical) 10
|
||||
* 0x05, 0x09, // Usage Page (Button) 12
|
||||
* 0x19, 0x01, // UsageMinimum (1) 14
|
||||
* 0x29, 0x03, // UsageMaximum (3) 16
|
||||
* 0x15, 0x00, // Logical Minimum (0) 18
|
||||
* 0x25, 0x01, // Logical Maximum (1) 20
|
||||
* 0x95, 0x03, // Report Count (3) 22
|
||||
* 0x75, 0x01, // Report Size (1) 24
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 26
|
||||
* 0x95, 0x05, // Report Count (5) 28
|
||||
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 30
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 32
|
||||
* 0x09, 0x30, // Usage (X) 34
|
||||
* 0x09, 0x31, // Usage (Y) 36
|
||||
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 38
|
||||
* 0x95, 0x02, // Report Count (2) 41
|
||||
* 0x75, 0x10, // Report Size (16) 43
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 45
|
||||
* 0x05, 0x0d, // Usage Page (Digitizers) 47
|
||||
* 0x09, 0x30, // Usage (Tip Pressure) 49
|
||||
* 0x26, 0xff, 0x07, // Logical Maximum (2047) 51
|
||||
* 0x95, 0x01, // Report Count (1) 54
|
||||
* 0x75, 0x10, // Report Size (16) 56
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 58
|
||||
* 0xc0, // End Collection 60
|
||||
* 0xc0, // End Collection 61
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 62
|
||||
* 0x09, 0x06, // Usage (Keyboard) 64
|
||||
* 0xa1, 0x01, // Collection (Application) 66
|
||||
* 0x85, 0x06, // Report ID (6) 68
|
||||
* 0x05, 0x07, // Usage Page (Keyboard/Keypad) 70
|
||||
* 0x19, 0xe0, // UsageMinimum (224) 72
|
||||
* 0x29, 0xe7, // UsageMaximum (231) 74
|
||||
* 0x15, 0x00, // Logical Minimum (0) 76
|
||||
* 0x25, 0x01, // Logical Maximum (1) 78
|
||||
* 0x75, 0x01, // Report Size (1) 80
|
||||
* 0x95, 0x08, // Report Count (8) 82
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 84
|
||||
* 0x05, 0x07, // Usage Page (Keyboard/Keypad) 86
|
||||
* 0x19, 0x00, // UsageMinimum (0) 88
|
||||
* 0x29, 0xff, // UsageMaximum (255) 90
|
||||
* 0x26, 0xff, 0x00, // Logical Maximum (255) 92
|
||||
* 0x75, 0x08, // Report Size (8) 95
|
||||
* 0x95, 0x06, // Report Count (6) 97
|
||||
* 0x81, 0x00, // Input (Data,Arr,Abs) 99
|
||||
* 0xc0, // End Collection 101
|
||||
*
|
||||
* And key events for buttons top->bottom are:
|
||||
* Buttons released: 06 00 00 00 00 00 00 00
|
||||
* Button1: 06 00 05 00 00 00 00 00 -> b
|
||||
* Button2: 06 00 08 00 00 00 00 00 -> e
|
||||
* Button3: 06 04 00 00 00 00 00 00 -> LAlt
|
||||
* Button4: 06 00 2c 00 00 00 00 00 -> Space
|
||||
* Button5: 06 01 16 00 00 00 00 00 -> LControl + s
|
||||
* Button6: 06 01 1d 00 00 00 00 00 -> LControl + z
|
||||
* Button7: 06 01 57 00 00 00 00 00 -> LControl + Keypad Plus
|
||||
* Button8: 06 01 56 00 00 00 00 00 -> LControl + Keypad Dash
|
||||
*
|
||||
* When multiple buttons are pressed at the same time, the values used to
|
||||
* identify the buttons are identical, but they appear in different bytes of the
|
||||
* record. For example, when button 2 (0x08) and button 1 (0x05) are pressed,
|
||||
* this is the report:
|
||||
*
|
||||
* Buttons 2 and 1: 06 00 08 05 00 00 00 00 -> e + b
|
||||
*
|
||||
* Buttons 1, 2, 4, 5 and 6 can be matched by finding their values in the
|
||||
* report.
|
||||
*
|
||||
* Button 3 is pressed when the 3rd bit is 1. For example, pressing buttons 3
|
||||
* and 5 generates this report:
|
||||
*
|
||||
* Buttons 3 and 5: 06 05 16 00 00 00 00 00 -> LControl + LAlt + s
|
||||
* -- --
|
||||
* | |
|
||||
* | `- Button 5 (0x16)
|
||||
* `- 0x05 = 0101. Button 3 is pressed
|
||||
* ^
|
||||
*
|
||||
* pad_buttons contains a list of buttons that can be matched in
|
||||
* HID_BPF_DEVICE_EVENT. Button 3 as it has a dedicated bit.
|
||||
*
|
||||
*
|
||||
* The Pen report descriptor announces a wrong tilt range:
|
||||
*
|
||||
* Report descriptor length: 109 bytes
|
||||
* 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, 0x01, // Collection (Application) 10
|
||||
* 0x09, 0x42, // Usage (Tip Switch) 12
|
||||
* 0x09, 0x44, // Usage (Barrel Switch) 14
|
||||
* 0x09, 0x45, // Usage (Eraser) 16
|
||||
* 0x09, 0x3c, // Usage (Invert) 18
|
||||
* 0x15, 0x00, // Logical Minimum (0) 20
|
||||
* 0x25, 0x01, // Logical Maximum (1) 22
|
||||
* 0x75, 0x01, // Report Size (1) 24
|
||||
* 0x95, 0x04, // Report Count (4) 26
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 28
|
||||
* 0x95, 0x01, // Report Count (1) 30
|
||||
* 0x81, 0x03, // Input (Cnst,Var,Abs) 32
|
||||
* 0x09, 0x32, // Usage (In Range) 34
|
||||
* 0x95, 0x01, // Report Count (1) 36
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 38
|
||||
* 0x95, 0x02, // Report Count (2) 40
|
||||
* 0x81, 0x03, // Input (Cnst,Var,Abs) 42
|
||||
* 0x75, 0x10, // Report Size (16) 44
|
||||
* 0x95, 0x01, // Report Count (1) 46
|
||||
* 0x35, 0x00, // Physical Minimum (0) 48
|
||||
* 0xa4, // Push 50
|
||||
* 0x05, 0x01, // Usage Page (Generic Desktop) 51
|
||||
* 0x09, 0x30, // Usage (X) 53
|
||||
* 0x65, 0x13, // Unit (EnglishLinear: in) 55
|
||||
* 0x55, 0x0d, // Unit Exponent (-3) 57
|
||||
* 0x46, 0x10, 0x27, // Physical Maximum (10000) 59
|
||||
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 62
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 65
|
||||
* 0x09, 0x31, // Usage (Y) 67
|
||||
* 0x46, 0x6a, 0x18, // Physical Maximum (6250) 69
|
||||
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 72
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 75
|
||||
* 0xb4, // Pop 77
|
||||
* 0x09, 0x30, // Usage (X) 78
|
||||
* 0x45, 0x00, // Physical Maximum (0) 80
|
||||
* 0x26, 0xff, 0x3f, // Logical Maximum (16383) 82
|
||||
* 0x81, 0x42, // Input (Data,Var,Abs,Null) 85
|
||||
* 0x09, 0x3d, // Usage (Start) 87
|
||||
* 0x15, 0x81, // Logical Minimum (-127) 89 <- Change from -127 to -60
|
||||
* 0x25, 0x7f, // Logical Maximum (127) 91 <- Change from 127 to 60
|
||||
* 0x75, 0x08, // Report Size (8) 93
|
||||
* 0x95, 0x01, // Report Count (1) 95
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 97
|
||||
* 0x09, 0x3e, // Usage (Select) 99
|
||||
* 0x15, 0x81, // Logical Minimum (-127) 101 <- Change from -127 to -60
|
||||
* 0x25, 0x7f, // Logical Maximum (127) 103 <- Change from 127 to 60
|
||||
* 0x81, 0x02, // Input (Data,Var,Abs) 105
|
||||
* 0xc0, // End Collection 107
|
||||
* 0xc0, // End Collection 108
|
||||
*/
|
||||
|
||||
#define PEN_REPORT_DESCRIPTOR_LENGTH 109
|
||||
#define PAD_REPORT_DESCRIPTOR_LENGTH 102
|
||||
#define PAD_REPORT_LENGTH 8
|
||||
#define PAD_REPORT_ID 6
|
||||
#define PAD_NUM_BUTTONS 8
|
||||
|
||||
static const __u8 fixed_rdesc_pad[] = {
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
// Byte 0 in report is the report ID
|
||||
ReportId(PAD_REPORT_ID)
|
||||
ReportCount(1)
|
||||
ReportSize(8)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
// Byte 1 is the button state
|
||||
UsagePage_Button
|
||||
UsageMinimum_i8(0x01)
|
||||
UsageMaximum_i8(PAD_NUM_BUTTONS)
|
||||
LogicalMinimum_i8(0x0)
|
||||
LogicalMaximum_i8(0x1)
|
||||
ReportCount(PAD_NUM_BUTTONS)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
// Byte 2 in report - just exists so we get to be a tablet pad
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_BarrelSwitch // BTN_STYLUS
|
||||
ReportCount(1)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(7) // padding
|
||||
Input(Const)
|
||||
// Bytes 3/4 in report - just exists so we get to be a tablet pad
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Var|Abs)
|
||||
// Byte 5-7 are padding so we match the original report lengtth
|
||||
ReportCount(3)
|
||||
ReportSize(8)
|
||||
Input(Const)
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(xppen_deco01v3_rdesc_fixup, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, HID_MAX_DESCRIPTOR_SIZE /* size */);
|
||||
|
||||
const __u8 wrong_logical_range[] = {0x15, 0x81, 0x25, 0x7f};
|
||||
const __u8 correct_logical_range[] = {0x15, 0xc4, 0x25, 0x3c};
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
switch (hctx->size) {
|
||||
case PAD_REPORT_DESCRIPTOR_LENGTH:
|
||||
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
|
||||
return sizeof(fixed_rdesc_pad);
|
||||
case PEN_REPORT_DESCRIPTOR_LENGTH:
|
||||
if (__builtin_memcmp(&data[89], wrong_logical_range,
|
||||
sizeof(wrong_logical_range)) == 0)
|
||||
__builtin_memcpy(&data[89], correct_logical_range,
|
||||
sizeof(correct_logical_range));
|
||||
if (__builtin_memcmp(&data[101], wrong_logical_range,
|
||||
sizeof(wrong_logical_range)) == 0)
|
||||
__builtin_memcpy(&data[101], correct_logical_range,
|
||||
sizeof(correct_logical_range));
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(xppen_deco01v3_device_event, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
static const __u8 pad_buttons[] = { 0x05, 0x08, 0x00, 0x2c, 0x16, 0x1d, 0x57, 0x56 };
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PAD_REPORT_LENGTH /* size */);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
if (data[0] == PAD_REPORT_ID) {
|
||||
__u8 button_mask = 0;
|
||||
size_t d, b;
|
||||
|
||||
/* data[1] stores the status of BTN_2 in the 3rd bit*/
|
||||
if (data[1] & BIT(2))
|
||||
button_mask |= BIT(2);
|
||||
|
||||
/* The rest of the descriptor stores the buttons as in pad_buttons */
|
||||
for (d = 2; d < 8; d++) {
|
||||
for (b = 0; b < sizeof(pad_buttons); b++) {
|
||||
if (data[d] != 0 && data[d] == pad_buttons[b])
|
||||
button_mask |= BIT(b);
|
||||
}
|
||||
}
|
||||
|
||||
__u8 report[8] = {PAD_REPORT_ID, button_mask, 0x00};
|
||||
|
||||
__builtin_memcpy(data, report, sizeof(report));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(xppen_deco01v3) = {
|
||||
.hid_rdesc_fixup = (void *)xppen_deco01v3_rdesc_fixup,
|
||||
.hid_device_event = (void *)xppen_deco01v3_device_event,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
switch (ctx->rdesc_size) {
|
||||
case PAD_REPORT_DESCRIPTOR_LENGTH:
|
||||
case PEN_REPORT_DESCRIPTOR_LENGTH:
|
||||
ctx->retval = 0;
|
||||
break;
|
||||
default:
|
||||
ctx->retval = -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -0,0 +1,359 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include "hid_bpf.h"
|
||||
#include "hid_bpf_helpers.h"
|
||||
#include "hid_report_helpers.h"
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
#define VID_UGEE 0x28BD
|
||||
#define PID_DECO_02 0x0803
|
||||
|
||||
HID_BPF_CONFIG(
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_DECO_02),
|
||||
);
|
||||
|
||||
/*
|
||||
* Devices are:
|
||||
* - Pad input, including pen (This is the only one we are interested in)
|
||||
* - Pen input as mouse
|
||||
* - Vendor
|
||||
*
|
||||
* Descriptors on main device are:
|
||||
* - 7: Pen
|
||||
* - 6: Vendor settings? Unclear
|
||||
* - 3: Keyboard (This is what we want to modify)
|
||||
* - 5: Feature report
|
||||
*
|
||||
* This creates three event nodes:
|
||||
* - XP-PEN DECO 02 Stylus
|
||||
* - XP-PEN DECO 02
|
||||
* - XP-PEN DECO 02 Keyboard (Again, what we want to modify)
|
||||
*
|
||||
* # Report descriptor length: 188 bytes
|
||||
* # 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, 0x45, // Usage (Eraser) 16
|
||||
* # 0x09, 0x3c, // Usage (Invert) 18
|
||||
* # 0x09, 0x32, // Usage (In Range) 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
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 30
|
||||
* # 0x95, 0x03, // Report Count (3) 32
|
||||
* # 0x81, 0x03, // Input (Cnst,Var,Abs) 34
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 36
|
||||
* # 0x09, 0x30, // Usage (X) 38
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 40
|
||||
* # 0x26, 0x50, 0x57, // Logical Maximum (22352) 42
|
||||
* # 0x55, 0x0d, // Unit Exponent (-3) 45
|
||||
* # 0x65, 0x13, // Unit (EnglishLinear: in) 47
|
||||
* # 0x35, 0x00, // Physical Minimum (0) 49
|
||||
* # 0x46, 0x50, 0x57, // Physical Maximum (22352) 51
|
||||
* # 0x75, 0x10, // Report Size (16) 54
|
||||
* # 0x95, 0x01, // Report Count (1) 56
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 58
|
||||
* # 0x09, 0x31, // Usage (Y) 60
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 62
|
||||
* # 0x26, 0x92, 0x36, // Logical Maximum (13970) 64
|
||||
* # 0x55, 0x0d, // Unit Exponent (-3) 67
|
||||
* # 0x65, 0x13, // Unit (EnglishLinear: in) 69
|
||||
* # 0x35, 0x00, // Physical Minimum (0) 71
|
||||
* # 0x46, 0x92, 0x36, // Physical Maximum (13970) 73
|
||||
* # 0x75, 0x10, // Report Size (16) 76
|
||||
* # 0x95, 0x01, // Report Count (1) 78
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 80
|
||||
* # 0x05, 0x0d, // Usage Page (Digitizers) 82
|
||||
* # 0x09, 0x30, // Usage (Tip Pressure) 84
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 86
|
||||
* # 0x26, 0xff, 0x1f, // Logical Maximum (8191) 88
|
||||
* # 0x75, 0x10, // Report Size (16) 91
|
||||
* # 0x95, 0x01, // Report Count (1) 93
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 95
|
||||
* # 0xc0, // End Collection 97
|
||||
* # 0xc0, // End Collection 98
|
||||
* # 0x09, 0x0e, // Usage (Device Configuration) 99
|
||||
* # 0xa1, 0x01, // Collection (Application) 101
|
||||
* # 0x85, 0x05, // Report ID (5) 103
|
||||
* # 0x09, 0x23, // Usage (Device Settings) 105
|
||||
* # 0xa1, 0x02, // Collection (Logical) 107
|
||||
* # 0x09, 0x52, // Usage (Inputmode) 109
|
||||
* # 0x09, 0x53, // Usage (Device Index) 111
|
||||
* # 0x25, 0x0a, // Logical Maximum (10) 113
|
||||
* # 0x75, 0x08, // Report Size (8) 115
|
||||
* # 0x95, 0x02, // Report Count (2) 117
|
||||
* # 0xb1, 0x02, // Feature (Data,Var,Abs) 119
|
||||
* # 0xc0, // End Collection 121
|
||||
* # 0xc0, // End Collection 122
|
||||
* # 0x05, 0x0c, // Usage Page (Consumer Devices) 123
|
||||
* # 0x09, 0x36, // Usage (Function Buttons) 125
|
||||
* # 0xa1, 0x00, // Collection (Physical) 127
|
||||
* # 0x85, 0x06, // Report ID (6) 129
|
||||
* # 0x05, 0x09, // Usage Page (Button) 131
|
||||
* # 0x19, 0x01, // Usage Minimum (1) 133
|
||||
* # 0x29, 0x20, // Usage Maximum (32) 135
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 137
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 139
|
||||
* # 0x95, 0x20, // Report Count (32) 141
|
||||
* # 0x75, 0x01, // Report Size (1) 143
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 145
|
||||
* # 0xc0, // End Collection 147
|
||||
* # 0x05, 0x01, // Usage Page (Generic Desktop) 148
|
||||
* # 0x09, 0x06, // Usage (Keyboard) 150
|
||||
* # 0xa1, 0x01, // Collection (Application) 152
|
||||
* # 0x85, 0x03, // Report ID (3) 154
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard) 156
|
||||
* # 0x19, 0xe0, // Usage Minimum (224) 158
|
||||
* # 0x29, 0xe7, // Usage Maximum (231) 160
|
||||
* # 0x15, 0x00, // Logical Minimum (0) 162
|
||||
* # 0x25, 0x01, // Logical Maximum (1) 164
|
||||
* # 0x75, 0x01, // Report Size (1) 166
|
||||
* # 0x95, 0x08, // Report Count (8) 168
|
||||
* # 0x81, 0x02, // Input (Data,Var,Abs) 170
|
||||
* # 0x05, 0x07, // Usage Page (Keyboard) 172
|
||||
* # 0x19, 0x00, // Usage Minimum (0) 174
|
||||
* # 0x29, 0xff, // Usage Maximum (255) 176
|
||||
* # 0x26, 0xff, 0x00, // Logical Maximum (255) 178
|
||||
* # 0x75, 0x08, // Report Size (8) 181
|
||||
* # 0x95, 0x06, // Report Count (6) 183
|
||||
* # 0x81, 0x00, // Input (Data,Arr,Abs) 185
|
||||
* # 0xc0, // End Collection 187
|
||||
*
|
||||
* Key events; top to bottom:
|
||||
* Buttons released: 03 00 00 00 00 00 00 00
|
||||
* Button1: 03 00 05 00 00 00 00 00 -> 'b and B'
|
||||
* Button2: 03 00 2c 00 00 00 00 00 -> 'Spacebar'
|
||||
* Button3: 03 00 08 00 00 00 00 00 -> 'e and E'
|
||||
* Button4: 03 00 0c 00 00 00 00 00 -> 'i and I'
|
||||
* Button5: 03 05 1d 00 00 00 00 00 -> LeftControl + LeftAlt + 'z and Z'
|
||||
* Button6: 03 01 16 00 00 00 00 00 -> LeftControl + 's and S'
|
||||
*
|
||||
* Dial Events:
|
||||
* Clockwise: 03 01 2e 00 00 00 00 00 -> LeftControl + '= and +'
|
||||
* Anticlockwise: 03 01 2d 00 00 00 00 00 -> LeftControl + '- and (underscore)'
|
||||
*
|
||||
* NOTE: Input event descriptions begin at byte 2, and progressively build
|
||||
* towards byte 7 as each new key is pressed maintaining the press order.
|
||||
* For example:
|
||||
* BTN1 followed by BTN2 is 03 00 05 2c 00 00 00 00
|
||||
* BTN2 followed by BTN1 is 03 00 2c 05 00 00 00 00
|
||||
*
|
||||
* Releasing a button causes its byte to be freed, and the next item in the list
|
||||
* is pushed forwards. Dial events are released immediately after an event is
|
||||
* registered (i.e. after each "click"), so will continually appear pushed
|
||||
* backwards in the report.
|
||||
*
|
||||
* When a button with a modifier key is pressed, the button identifier stacks in
|
||||
* an abnormal way, where the highest modifier byte always supersedes others.
|
||||
* In these cases, the button with the higher modifier is always last.
|
||||
* For example:
|
||||
* BTN6 followed by BTN5 is 03 05 1d 16 00 00 00 00
|
||||
* BTN5 followed by BTN6 is 03 05 1d 16 00 00 00 00
|
||||
* BTN5 followed by BTN1 is 03 05 05 1d 00 00 00 00
|
||||
*
|
||||
* For three button presses in order, demonstrating strictly above rules:
|
||||
* BTN6, BTN1, BTN5 is 03 05 05 1d 16 00 00 00
|
||||
* BTN5, BTN1, BTN6 is 03 05 05 1d 16 00 00 00
|
||||
*
|
||||
* In short, when BTN5/6 are pressed, the order of operations is lost, as they
|
||||
* will always float to the end when pressed in combination with others.
|
||||
*
|
||||
* Fortunately, all states are recorded in the same way, with no overlaps.
|
||||
* Byte 1 can be used as a spare for the wheel, since this is for mod keys.
|
||||
*/
|
||||
|
||||
#define RDESC_SIZE_PAD 188
|
||||
#define REPORT_SIZE_PAD 8
|
||||
#define REPORT_ID_BUTTONS 3
|
||||
#define PAD_BUTTON_COUNT 6
|
||||
#define RDESC_KEYBOARD_OFFSET 148
|
||||
|
||||
static const __u8 fixed_rdesc_pad[] = {
|
||||
/* Copy of pen descriptor to avoid losing functionality */
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_Pen
|
||||
CollectionApplication(
|
||||
ReportId(7)
|
||||
Usage_Dig_Stylus
|
||||
CollectionPhysical(
|
||||
Usage_Dig_TipSwitch
|
||||
Usage_Dig_BarrelSwitch
|
||||
Usage_Dig_Eraser
|
||||
Usage_Dig_Invert
|
||||
Usage_Dig_InRange
|
||||
LogicalMinimum_i8(0)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportSize(1)
|
||||
ReportCount(5)
|
||||
Input(Var|Abs)
|
||||
ReportCount(3)
|
||||
Input(Const) /* Input (Const, Var, Abs) */
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(22352)
|
||||
UnitExponent(-3)
|
||||
Unit(in) /* (EnglishLinear: in) */
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(22352)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
Usage_GD_Y
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(13970)
|
||||
UnitExponent(-3)
|
||||
Unit(in) /* (EnglishLinear: in) */
|
||||
PhysicalMinimum_i16(0)
|
||||
PhysicalMaximum_i16(13970)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TipPressure
|
||||
LogicalMinimum_i16(0)
|
||||
LogicalMaximum_i16(8191)
|
||||
ReportSize(16)
|
||||
ReportCount(1)
|
||||
Input(Var|Abs)
|
||||
)
|
||||
)
|
||||
|
||||
/* FIXES BEGIN */
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Keypad
|
||||
CollectionApplication(
|
||||
ReportId(REPORT_ID_BUTTONS) /* Retain original ID on byte 0 */
|
||||
ReportCount(1)
|
||||
ReportSize(REPORT_SIZE_PAD)
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_TabletFunctionKeys
|
||||
CollectionPhysical(
|
||||
/* Byte 1: Dial state */
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_Dial
|
||||
LogicalMinimum_i8(-1)
|
||||
LogicalMaximum_i8(1)
|
||||
ReportCount(1)
|
||||
ReportSize(REPORT_SIZE_PAD)
|
||||
Input(Var|Rel)
|
||||
/* Byte 2: Button state */
|
||||
UsagePage_Button
|
||||
ReportSize(1)
|
||||
ReportCount(PAD_BUTTON_COUNT)
|
||||
UsageMinimum_i8(0x01)
|
||||
UsageMaximum_i8(PAD_BUTTON_COUNT) /* Number of buttons */
|
||||
LogicalMinimum_i8(0x0)
|
||||
LogicalMaximum_i8(0x1)
|
||||
Input(Var|Abs)
|
||||
/* Byte 3: Exists to be tablet pad */
|
||||
UsagePage_Digitizers
|
||||
Usage_Dig_BarrelSwitch
|
||||
ReportCount(1)
|
||||
ReportSize(1)
|
||||
Input(Var|Abs)
|
||||
ReportCount(7) /* Padding, to fill full report space */
|
||||
Input(Const)
|
||||
/* Byte 4/5: Exists to be a tablet pad */
|
||||
UsagePage_GenericDesktop
|
||||
Usage_GD_X
|
||||
Usage_GD_Y
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Var|Abs)
|
||||
/* Bytes 6/7: Padding, to match original length */
|
||||
ReportCount(2)
|
||||
ReportSize(8)
|
||||
Input(Const)
|
||||
)
|
||||
FixedSizeVendorReport(RDESC_SIZE_PAD)
|
||||
)
|
||||
};
|
||||
|
||||
SEC(HID_BPF_RDESC_FIXUP)
|
||||
int BPF_PROG(xppen_deco02_rdesc_fixup, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM Check */
|
||||
|
||||
if (hctx->size == RDESC_SIZE_PAD) {
|
||||
__builtin_memcpy(data, fixed_rdesc_pad, sizeof(fixed_rdesc_pad));
|
||||
return sizeof(fixed_rdesc_pad);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC(HID_BPF_DEVICE_EVENT)
|
||||
int BPF_PROG(xppen_deco02_device_event, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0, REPORT_SIZE_PAD);
|
||||
|
||||
if (!data || data[0] != REPORT_ID_BUTTONS)
|
||||
return 0; /* EPERM or wrong report */
|
||||
|
||||
__u8 dial_code = 0;
|
||||
__u8 button_mask = 0;
|
||||
size_t d;
|
||||
|
||||
/* Start from 2; 0 is report ID, 1 is modifier keys, replaced by dial */
|
||||
for (d = 2; d < 8; d++) {
|
||||
switch (data[d]) {
|
||||
case 0x2e:
|
||||
dial_code = 1;
|
||||
break;
|
||||
case 0x2d:
|
||||
dial_code = -1;
|
||||
break;
|
||||
/* below are buttons, top to bottom */
|
||||
case 0x05:
|
||||
button_mask |= BIT(0);
|
||||
break;
|
||||
case 0x2c:
|
||||
button_mask |= BIT(1);
|
||||
break;
|
||||
case 0x08:
|
||||
button_mask |= BIT(2);
|
||||
break;
|
||||
case 0x0c:
|
||||
button_mask |= BIT(3);
|
||||
break;
|
||||
case 0x1d:
|
||||
button_mask |= BIT(4);
|
||||
break;
|
||||
case 0x16:
|
||||
button_mask |= BIT(05);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__u8 report[8] = { REPORT_ID_BUTTONS, dial_code, button_mask, 0x00 };
|
||||
|
||||
__builtin_memcpy(data, report, sizeof(report));
|
||||
return 0;
|
||||
}
|
||||
|
||||
HID_BPF_OPS(xppen_deco02) = {
|
||||
.hid_rdesc_fixup = (void *)xppen_deco02_rdesc_fixup,
|
||||
.hid_device_event = (void *)xppen_deco02_device_event,
|
||||
};
|
||||
|
||||
SEC("syscall")
|
||||
int probe(struct hid_bpf_probe_args *ctx)
|
||||
{
|
||||
ctx->retval = ctx->rdesc_size != RDESC_SIZE_PAD ? -EINVAL : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
@@ -173,7 +173,7 @@ int BPF_PROG(hid_device_event_xppen_deco_mini_4, struct hid_bpf_ctx *hctx)
|
||||
{
|
||||
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 8 /* size */);
|
||||
__u8 button_mask = 0;
|
||||
int d, b;
|
||||
size_t d, b;
|
||||
|
||||
if (!data)
|
||||
return 0; /* EPERM check */
|
||||
|
||||
@@ -116,15 +116,14 @@ static int hid_bpf_async_find_empty_key(void)
|
||||
if (!elem)
|
||||
return -ENOMEM; /* should never happen */
|
||||
|
||||
bpf_spin_lock(&elem->lock);
|
||||
{
|
||||
guard(bpf_spin)(&elem->lock);
|
||||
|
||||
if (elem->state == HID_BPF_ASYNC_STATE_UNSET) {
|
||||
elem->state = HID_BPF_ASYNC_STATE_INITIALIZING;
|
||||
bpf_spin_unlock(&elem->lock);
|
||||
return i;
|
||||
}
|
||||
|
||||
bpf_spin_unlock(&elem->lock);
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
@@ -175,18 +174,19 @@ static int hid_bpf_async_delayed_call(struct hid_bpf_ctx *hctx, u64 milliseconds
|
||||
if (!elem)
|
||||
return -EINVAL;
|
||||
|
||||
bpf_spin_lock(&elem->lock);
|
||||
{
|
||||
guard(bpf_spin)(&elem->lock);
|
||||
|
||||
/* The wq must be:
|
||||
* - HID_BPF_ASYNC_STATE_INITIALIZED -> it's been initialized and ready to be called
|
||||
* - HID_BPF_ASYNC_STATE_RUNNING -> possible re-entry from the wq itself
|
||||
*/
|
||||
if (elem->state != HID_BPF_ASYNC_STATE_INITIALIZED &&
|
||||
elem->state != HID_BPF_ASYNC_STATE_RUNNING) {
|
||||
bpf_spin_unlock(&elem->lock);
|
||||
elem->state != HID_BPF_ASYNC_STATE_RUNNING)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
elem->state = HID_BPF_ASYNC_STATE_STARTING;
|
||||
bpf_spin_unlock(&elem->lock);
|
||||
}
|
||||
|
||||
elem->hid = hctx->hid->id;
|
||||
|
||||
|
||||
@@ -7,7 +7,18 @@
|
||||
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <linux/errno.h>
|
||||
#include "hid_report_descriptor_helpers.h"
|
||||
|
||||
/* Compiler attributes */
|
||||
#ifndef __packed
|
||||
#define __packed __attribute__((packed))
|
||||
#endif
|
||||
|
||||
#ifndef __maybe_unused
|
||||
#define __maybe_unused __attribute__((__unused__))
|
||||
#endif
|
||||
|
||||
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
|
||||
unsigned int offset,
|
||||
@@ -42,6 +53,86 @@ extern int bpf_wq_set_callback_impl(struct bpf_wq *wq,
|
||||
#define HID_MAX_DESCRIPTOR_SIZE 4096
|
||||
#define HID_IGNORE_EVENT -1
|
||||
|
||||
/**
|
||||
* Use: _cleanup_(somefunction) struct foo *bar;
|
||||
*/
|
||||
#define _cleanup_(_x) __attribute__((cleanup(_x)))
|
||||
|
||||
/**
|
||||
* Use: _release_(foo) *bar;
|
||||
*
|
||||
* This requires foo_releasep() to be present, use DEFINE_RELEASE_CLEANUP_FUNC.
|
||||
*/
|
||||
#define _release_(_type) struct _type __attribute__((cleanup(_type##_releasep)))
|
||||
|
||||
/**
|
||||
* Define a cleanup function for the struct type foo with a matching
|
||||
* foo_release(). Use:
|
||||
* DEFINE_RELEASE_CLEANUP_FUNC(foo)
|
||||
* _unref_(foo) struct foo *bar;
|
||||
*/
|
||||
#define DEFINE_RELEASE_CLEANUP_FUNC(_type) \
|
||||
static inline void _type##_releasep(struct _type **_p) { \
|
||||
if (*_p) \
|
||||
_type##_release(*_p); \
|
||||
} \
|
||||
struct __useless_struct_to_allow_trailing_semicolon__
|
||||
|
||||
/* for being able to have a cleanup function */
|
||||
#define hid_bpf_ctx_release hid_bpf_release_context
|
||||
DEFINE_RELEASE_CLEANUP_FUNC(hid_bpf_ctx);
|
||||
|
||||
/*
|
||||
* Kernel-style guard macros adapted for BPF
|
||||
* Based on include/linux/cleanup.h from the Linux kernel
|
||||
*
|
||||
* These provide automatic lock/unlock using __attribute__((cleanup))
|
||||
* similar to how _release_() works for contexts.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DEFINE_GUARD(name, type, lock, unlock):
|
||||
* Define a guard for automatic lock/unlock using the same pattern as _release_()
|
||||
* @name: identifier for the guard (e.g., bpf_spin)
|
||||
* @type: lock variable type (e.g., struct bpf_spin_lock)
|
||||
* @lock: lock function name (e.g., bpf_spin_lock)
|
||||
* @unlock: unlock function name (e.g., bpf_spin_unlock)
|
||||
*
|
||||
* guard(name):
|
||||
* Declare and lock in one statement - lock held until end of scope
|
||||
*
|
||||
* Example:
|
||||
* DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock)
|
||||
*
|
||||
* void foo(struct bpf_spin_lock *lock) {
|
||||
* guard(bpf_spin)(lock);
|
||||
* // lock held until end of scope
|
||||
* }
|
||||
*/
|
||||
|
||||
/* Guard helper struct - stores lock pointer for cleanup */
|
||||
#define DEFINE_GUARD(_name, _type, _lock, _unlock) \
|
||||
struct _name##_guard { \
|
||||
_type *lock; \
|
||||
}; \
|
||||
static inline void _name##_guard_cleanup(struct _name##_guard *g) { \
|
||||
if (g && g->lock) \
|
||||
_unlock(g->lock); \
|
||||
} \
|
||||
static inline struct _name##_guard _name##_guard_init(_type *l) { \
|
||||
if (l) \
|
||||
_lock(l); \
|
||||
return (struct _name##_guard){.lock = l}; \
|
||||
} \
|
||||
struct __useless_struct_to_allow_trailing_semicolon__
|
||||
|
||||
#define guard(_name) \
|
||||
struct _name##_guard COMBINE(guard, __LINE__) __attribute__((cleanup(_name##_guard_cleanup))) = \
|
||||
_name##_guard_init
|
||||
|
||||
/* Define BPF spinlock guard */
|
||||
DEFINE_GUARD(bpf_spin, struct bpf_spin_lock, bpf_spin_lock, bpf_spin_unlock);
|
||||
|
||||
/* extracted from <linux/input.h> */
|
||||
#define BUS_ANY 0x00
|
||||
#define BUS_PCI 0x01
|
||||
@@ -185,4 +276,234 @@ extern int bpf_wq_set_callback_impl(struct bpf_wq *wq,
|
||||
_EXPAND(_ARG, __VA_ARGS__) \
|
||||
} _device_ids SEC(".hid_bpf_config")
|
||||
|
||||
|
||||
/* Equivalency macros for bpf_htons and friends which are
|
||||
* Big Endian only - HID needs little endian so these are the
|
||||
* corresponding macros for that. See bpf/bpf_endian.h
|
||||
*/
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __hid_bpf_le16_to_cpu(x) (x)
|
||||
# define __hid_bpf_le32_to_cpu(x) (x)
|
||||
# define __hid_bpf_le64_to_cpu(x) (x)
|
||||
# define __hid_bpf_cpu_to_le16(x) (x)
|
||||
# define __hid_bpf_cpu_to_le32(x) (x)
|
||||
# define __hid_bpf_cpu_to_le64(x) (x)
|
||||
# define __hid_bpf_constant_le16_to_cpu(x) (x)
|
||||
# define __hid_bpf_constant_le32_to_cpu(x) (x)
|
||||
# define __hid_bpf_constant_le64_to_cpu(x) (x)
|
||||
# define __hid_bpf_constant_cpu_to_le16(x) (x)
|
||||
# define __hid_bpf_constant_cpu_to_le32(x) (x)
|
||||
# define __hid_bpf_constant_cpu_to_le64(x) (x)
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __hid_bpf_le16_to_cpu(x) __builtin_bswap16(x)
|
||||
# define __hid_bpf_le32_to_cpu(x) __builtin_bswap32(x)
|
||||
# define __hid_bpf_le64_to_cpu(x) __builtin_bswap64(x)
|
||||
# define __hid_bpf_cpu_to_le16(x) __builtin_bswap16(x)
|
||||
# define __hid_bpf_cpu_to_le32(x) __builtin_bswap32(x)
|
||||
# define __hid_bpf_cpu_to_le64(x) __builtin_bswap64(x)
|
||||
# define __hid_bpf_constant_le16_to_cpu(x) __bpf_swab16(x)
|
||||
# define __hid_bpf_constant_le32_to_cpu(x) __bpf_swab32(x)
|
||||
# define __hid_bpf_constant_le64_to_cpu(x) __bpf_swab64(x)
|
||||
# define __hid_bpf_constant_cpu_to_le16(x) __bpf_swab16(x)
|
||||
# define __hid_bpf_constant_cpu_to_le32(x) __bpf_swab32(x)
|
||||
# define __hid_bpf_constant_cpu_to_le64(x) __bpf_swab64(x)
|
||||
#else
|
||||
# error "Invalid __BYTE_ORDER__"
|
||||
#endif
|
||||
|
||||
#define hid_bpf_le16_to_cpu(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_le16_to_cpu(x) : __hid_bpf_le16_to_cpu(x))
|
||||
|
||||
#define hid_bpf_le32_to_cpu(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_le32_to_cpu(x) : __hid_bpf_le32_to_cpu(x))
|
||||
|
||||
#define hid_bpf_le64_to_cpu(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_le64_to_cpu(x) : __hid_bpf_le64_to_cpu(x))
|
||||
|
||||
#define hid_bpf_cpu_to_le16(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_cpu_to_le16(x) : __hid_bpf_cpu_to_le16(x))
|
||||
|
||||
#define hid_bpf_cpu_to_le32(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_cpu_to_le32(x) : __hid_bpf_cpu_to_le32(x))
|
||||
|
||||
#define hid_bpf_cpu_to_le64(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__hid_bpf_constant_cpu_to_le64(x) : __hid_bpf_cpu_to_le64(x))
|
||||
|
||||
#define hid_bpf_be16_to_cpu(x) bpf_ntohs(x)
|
||||
#define hid_bpf_be32_to_cpu(x) bpf_ntohl(x)
|
||||
#define hid_bpf_be64_to_cpu(x) bpf_be64_to_cpu(x)
|
||||
#define hid_bpf_cpu_to_be16(x) bpf_htons(x)
|
||||
#define hid_bpf_cpu_to_be32(x) bpf_htonl(x)
|
||||
#define hid_bpf_cpu_to_be64(x) bpf_cpu_to_be64(x)
|
||||
|
||||
/*
|
||||
* The following macros are helpers for exporting udev properties:
|
||||
*
|
||||
* EXPORT_UDEV_PROP(name, len) generates:
|
||||
* - a map with a single element UDEV_PROP_##name, of size len
|
||||
* - a const global declaration of that len: SIZEOF_##name
|
||||
*
|
||||
* udev_prop_ptr(name) retrieves the data pointer behind the map.
|
||||
*
|
||||
* UDEV_PROP_SPRINTF(name, fmt, ...) writes data into the udev property.
|
||||
*
|
||||
* Can be used as such:
|
||||
* EXPORT_UDEV_PROP(HID_FOO, 32);
|
||||
*
|
||||
* SEC("syscall")
|
||||
* int probe(struct hid_bpf_probe_args *ctx)
|
||||
* {
|
||||
* const char *foo = "foo";
|
||||
* UDEV_PROP_SPRINTF(HID_FOO, "%s", foo);
|
||||
*
|
||||
* return 0;
|
||||
* }
|
||||
*/
|
||||
#define EXPORT_UDEV_PROP(name, len) \
|
||||
const __u32 SIZEOF_##name = len; \
|
||||
struct COMBINE(udev_prop, __LINE__) { \
|
||||
__uint(type, BPF_MAP_TYPE_ARRAY); \
|
||||
__uint(max_entries, 1); \
|
||||
__type(key, __u32); \
|
||||
__type(value, __u8[len]); \
|
||||
} UDEV_PROP_##name SEC(".maps");
|
||||
|
||||
#define udev_prop_ptr(name) \
|
||||
bpf_map_lookup_elem(&UDEV_PROP_##name, &(__u32){0})
|
||||
|
||||
#define UDEV_PROP_SPRINTF(name, fmt, ...) \
|
||||
BPF_SNPRINTF(udev_prop_ptr(name), SIZEOF_##name, fmt, ##__VA_ARGS__)
|
||||
|
||||
static inline __maybe_unused __u16 field_start_byte(struct hid_rdesc_field *field)
|
||||
{
|
||||
return field->bits_start / 8;
|
||||
}
|
||||
|
||||
static inline __maybe_unused __u16 field_end_byte(struct hid_rdesc_field *field)
|
||||
{
|
||||
if (!field->bits_end)
|
||||
return 0;
|
||||
|
||||
return (__u16)(field->bits_end - 1) / 8;
|
||||
}
|
||||
|
||||
static __maybe_unused __u32 extract_bits(__u8 *buffer, const size_t size, struct hid_rdesc_field *field)
|
||||
{
|
||||
__s32 nbits = field->bits_end - field->bits_start;
|
||||
__u32 start = field_start_byte(field);
|
||||
__u32 end = field_end_byte(field);
|
||||
__u8 base_shift = field->bits_start % 8;
|
||||
|
||||
if (nbits <= 0 || nbits > 32 || start >= size || end >= size)
|
||||
return 0;
|
||||
|
||||
/* Fast path for byte-aligned standard-sized reads */
|
||||
if (base_shift == 0) {
|
||||
/* 8-bit aligned read */
|
||||
if (nbits == 8 && start < size)
|
||||
return buffer[start];
|
||||
|
||||
/* 16-bit aligned read - use separate variables for verifier */
|
||||
if (nbits == 16) {
|
||||
__u32 off0 = start;
|
||||
__u32 off1 = start + 1;
|
||||
|
||||
if (off0 < size && off1 < size) {
|
||||
return buffer[off0] |
|
||||
((__u32)buffer[off1] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
/* 32-bit aligned read - use separate variables for verifier */
|
||||
if (nbits == 32) {
|
||||
__u32 off0 = start;
|
||||
__u32 off1 = start + 1;
|
||||
__u32 off2 = start + 2;
|
||||
__u32 off3 = start + 3;
|
||||
|
||||
if (off0 < size && off1 < size &&
|
||||
off2 < size && off3 < size) {
|
||||
return buffer[off0] |
|
||||
((__u32)buffer[off1] << 8) |
|
||||
((__u32)buffer[off2] << 16) |
|
||||
((__u32)buffer[off3] << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* General case: bit manipulation for unaligned or non-standard sizes */
|
||||
int mask = 0xffffffff >> (32 - nbits);
|
||||
__u64 value = 0;
|
||||
__u32 i;
|
||||
|
||||
bpf_for (i, start, end + 1) {
|
||||
value |= (__u64)buffer[i] << ((i - start) * 8);
|
||||
}
|
||||
|
||||
return (value >> base_shift) & mask;
|
||||
}
|
||||
|
||||
#define EXTRACT_BITS(buffer, field) extract_bits(buffer, sizeof(buffer), field)
|
||||
|
||||
/* Base macro for iterating over HID arrays with bounds checking.
|
||||
* Follows the bpf_for pattern from libbpf.
|
||||
*/
|
||||
#define __hid_bpf_for_each_array(array, num_elements, max_elements, var) \
|
||||
for ( \
|
||||
/* initialize and define destructor */ \
|
||||
struct bpf_iter_num ___it __attribute__((aligned(8), \
|
||||
cleanup(bpf_iter_num_destroy))), \
|
||||
/* ___p pointer is necessary to call bpf_iter_num_new() *once* */ \
|
||||
*___p __attribute__((unused)) = ( \
|
||||
/* always initialize iterator; if bounds fail, iterate 0 times */ \
|
||||
bpf_iter_num_new(&___it, 0, \
|
||||
(num_elements) > (max_elements) ? \
|
||||
0 : (num_elements)), \
|
||||
/* workaround for Clang bug */ \
|
||||
(void)bpf_iter_num_destroy, (void *)0); \
|
||||
({ \
|
||||
/* iteration step */ \
|
||||
int *___t = bpf_iter_num_next(&___it); \
|
||||
int ___i; \
|
||||
/* termination and bounds check, assign var */ \
|
||||
(___t && (___i = *___t, ___i >= 0 && ___i < (num_elements)) && \
|
||||
((num_elements) <= (max_elements)) && \
|
||||
(var = &(array)[___i], 1)); \
|
||||
}); \
|
||||
)
|
||||
|
||||
/* Iterate over input reports in a descriptor */
|
||||
#define hid_bpf_for_each_input_report(descriptor, report_var) \
|
||||
__hid_bpf_for_each_array((descriptor)->input_reports, \
|
||||
(descriptor)->num_input_reports, \
|
||||
HID_MAX_REPORTS, report_var)
|
||||
|
||||
/* Iterate over feature reports in a descriptor */
|
||||
#define hid_bpf_for_each_feature_report(descriptor, report_var) \
|
||||
__hid_bpf_for_each_array((descriptor)->feature_reports, \
|
||||
(descriptor)->num_feature_reports, \
|
||||
HID_MAX_REPORTS, report_var)
|
||||
|
||||
/* Iterate over output reports in a descriptor */
|
||||
#define hid_bpf_for_each_output_report(descriptor, report_var) \
|
||||
__hid_bpf_for_each_array((descriptor)->output_reports, \
|
||||
(descriptor)->num_output_reports, \
|
||||
HID_MAX_REPORTS, report_var)
|
||||
|
||||
/* Iterate over fields in a report */
|
||||
#define hid_bpf_for_each_field(report, field_var) \
|
||||
__hid_bpf_for_each_array((report)->fields, (report)->num_fields, \
|
||||
HID_MAX_FIELDS, field_var)
|
||||
|
||||
/* Iterate over collections in a field */
|
||||
#define hid_bpf_for_each_collection(field, collection_var) \
|
||||
__hid_bpf_for_each_array((field)->collections, (field)->num_collections, \
|
||||
HID_MAX_COLLECTIONS, collection_var)
|
||||
|
||||
#endif /* __HID_BPF_HELPERS_H */
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/* Copyright (c) 2022 Benjamin Tissoires
|
||||
*/
|
||||
|
||||
#ifndef __HID_REPORT_DESCRIPTOR_HELPERS_H
|
||||
#define __HID_REPORT_DESCRIPTOR_HELPERS_H
|
||||
|
||||
#include "vmlinux.h"
|
||||
|
||||
/* Compiler attributes */
|
||||
#ifndef __packed
|
||||
#define __packed __attribute__((packed))
|
||||
#endif
|
||||
|
||||
#ifndef __maybe_unused
|
||||
#define __maybe_unused __attribute__((__unused__))
|
||||
#endif
|
||||
|
||||
/* Report Descriptor Structures */
|
||||
#define HID_MAX_COLLECTIONS 32
|
||||
#define HID_MAX_FIELDS 64
|
||||
#define HID_MAX_REPORTS 16
|
||||
|
||||
enum hid_rdesc_field_type {
|
||||
HID_FIELD_VARIABLE = 0,
|
||||
HID_FIELD_ARRAY = 1,
|
||||
HID_FIELD_CONSTANT = 2,
|
||||
};
|
||||
|
||||
struct hid_rdesc_collection {
|
||||
__u16 usage_page;
|
||||
__u16 usage_id;
|
||||
__u8 collection_type;
|
||||
} __packed;
|
||||
|
||||
struct hid_rdesc_field {
|
||||
__u8 field_type; /* enum hid_rdesc_field_type */
|
||||
__u8 num_collections;
|
||||
__u16 bits_start;
|
||||
__u16 bits_end;
|
||||
__u16 usage_page;
|
||||
union {
|
||||
__u16 usage_id; /* For Variable fields */
|
||||
struct __packed { /* For Array fields */
|
||||
__u16 usage_minimum;
|
||||
__u16 usage_maximum;
|
||||
};
|
||||
};
|
||||
__s32 logical_minimum;
|
||||
__s32 logical_maximum;
|
||||
struct {
|
||||
__u8 is_relative:1; /* Data is relative to previous value */
|
||||
__u8 wraps:1; /* Value wraps around (e.g., rotary encoder) */
|
||||
__u8 is_nonlinear:1; /* Non-linear relationship between logical/physical */
|
||||
__u8 has_no_preferred_state:1; /* No rest position (e.g., free-floating joystick) */
|
||||
__u8 has_null_state:1; /* Can report null/no-data values */
|
||||
__u8 is_volatile:1; /* Volatile (Output/Feature) - NOT POPULATED, always 0 */
|
||||
__u8 is_buffered_bytes:1; /* Fixed-size byte stream vs bitfield */
|
||||
__u8 reserved:1; /* Reserved for future use */
|
||||
} flags;
|
||||
struct hid_rdesc_collection collections[HID_MAX_COLLECTIONS];
|
||||
} __packed;
|
||||
|
||||
struct hid_rdesc_report {
|
||||
__u8 report_id; /* 0 means no report ID */
|
||||
__u16 size_in_bits;
|
||||
__u8 num_fields;
|
||||
struct hid_rdesc_field fields[HID_MAX_FIELDS];
|
||||
} __packed;
|
||||
|
||||
struct hid_rdesc_descriptor {
|
||||
__u8 num_input_reports;
|
||||
__u8 num_output_reports;
|
||||
__u8 num_feature_reports;
|
||||
struct hid_rdesc_report input_reports[HID_MAX_REPORTS];
|
||||
struct hid_rdesc_report output_reports[HID_MAX_REPORTS];
|
||||
struct hid_rdesc_report feature_reports[HID_MAX_REPORTS];
|
||||
} __packed;
|
||||
|
||||
#endif /* __HID_REPORT_DESCRIPTOR_HELPERS_H */
|
||||
@@ -143,8 +143,11 @@
|
||||
* report with Report ID 0xac of the given size in bytes.
|
||||
* The size is inclusive of the 1 byte Report ID prefix.
|
||||
*
|
||||
* HID-BPF requires that at least one report has
|
||||
* the same size as the original report from the device.
|
||||
* The kernel discards any HID reports that are larger
|
||||
* than the largest report in a HID report descriptor.
|
||||
* Thus at least one report must have (at least)
|
||||
* the same size as the largest original report from
|
||||
* the device.
|
||||
* The easy way to ensure that is to add this
|
||||
* macro as the last element of your CollectionApplication
|
||||
* other reports can be of any size less than this.
|
||||
@@ -295,6 +298,7 @@
|
||||
#define Usage_GD_SystemSpeakerMute Usage_i8(0xa7)
|
||||
#define Usage_GD_SystemHibernate Usage_i8(0xa8)
|
||||
#define Usage_GD_SystemMicrophoneMute Usage_i8(0xa9)
|
||||
#define Usage_GD_SystemAccessibilityBinding Usage_i8(0xaa)
|
||||
#define Usage_GD_SystemDisplayInvert Usage_i8(0xb0)
|
||||
#define Usage_GD_SystemDisplayInternal Usage_i8(0xb1)
|
||||
#define Usage_GD_SystemDisplayExternal Usage_i8(0xb2)
|
||||
@@ -2669,7 +2673,7 @@
|
||||
#define Usage_BS_iDeviceName Usage_i8(0x88)
|
||||
#define Usage_BS_iDeviceChemistry Usage_i8(0x89)
|
||||
#define Usage_BS_ManufacturerData Usage_i8(0x8a)
|
||||
#define Usage_BS_Rechargable Usage_i8(0x8b)
|
||||
#define Usage_BS_Rechargeable Usage_i8(0x8b)
|
||||
#define Usage_BS_WarningCapacityLimit Usage_i8(0x8c)
|
||||
#define Usage_BS_CapacityGranularity1 Usage_i8(0x8d)
|
||||
#define Usage_BS_CapacityGranularity2 Usage_i8(0x8e)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -437,6 +437,9 @@ static int alps_raw_event(struct hid_device *hdev,
|
||||
int ret = 0;
|
||||
struct alps_dev *hdata = hid_get_drvdata(hdev);
|
||||
|
||||
if (!(hdev->claimed & HID_CLAIMED_INPUT) || !hdata->input)
|
||||
return 0;
|
||||
|
||||
switch (hdev->product) {
|
||||
case HID_PRODUCT_ID_T4_BTNLESS:
|
||||
ret = t4_raw_event(hdata, data, size);
|
||||
@@ -840,10 +843,8 @@ static struct hid_driver alps_driver = {
|
||||
.raw_event = alps_raw_event,
|
||||
.input_mapping = alps_input_mapping,
|
||||
.input_configured = alps_input_configured,
|
||||
#ifdef CONFIG_PM
|
||||
.resume = alps_post_resume,
|
||||
.reset_resume = alps_post_reset,
|
||||
#endif
|
||||
.resume = pm_ptr(alps_post_resume),
|
||||
.reset_resume = pm_ptr(alps_post_reset),
|
||||
};
|
||||
|
||||
module_hid_driver(alps_driver);
|
||||
|
||||
+15
-8
@@ -354,7 +354,9 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
|
||||
};
|
||||
|
||||
static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
|
||||
{ "SONiX KN85 Keyboard" },
|
||||
{ "SONiX USB DEVICE" },
|
||||
{ "SONiX AK870 PRO" },
|
||||
{ "Keychron" },
|
||||
{ "AONE" },
|
||||
{ "GANSS" },
|
||||
@@ -363,6 +365,9 @@ static const struct apple_non_apple_keyboard non_apple_keyboards[] = {
|
||||
{ "A3R" },
|
||||
{ "hfd.cn" },
|
||||
{ "WKB603" },
|
||||
{ "TH87" }, /* EPOMAKER TH87 BT mode */
|
||||
{ "HFD Epomaker TH87" }, /* EPOMAKER TH87 USB mode */
|
||||
{ "2.4G Wireless Receiver" }, /* EPOMAKER TH87 dongle */
|
||||
};
|
||||
|
||||
static bool apple_is_non_apple_keyboard(struct hid_device *hdev)
|
||||
@@ -618,17 +623,19 @@ static int apple_fetch_battery(struct hid_device *hdev)
|
||||
struct apple_sc *asc = hid_get_drvdata(hdev);
|
||||
struct hid_report_enum *report_enum;
|
||||
struct hid_report *report;
|
||||
struct hid_battery *bat;
|
||||
|
||||
if (!(asc->quirks & APPLE_RDESC_BATTERY) || !hdev->battery)
|
||||
bat = hid_get_battery(hdev);
|
||||
if (!(asc->quirks & APPLE_RDESC_BATTERY) || !bat)
|
||||
return -1;
|
||||
|
||||
report_enum = &hdev->report_enum[hdev->battery_report_type];
|
||||
report = report_enum->report_id_hash[hdev->battery_report_id];
|
||||
report_enum = &hdev->report_enum[bat->report_type];
|
||||
report = report_enum->report_id_hash[bat->report_id];
|
||||
|
||||
if (!report || report->maxfield < 1)
|
||||
return -1;
|
||||
|
||||
if (hdev->battery_capacity == hdev->battery_max)
|
||||
if (bat->capacity == bat->max)
|
||||
return -1;
|
||||
|
||||
hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
|
||||
@@ -684,9 +691,7 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
hid_info(hdev,
|
||||
"fixing up Magic Keyboard battery report descriptor\n");
|
||||
*rsize = *rsize - 1;
|
||||
rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
|
||||
if (!rdesc)
|
||||
return NULL;
|
||||
rdesc = rdesc + 1;
|
||||
|
||||
rdesc[0] = 0x05;
|
||||
rdesc[1] = 0x01;
|
||||
@@ -792,7 +797,7 @@ static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate)
|
||||
int ret = 0;
|
||||
struct apple_backlight_set_report *rep;
|
||||
|
||||
rep = kmalloc(sizeof(*rep), GFP_KERNEL);
|
||||
rep = kmalloc_obj(*rep);
|
||||
if (rep == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -855,6 +860,7 @@ static int apple_backlight_init(struct hid_device *hdev)
|
||||
asc->backlight->cdev.name = "apple::kbd_backlight";
|
||||
asc->backlight->cdev.max_brightness = rep->backlight_on_max;
|
||||
asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set;
|
||||
asc->backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = apple_backlight_set(hdev, 0, 0);
|
||||
if (ret < 0) {
|
||||
@@ -923,6 +929,7 @@ static int apple_magic_backlight_init(struct hid_device *hdev)
|
||||
backlight->cdev.name = ":white:" LED_FUNCTION_KBD_BACKLIGHT;
|
||||
backlight->cdev.max_brightness = backlight->brightness->field[0]->logical_maximum;
|
||||
backlight->cdev.brightness_set_blocking = apple_magic_backlight_led_set;
|
||||
backlight->cdev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
apple_magic_backlight_set(backlight, 0, 0);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
@@ -62,7 +62,8 @@ struct appletb_kbd {
|
||||
struct input_handle kbd_handle;
|
||||
struct input_handle tpd_handle;
|
||||
struct backlight_device *backlight_dev;
|
||||
struct timer_list inactivity_timer;
|
||||
struct delayed_work inactivity_work;
|
||||
struct work_struct restore_brightness_work;
|
||||
bool has_dimmed;
|
||||
bool has_turned_off;
|
||||
u8 saved_mode;
|
||||
@@ -164,16 +165,18 @@ static int appletb_tb_key_to_slot(unsigned int code)
|
||||
}
|
||||
}
|
||||
|
||||
static void appletb_inactivity_timer(struct timer_list *t)
|
||||
static void appletb_inactivity_work(struct work_struct *work)
|
||||
{
|
||||
struct appletb_kbd *kbd = timer_container_of(kbd, t, inactivity_timer);
|
||||
struct appletb_kbd *kbd = container_of(to_delayed_work(work),
|
||||
struct appletb_kbd,
|
||||
inactivity_work);
|
||||
|
||||
if (kbd->backlight_dev && appletb_tb_autodim) {
|
||||
if (!kbd->has_dimmed) {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 1);
|
||||
kbd->has_dimmed = true;
|
||||
mod_timer(&kbd->inactivity_timer,
|
||||
jiffies + secs_to_jiffies(appletb_tb_idle_timeout));
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_idle_timeout));
|
||||
} else if (!kbd->has_turned_off) {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 0);
|
||||
kbd->has_turned_off = true;
|
||||
@@ -181,16 +184,25 @@ static void appletb_inactivity_timer(struct timer_list *t)
|
||||
}
|
||||
}
|
||||
|
||||
static void appletb_restore_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct appletb_kbd *kbd = container_of(work, struct appletb_kbd,
|
||||
restore_brightness_work);
|
||||
|
||||
if (kbd->backlight_dev)
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 2);
|
||||
}
|
||||
|
||||
static void reset_inactivity_timer(struct appletb_kbd *kbd)
|
||||
{
|
||||
if (kbd->backlight_dev && appletb_tb_autodim) {
|
||||
if (kbd->has_dimmed || kbd->has_turned_off) {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 2);
|
||||
kbd->has_dimmed = false;
|
||||
kbd->has_turned_off = false;
|
||||
schedule_work(&kbd->restore_brightness_work);
|
||||
}
|
||||
mod_timer(&kbd->inactivity_timer,
|
||||
jiffies + secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,9 +420,11 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
|
||||
dev_err_probe(dev, -ENODEV, "Failed to get backlight device\n");
|
||||
} else {
|
||||
backlight_device_set_brightness(kbd->backlight_dev, 2);
|
||||
timer_setup(&kbd->inactivity_timer, appletb_inactivity_timer, 0);
|
||||
mod_timer(&kbd->inactivity_timer,
|
||||
jiffies + secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
INIT_DELAYED_WORK(&kbd->inactivity_work, appletb_inactivity_work);
|
||||
INIT_WORK(&kbd->restore_brightness_work,
|
||||
appletb_restore_brightness_work);
|
||||
mod_delayed_work(system_wq, &kbd->inactivity_work,
|
||||
secs_to_jiffies(appletb_tb_dim_timeout));
|
||||
}
|
||||
|
||||
kbd->inp_handler.event = appletb_kbd_inp_event;
|
||||
@@ -440,13 +454,14 @@ static int appletb_kbd_probe(struct hid_device *hdev, const struct hid_device_id
|
||||
unregister_handler:
|
||||
input_unregister_handler(&kbd->inp_handler);
|
||||
close_hw:
|
||||
if (kbd->backlight_dev) {
|
||||
put_device(&kbd->backlight_dev->dev);
|
||||
timer_delete_sync(&kbd->inactivity_timer);
|
||||
}
|
||||
hid_hw_close(hdev);
|
||||
stop_hw:
|
||||
hid_hw_stop(hdev);
|
||||
if (kbd->backlight_dev) {
|
||||
cancel_delayed_work_sync(&kbd->inactivity_work);
|
||||
cancel_work_sync(&kbd->restore_brightness_work);
|
||||
put_device(&kbd->backlight_dev->dev);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -457,16 +472,16 @@ static void appletb_kbd_remove(struct hid_device *hdev)
|
||||
appletb_kbd_set_mode(kbd, APPLETB_KBD_MODE_OFF);
|
||||
|
||||
input_unregister_handler(&kbd->inp_handler);
|
||||
if (kbd->backlight_dev) {
|
||||
put_device(&kbd->backlight_dev->dev);
|
||||
timer_delete_sync(&kbd->inactivity_timer);
|
||||
}
|
||||
|
||||
hid_hw_close(hdev);
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
if (kbd->backlight_dev) {
|
||||
cancel_delayed_work_sync(&kbd->inactivity_work);
|
||||
cancel_work_sync(&kbd->restore_brightness_work);
|
||||
put_device(&kbd->backlight_dev->dev);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
|
||||
{
|
||||
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
|
||||
@@ -477,7 +492,7 @@ static int appletb_kbd_suspend(struct hid_device *hdev, pm_message_t msg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int appletb_kbd_reset_resume(struct hid_device *hdev)
|
||||
static int appletb_kbd_resume(struct hid_device *hdev)
|
||||
{
|
||||
struct appletb_kbd *kbd = hid_get_drvdata(hdev);
|
||||
|
||||
@@ -485,7 +500,6 @@ static int appletb_kbd_reset_resume(struct hid_device *hdev)
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct hid_device_id appletb_kbd_hid_ids[] = {
|
||||
/* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */
|
||||
@@ -501,10 +515,9 @@ static struct hid_driver appletb_kbd_hid_driver = {
|
||||
.remove = appletb_kbd_remove,
|
||||
.event = appletb_kbd_hid_event,
|
||||
.input_configured = appletb_kbd_input_configured,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = appletb_kbd_suspend,
|
||||
.reset_resume = appletb_kbd_reset_resume,
|
||||
#endif
|
||||
.suspend = pm_ptr(appletb_kbd_suspend),
|
||||
.resume = pm_ptr(appletb_kbd_resume),
|
||||
.reset_resume = pm_ptr(appletb_kbd_resume),
|
||||
.driver.dev_groups = appletb_kbd_groups,
|
||||
};
|
||||
module_hid_driver(appletb_kbd_hid_driver);
|
||||
|
||||
+36
-9
@@ -56,6 +56,13 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
|
||||
#define ROG_ALLY_X_MIN_MCU 313
|
||||
#define ROG_ALLY_MIN_MCU 319
|
||||
|
||||
/* Spurious HID codes sent by QUIRK_ROG_NKEY_KEYBOARD devices */
|
||||
#define ASUS_SPURIOUS_CODE_0XEA 0xea
|
||||
#define ASUS_SPURIOUS_CODE_0XEC 0xec
|
||||
#define ASUS_SPURIOUS_CODE_0X02 0x02
|
||||
#define ASUS_SPURIOUS_CODE_0X8A 0x8a
|
||||
#define ASUS_SPURIOUS_CODE_0X9E 0x9e
|
||||
|
||||
#define SUPPORT_KBD_BACKLIGHT BIT(0)
|
||||
|
||||
#define MAX_TOUCH_MAJOR 8
|
||||
@@ -316,7 +323,7 @@ static int asus_e1239t_event(struct asus_drvdata *drvdat, u8 *data, int size)
|
||||
static int asus_event(struct hid_device *hdev, struct hid_field *field,
|
||||
struct hid_usage *usage, __s32 value)
|
||||
{
|
||||
if ((usage->hid & HID_USAGE_PAGE) == 0xff310000 &&
|
||||
if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR &&
|
||||
(usage->hid & HID_USAGE) != 0x00 &&
|
||||
(usage->hid & HID_USAGE) != 0xff && !usage->type) {
|
||||
hid_warn(hdev, "Unmapped Asus vendor usagepage code 0x%02x\n",
|
||||
@@ -347,6 +354,21 @@ static int asus_raw_event(struct hid_device *hdev,
|
||||
if (report->id == FEATURE_KBD_LED_REPORT_ID1 || report->id == FEATURE_KBD_LED_REPORT_ID2)
|
||||
return -1;
|
||||
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
|
||||
/*
|
||||
* ASUS ROG laptops send these codes during normal operation
|
||||
* with no discernable reason. Filter them out to avoid
|
||||
* unmapped warning messages.
|
||||
*/
|
||||
if (report->id == FEATURE_KBD_REPORT_ID) {
|
||||
if (data[1] == ASUS_SPURIOUS_CODE_0XEA ||
|
||||
data[1] == ASUS_SPURIOUS_CODE_0XEC ||
|
||||
data[1] == ASUS_SPURIOUS_CODE_0X02 ||
|
||||
data[1] == ASUS_SPURIOUS_CODE_0X8A ||
|
||||
data[1] == ASUS_SPURIOUS_CODE_0X9E) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* G713 and G733 send these codes on some keypresses, depending on
|
||||
* the key pressed it can trigger a shutdown event if not caught.
|
||||
@@ -1302,14 +1324,21 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*/
|
||||
if (*rsize == rsize_orig &&
|
||||
rdesc[offs] == 0x09 && rdesc[offs + 1] == 0x76) {
|
||||
*rsize = rsize_orig + 1;
|
||||
rdesc = kmemdup(rdesc, *rsize, GFP_KERNEL);
|
||||
if (!rdesc)
|
||||
return NULL;
|
||||
__u8 *new_rdesc;
|
||||
|
||||
new_rdesc = devm_kzalloc(&hdev->dev, rsize_orig + 1,
|
||||
GFP_KERNEL);
|
||||
if (!new_rdesc)
|
||||
return rdesc;
|
||||
|
||||
hid_info(hdev, "Fixing up %s keyb report descriptor\n",
|
||||
drvdata->quirks & QUIRK_T100CHI ?
|
||||
"T100CHI" : "T90CHI");
|
||||
|
||||
memcpy(new_rdesc, rdesc, rsize_orig);
|
||||
*rsize = rsize_orig + 1;
|
||||
rdesc = new_rdesc;
|
||||
|
||||
memmove(rdesc + offs + 4, rdesc + offs + 2, 12);
|
||||
rdesc[offs] = 0x19;
|
||||
rdesc[offs + 1] = 0x00;
|
||||
@@ -1430,10 +1459,8 @@ static struct hid_driver asus_driver = {
|
||||
.remove = asus_remove,
|
||||
.input_mapping = asus_input_mapping,
|
||||
.input_configured = asus_input_configured,
|
||||
#ifdef CONFIG_PM
|
||||
.reset_resume = asus_reset_resume,
|
||||
.resume = asus_resume,
|
||||
#endif
|
||||
.reset_resume = pm_ptr(asus_reset_resume),
|
||||
.resume = pm_ptr(asus_resume),
|
||||
.event = asus_event,
|
||||
.raw_event = asus_raw_event
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ static int axff_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
axff = kzalloc(sizeof(struct axff_device), GFP_KERNEL);
|
||||
axff = kzalloc_obj(struct axff_device);
|
||||
if (!axff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ static int betopff_init(struct hid_device *hid)
|
||||
}
|
||||
}
|
||||
|
||||
betopff = kzalloc(sizeof(*betopff), GFP_KERNEL);
|
||||
betopff = kzalloc_obj(*betopff);
|
||||
if (!betopff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report,
|
||||
{
|
||||
struct cmhid *cm = hid_get_drvdata(hid);
|
||||
|
||||
if (len != CM6533_JD_RAWEV_LEN)
|
||||
if (len != CM6533_JD_RAWEV_LEN || !(hid->claimed & HID_CLAIMED_INPUT))
|
||||
goto out;
|
||||
if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx)))
|
||||
goto out;
|
||||
@@ -145,7 +145,7 @@ static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id)
|
||||
int ret;
|
||||
struct cmhid *cm;
|
||||
|
||||
cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL);
|
||||
cm = kzalloc_obj(struct cmhid);
|
||||
if (!cm) {
|
||||
ret = -ENOMEM;
|
||||
goto allocfail;
|
||||
|
||||
+200
-116
@@ -71,6 +71,9 @@ static u32 s32ton(__s32 value, unsigned int n)
|
||||
if (!value || !n)
|
||||
return 0;
|
||||
|
||||
if (n > 32)
|
||||
n = 32;
|
||||
|
||||
a = value >> (n - 1);
|
||||
if (a && a != -1)
|
||||
return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
|
||||
@@ -93,7 +96,7 @@ struct hid_report *hid_register_report(struct hid_device *device,
|
||||
if (report_enum->report_id_hash[id])
|
||||
return report_enum->report_id_hash[id];
|
||||
|
||||
report = kzalloc(sizeof(struct hid_report), GFP_KERNEL);
|
||||
report = kzalloc_obj(struct hid_report);
|
||||
if (!report)
|
||||
return NULL;
|
||||
|
||||
@@ -924,7 +927,6 @@ static int hid_scan_main(struct hid_parser *parser, struct hid_item *item)
|
||||
*/
|
||||
static int hid_scan_report(struct hid_device *hid)
|
||||
{
|
||||
struct hid_parser *parser;
|
||||
struct hid_item item;
|
||||
const __u8 *start = hid->dev_rdesc;
|
||||
const __u8 *end = start + hid->dev_rsize;
|
||||
@@ -936,13 +938,22 @@ static int hid_scan_report(struct hid_device *hid)
|
||||
hid_parser_reserved
|
||||
};
|
||||
|
||||
parser = vzalloc(sizeof(struct hid_parser));
|
||||
struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser));
|
||||
if (!parser)
|
||||
return -ENOMEM;
|
||||
|
||||
parser->device = hid;
|
||||
hid->group = HID_GROUP_GENERIC;
|
||||
|
||||
/*
|
||||
* In case we are re-scanning after a BPF has been loaded,
|
||||
* we need to use the bpf report descriptor, not the original one.
|
||||
*/
|
||||
if (hid->bpf_rdesc && hid->bpf_rsize) {
|
||||
start = hid->bpf_rdesc;
|
||||
end = start + hid->bpf_rsize;
|
||||
}
|
||||
|
||||
/*
|
||||
* The parsing is simpler than the one in hid_open_report() as we should
|
||||
* be robust against hid errors. Those errors will be raised by
|
||||
@@ -978,7 +989,6 @@ static int hid_scan_report(struct hid_device *hid)
|
||||
}
|
||||
|
||||
kfree(parser->collection_stack);
|
||||
vfree(parser);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1235,6 +1245,90 @@ void hid_setup_resolution_multiplier(struct hid_device *hid)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier);
|
||||
|
||||
static int hid_parse_collections(struct hid_device *device)
|
||||
{
|
||||
struct hid_item item;
|
||||
const u8 *start = device->rdesc;
|
||||
const u8 *end = start + device->rsize;
|
||||
const u8 *next;
|
||||
int ret;
|
||||
static typeof(hid_parser_main) (* const dispatch_type[]) = {
|
||||
hid_parser_main,
|
||||
hid_parser_global,
|
||||
hid_parser_local,
|
||||
hid_parser_reserved
|
||||
};
|
||||
|
||||
struct hid_parser *parser __free(kvfree) = vzalloc(sizeof(*parser));
|
||||
if (!parser)
|
||||
return -ENOMEM;
|
||||
|
||||
parser->device = device;
|
||||
|
||||
device->collection = kzalloc_objs(*device->collection,
|
||||
HID_DEFAULT_NUM_COLLECTIONS);
|
||||
if (!device->collection)
|
||||
return -ENOMEM;
|
||||
|
||||
device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
|
||||
for (unsigned int i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++)
|
||||
device->collection[i].parent_idx = -1;
|
||||
|
||||
ret = -EINVAL;
|
||||
if (start == end) {
|
||||
hid_err(device, "rejecting 0-sized report descriptor\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
while ((next = fetch_item(start, end, &item)) != NULL) {
|
||||
start = next;
|
||||
|
||||
if (item.format != HID_ITEM_FORMAT_SHORT) {
|
||||
hid_err(device, "unexpected long global item\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dispatch_type[item.type](parser, &item)) {
|
||||
hid_err(device, "item %u %u %u %u parsing failed\n",
|
||||
item.format,
|
||||
(unsigned int)item.size,
|
||||
(unsigned int)item.type,
|
||||
(unsigned int)item.tag);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (start != end) {
|
||||
hid_err(device, "item fetching failed at offset %u/%u\n",
|
||||
device->rsize - (unsigned int)(end - start),
|
||||
device->rsize);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (parser->collection_stack_ptr) {
|
||||
hid_err(device, "unbalanced collection at end of report description\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (parser->local.delimiter_depth) {
|
||||
hid_err(device, "unbalanced delimiter at end of report description\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* fetch initial values in case the device's
|
||||
* default multiplier isn't the recommended 1
|
||||
*/
|
||||
hid_setup_resolution_multiplier(device);
|
||||
|
||||
device->status |= HID_STAT_PARSED;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
kfree(parser->collection_stack);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* hid_open_report - open a driver-specific device report
|
||||
*
|
||||
@@ -1249,21 +1343,9 @@ EXPORT_SYMBOL_GPL(hid_setup_resolution_multiplier);
|
||||
*/
|
||||
int hid_open_report(struct hid_device *device)
|
||||
{
|
||||
struct hid_parser *parser;
|
||||
struct hid_item item;
|
||||
unsigned int size;
|
||||
const __u8 *start;
|
||||
const __u8 *end;
|
||||
const __u8 *next;
|
||||
int ret;
|
||||
int i;
|
||||
static int (*dispatch_type[])(struct hid_parser *parser,
|
||||
struct hid_item *item) = {
|
||||
hid_parser_main,
|
||||
hid_parser_global,
|
||||
hid_parser_local,
|
||||
hid_parser_reserved
|
||||
};
|
||||
const u8 *start;
|
||||
int error;
|
||||
|
||||
if (WARN_ON(device->status & HID_STAT_PARSED))
|
||||
return -EBUSY;
|
||||
@@ -1279,9 +1361,9 @@ int hid_open_report(struct hid_device *device)
|
||||
* on a copy of our report descriptor so it can
|
||||
* change it.
|
||||
*/
|
||||
__u8 *buf = kmemdup(start, size, GFP_KERNEL);
|
||||
u8 *buf __free(kfree) = kmemdup(start, size, GFP_KERNEL);
|
||||
|
||||
if (buf == NULL)
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
start = device->driver->report_fixup(device, buf, &size);
|
||||
@@ -1292,83 +1374,21 @@ int hid_open_report(struct hid_device *device)
|
||||
* needs to be cleaned up or not at the end.
|
||||
*/
|
||||
start = kmemdup(start, size, GFP_KERNEL);
|
||||
kfree(buf);
|
||||
if (start == NULL)
|
||||
if (!start)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
device->rdesc = start;
|
||||
device->rsize = size;
|
||||
|
||||
parser = vzalloc(sizeof(struct hid_parser));
|
||||
if (!parser) {
|
||||
ret = -ENOMEM;
|
||||
goto alloc_err;
|
||||
error = hid_parse_collections(device);
|
||||
if (error) {
|
||||
hid_close_report(device);
|
||||
return error;
|
||||
}
|
||||
|
||||
parser->device = device;
|
||||
|
||||
end = start + size;
|
||||
|
||||
device->collection = kcalloc(HID_DEFAULT_NUM_COLLECTIONS,
|
||||
sizeof(struct hid_collection), GFP_KERNEL);
|
||||
if (!device->collection) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
|
||||
for (i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++)
|
||||
device->collection[i].parent_idx = -1;
|
||||
|
||||
ret = -EINVAL;
|
||||
while ((next = fetch_item(start, end, &item)) != NULL) {
|
||||
start = next;
|
||||
|
||||
if (item.format != HID_ITEM_FORMAT_SHORT) {
|
||||
hid_err(device, "unexpected long global item\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (dispatch_type[item.type](parser, &item)) {
|
||||
hid_err(device, "item %u %u %u %u parsing failed\n",
|
||||
item.format, (unsigned)item.size,
|
||||
(unsigned)item.type, (unsigned)item.tag);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (start == end) {
|
||||
if (parser->collection_stack_ptr) {
|
||||
hid_err(device, "unbalanced collection at end of report description\n");
|
||||
goto err;
|
||||
}
|
||||
if (parser->local.delimiter_depth) {
|
||||
hid_err(device, "unbalanced delimiter at end of report description\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* fetch initial values in case the device's
|
||||
* default multiplier isn't the recommended 1
|
||||
*/
|
||||
hid_setup_resolution_multiplier(device);
|
||||
|
||||
kfree(parser->collection_stack);
|
||||
vfree(parser);
|
||||
device->status |= HID_STAT_PARSED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
hid_err(device, "item fetching failed at offset %u/%u\n",
|
||||
size - (unsigned int)(end - start), size);
|
||||
err:
|
||||
kfree(parser->collection_stack);
|
||||
alloc_err:
|
||||
vfree(parser);
|
||||
hid_close_report(device);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_open_report);
|
||||
|
||||
/*
|
||||
@@ -1787,7 +1807,7 @@ static void hid_report_process_ordering(struct hid_device *hid,
|
||||
}
|
||||
|
||||
/* allocate the memory to process the fields */
|
||||
entries = kcalloc(count, sizeof(*entries), GFP_KERNEL);
|
||||
entries = kzalloc_objs(*entries, count);
|
||||
if (!entries)
|
||||
return;
|
||||
|
||||
@@ -1980,11 +2000,11 @@ static struct hid_report *hid_get_report(struct hid_report_enum *report_enum,
|
||||
int __hid_request(struct hid_device *hid, struct hid_report *report,
|
||||
enum hid_class_request reqtype)
|
||||
{
|
||||
char *buf, *data_buf;
|
||||
u8 *data_buf;
|
||||
int ret;
|
||||
u32 len;
|
||||
|
||||
buf = hid_alloc_report_buf(report, GFP_KERNEL);
|
||||
u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -2003,38 +2023,42 @@ int __hid_request(struct hid_device *hid, struct hid_report *report,
|
||||
ret = hid_hw_raw_request(hid, report->id, buf, len, report->type, reqtype);
|
||||
if (ret < 0) {
|
||||
dbg_hid("unable to complete request: %d\n", ret);
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (reqtype == HID_REQ_GET_REPORT)
|
||||
hid_input_report(hid, report->type, buf, ret, 0);
|
||||
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__hid_request);
|
||||
|
||||
int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
|
||||
int interrupt)
|
||||
int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
|
||||
size_t bufsize, u32 size, int interrupt)
|
||||
{
|
||||
struct hid_report_enum *report_enum = hid->report_enum + type;
|
||||
struct hid_report *report;
|
||||
struct hid_driver *hdrv;
|
||||
int max_buffer_size = HID_MAX_BUFFER_SIZE;
|
||||
u32 rsize, csize = size;
|
||||
size_t bsize = bufsize;
|
||||
u8 *cdata = data;
|
||||
int ret = 0;
|
||||
|
||||
report = hid_get_report(report_enum, data);
|
||||
if (!report)
|
||||
goto out;
|
||||
return 0;
|
||||
|
||||
if (unlikely(bsize < csize)) {
|
||||
hid_warn_ratelimited(hid, "Event data for report %d is incorrect (%d vs %zu)\n",
|
||||
report->id, csize, bsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (report_enum->numbered) {
|
||||
cdata++;
|
||||
csize--;
|
||||
bsize--;
|
||||
}
|
||||
|
||||
rsize = hid_compute_report_size(report);
|
||||
@@ -2047,6 +2071,12 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
|
||||
else if (rsize > max_buffer_size)
|
||||
rsize = max_buffer_size;
|
||||
|
||||
if (bsize < rsize) {
|
||||
hid_warn_ratelimited(hid, "Event data for report %d was too short (%d vs %zu)\n",
|
||||
report->id, rsize, bsize);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (csize < rsize) {
|
||||
dbg_hid("report %d is too short, (%d < %d)\n", report->id,
|
||||
csize, rsize);
|
||||
@@ -2058,7 +2088,7 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
|
||||
if (hid->claimed & HID_CLAIMED_HIDRAW) {
|
||||
ret = hidraw_report_event(hid, data, size);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) {
|
||||
@@ -2070,15 +2100,15 @@ int hid_report_raw_event(struct hid_device *hid, enum hid_report_type type, u8 *
|
||||
|
||||
if (hid->claimed & HID_CLAIMED_INPUT)
|
||||
hidinput_report_event(hid, report);
|
||||
out:
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_report_raw_event);
|
||||
|
||||
|
||||
static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
|
||||
u8 *data, u32 size, int interrupt, u64 source, bool from_bpf,
|
||||
bool lock_already_taken)
|
||||
u8 *data, size_t bufsize, u32 size, int interrupt, u64 source,
|
||||
bool from_bpf, bool lock_already_taken)
|
||||
{
|
||||
struct hid_report_enum *report_enum;
|
||||
struct hid_driver *hdrv;
|
||||
@@ -2103,7 +2133,8 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
|
||||
report_enum = hid->report_enum + type;
|
||||
hdrv = hid->driver;
|
||||
|
||||
data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt, source, from_bpf);
|
||||
data = dispatch_hid_bpf_device_event(hid, type, data, &bufsize, &size, interrupt,
|
||||
source, from_bpf);
|
||||
if (IS_ERR(data)) {
|
||||
ret = PTR_ERR(data);
|
||||
goto unlock;
|
||||
@@ -2132,7 +2163,7 @@ static int __hid_input_report(struct hid_device *hid, enum hid_report_type type,
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
ret = hid_report_raw_event(hid, type, data, size, interrupt);
|
||||
ret = hid_report_raw_event(hid, type, data, bufsize, size, interrupt);
|
||||
|
||||
unlock:
|
||||
if (!lock_already_taken)
|
||||
@@ -2150,16 +2181,41 @@ unlock:
|
||||
* @interrupt: distinguish between interrupt and control transfers
|
||||
*
|
||||
* This is data entry for lower layers.
|
||||
* Legacy, please use hid_safe_input_report() instead.
|
||||
*/
|
||||
int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data, u32 size,
|
||||
int interrupt)
|
||||
{
|
||||
return __hid_input_report(hid, type, data, size, interrupt, 0,
|
||||
return __hid_input_report(hid, type, data, size, size, interrupt, 0,
|
||||
false, /* from_bpf */
|
||||
false /* lock_already_taken */);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_input_report);
|
||||
|
||||
/**
|
||||
* hid_safe_input_report - report data from lower layer (usb, bt...)
|
||||
*
|
||||
* @hid: hid device
|
||||
* @type: HID report type (HID_*_REPORT)
|
||||
* @data: report contents
|
||||
* @bufsize: allocated size of the data buffer
|
||||
* @size: useful size of data parameter
|
||||
* @interrupt: distinguish between interrupt and control transfers
|
||||
*
|
||||
* This is data entry for lower layers.
|
||||
* Please use this function instead of the non safe version because we provide
|
||||
* here the size of the buffer, allowing hid-core to make smarter decisions
|
||||
* regarding the incoming buffer.
|
||||
*/
|
||||
int hid_safe_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data,
|
||||
size_t bufsize, u32 size, int interrupt)
|
||||
{
|
||||
return __hid_input_report(hid, type, data, bufsize, size, interrupt, 0,
|
||||
false, /* from_bpf */
|
||||
false /* lock_already_taken */);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_safe_input_report);
|
||||
|
||||
bool hid_match_one_id(const struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
@@ -2601,7 +2657,7 @@ static ssize_t new_id_store(struct device_driver *drv, const char *buf,
|
||||
if (ret < 3)
|
||||
return -EINVAL;
|
||||
|
||||
dynid = kzalloc(sizeof(*dynid), GFP_KERNEL);
|
||||
dynid = kzalloc_obj(*dynid);
|
||||
if (!dynid)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -2708,12 +2764,32 @@ static bool hid_check_device_match(struct hid_device *hdev,
|
||||
return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
|
||||
}
|
||||
|
||||
static void hid_set_group(struct hid_device *hdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (hid_ignore_special_drivers) {
|
||||
hdev->group = HID_GROUP_GENERIC;
|
||||
} else if (!hdev->group &&
|
||||
!(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
|
||||
ret = hid_scan_report(hdev);
|
||||
if (ret)
|
||||
hid_warn(hdev, "bad device descriptor (%d)\n", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
||||
{
|
||||
const struct hid_device_id *id;
|
||||
int ret;
|
||||
|
||||
if (!hdev->bpf_rsize) {
|
||||
/* we keep a reference to the currently scanned report descriptor */
|
||||
const __u8 *original_rdesc = hdev->bpf_rdesc;
|
||||
|
||||
if (!original_rdesc)
|
||||
original_rdesc = hdev->dev_rdesc;
|
||||
|
||||
/* in case a bpf program gets detached, we need to free the old one */
|
||||
hid_free_bpf_rdesc(hdev);
|
||||
|
||||
@@ -2723,6 +2799,12 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
||||
/* call_hid_bpf_rdesc_fixup will always return a valid pointer */
|
||||
hdev->bpf_rdesc = call_hid_bpf_rdesc_fixup(hdev, hdev->dev_rdesc,
|
||||
&hdev->bpf_rsize);
|
||||
|
||||
/* the report descriptor changed, we need to re-scan it */
|
||||
if (original_rdesc != hdev->bpf_rdesc) {
|
||||
hdev->group = 0;
|
||||
hid_set_group(hdev);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hid_check_device_match(hdev, hdrv, &id))
|
||||
@@ -2852,6 +2934,11 @@ static int hid_uevent(const struct device *dev, struct kobj_uevent_env *env)
|
||||
if (add_uevent_var(env, "MODALIAS=hid:b%04Xg%04Xv%08Xp%08X",
|
||||
hdev->bus, hdev->group, hdev->vendor, hdev->product))
|
||||
return -ENOMEM;
|
||||
if (hdev->firmware_version) {
|
||||
if (add_uevent_var(env, "HID_FIRMWARE_VERSION=0x%04llX",
|
||||
hdev->firmware_version))
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2903,14 +2990,7 @@ int hid_add_device(struct hid_device *hdev)
|
||||
/*
|
||||
* Scan generic devices for group information
|
||||
*/
|
||||
if (hid_ignore_special_drivers) {
|
||||
hdev->group = HID_GROUP_GENERIC;
|
||||
} else if (!hdev->group &&
|
||||
!(hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)) {
|
||||
ret = hid_scan_report(hdev);
|
||||
if (ret)
|
||||
hid_warn(hdev, "bad device descriptor (%d)\n", ret);
|
||||
}
|
||||
hid_set_group(hdev);
|
||||
|
||||
hdev->id = atomic_inc_return(&id);
|
||||
|
||||
@@ -2944,7 +3024,7 @@ struct hid_device *hid_allocate_device(void)
|
||||
struct hid_device *hdev;
|
||||
int ret = -ENOMEM;
|
||||
|
||||
hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
|
||||
hdev = kzalloc_obj(*hdev);
|
||||
if (hdev == NULL)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
@@ -2962,6 +3042,10 @@ struct hid_device *hid_allocate_device(void)
|
||||
mutex_init(&hdev->ll_open_lock);
|
||||
kref_init(&hdev->ref);
|
||||
|
||||
#ifdef CONFIG_HID_BATTERY_STRENGTH
|
||||
INIT_LIST_HEAD(&hdev->batteries);
|
||||
#endif
|
||||
|
||||
ret = hid_bpf_device_init(hdev);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
|
||||
@@ -553,9 +553,8 @@ static void corsair_void_add_battery(struct corsair_void_drvdata *drvdata)
|
||||
|
||||
if (IS_ERR(new_supply)) {
|
||||
hid_err(drvdata->hid_dev,
|
||||
"failed to register battery '%s' (reason: %ld)\n",
|
||||
drvdata->battery_desc.name,
|
||||
PTR_ERR(new_supply));
|
||||
"failed to register battery '%s' (reason: %pe)\n",
|
||||
drvdata->battery_desc.name, new_supply);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -427,7 +427,7 @@ static int k90_init_backlight(struct hid_device *dev)
|
||||
size_t name_sz;
|
||||
char *name;
|
||||
|
||||
drvdata->backlight = kzalloc(sizeof(struct k90_led), GFP_KERNEL);
|
||||
drvdata->backlight = kzalloc_obj(struct k90_led);
|
||||
if (!drvdata->backlight) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_backlight_alloc;
|
||||
@@ -471,7 +471,7 @@ static int k90_init_macro_functions(struct hid_device *dev)
|
||||
size_t name_sz;
|
||||
char *name;
|
||||
|
||||
k90 = kzalloc(sizeof(struct k90_drvdata), GFP_KERNEL);
|
||||
k90 = kzalloc_obj(struct k90_drvdata);
|
||||
if (!k90) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_drvdata;
|
||||
|
||||
@@ -166,7 +166,7 @@ static int cougar_bind_shared_data(struct hid_device *hdev,
|
||||
|
||||
shared = cougar_get_shared_data(hdev);
|
||||
if (!shared) {
|
||||
shared = kzalloc(sizeof(*shared), GFP_KERNEL);
|
||||
shared = kzalloc_obj(*shared);
|
||||
if (!shared) {
|
||||
error = -ENOMEM;
|
||||
goto out;
|
||||
|
||||
@@ -689,6 +689,13 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
count = cp2112_write_read_req(buf, addr, read_length,
|
||||
command, NULL, 0);
|
||||
} else {
|
||||
/* Copy starts from data->block[1] so the length can
|
||||
* be at max I2C_SMBUS_CLOCK_MAX + 1
|
||||
*/
|
||||
|
||||
if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
|
||||
count = -EINVAL;
|
||||
else
|
||||
count = cp2112_write_req(buf, addr, command,
|
||||
data->block + 1,
|
||||
data->block[0]);
|
||||
@@ -700,6 +707,13 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
I2C_SMBUS_BLOCK_MAX,
|
||||
command, NULL, 0);
|
||||
} else {
|
||||
/* data_length here is data->block[0] + 1
|
||||
* so make sure that the data->block[0] is
|
||||
* less than or equals I2C_SMBUS_BLOCK_MAX + 1
|
||||
*/
|
||||
if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
|
||||
count = -EINVAL;
|
||||
else
|
||||
count = cp2112_write_req(buf, addr, command,
|
||||
data->block,
|
||||
data->block[0] + 1);
|
||||
@@ -709,6 +723,13 @@ static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
|
||||
size = I2C_SMBUS_BLOCK_DATA;
|
||||
read_write = I2C_SMBUS_READ;
|
||||
|
||||
/* data_length is data->block[0] + 1, so
|
||||
* so data->block[0] should be less than or
|
||||
* equal to the I2C_SMBUS_BLOCK_MAX + 1
|
||||
*/
|
||||
if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
|
||||
count = -EINVAL;
|
||||
else
|
||||
count = cp2112_write_read_req(buf, addr, I2C_SMBUS_BLOCK_MAX,
|
||||
command, data->block,
|
||||
data->block[0] + 1);
|
||||
|
||||
@@ -153,7 +153,7 @@ static int creative_sb0540_raw_event(struct hid_device *hid,
|
||||
u64 code, main_code;
|
||||
int key;
|
||||
|
||||
if (len != 6)
|
||||
if (len != 6 || !(hid->claimed & HID_CLAIMED_INPUT))
|
||||
return 0;
|
||||
|
||||
/* From daemons/hw_hiddev.c sb0540_rec() in lirc */
|
||||
|
||||
@@ -990,6 +990,9 @@ static const struct hid_usage_entry hid_usage_table[] = {
|
||||
{ 0x0c, 0x01c9, "ALContactSync" },
|
||||
{ 0x0c, 0x01ca, "ALNavigation" },
|
||||
{ 0x0c, 0x01cb, "ALContextawareDesktopAssistant" },
|
||||
{ 0x0c, 0x01cc, "ALActionOnSelection" },
|
||||
{ 0x0c, 0x01cd, "ALContextualInsertion" },
|
||||
{ 0x0c, 0x01ce, "ALContextualQuery" },
|
||||
{ 0x0c, 0x0200, "GenericGUIApplicationControls" },
|
||||
{ 0x0c, 0x0201, "ACNew" },
|
||||
{ 0x0c, 0x0202, "ACOpen" },
|
||||
@@ -2523,7 +2526,7 @@ static const struct hid_usage_entry hid_usage_table[] = {
|
||||
{ 0x85, 0x0088, "iDeviceName" },
|
||||
{ 0x85, 0x0089, "iDeviceChemistry" },
|
||||
{ 0x85, 0x008a, "ManufacturerData" },
|
||||
{ 0x85, 0x008b, "Rechargable" },
|
||||
{ 0x85, 0x008b, "Rechargeable" },
|
||||
{ 0x85, 0x008c, "WarningCapacityLimit" },
|
||||
{ 0x85, 0x008d, "CapacityGranularity1" },
|
||||
{ 0x85, 0x008e, "CapacityGranularity2" },
|
||||
@@ -3375,6 +3378,9 @@ static const char *keys[KEY_MAX + 1] = {
|
||||
[KEY_BRIGHTNESS_MIN] = "BrightnessMin",
|
||||
[KEY_BRIGHTNESS_MAX] = "BrightnessMax",
|
||||
[KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",
|
||||
[KEY_ACTION_ON_SELECTION] = "ActionOnSelection",
|
||||
[KEY_CONTEXTUAL_INSERT] = "ContextualInsert",
|
||||
[KEY_CONTEXTUAL_QUERY] = "ContextualQuery",
|
||||
[KEY_KBDINPUTASSIST_PREV] = "KbdInputAssistPrev",
|
||||
[KEY_KBDINPUTASSIST_NEXT] = "KbdInputAssistNext",
|
||||
[KEY_KBDINPUTASSIST_PREVGROUP] = "KbdInputAssistPrevGroup",
|
||||
@@ -3513,6 +3519,7 @@ static const char *absolutes[ABS_CNT] = {
|
||||
[ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt",
|
||||
[ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth",
|
||||
[ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile",
|
||||
[ABS_SND_PROFILE] = "SoundProfile",
|
||||
[ABS_MISC] = "Misc",
|
||||
[ABS_MT_SLOT] = "MTSlot",
|
||||
[ABS_MT_TOUCH_MAJOR] = "MTMajor",
|
||||
@@ -3680,7 +3687,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
|
||||
struct hid_debug_list *list;
|
||||
unsigned long flags;
|
||||
|
||||
if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) {
|
||||
if (!(list = kzalloc_obj(struct hid_debug_list))) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ static int drff_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
drff = kzalloc(sizeof(struct drff_device), GFP_KERNEL);
|
||||
drff = kzalloc_obj(struct drff_device);
|
||||
if (!drff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -513,6 +513,7 @@ static const struct hid_device_id elan_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2_10_COVER),
|
||||
.driver_data = ELAN_HAS_LED },
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_TOSHIBA_CLICK_L9W) },
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_SB974D) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, elan_devices);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* - EX-G Trackballs (M-XT3DRBK, M-XT3URBK, M-XT4DRBK)
|
||||
* - DEFT Trackballs (M-DT1DRBK, M-DT1URBK, M-DT2DRBK, M-DT2URBK)
|
||||
* - HUGE Trackballs (M-HT1DRBK, M-HT1URBK)
|
||||
* - HUGE Plus Trackball (M-HT1MRBK)
|
||||
*
|
||||
* Copyright (c) 2010 Richard Nauber <Richard.Nauber@gmail.com>
|
||||
* Copyright (c) 2016 Yuxuan Shui <yshuiv7@gmail.com>
|
||||
@@ -75,8 +76,9 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*/
|
||||
mouse_button_fixup(hdev, rdesc, *rsize, 20, 28, 22, 14, 8);
|
||||
break;
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3URBK:
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3DRBK:
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB:
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3URBK_018F:
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC:
|
||||
case USB_DEVICE_ID_ELECOM_M_XT4DRBK:
|
||||
/*
|
||||
* Report descriptor format:
|
||||
@@ -101,6 +103,16 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*/
|
||||
mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8);
|
||||
break;
|
||||
case USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C:
|
||||
/*
|
||||
* Report descriptor format:
|
||||
* 22: button bit count
|
||||
* 30: padding bit count
|
||||
* 24: button report size
|
||||
* 16: button usage maximum
|
||||
*/
|
||||
mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 6);
|
||||
break;
|
||||
case USB_DEVICE_ID_ELECOM_M_DT2DRBK:
|
||||
case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C:
|
||||
/*
|
||||
@@ -112,15 +124,30 @@ static const __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*/
|
||||
mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 8);
|
||||
break;
|
||||
case USB_DEVICE_ID_ELECOM_M_HT1MRBK:
|
||||
case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB:
|
||||
case USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC:
|
||||
/*
|
||||
* Report descriptor format:
|
||||
* 24: button bit count
|
||||
* 28: padding bit count
|
||||
* 22: button report size
|
||||
* 16: button usage maximum
|
||||
*/
|
||||
mouse_button_fixup(hdev, rdesc, *rsize, 24, 28, 22, 16, 8);
|
||||
break;
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static const struct hid_device_id elecom_devices[] = {
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
|
||||
@@ -129,6 +156,8 @@ static const struct hid_device_id elecom_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, elecom_devices);
|
||||
|
||||
@@ -232,7 +232,7 @@ static int elo_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
if (!hid_is_usb(hdev))
|
||||
return -EINVAL;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
priv = kzalloc_obj(*priv);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ static int emsff_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
emsff = kzalloc(sizeof(struct emsff_device), GFP_KERNEL);
|
||||
emsff = kzalloc_obj(struct emsff_device);
|
||||
if (!emsff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
/* mapping only applies to USB_DEVICE_ID_EVISION_ICL01 */
|
||||
if (hdev->product != USB_DEVICE_ID_EVISION_ICL01)
|
||||
return 0;
|
||||
|
||||
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
|
||||
return 0;
|
||||
|
||||
@@ -37,8 +41,24 @@ static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define REP_DSC_SIZE 236
|
||||
#define USAGE_MAX_INDEX 59
|
||||
|
||||
static const __u8 *evision_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
if (hdev->product == USB_DEVICE_ID_EV_TELINK_RECEIVER &&
|
||||
*rsize == REP_DSC_SIZE && rdesc[USAGE_MAX_INDEX] == 0x29 &&
|
||||
rdesc[USAGE_MAX_INDEX + 1] == 3) {
|
||||
hid_info(hdev, "fixing EVision:TeLink Receiver report descriptor\n");
|
||||
rdesc[USAGE_MAX_INDEX + 1] = 5; // change usage max from 3 to 5
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static const struct hid_device_id evision_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EVISION_ICL01) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EV_TELINK_RECEIVER) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, evision_devices);
|
||||
@@ -47,6 +67,7 @@ static struct hid_driver evision_driver = {
|
||||
.name = "evision",
|
||||
.id_table = evision_devices,
|
||||
.input_mapping = evision_input_mapping,
|
||||
.report_fixup = evision_report_fixup,
|
||||
};
|
||||
module_hid_driver(evision_driver);
|
||||
|
||||
|
||||
+14
-2
@@ -1068,10 +1068,22 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
struct ft260_device *dev = hid_get_drvdata(hdev);
|
||||
struct ft260_i2c_input_report *xfer = (void *)data;
|
||||
|
||||
if (size < offsetof(struct ft260_i2c_input_report, data)) {
|
||||
hid_err(hdev, "short report %d\n", size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (xfer->report >= FT260_I2C_REPORT_MIN &&
|
||||
xfer->report <= FT260_I2C_REPORT_MAX) {
|
||||
ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
|
||||
xfer->length);
|
||||
ft260_dbg("i2c resp: rep %#02x len %d size %d\n",
|
||||
xfer->report, xfer->length, size);
|
||||
|
||||
if (xfer->length > size -
|
||||
offsetof(struct ft260_i2c_input_report, data)) {
|
||||
hid_err(hdev, "report %#02x: length %d exceeds HID report size\n",
|
||||
xfer->report, xfer->length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((dev->read_buf == NULL) ||
|
||||
(xfer->length > dev->read_len - dev->read_idx)) {
|
||||
|
||||
@@ -96,7 +96,7 @@ static int gaff_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
gaff = kzalloc(sizeof(struct gaff_device), GFP_KERNEL);
|
||||
gaff = kzalloc_obj(struct gaff_device);
|
||||
if (!gaff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -70,6 +70,14 @@ static int hid_generic_probe(struct hid_device *hdev,
|
||||
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
static int hid_generic_reset_resume(struct hid_device *hdev)
|
||||
{
|
||||
if (hdev->claimed & HID_CLAIMED_INPUT)
|
||||
hidinput_reset_resume(hdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hid_device_id hid_table[] = {
|
||||
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
|
||||
{ }
|
||||
@@ -81,6 +89,7 @@ static struct hid_driver hid_generic = {
|
||||
.id_table = hid_table,
|
||||
.match = hid_generic_match,
|
||||
.probe = hid_generic_probe,
|
||||
.reset_resume = hid_generic_reset_resume,
|
||||
};
|
||||
module_hid_driver(hid_generic);
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
switch (data[1]) {
|
||||
case GFRM100_SEARCH_KEY_DOWN:
|
||||
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_dn,
|
||||
sizeof(search_key_dn), 1);
|
||||
sizeof(search_key_dn), sizeof(search_key_dn), 1);
|
||||
break;
|
||||
|
||||
case GFRM100_SEARCH_KEY_AUDIO_DATA:
|
||||
@@ -74,7 +74,7 @@ static int gfrm_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
|
||||
case GFRM100_SEARCH_KEY_UP:
|
||||
ret = hid_report_raw_event(hdev, HID_INPUT_REPORT, search_key_up,
|
||||
sizeof(search_key_up), 1);
|
||||
sizeof(search_key_up), sizeof(search_key_up), 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -59,8 +59,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
|
||||
struct cros_ec_command *msg;
|
||||
int ret;
|
||||
|
||||
msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))),
|
||||
GFP_KERNEL);
|
||||
msg = kzalloc_flex(*msg, data, max(sizeof(u32), sizeof(*params)));
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -497,7 +496,7 @@ static int hammer_probe(struct hid_device *hdev,
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
error = devm_add_action(&hdev->dev, hammer_stop, hdev);
|
||||
error = devm_add_action_or_reset(&hdev->dev, hammer_stop, hdev);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
|
||||
@@ -0,0 +1,579 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/input/mt.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage *usage)
|
||||
{
|
||||
u16 usage_hid;
|
||||
|
||||
if (usage->hid == HID_HP_AUTOTRIGGER) {
|
||||
if (usage->usage_index >= field->report_count) {
|
||||
dev_err(&hdev->dev,
|
||||
"HID_HP_AUTOTRIGGER out of range\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hid_device_io_start(hdev);
|
||||
hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT);
|
||||
hid_hw_wait(hdev);
|
||||
hid_device_io_stop(hdev);
|
||||
haptic->default_auto_trigger =
|
||||
field->value[usage->usage_index];
|
||||
haptic->auto_trigger_report = field->report;
|
||||
} else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_ORDINAL) {
|
||||
usage_hid = usage->hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
if (usage_hid > haptic->max_waveform_id)
|
||||
haptic->max_waveform_id = usage_hid;
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
if (usage_hid > haptic->max_duration_id)
|
||||
haptic->max_duration_id = usage_hid;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_feature_mapping);
|
||||
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
if (field->unit == HID_UNIT_GRAM || field->unit == HID_UNIT_NEWTON) {
|
||||
haptic->force_logical_minimum = field->logical_minimum;
|
||||
haptic->force_physical_minimum = field->physical_minimum;
|
||||
haptic->force_resolution = input_abs_get_res(hi->input,
|
||||
ABS_MT_PRESSURE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_check_pressure_unit);
|
||||
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
if (usage->hid == HID_HP_MANUALTRIGGER) {
|
||||
haptic->manual_trigger_report = field->report;
|
||||
/* we don't really want to map these fields */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_mapping);
|
||||
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
|
||||
if (hi->application == HID_DG_TOUCHPAD) {
|
||||
if (haptic->auto_trigger_report &&
|
||||
haptic->manual_trigger_report) {
|
||||
__set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_input_configured);
|
||||
|
||||
static void parse_auto_trigger_field(struct hid_haptic_device *haptic,
|
||||
struct hid_field *field)
|
||||
{
|
||||
int count = field->report_count;
|
||||
int n;
|
||||
u16 usage_hid;
|
||||
|
||||
for (n = 0; n < count; n++) {
|
||||
switch (field->usage[n].hid & HID_USAGE_PAGE) {
|
||||
case HID_UP_ORDINAL:
|
||||
usage_hid = field->usage[n].hid & HID_USAGE;
|
||||
switch (field->logical) {
|
||||
case HID_HP_WAVEFORMLIST:
|
||||
haptic->hid_usage_map[usage_hid] = field->value[n];
|
||||
if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
haptic->press_ordinal = usage_hid;
|
||||
} else if (field->value[n] ==
|
||||
(HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
haptic->release_ordinal = usage_hid;
|
||||
}
|
||||
break;
|
||||
case HID_HP_DURATIONLIST:
|
||||
haptic->duration_map[usage_hid] =
|
||||
field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case HID_UP_HAPTIC:
|
||||
switch (field->usage[n].hid) {
|
||||
case HID_HP_WAVEFORMVENDORID:
|
||||
haptic->vendor_id = field->value[n];
|
||||
break;
|
||||
case HID_HP_WAVEFORMVENDORPAGE:
|
||||
haptic->vendor_page = field->value[n];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Should not really happen */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fill_effect_buf(struct hid_haptic_device *haptic,
|
||||
struct ff_haptic_effect *effect,
|
||||
struct hid_haptic_effect *haptic_effect,
|
||||
int waveform_ordinal)
|
||||
{
|
||||
struct hid_report *rep = haptic->manual_trigger_report;
|
||||
struct hid_usage *usage;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
u8 *buf = haptic_effect->report_buf;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
usage = &field->usage[j];
|
||||
|
||||
switch (usage->hid) {
|
||||
case HID_HP_INTENSITY:
|
||||
if (effect->intensity > 100) {
|
||||
value = field->logical_maximum;
|
||||
} else {
|
||||
value = field->logical_minimum +
|
||||
effect->intensity *
|
||||
(field->logical_maximum -
|
||||
field->logical_minimum) / 100;
|
||||
}
|
||||
break;
|
||||
case HID_HP_REPEATCOUNT:
|
||||
value = effect->repeat_count;
|
||||
break;
|
||||
case HID_HP_RETRIGGERPERIOD:
|
||||
value = effect->retrigger_period;
|
||||
break;
|
||||
case HID_HP_MANUALTRIGGER:
|
||||
value = waveform_ordinal;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
hid_output_report(rep, buf);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
}
|
||||
|
||||
static void switch_mode(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
int mode)
|
||||
{
|
||||
struct hid_report *rep = haptic->auto_trigger_report;
|
||||
struct hid_field *field;
|
||||
s32 value;
|
||||
int i, j;
|
||||
|
||||
if (mode == HID_HAPTIC_MODE_HOST)
|
||||
value = HID_HAPTIC_ORDINAL_WAVEFORMSTOP;
|
||||
else
|
||||
value = haptic->default_auto_trigger;
|
||||
|
||||
mutex_lock(&haptic->auto_trigger_mutex);
|
||||
for (i = 0; i < rep->maxfield; i++) {
|
||||
field = rep->field[i];
|
||||
/* Ignore if report count is out of bounds. */
|
||||
if (field->report_count < 1)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < field->maxusage; j++) {
|
||||
if (field->usage[j].hid == HID_HP_AUTOTRIGGER)
|
||||
field->value[j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* send the report */
|
||||
hid_hw_request(hdev, rep, HID_REQ_SET_REPORT);
|
||||
mutex_unlock(&haptic->auto_trigger_mutex);
|
||||
haptic->mode = mode;
|
||||
}
|
||||
|
||||
static int hid_haptic_upload_effect(struct input_dev *dev, struct ff_effect *effect,
|
||||
struct ff_effect *old)
|
||||
{
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_device *ff = dev->ff;
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
int i, ordinal = 0;
|
||||
bool switch_modes = false;
|
||||
|
||||
/* If vendor range, check vendor id and page */
|
||||
if (effect->u.haptic.hid_usage >= (HID_HP_VENDORWAVEFORMMIN & HID_USAGE) &&
|
||||
effect->u.haptic.hid_usage <= (HID_HP_VENDORWAVEFORMMAX & HID_USAGE) &&
|
||||
(effect->u.haptic.vendor_id != haptic->vendor_id ||
|
||||
effect->u.haptic.vendor_waveform_page != haptic->vendor_page))
|
||||
return -EINVAL;
|
||||
|
||||
/* Check hid_usage */
|
||||
for (i = 1; i <= haptic->max_waveform_id; i++) {
|
||||
if (haptic->hid_usage_map[i] == effect->u.haptic.hid_usage) {
|
||||
ordinal = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ordinal < 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Fill the buffer for the effect id */
|
||||
fill_effect_buf(haptic, &effect->u.haptic, &haptic->effect[effect->id],
|
||||
ordinal);
|
||||
|
||||
if (effect->u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE) ||
|
||||
effect->u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE))
|
||||
switch_modes = true;
|
||||
|
||||
/* If device is in autonomous mode, and the uploaded effect signals userspace
|
||||
* wants control of the device, change modes
|
||||
*/
|
||||
if (switch_modes && haptic->mode == HID_HAPTIC_MODE_DEVICE)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_HOST);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int play_effect(struct hid_device *hdev, struct hid_haptic_device *haptic,
|
||||
struct hid_haptic_effect *effect)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = hid_hw_output_report(hdev, effect->report_buf,
|
||||
haptic->manual_trigger_report_len);
|
||||
if (ret < 0) {
|
||||
ret = hid_hw_raw_request(hdev,
|
||||
haptic->manual_trigger_report->id,
|
||||
effect->report_buf,
|
||||
haptic->manual_trigger_report_len,
|
||||
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void haptic_work_handler(struct work_struct *work)
|
||||
{
|
||||
|
||||
struct hid_haptic_effect *effect = container_of(work,
|
||||
struct hid_haptic_effect,
|
||||
work);
|
||||
struct input_dev *dev = effect->input_dev;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
mutex_lock(&haptic->manual_trigger_mutex);
|
||||
if (effect != &haptic->stop_effect)
|
||||
play_effect(hdev, haptic, &haptic->stop_effect);
|
||||
|
||||
play_effect(hdev, haptic, effect);
|
||||
mutex_unlock(&haptic->manual_trigger_mutex);
|
||||
|
||||
}
|
||||
|
||||
static int hid_haptic_playback(struct input_dev *dev, int effect_id, int value)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
|
||||
if (value)
|
||||
queue_work(haptic->wq, &haptic->effect[effect_id].work);
|
||||
else
|
||||
queue_work(haptic->wq, &haptic->stop_effect.work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void effect_set_default(struct ff_effect *effect)
|
||||
{
|
||||
effect->type = FF_HAPTIC;
|
||||
effect->id = -1;
|
||||
effect->u.haptic.hid_usage = HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
effect->u.haptic.intensity = 100;
|
||||
effect->u.haptic.retrigger_period = 0;
|
||||
effect->u.haptic.repeat_count = 0;
|
||||
}
|
||||
|
||||
static int hid_haptic_erase(struct input_dev *dev, int effect_id)
|
||||
{
|
||||
struct hid_haptic_device *haptic = dev->ff->private;
|
||||
struct hid_device *hdev = input_get_drvdata(dev);
|
||||
struct ff_effect effect;
|
||||
int ordinal;
|
||||
|
||||
effect_set_default(&effect);
|
||||
|
||||
if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMRELEASE & HID_USAGE)) {
|
||||
ordinal = haptic->release_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
} else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMRELEASE & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
} else if (effect.u.haptic.hid_usage == (HID_HP_WAVEFORMPRESS & HID_USAGE)) {
|
||||
ordinal = haptic->press_ordinal;
|
||||
if (!ordinal) {
|
||||
ordinal = HID_HAPTIC_ORDINAL_WAVEFORMNONE;
|
||||
if (haptic->mode == HID_HAPTIC_MODE_HOST)
|
||||
switch_mode(hdev, haptic, HID_HAPTIC_MODE_DEVICE);
|
||||
}
|
||||
else
|
||||
effect.u.haptic.hid_usage = HID_HP_WAVEFORMPRESS & HID_USAGE;
|
||||
|
||||
fill_effect_buf(haptic, &effect.u.haptic, &haptic->effect[effect_id],
|
||||
ordinal);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hid_haptic_destroy(struct ff_device *ff)
|
||||
{
|
||||
struct hid_haptic_device *haptic = ff->private;
|
||||
struct hid_device *hdev = haptic->hdev;
|
||||
int r;
|
||||
|
||||
if (hdev)
|
||||
put_device(&hdev->dev);
|
||||
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
|
||||
if (haptic->effect) {
|
||||
for (r = 0; r < ff->max_effects; r++)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
}
|
||||
haptic->effect = NULL;
|
||||
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
||||
int hid_haptic_init(struct hid_device *hdev,
|
||||
struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
struct hid_haptic_device *haptic = *haptic_ptr;
|
||||
struct input_dev *dev = NULL;
|
||||
struct hid_input *hidinput;
|
||||
struct ff_device *ff;
|
||||
int ret = 0, r;
|
||||
struct ff_haptic_effect stop_effect = {
|
||||
.hid_usage = HID_HP_WAVEFORMSTOP & HID_USAGE,
|
||||
};
|
||||
const char *prefix = "hid-haptic";
|
||||
char *name;
|
||||
int (*flush)(struct input_dev *dev, struct file *file);
|
||||
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
|
||||
|
||||
haptic->hdev = hdev;
|
||||
haptic->max_waveform_id = max(2u, haptic->max_waveform_id);
|
||||
haptic->max_duration_id = max(2u, haptic->max_duration_id);
|
||||
|
||||
haptic->hid_usage_map = kcalloc(haptic->max_waveform_id + 1,
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
if (!haptic->hid_usage_map) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
haptic->duration_map = kcalloc(haptic->max_duration_id + 1,
|
||||
sizeof(u32), GFP_KERNEL);
|
||||
if (!haptic->duration_map) {
|
||||
ret = -ENOMEM;
|
||||
goto usage_map;
|
||||
}
|
||||
|
||||
if (haptic->max_waveform_id != haptic->max_duration_id)
|
||||
dev_warn(&hdev->dev,
|
||||
"Haptic duration and waveform lists have different max id (%u and %u).\n",
|
||||
haptic->max_duration_id, haptic->max_waveform_id);
|
||||
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMNONE] =
|
||||
HID_HP_WAVEFORMNONE & HID_USAGE;
|
||||
haptic->hid_usage_map[HID_HAPTIC_ORDINAL_WAVEFORMSTOP] =
|
||||
HID_HP_WAVEFORMSTOP & HID_USAGE;
|
||||
|
||||
mutex_init(&haptic->auto_trigger_mutex);
|
||||
for (r = 0; r < haptic->auto_trigger_report->maxfield; r++)
|
||||
parse_auto_trigger_field(haptic, haptic->auto_trigger_report->field[r]);
|
||||
|
||||
list_for_each_entry(hidinput, &hdev->inputs, list) {
|
||||
if (hidinput->application == HID_DG_TOUCHPAD) {
|
||||
dev = hidinput->input;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dev) {
|
||||
dev_err(&hdev->dev, "Failed to find the input device\n");
|
||||
ret = -ENODEV;
|
||||
goto duration_map;
|
||||
}
|
||||
|
||||
haptic->input_dev = dev;
|
||||
haptic->manual_trigger_report_len =
|
||||
hid_report_len(haptic->manual_trigger_report);
|
||||
mutex_init(&haptic->manual_trigger_mutex);
|
||||
name = kmalloc(strlen(prefix) + strlen(hdev->name) + 2, GFP_KERNEL);
|
||||
if (name) {
|
||||
sprintf(name, "%s %s", prefix, hdev->name);
|
||||
haptic->wq = create_singlethread_workqueue(name);
|
||||
kfree(name);
|
||||
}
|
||||
if (!haptic->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto duration_map;
|
||||
}
|
||||
haptic->effect = kzalloc_objs(struct hid_haptic_effect, FF_MAX_EFFECTS);
|
||||
if (!haptic->effect) {
|
||||
ret = -ENOMEM;
|
||||
goto output_queue;
|
||||
}
|
||||
for (r = 0; r < FF_MAX_EFFECTS; r++) {
|
||||
haptic->effect[r].report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->effect[r].report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for an effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->effect[r].input_dev = dev;
|
||||
INIT_WORK(&haptic->effect[r].work, haptic_work_handler);
|
||||
}
|
||||
haptic->stop_effect.report_buf =
|
||||
hid_alloc_report_buf(haptic->manual_trigger_report,
|
||||
GFP_KERNEL);
|
||||
if (!haptic->stop_effect.report_buf) {
|
||||
dev_err(&hdev->dev,
|
||||
"Failed to allocate a buffer for stop effect.\n");
|
||||
ret = -ENOMEM;
|
||||
goto buffer_free;
|
||||
}
|
||||
haptic->stop_effect.input_dev = dev;
|
||||
INIT_WORK(&haptic->stop_effect.work, haptic_work_handler);
|
||||
fill_effect_buf(haptic, &stop_effect, &haptic->stop_effect,
|
||||
HID_HAPTIC_ORDINAL_WAVEFORMSTOP);
|
||||
|
||||
input_set_capability(dev, EV_FF, FF_HAPTIC);
|
||||
|
||||
flush = dev->flush;
|
||||
event = dev->event;
|
||||
ret = input_ff_create(dev, FF_MAX_EFFECTS);
|
||||
if (ret) {
|
||||
dev_err(&hdev->dev, "Failed to create ff device.\n");
|
||||
goto stop_buffer_free;
|
||||
}
|
||||
|
||||
ff = dev->ff;
|
||||
ff->private = haptic;
|
||||
ff->upload = hid_haptic_upload_effect;
|
||||
ff->playback = hid_haptic_playback;
|
||||
ff->erase = hid_haptic_erase;
|
||||
ff->destroy = hid_haptic_destroy;
|
||||
if (!try_module_get(THIS_MODULE)) {
|
||||
dev_err(&hdev->dev, "Failed to increase module count.\n");
|
||||
goto input_free;
|
||||
}
|
||||
if (!get_device(&hdev->dev)) {
|
||||
dev_err(&hdev->dev, "Failed to get hdev device.\n");
|
||||
module_put(THIS_MODULE);
|
||||
goto input_free;
|
||||
}
|
||||
return 0;
|
||||
|
||||
input_free:
|
||||
input_ff_destroy(dev);
|
||||
/* Do not let double free happen, input_ff_destroy will call
|
||||
* hid_haptic_destroy.
|
||||
*/
|
||||
*haptic_ptr = NULL;
|
||||
/* Restore dev flush and event */
|
||||
dev->flush = flush;
|
||||
dev->event = event;
|
||||
return ret;
|
||||
stop_buffer_free:
|
||||
kfree(haptic->stop_effect.report_buf);
|
||||
haptic->stop_effect.report_buf = NULL;
|
||||
buffer_free:
|
||||
while (--r >= 0)
|
||||
kfree(haptic->effect[r].report_buf);
|
||||
kfree(haptic->effect);
|
||||
haptic->effect = NULL;
|
||||
output_queue:
|
||||
destroy_workqueue(haptic->wq);
|
||||
haptic->wq = NULL;
|
||||
duration_map:
|
||||
kfree(haptic->duration_map);
|
||||
haptic->duration_map = NULL;
|
||||
usage_map:
|
||||
kfree(haptic->hid_usage_map);
|
||||
haptic->hid_usage_map = NULL;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_init);
|
||||
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic)
|
||||
{
|
||||
haptic->pressure_sum = 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_reset);
|
||||
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{
|
||||
haptic->pressure_sum += pressure;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hid_haptic_pressure_increase);
|
||||
@@ -0,0 +1,127 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* HID Haptic support for Linux
|
||||
*
|
||||
* Copyright (c) 2021 Angela Czubak <acz@semihalf.com>
|
||||
*/
|
||||
|
||||
#include <linux/hid.h>
|
||||
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMNONE 1
|
||||
#define HID_HAPTIC_ORDINAL_WAVEFORMSTOP 2
|
||||
|
||||
#define HID_HAPTIC_MODE_DEVICE 0
|
||||
#define HID_HAPTIC_MODE_HOST 1
|
||||
|
||||
struct hid_haptic_effect {
|
||||
u8 *report_buf;
|
||||
struct input_dev *input_dev;
|
||||
struct work_struct work;
|
||||
struct list_head control;
|
||||
struct mutex control_mutex;
|
||||
};
|
||||
|
||||
struct hid_haptic_effect_node {
|
||||
struct list_head node;
|
||||
struct file *file;
|
||||
};
|
||||
|
||||
struct hid_haptic_device {
|
||||
struct input_dev *input_dev;
|
||||
struct hid_device *hdev;
|
||||
struct hid_report *auto_trigger_report;
|
||||
struct mutex auto_trigger_mutex;
|
||||
struct workqueue_struct *wq;
|
||||
struct hid_report *manual_trigger_report;
|
||||
struct mutex manual_trigger_mutex;
|
||||
size_t manual_trigger_report_len;
|
||||
int pressed_state;
|
||||
s32 pressure_sum;
|
||||
s32 force_logical_minimum;
|
||||
s32 force_physical_minimum;
|
||||
s32 force_resolution;
|
||||
u32 mode;
|
||||
u32 default_auto_trigger;
|
||||
u32 vendor_page;
|
||||
u32 vendor_id;
|
||||
u32 max_waveform_id;
|
||||
u32 max_duration_id;
|
||||
u16 *hid_usage_map;
|
||||
u32 *duration_map;
|
||||
u16 press_ordinal;
|
||||
u16 release_ordinal;
|
||||
struct hid_haptic_effect *effect;
|
||||
struct hid_haptic_effect stop_effect;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_HID_HAPTIC)
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage);
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field);
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max);
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi);
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr);
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic);
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure);
|
||||
#else
|
||||
static inline
|
||||
void hid_haptic_feature_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_field *field, struct hid_usage
|
||||
*usage)
|
||||
{}
|
||||
static inline
|
||||
bool hid_haptic_check_pressure_unit(struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi, struct hid_field *field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_mapping(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi,
|
||||
struct hid_field *field, struct hid_usage *usage,
|
||||
unsigned long **bit, int *max)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
int hid_haptic_input_configured(struct hid_device *hdev,
|
||||
struct hid_haptic_device *haptic,
|
||||
struct hid_input *hi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_reset(struct hid_device *hdev, struct hid_haptic_device *haptic)
|
||||
{}
|
||||
static inline
|
||||
int hid_haptic_init(struct hid_device *hdev, struct hid_haptic_device **haptic_ptr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_handle_press_release(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
bool hid_haptic_handle_input(struct hid_haptic_device *haptic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline
|
||||
void hid_haptic_pressure_reset(struct hid_haptic_device *haptic) {}
|
||||
static inline
|
||||
void hid_haptic_pressure_increase(struct hid_haptic_device *haptic,
|
||||
__s32 pressure)
|
||||
{}
|
||||
#endif
|
||||
@@ -149,7 +149,7 @@ static int holtekff_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
holtekff = kzalloc(sizeof(*holtekff), GFP_KERNEL);
|
||||
holtekff = kzalloc_obj(*holtekff);
|
||||
if (!holtekff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* HID driver for some huawei "special" devices
|
||||
*
|
||||
* Copyright (c) 2026 Miao Li <limiao@kylinos.cn>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
static const __u8 huawei_cd30_kbd_rdesc_fixed[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x80, /* Usage (System Control) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x85, 0x01, /* Report ID (1) */
|
||||
0x19, 0x81, /* Usage Minimum (System Power Down) */
|
||||
0x29, 0x83, /* Usage Maximum (System Wake Up) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x75, 0x01, /* Report Size (1 bit) */
|
||||
0x95, 0x03, /* Report Count (3) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x81, 0x01, /* Input (Cnst,Ary,Abs) */
|
||||
0xc0, /* End Collection */
|
||||
0x05, 0x0c, /* Usage Page (Consumer) */
|
||||
0x09, 0x01, /* Usage (Consumer Control) */
|
||||
0xa1, 0x01, /* Collection (Application) */
|
||||
0x85, 0x02, /* Report ID (2) */
|
||||
0x19, 0x00, /* Usage Minimum (0) */
|
||||
0x2a, 0x3c, 0x02, /* Usage Maximum (0x023C) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x26, 0x3c, 0x02, /* Logical Maximum (0x023C) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x10, /* Report Size (16 bits) */
|
||||
0x81, 0x00, /* Input (Data,Ary,Abs) */
|
||||
0xc0 /* End Collection */
|
||||
};
|
||||
|
||||
static const __u8 *huawei_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
|
||||
switch (hdev->product) {
|
||||
case USB_DEVICE_ID_HUAWEI_CD30KBD:
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||
if (*rsize != sizeof(huawei_cd30_kbd_rdesc_fixed) ||
|
||||
memcmp(huawei_cd30_kbd_rdesc_fixed, rdesc,
|
||||
sizeof(huawei_cd30_kbd_rdesc_fixed)) != 0) {
|
||||
hid_info(hdev, "Replacing Huawei cd30 keyboard report descriptor.\n");
|
||||
*rsize = sizeof(huawei_cd30_kbd_rdesc_fixed);
|
||||
return huawei_cd30_kbd_rdesc_fixed;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return rdesc;
|
||||
}
|
||||
|
||||
static const struct hid_device_id huawei_devices[] = {
|
||||
/* HUAWEI cd30 keyboard */
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HUAWEI, USB_DEVICE_ID_HUAWEI_CD30KBD)},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, huawei_devices);
|
||||
|
||||
static struct hid_driver huawei_driver = {
|
||||
.name = "huawei",
|
||||
.id_table = huawei_devices,
|
||||
.report_fixup = huawei_report_fixup,
|
||||
};
|
||||
module_hid_driver(huawei_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Miao Li <limiao@kylinos.cn>");
|
||||
MODULE_DESCRIPTION("HID driver for some huawei \"special\" devices");
|
||||
@@ -149,7 +149,7 @@ static struct mousevsc_dev *mousevsc_alloc_device(struct hv_device *device)
|
||||
{
|
||||
struct mousevsc_dev *input_dev;
|
||||
|
||||
input_dev = kzalloc(sizeof(struct mousevsc_dev), GFP_KERNEL);
|
||||
input_dev = kzalloc_obj(struct mousevsc_dev);
|
||||
|
||||
if (!input_dev)
|
||||
return NULL;
|
||||
|
||||
+82
-6
@@ -22,6 +22,9 @@
|
||||
#define USB_DEVICE_ID_3M2256 0x0502
|
||||
#define USB_DEVICE_ID_3M3266 0x0506
|
||||
|
||||
#define USB_VENDOR_ID_8BITDO 0x2dc8
|
||||
#define USB_DEVICE_ID_8BITDO_PRO_3 0x6009
|
||||
|
||||
#define USB_VENDOR_ID_A4TECH 0x09da
|
||||
#define USB_DEVICE_ID_A4TECH_WCP32PU 0x0006
|
||||
#define USB_DEVICE_ID_A4TECH_X5_005D 0x000a
|
||||
@@ -272,6 +275,9 @@
|
||||
#define USB_VENDOR_ID_BIGBEN 0x146b
|
||||
#define USB_DEVICE_ID_BIGBEN_PS3OFMINIPAD 0x0902
|
||||
|
||||
#define I2C_VENDOR_ID_BLTP 0x36b6
|
||||
#define I2C_PRODUCT_ID_BLTP7853 0xc001
|
||||
|
||||
#define USB_VENDOR_ID_BTC 0x046e
|
||||
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE 0x5578
|
||||
#define USB_DEVICE_ID_BTC_EMPREX_REMOTE_2 0x5577
|
||||
@@ -317,6 +323,7 @@
|
||||
#define USB_DEVICE_ID_CHICONY_ACER_SWITCH12 0x1421
|
||||
#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA 0xb824
|
||||
#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2 0xb82c
|
||||
#define USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3 0xb882
|
||||
|
||||
#define USB_VENDOR_ID_CHUNGHWAT 0x2247
|
||||
#define USB_DEVICE_ID_CHUNGHWAT_MULTITOUCH 0x0001
|
||||
@@ -363,6 +370,12 @@
|
||||
#define USB_DEVICE_ID_PRODIKEYS_PCMIDI 0x2801
|
||||
#define USB_DEVICE_ID_CREATIVE_SB0540 0x3100
|
||||
|
||||
#define USB_VENDOR_ID_CRKD 0x3651
|
||||
#define USB_DEVICE_ID_CRKD_PS4_GIBSON_SG 0x1500
|
||||
#define USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE 0x5500
|
||||
#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG 0x1600
|
||||
#define USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE 0x5600
|
||||
|
||||
#define USB_VENDOR_ID_CVTOUCH 0x1ff7
|
||||
#define USB_DEVICE_ID_CVTOUCH_SCREEN 0x0013
|
||||
|
||||
@@ -436,21 +449,26 @@
|
||||
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_7349 0x7349
|
||||
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_73F7 0x73f7
|
||||
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001 0xa001
|
||||
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000 0xc000
|
||||
#define USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002 0xc002
|
||||
|
||||
#define USB_VENDOR_ID_EDIFIER 0x2d99
|
||||
#define USB_DEVICE_ID_EDIFIER_QR30 0xa101 /* EDIFIER Hal0 2.0 SE */
|
||||
|
||||
#define USB_VENDOR_ID_ELAN 0x04f3
|
||||
#define USB_DEVICE_ID_SB974D 0x0400
|
||||
#define USB_DEVICE_ID_TOSHIBA_CLICK_L9W 0x0401
|
||||
#define USB_DEVICE_ID_HP_X2 0x074d
|
||||
#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
|
||||
#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
|
||||
#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
|
||||
#define I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM 0x2F81
|
||||
|
||||
#define USB_VENDOR_ID_ELECOM 0x056e
|
||||
#define USB_DEVICE_ID_ELECOM_BM084 0x0061
|
||||
#define USB_DEVICE_ID_ELECOM_M_XGL20DLBK 0x00e6
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3URBK 0x00fb
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3DRBK 0x00fc
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB 0x00fb
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3URBK_018F 0x018f
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC 0x00fc
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C 0x018c
|
||||
#define USB_DEVICE_ID_ELECOM_M_XT4DRBK 0x00fd
|
||||
#define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe
|
||||
#define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff
|
||||
@@ -459,6 +477,9 @@
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1URBK_019B 0x019b
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1MRBK 0x01aa
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB 0x01ab
|
||||
#define USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC 0x01ac
|
||||
|
||||
#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
|
||||
#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004
|
||||
@@ -476,6 +497,7 @@
|
||||
#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
|
||||
|
||||
#define USB_VENDOR_ID_EVISION 0x320f
|
||||
#define USB_DEVICE_ID_EV_TELINK_RECEIVER 0x226f
|
||||
#define USB_DEVICE_ID_EVISION_ICL01 0x5041
|
||||
|
||||
#define USB_VENDOR_ID_FFBEAST 0x045b
|
||||
@@ -647,6 +669,18 @@
|
||||
#define USB_DEVICE_ID_UGCI_FLYING 0x0020
|
||||
#define USB_DEVICE_ID_UGCI_FIGHTING 0x0030
|
||||
|
||||
#define USB_VENDOR_ID_HARMONIX 0x1bad
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR 0x0004
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR 0x3010
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS 0x0005
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS 0x3110
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE 0x3138
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR 0x3430
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE 0x3438
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE 0x3538
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD 0x3330
|
||||
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE 0x3338
|
||||
|
||||
#define USB_VENDOR_ID_HP 0x03f0
|
||||
#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
|
||||
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
|
||||
@@ -718,9 +752,14 @@
|
||||
#define USB_DEVICE_ID_ITE_LENOVO_YOGA2 0x8350
|
||||
#define I2C_DEVICE_ID_ITE_LENOVO_LEGION_Y720 0x837a
|
||||
#define USB_DEVICE_ID_ITE_LENOVO_YOGA900 0x8396
|
||||
#define I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD 0x8987
|
||||
#define USB_DEVICE_ID_ITE8595 0x8595
|
||||
#define USB_DEVICE_ID_ITE_MEDION_E1239T 0xce50
|
||||
|
||||
#define USB_VENDOR_ID_QHE 0x1a86
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_XINPUT 0xe310
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO_S_DINPUT 0xe311
|
||||
|
||||
#define USB_VENDOR_ID_JABRA 0x0b0e
|
||||
#define USB_DEVICE_ID_JABRA_SPEAK_410 0x0412
|
||||
#define USB_DEVICE_ID_JABRA_SPEAK_510 0x0420
|
||||
@@ -833,13 +872,17 @@
|
||||
#define USB_DEVICE_ID_LENOVO_X1_TAB3 0x60b5
|
||||
#define USB_DEVICE_ID_LENOVO_X12_TAB 0x60fe
|
||||
#define USB_DEVICE_ID_LENOVO_X12_TAB2 0x61ae
|
||||
#define USB_DEVICE_ID_LENOVO_YOGABOOK9I 0x6161
|
||||
#define USB_DEVICE_ID_LENOVO_OPTICAL_USB_MOUSE_600E 0x600e
|
||||
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d
|
||||
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019 0x6019
|
||||
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e
|
||||
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO_DUAL_DINPUT 0x6184
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO2_XINPUT 0x61eb
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DINPUT 0x61ec
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO2_DUAL_DINPUT 0x61ed
|
||||
#define USB_DEVICE_ID_LENOVO_LEGION_GO2_FPS 0x61ee
|
||||
|
||||
#define USB_VENDOR_ID_LETSKETCH 0x6161
|
||||
#define USB_DEVICE_ID_WP9620N 0x4d15
|
||||
@@ -879,6 +922,7 @@
|
||||
#define USB_DEVICE_ID_LOGITECH_DUAL_ACTION 0xc216
|
||||
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2 0xc218
|
||||
#define USB_DEVICE_ID_LOGITECH_RUMBLEPAD2_2 0xc219
|
||||
#define USB_DEVICE_ID_LOGITECH_G13 0xc21c
|
||||
#define USB_DEVICE_ID_LOGITECH_G15_LCD 0xc222
|
||||
#define USB_DEVICE_ID_LOGITECH_G11 0xc225
|
||||
#define USB_DEVICE_ID_LOGITECH_G15_V2_LCD 0xc227
|
||||
@@ -914,6 +958,8 @@
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1 0xc539
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_1 0xc53f
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2 0xc543
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3 0xc547
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4 0xc54d
|
||||
#define USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_POWERPLAY 0xc53a
|
||||
#define USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER 0xc548
|
||||
#define USB_DEVICE_ID_SPACETRAVELLER 0xc623
|
||||
@@ -1091,6 +1137,11 @@
|
||||
#define USB_DEVICE_ID_ORTEK_WKB2000 0x2000
|
||||
#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003
|
||||
|
||||
#define USB_VENDOR_ID_PDP 0x0e6F
|
||||
#define USB_DEVICE_ID_PDP_PS4_JAGUAR 0x0173
|
||||
#define USB_DEVICE_ID_PDP_PS4_RIFFMASTER 0x024a
|
||||
#define USB_DEVICE_ID_PDP_PS5_RIFFMASTER 0x0249
|
||||
|
||||
#define USB_VENDOR_ID_PLANTRONICS 0x047f
|
||||
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055
|
||||
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
|
||||
@@ -1155,6 +1206,9 @@
|
||||
#define I2C_PRODUCT_ID_RAYDIUM_4B33 0x4b33
|
||||
#define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118
|
||||
|
||||
#define USB_VENDOR_ID_RAPOO 0x24ae
|
||||
#define USB_DEVICE_ID_RAPOO_2_4G_RECEIVER 0x2015
|
||||
|
||||
#define USB_VENDOR_ID_RAZER 0x1532
|
||||
#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D
|
||||
#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e
|
||||
@@ -1228,6 +1282,7 @@
|
||||
|
||||
#define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f
|
||||
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002
|
||||
#define USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE 0x0034
|
||||
#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059
|
||||
|
||||
#define USB_VENDOR_ID_SIGMATEL 0x066F
|
||||
@@ -1268,8 +1323,18 @@
|
||||
#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER 0x1000
|
||||
|
||||
#define USB_VENDOR_ID_SONY_RHYTHM 0x12ba
|
||||
#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE_DONGLE 0x074b
|
||||
#define USB_DEVICE_ID_SONY_PS3_GUITAR_DONGLE 0x0100
|
||||
#define USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE 0x074b
|
||||
#define USB_DEVICE_ID_SONY_PS3_GH_GUITAR 0x0100
|
||||
#define USB_DEVICE_ID_SONY_PS3_GH_DRUMS 0x0120
|
||||
#define USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE 0x0140
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB_GUITAR 0x0200
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB_DRUMS 0x0210
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE 0x0218
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR 0x2430
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE 0x2438
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE 0x2538
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD 0x2330
|
||||
#define USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE 0x2338
|
||||
|
||||
#define USB_VENDOR_ID_SINO_LITE 0x1345
|
||||
#define USB_DEVICE_ID_SINO_LITE_CONTROLLER 0x3008
|
||||
@@ -1419,6 +1484,7 @@
|
||||
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933
|
||||
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
|
||||
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO 0x091b
|
||||
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO 0x092d
|
||||
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
|
||||
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
|
||||
#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055
|
||||
@@ -1440,6 +1506,10 @@
|
||||
#define USB_VENDOR_ID_VTL 0x0306
|
||||
#define USB_DEVICE_ID_VTL_MULTITOUCH_FF3F 0xff3f
|
||||
|
||||
#define USB_VENDOR_ID_VXE 0x3554
|
||||
#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE 0xf58a
|
||||
#define USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED 0xf58c
|
||||
|
||||
#define USB_VENDOR_ID_WACOM 0x056a
|
||||
#define USB_DEVICE_ID_WACOM_GRAPHIRE_BLUETOOTH 0x81
|
||||
#define USB_DEVICE_ID_WACOM_INTUOS4_BLUETOOTH 0x00BD
|
||||
@@ -1524,6 +1594,9 @@
|
||||
#define USB_VENDOR_ID_RISO_KAGAKU 0x1294 /* Riso Kagaku Corp. */
|
||||
#define USB_DEVICE_ID_RI_KA_WEBMAIL 0x1320 /* Webmail Notifier */
|
||||
|
||||
#define USB_VENDOR_ID_MADCATZ 0x0738
|
||||
#define USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER 0x8261
|
||||
|
||||
#define USB_VENDOR_ID_MULTIPLE_1781 0x1781
|
||||
#define USB_DEVICE_ID_RAPHNET_4NES4SNES_OLD 0x0a9d
|
||||
#define USB_DEVICE_ID_PHOENIXRC 0x0898
|
||||
@@ -1546,4 +1619,7 @@
|
||||
#define USB_VENDOR_ID_JIELI_SDK_DEFAULT 0x4c4a
|
||||
#define USB_DEVICE_ID_JIELI_SDK_4155 0x4155
|
||||
|
||||
#define USB_VENDOR_ID_HUAWEI 0x12d1
|
||||
#define USB_DEVICE_ID_HUAWEI_CD30KBD 0x109b
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,54 +9,59 @@
|
||||
|
||||
static void hid_test_input_update_battery_charge_status(struct kunit *test)
|
||||
{
|
||||
struct hid_device *dev;
|
||||
struct hid_battery *bat;
|
||||
bool handled;
|
||||
|
||||
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
|
||||
bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat);
|
||||
|
||||
handled = hidinput_update_battery_charge_status(dev, HID_DG_HEIGHT, 0);
|
||||
handled = hidinput_update_battery_charge_status(bat, HID_DG_HEIGHT, 0);
|
||||
KUNIT_EXPECT_FALSE(test, handled);
|
||||
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
|
||||
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
|
||||
|
||||
handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 0);
|
||||
handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 0);
|
||||
KUNIT_EXPECT_TRUE(test, handled);
|
||||
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
|
||||
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
|
||||
|
||||
handled = hidinput_update_battery_charge_status(dev, HID_BAT_CHARGING, 1);
|
||||
handled = hidinput_update_battery_charge_status(bat, HID_BAT_CHARGING, 1);
|
||||
KUNIT_EXPECT_TRUE(test, handled);
|
||||
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
|
||||
KUNIT_EXPECT_EQ(test, bat->charge_status, POWER_SUPPLY_STATUS_CHARGING);
|
||||
}
|
||||
|
||||
static void hid_test_input_get_battery_property(struct kunit *test)
|
||||
{
|
||||
struct power_supply *psy;
|
||||
struct hid_battery *bat;
|
||||
struct hid_device *dev;
|
||||
union power_supply_propval val;
|
||||
int ret;
|
||||
|
||||
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
|
||||
dev->battery_avoid_query = true;
|
||||
|
||||
bat = kunit_kzalloc(test, sizeof(*bat), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bat);
|
||||
bat->dev = dev;
|
||||
bat->avoid_query = true;
|
||||
|
||||
psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
|
||||
psy->drv_data = dev;
|
||||
psy->drv_data = bat;
|
||||
|
||||
dev->battery_status = HID_BATTERY_UNKNOWN;
|
||||
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
bat->status = HID_BATTERY_UNKNOWN;
|
||||
bat->charge_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN);
|
||||
|
||||
dev->battery_status = HID_BATTERY_REPORTED;
|
||||
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
bat->status = HID_BATTERY_REPORTED;
|
||||
bat->charge_status = POWER_SUPPLY_STATUS_CHARGING;
|
||||
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING);
|
||||
|
||||
dev->battery_status = HID_BATTERY_REPORTED;
|
||||
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
bat->status = HID_BATTERY_REPORTED;
|
||||
bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING);
|
||||
|
||||
+169
-107
@@ -303,6 +303,19 @@ __s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code)
|
||||
}
|
||||
break;
|
||||
|
||||
case ABS_PRESSURE:
|
||||
case ABS_MT_PRESSURE:
|
||||
if (field->unit == HID_UNIT_NEWTON) {
|
||||
/* Convert to grams, 1 newton is 101.97 grams */
|
||||
prev = physical_extents;
|
||||
physical_extents *= 10197;
|
||||
if (physical_extents < prev)
|
||||
return 0;
|
||||
unit_exponent -= 2;
|
||||
} else if (field->unit != HID_UNIT_GRAM) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -341,6 +354,7 @@ static enum power_supply_property hidinput_battery_props[] = {
|
||||
#define HID_BATTERY_QUIRK_FEATURE (1 << 1) /* ask for feature report */
|
||||
#define HID_BATTERY_QUIRK_IGNORE (1 << 2) /* completely ignore the battery */
|
||||
#define HID_BATTERY_QUIRK_AVOID_QUERY (1 << 3) /* do not query the battery */
|
||||
#define HID_BATTERY_QUIRK_DYNAMIC (1 << 4) /* report present only after life signs */
|
||||
|
||||
static const struct hid_device_id hid_battery_quirks[] = {
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
|
||||
@@ -373,10 +387,6 @@ static const struct hid_device_id hid_battery_quirks[] = {
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN),
|
||||
HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L),
|
||||
HID_BATTERY_QUIRK_AVOID_QUERY },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
|
||||
@@ -386,10 +396,11 @@ static const struct hid_device_id hid_battery_quirks[] = {
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_CHROMEBOOK_TROGDOR_POMPOM),
|
||||
HID_BATTERY_QUIRK_AVOID_QUERY },
|
||||
/*
|
||||
* Elan I2C-HID touchscreens seem to all report a non present battery,
|
||||
* set HID_BATTERY_QUIRK_IGNORE for all Elan I2C-HID devices.
|
||||
* Elan HID touchscreens seem to all report a non present battery,
|
||||
* set HID_BATTERY_QUIRK_IGNORE for all Elan I2C and USB HID devices.
|
||||
*/
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_IGNORE },
|
||||
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_BATTERY_QUIRK_DYNAMIC },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -405,60 +416,59 @@ static unsigned find_battery_quirk(struct hid_device *hdev)
|
||||
return quirks;
|
||||
}
|
||||
|
||||
static int hidinput_scale_battery_capacity(struct hid_device *dev,
|
||||
static int hidinput_scale_battery_capacity(struct hid_battery *bat,
|
||||
int value)
|
||||
{
|
||||
if (dev->battery_min < dev->battery_max &&
|
||||
value >= dev->battery_min && value <= dev->battery_max)
|
||||
value = ((value - dev->battery_min) * 100) /
|
||||
(dev->battery_max - dev->battery_min);
|
||||
if (bat->min < bat->max &&
|
||||
value >= bat->min && value <= bat->max)
|
||||
value = ((value - bat->min) * 100) /
|
||||
(bat->max - bat->min);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static int hidinput_query_battery_capacity(struct hid_device *dev)
|
||||
static int hidinput_query_battery_capacity(struct hid_battery *bat)
|
||||
{
|
||||
u8 *buf;
|
||||
int ret;
|
||||
|
||||
buf = kmalloc(4, GFP_KERNEL);
|
||||
u8 *buf __free(kfree) = kmalloc(4, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4,
|
||||
dev->battery_report_type, HID_REQ_GET_REPORT);
|
||||
if (ret < 2) {
|
||||
kfree(buf);
|
||||
ret = hid_hw_raw_request(bat->dev, bat->report_id, buf, 4,
|
||||
bat->report_type, HID_REQ_GET_REPORT);
|
||||
if (ret < 2)
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
ret = hidinput_scale_battery_capacity(dev, buf[1]);
|
||||
kfree(buf);
|
||||
return ret;
|
||||
return hidinput_scale_battery_capacity(bat, buf[1]);
|
||||
}
|
||||
|
||||
static int hidinput_get_battery_property(struct power_supply *psy,
|
||||
enum power_supply_property prop,
|
||||
union power_supply_propval *val)
|
||||
{
|
||||
struct hid_device *dev = power_supply_get_drvdata(psy);
|
||||
struct hid_battery *bat = power_supply_get_drvdata(psy);
|
||||
struct hid_device *dev = bat->dev;
|
||||
int value;
|
||||
int ret = 0;
|
||||
|
||||
switch (prop) {
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
case POWER_SUPPLY_PROP_ONLINE:
|
||||
val->intval = 1;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_PRESENT:
|
||||
val->intval = bat->present;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_CAPACITY:
|
||||
if (dev->battery_status != HID_BATTERY_REPORTED &&
|
||||
!dev->battery_avoid_query) {
|
||||
value = hidinput_query_battery_capacity(dev);
|
||||
if (bat->status != HID_BATTERY_REPORTED &&
|
||||
!bat->avoid_query) {
|
||||
value = hidinput_query_battery_capacity(bat);
|
||||
if (value < 0)
|
||||
return value;
|
||||
} else {
|
||||
value = dev->battery_capacity;
|
||||
value = bat->capacity;
|
||||
}
|
||||
|
||||
val->intval = value;
|
||||
@@ -469,20 +479,20 @@ static int hidinput_get_battery_property(struct power_supply *psy,
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_STATUS:
|
||||
if (dev->battery_status != HID_BATTERY_REPORTED &&
|
||||
!dev->battery_avoid_query) {
|
||||
value = hidinput_query_battery_capacity(dev);
|
||||
if (bat->status != HID_BATTERY_REPORTED &&
|
||||
!bat->avoid_query) {
|
||||
value = hidinput_query_battery_capacity(bat);
|
||||
if (value < 0)
|
||||
return value;
|
||||
|
||||
dev->battery_capacity = value;
|
||||
dev->battery_status = HID_BATTERY_QUERIED;
|
||||
bat->capacity = value;
|
||||
bat->status = HID_BATTERY_QUERIED;
|
||||
}
|
||||
|
||||
if (dev->battery_status == HID_BATTERY_UNKNOWN)
|
||||
if (bat->status == HID_BATTERY_UNKNOWN)
|
||||
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||
else
|
||||
val->intval = dev->battery_charge_status;
|
||||
val->intval = bat->charge_status;
|
||||
break;
|
||||
|
||||
case POWER_SUPPLY_PROP_SCOPE:
|
||||
@@ -497,36 +507,66 @@ static int hidinput_get_battery_property(struct power_supply *psy,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct hid_battery *hidinput_find_battery(struct hid_device *dev,
|
||||
int report_id)
|
||||
{
|
||||
struct hid_battery *bat;
|
||||
|
||||
list_for_each_entry(bat, &dev->batteries, list) {
|
||||
if (bat->report_id == report_id)
|
||||
return bat;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void hidinput_cleanup_battery(void *res)
|
||||
{
|
||||
struct hid_battery *bat = res;
|
||||
|
||||
list_del(&bat->list);
|
||||
}
|
||||
|
||||
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
|
||||
struct hid_field *field, bool is_percentage)
|
||||
{
|
||||
struct hid_battery *bat;
|
||||
struct power_supply_desc *psy_desc;
|
||||
struct power_supply_config psy_cfg = { .drv_data = dev, };
|
||||
struct power_supply_config psy_cfg = { 0 };
|
||||
unsigned quirks;
|
||||
s32 min, max;
|
||||
int error;
|
||||
|
||||
if (dev->battery)
|
||||
return 0; /* already initialized? */
|
||||
/* Check if battery for this report ID already exists */
|
||||
if (hidinput_find_battery(dev, field->report->id))
|
||||
return 0;
|
||||
|
||||
quirks = find_battery_quirk(dev);
|
||||
|
||||
hid_dbg(dev, "device %x:%x:%x %d quirks %d\n",
|
||||
dev->bus, dev->vendor, dev->product, dev->version, quirks);
|
||||
hid_dbg(dev, "device %x:%x:%x %d quirks %d report_id %d\n",
|
||||
dev->bus, dev->vendor, dev->product, dev->version, quirks,
|
||||
field->report->id);
|
||||
|
||||
if (quirks & HID_BATTERY_QUIRK_IGNORE)
|
||||
return 0;
|
||||
|
||||
psy_desc = kzalloc(sizeof(*psy_desc), GFP_KERNEL);
|
||||
if (!psy_desc)
|
||||
bat = devm_kzalloc(&dev->dev, sizeof(*bat), GFP_KERNEL);
|
||||
if (!bat)
|
||||
return -ENOMEM;
|
||||
|
||||
psy_desc->name = kasprintf(GFP_KERNEL, "hid-%s-battery",
|
||||
psy_desc = devm_kzalloc(&dev->dev, sizeof(*psy_desc), GFP_KERNEL);
|
||||
if (!psy_desc) {
|
||||
error = -ENOMEM;
|
||||
goto err_free_bat;
|
||||
}
|
||||
|
||||
psy_desc->name = devm_kasprintf(&dev->dev, GFP_KERNEL,
|
||||
"hid-%s-battery-%d",
|
||||
strlen(dev->uniq) ?
|
||||
dev->uniq : dev_name(&dev->dev));
|
||||
dev->uniq : dev_name(&dev->dev),
|
||||
field->report->id);
|
||||
if (!psy_desc->name) {
|
||||
error = -ENOMEM;
|
||||
goto err_free_mem;
|
||||
goto err_free_desc;
|
||||
}
|
||||
|
||||
psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
|
||||
@@ -546,61 +586,60 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
|
||||
if (quirks & HID_BATTERY_QUIRK_FEATURE)
|
||||
report_type = HID_FEATURE_REPORT;
|
||||
|
||||
dev->battery_min = min;
|
||||
dev->battery_max = max;
|
||||
dev->battery_report_type = report_type;
|
||||
dev->battery_report_id = field->report->id;
|
||||
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
bat->dev = dev;
|
||||
bat->min = min;
|
||||
bat->max = max;
|
||||
bat->report_type = report_type;
|
||||
bat->report_id = field->report->id;
|
||||
bat->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
bat->status = HID_BATTERY_UNKNOWN;
|
||||
|
||||
/*
|
||||
* Stylus is normally not connected to the device and thus we
|
||||
* can't query the device and get meaningful battery strength.
|
||||
* We have to wait for the device to report it on its own.
|
||||
*/
|
||||
dev->battery_avoid_query = report_type == HID_INPUT_REPORT &&
|
||||
bat->avoid_query = report_type == HID_INPUT_REPORT &&
|
||||
field->physical == HID_DG_STYLUS;
|
||||
|
||||
if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY)
|
||||
dev->battery_avoid_query = true;
|
||||
bat->avoid_query = true;
|
||||
|
||||
dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
|
||||
if (IS_ERR(dev->battery)) {
|
||||
error = PTR_ERR(dev->battery);
|
||||
bat->present = (quirks & HID_BATTERY_QUIRK_DYNAMIC) ? false : true;
|
||||
|
||||
psy_cfg.drv_data = bat;
|
||||
bat->ps = devm_power_supply_register(&dev->dev, psy_desc, &psy_cfg);
|
||||
if (IS_ERR(bat->ps)) {
|
||||
error = PTR_ERR(bat->ps);
|
||||
hid_warn(dev, "can't register power supply: %d\n", error);
|
||||
goto err_free_name;
|
||||
}
|
||||
|
||||
power_supply_powers(dev->battery, &dev->dev);
|
||||
power_supply_powers(bat->ps, &dev->dev);
|
||||
list_add_tail(&bat->list, &dev->batteries);
|
||||
|
||||
error = devm_add_action_or_reset(&dev->dev,
|
||||
hidinput_cleanup_battery, bat);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_name:
|
||||
kfree(psy_desc->name);
|
||||
err_free_mem:
|
||||
kfree(psy_desc);
|
||||
dev->battery = NULL;
|
||||
devm_kfree(&dev->dev, psy_desc->name);
|
||||
err_free_desc:
|
||||
devm_kfree(&dev->dev, psy_desc);
|
||||
err_free_bat:
|
||||
devm_kfree(&dev->dev, bat);
|
||||
return error;
|
||||
}
|
||||
|
||||
static void hidinput_cleanup_battery(struct hid_device *dev)
|
||||
{
|
||||
const struct power_supply_desc *psy_desc;
|
||||
|
||||
if (!dev->battery)
|
||||
return;
|
||||
|
||||
psy_desc = dev->battery->desc;
|
||||
power_supply_unregister(dev->battery);
|
||||
kfree(psy_desc->name);
|
||||
kfree(psy_desc);
|
||||
dev->battery = NULL;
|
||||
}
|
||||
|
||||
static bool hidinput_update_battery_charge_status(struct hid_device *dev,
|
||||
static bool hidinput_update_battery_charge_status(struct hid_battery *bat,
|
||||
unsigned int usage, int value)
|
||||
{
|
||||
switch (usage) {
|
||||
case HID_BAT_CHARGING:
|
||||
dev->battery_charge_status = value ?
|
||||
bat->charge_status = value ?
|
||||
POWER_SUPPLY_STATUS_CHARGING :
|
||||
POWER_SUPPLY_STATUS_DISCHARGING;
|
||||
return true;
|
||||
@@ -609,35 +648,39 @@ static bool hidinput_update_battery_charge_status(struct hid_device *dev,
|
||||
return false;
|
||||
}
|
||||
|
||||
static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
|
||||
int value)
|
||||
static void hidinput_update_battery(struct hid_device *dev, int report_id,
|
||||
unsigned int usage, int value)
|
||||
{
|
||||
struct hid_battery *bat;
|
||||
int capacity;
|
||||
|
||||
if (!dev->battery)
|
||||
bat = hidinput_find_battery(dev, report_id);
|
||||
if (!bat)
|
||||
return;
|
||||
|
||||
if (hidinput_update_battery_charge_status(dev, usage, value)) {
|
||||
power_supply_changed(dev->battery);
|
||||
if (hidinput_update_battery_charge_status(bat, usage, value)) {
|
||||
bat->present = true;
|
||||
power_supply_changed(bat->ps);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((usage & HID_USAGE_PAGE) == HID_UP_DIGITIZER && value == 0)
|
||||
return;
|
||||
|
||||
if (value < dev->battery_min || value > dev->battery_max)
|
||||
if (value < bat->min || value > bat->max)
|
||||
return;
|
||||
|
||||
capacity = hidinput_scale_battery_capacity(dev, value);
|
||||
capacity = hidinput_scale_battery_capacity(bat, value);
|
||||
|
||||
if (dev->battery_status != HID_BATTERY_REPORTED ||
|
||||
capacity != dev->battery_capacity ||
|
||||
ktime_after(ktime_get_coarse(), dev->battery_ratelimit_time)) {
|
||||
dev->battery_capacity = capacity;
|
||||
dev->battery_status = HID_BATTERY_REPORTED;
|
||||
dev->battery_ratelimit_time =
|
||||
if (bat->status != HID_BATTERY_REPORTED ||
|
||||
capacity != bat->capacity ||
|
||||
ktime_after(ktime_get_coarse(), bat->ratelimit_time)) {
|
||||
bat->present = true;
|
||||
bat->capacity = capacity;
|
||||
bat->status = HID_BATTERY_REPORTED;
|
||||
bat->ratelimit_time =
|
||||
ktime_add_ms(ktime_get_coarse(), 30 * 1000);
|
||||
power_supply_changed(dev->battery);
|
||||
power_supply_changed(bat->ps);
|
||||
}
|
||||
}
|
||||
#else /* !CONFIG_HID_BATTERY_STRENGTH */
|
||||
@@ -647,12 +690,8 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hidinput_cleanup_battery(struct hid_device *dev)
|
||||
{
|
||||
}
|
||||
|
||||
static void hidinput_update_battery(struct hid_device *dev, unsigned int usage,
|
||||
int value)
|
||||
static void hidinput_update_battery(struct hid_device *dev, int report_id,
|
||||
unsigned int usage, int value)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_HID_BATTERY_STRENGTH */
|
||||
@@ -686,9 +725,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
if (field->report_count < 1)
|
||||
goto ignore;
|
||||
|
||||
/* only LED usages are supported in output fields */
|
||||
/* only LED and HAPTIC usages are supported in output fields */
|
||||
if (field->report_type == HID_OUTPUT_REPORT &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED) {
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_LED &&
|
||||
(usage->hid & HID_USAGE_PAGE) != HID_UP_HAPTIC) {
|
||||
goto ignore;
|
||||
}
|
||||
|
||||
@@ -863,7 +903,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
|
||||
switch (usage->hid) {
|
||||
/* These usage IDs map directly to the usage codes. */
|
||||
case HID_GD_X: case HID_GD_Y: case HID_GD_Z:
|
||||
case HID_GD_X: case HID_GD_Y:
|
||||
case HID_GD_RX: case HID_GD_RY: case HID_GD_RZ:
|
||||
if (field->flags & HID_MAIN_ITEM_RELATIVE)
|
||||
map_rel(usage->hid & 0xf);
|
||||
@@ -871,6 +911,22 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
map_abs_clear(usage->hid & 0xf);
|
||||
break;
|
||||
|
||||
case HID_GD_Z:
|
||||
/* HID_GD_Z is mapped to ABS_DISTANCE for stylus/pen */
|
||||
if (field->flags & HID_MAIN_ITEM_RELATIVE) {
|
||||
map_rel(usage->hid & 0xf);
|
||||
} else {
|
||||
if (field->application == HID_DG_PEN ||
|
||||
field->physical == HID_DG_PEN ||
|
||||
field->logical == HID_DG_STYLUS ||
|
||||
field->physical == HID_DG_STYLUS ||
|
||||
field->application == HID_DG_DIGITIZER)
|
||||
map_abs_clear(ABS_DISTANCE);
|
||||
else
|
||||
map_abs_clear(usage->hid & 0xf);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_GD_WHEEL:
|
||||
if (field->flags & HID_MAIN_ITEM_RELATIVE) {
|
||||
set_bit(REL_WHEEL, input->relbit);
|
||||
@@ -1192,6 +1248,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
|
||||
case 0x1bc: map_key_clear(KEY_MESSENGER); break;
|
||||
case 0x1bd: map_key_clear(KEY_INFO); break;
|
||||
case 0x1cb: map_key_clear(KEY_ASSISTANT); break;
|
||||
case 0x1cc: map_key_clear(KEY_ACTION_ON_SELECTION); break;
|
||||
case 0x1cd: map_key_clear(KEY_CONTEXTUAL_INSERT); break;
|
||||
case 0x1ce: map_key_clear(KEY_CONTEXTUAL_QUERY); break;
|
||||
case 0x201: map_key_clear(KEY_NEW); break;
|
||||
case 0x202: map_key_clear(KEY_OPEN); break;
|
||||
case 0x203: map_key_clear(KEY_CLOSE); break;
|
||||
@@ -1519,7 +1578,7 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
|
||||
return;
|
||||
|
||||
if (usage->type == EV_PWR) {
|
||||
hidinput_update_battery(hid, usage->hid, value);
|
||||
hidinput_update_battery(hid, report->id, usage->hid, value);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1801,7 +1860,6 @@ static void hidinput_led_worker(struct work_struct *work)
|
||||
struct hid_report *report;
|
||||
int ret;
|
||||
u32 len;
|
||||
__u8 *buf;
|
||||
|
||||
field = hidinput_get_led_field(hid);
|
||||
if (!field)
|
||||
@@ -1828,7 +1886,7 @@ static void hidinput_led_worker(struct work_struct *work)
|
||||
|
||||
/* fall back to generic raw-output-report */
|
||||
len = hid_report_len(report);
|
||||
buf = hid_alloc_report_buf(report, GFP_KERNEL);
|
||||
u8 *buf __free(kfree) = hid_alloc_report_buf(report, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
@@ -1838,7 +1896,6 @@ static void hidinput_led_worker(struct work_struct *work)
|
||||
if (ret == -ENOSYS)
|
||||
hid_hw_raw_request(hid, report->id, buf, len, HID_OUTPUT_REPORT,
|
||||
HID_REQ_SET_REPORT);
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
static int hidinput_input_event(struct input_dev *dev, unsigned int type,
|
||||
@@ -1992,7 +2049,7 @@ static void report_features(struct hid_device *hid)
|
||||
static struct hid_input *hidinput_allocate(struct hid_device *hid,
|
||||
unsigned int application)
|
||||
{
|
||||
struct hid_input *hidinput = kzalloc(sizeof(*hidinput), GFP_KERNEL);
|
||||
struct hid_input *hidinput = kzalloc_obj(*hidinput);
|
||||
struct input_dev *input_dev = input_allocate_device();
|
||||
const char *suffix = NULL;
|
||||
size_t suffix_len, name_len;
|
||||
@@ -2365,8 +2422,6 @@ void hidinput_disconnect(struct hid_device *hid)
|
||||
{
|
||||
struct hid_input *hidinput, *next;
|
||||
|
||||
hidinput_cleanup_battery(hid);
|
||||
|
||||
list_for_each_entry_safe(hidinput, next, &hid->inputs, list) {
|
||||
list_del(&hidinput->list);
|
||||
if (hidinput->registered)
|
||||
@@ -2385,6 +2440,13 @@ void hidinput_disconnect(struct hid_device *hid)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hidinput_disconnect);
|
||||
|
||||
void hidinput_reset_resume(struct hid_device *hid)
|
||||
{
|
||||
/* renegotiate host-device shared state after reset */
|
||||
hidinput_change_resolution_multipliers(hid);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(hidinput_reset_resume);
|
||||
|
||||
#ifdef CONFIG_HID_KUNIT_TEST
|
||||
#include "hid-input-test.c"
|
||||
#endif
|
||||
|
||||
@@ -272,6 +272,8 @@ static void kysona_remove(struct hid_device *hdev)
|
||||
static const struct hid_device_id kysona_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_KYSONA, USB_DEVICE_ID_KYSONA_M600_WIRED) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_VXE, USB_DEVICE_ID_VXE_DRAGONFLY_R1_PRO_WIRED) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, kysona_devices);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,7 @@
|
||||
#include <linux/hid.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
@@ -148,6 +149,14 @@ static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = {
|
||||
0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */
|
||||
};
|
||||
|
||||
static const __u8 lenovo_yoga7x_kbd_need_fixup_collection[] = {
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x65, // Logical Maximum (101)
|
||||
0x05, 0x07, // Usage Page (Keyboard)
|
||||
0x19, 0x00, // Usage Minimum (0)
|
||||
0x29, 0xDD, // Usage Maximum (221)
|
||||
};
|
||||
|
||||
static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
unsigned int *rsize)
|
||||
{
|
||||
@@ -177,6 +186,13 @@ static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
rdesc[260] = 0x01; /* report count (2) = 0x01 */
|
||||
}
|
||||
break;
|
||||
case I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD:
|
||||
if (*rsize == 176 &&
|
||||
memcmp(&rdesc[52], lenovo_yoga7x_kbd_need_fixup_collection,
|
||||
sizeof(lenovo_yoga7x_kbd_need_fixup_collection)) == 0) {
|
||||
rdesc[55] = rdesc[61]; // logical maximum = usage maximum
|
||||
}
|
||||
break;
|
||||
}
|
||||
return rdesc;
|
||||
}
|
||||
@@ -778,8 +794,8 @@ static int lenovo_raw_event(struct hid_device *hdev,
|
||||
*/
|
||||
if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB
|
||||
|| hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2)
|
||||
&& size >= 3 && report->id == 0x03))
|
||||
return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data));
|
||||
&& size >= 4 && report->id == 0x03))
|
||||
return lenovo_raw_event_TP_X12_tab(hdev, get_unaligned_le32(data));
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1407,7 +1423,6 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int lenovo_reset_resume(struct hid_device *hdev)
|
||||
{
|
||||
switch (hdev->product) {
|
||||
@@ -1423,7 +1438,6 @@ static int lenovo_reset_resume(struct hid_device *hdev)
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void lenovo_remove_tpkbd(struct hid_device *hdev)
|
||||
{
|
||||
@@ -1538,6 +1552,8 @@ static const struct hid_device_id lenovo_devices[] = {
|
||||
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) },
|
||||
{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
|
||||
USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) },
|
||||
{ HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC,
|
||||
USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD) },
|
||||
{ }
|
||||
};
|
||||
|
||||
@@ -1553,9 +1569,7 @@ static struct hid_driver lenovo_driver = {
|
||||
.raw_event = lenovo_raw_event,
|
||||
.event = lenovo_event,
|
||||
.report_fixup = lenovo_report_fixup,
|
||||
#ifdef CONFIG_PM
|
||||
.reset_resume = lenovo_reset_resume,
|
||||
#endif
|
||||
.reset_resume = pm_ptr(lenovo_reset_resume),
|
||||
};
|
||||
module_hid_driver(lenovo_driver);
|
||||
|
||||
|
||||
+517
-88
@@ -8,11 +8,13 @@
|
||||
#include <linux/device.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/led-class-multicolor.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/wait.h>
|
||||
#include <dt-bindings/leds/common.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
@@ -24,7 +26,24 @@
|
||||
#define LG_G510_FEATURE_BACKLIGHT_RGB 0x05
|
||||
#define LG_G510_FEATURE_POWER_ON_RGB 0x06
|
||||
|
||||
#define LG_G510_INPUT_MACRO_KEYS 0x03
|
||||
#define LG_G510_INPUT_KBD_BACKLIGHT 0x04
|
||||
|
||||
#define LG_G13_INPUT_REPORT 0x01
|
||||
#define LG_G13_FEATURE_M_KEYS_LEDS 0x05
|
||||
#define LG_G13_FEATURE_BACKLIGHT_RGB 0x07
|
||||
#define LG_G13_BACKLIGHT_HW_ON_BIT 23
|
||||
|
||||
/**
|
||||
* g13_input_report.keybits[] is not 32-bit aligned, so we can't use the bitops macros.
|
||||
*
|
||||
* @ary: Pointer to array of u8s
|
||||
* @b: Bit index into ary, LSB first. Not range checked.
|
||||
*/
|
||||
#define TEST_BIT(ary, b) ((1 << ((b) & 7)) & (ary)[(b) >> 3])
|
||||
|
||||
enum lg_g15_model {
|
||||
LG_G13,
|
||||
LG_G15,
|
||||
LG_G15_V2,
|
||||
LG_G510,
|
||||
@@ -43,10 +62,20 @@ enum lg_g15_led_type {
|
||||
LG_G15_LED_MAX
|
||||
};
|
||||
|
||||
struct g13_input_report {
|
||||
u8 report_id; /* Report ID is always set to 1. */
|
||||
u8 joy_x, joy_y;
|
||||
u8 keybits[5];
|
||||
};
|
||||
|
||||
struct lg_g15_led {
|
||||
union {
|
||||
struct led_classdev cdev;
|
||||
struct led_classdev_mc mcdev;
|
||||
};
|
||||
enum led_brightness brightness;
|
||||
enum lg_g15_led_type led;
|
||||
/* Used to store initial color intensities before subled_info is allocated */
|
||||
u8 red, green, blue;
|
||||
};
|
||||
|
||||
@@ -57,12 +86,188 @@ struct lg_g15_data {
|
||||
struct mutex mutex;
|
||||
struct work_struct work;
|
||||
struct input_dev *input;
|
||||
struct input_dev *input_js; /* Separate joystick device for G13. */
|
||||
struct hid_device *hdev;
|
||||
enum lg_g15_model model;
|
||||
struct lg_g15_led leds[LG_G15_LED_MAX];
|
||||
bool game_mode_enabled;
|
||||
bool backlight_disabled; /* true == HW backlight toggled *OFF* */
|
||||
};
|
||||
|
||||
/********* G13 LED functions ***********/
|
||||
/*
|
||||
* G13 retains no state across power cycles, and always powers up with the backlight on,
|
||||
* color #5AFF6E, all macro key LEDs off.
|
||||
*/
|
||||
static int lg_g13_get_leds_state(struct lg_g15_data *g15)
|
||||
{
|
||||
u8 * const tbuf = g15->transfer_buf;
|
||||
int ret, high;
|
||||
|
||||
/* RGB backlight. */
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB,
|
||||
tbuf, 5,
|
||||
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
||||
if (ret != 5) {
|
||||
hid_err(g15->hdev, "Error getting backlight brightness: %d\n", ret);
|
||||
return (ret < 0) ? ret : -EIO;
|
||||
}
|
||||
|
||||
/* Normalize RGB intensities against the highest component. */
|
||||
high = max3(tbuf[1], tbuf[2], tbuf[3]);
|
||||
if (high) {
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].red =
|
||||
DIV_ROUND_CLOSEST(tbuf[1] * 255, high);
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].green =
|
||||
DIV_ROUND_CLOSEST(tbuf[2] * 255, high);
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].blue =
|
||||
DIV_ROUND_CLOSEST(tbuf[3] * 255, high);
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = high;
|
||||
} else {
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].red = 255;
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].green = 255;
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].blue = 255;
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].brightness = 0;
|
||||
}
|
||||
|
||||
/* Macro LEDs. */
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS,
|
||||
tbuf, 5,
|
||||
HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
|
||||
if (ret != 5) {
|
||||
hid_err(g15->hdev, "Error getting macro LED brightness: %d\n", ret);
|
||||
return (ret < 0) ? ret : -EIO;
|
||||
}
|
||||
|
||||
for (int i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i)
|
||||
g15->leds[i].brightness = !!(tbuf[1] & (1 << (i - LG_G15_MACRO_PRESET1)));
|
||||
|
||||
/*
|
||||
* Bit 23 of g13_input_report.keybits[] contains the backlight's
|
||||
* current HW toggle state. Retrieve it from the device.
|
||||
*/
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G13_INPUT_REPORT,
|
||||
tbuf, sizeof(struct g13_input_report),
|
||||
HID_INPUT_REPORT, HID_REQ_GET_REPORT);
|
||||
if (ret != sizeof(struct g13_input_report)) {
|
||||
hid_err(g15->hdev, "Error getting backlight on/off state: %d\n", ret);
|
||||
return (ret < 0) ? ret : -EIO;
|
||||
}
|
||||
g15->backlight_disabled =
|
||||
!TEST_BIT(((struct g13_input_report *) tbuf)->keybits,
|
||||
LG_G13_BACKLIGHT_HW_ON_BIT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lg_g13_kbd_led_write(struct lg_g15_data *g15,
|
||||
struct lg_g15_led *g15_led,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct mc_subled const * const subleds = g15_led->mcdev.subled_info;
|
||||
u8 * const tbuf = g15->transfer_buf;
|
||||
int ret;
|
||||
|
||||
guard(mutex)(&g15->mutex);
|
||||
|
||||
led_mc_calc_color_components(&g15_led->mcdev, brightness);
|
||||
|
||||
tbuf[0] = 5;
|
||||
tbuf[1] = subleds[0].brightness;
|
||||
tbuf[2] = subleds[1].brightness;
|
||||
tbuf[3] = subleds[2].brightness;
|
||||
tbuf[4] = 0;
|
||||
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_BACKLIGHT_RGB,
|
||||
tbuf, 5,
|
||||
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
||||
if (ret != 5) {
|
||||
hid_err(g15->hdev, "Error setting backlight brightness: %d\n", ret);
|
||||
return (ret < 0) ? ret : -EIO;
|
||||
}
|
||||
|
||||
g15_led->brightness = brightness;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lg_g13_kbd_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev);
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(mc, struct lg_g15_led, mcdev);
|
||||
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
|
||||
|
||||
/* Ignore LED off on unregister / keyboard unplug */
|
||||
if (led_cdev->flags & LED_UNREGISTERING)
|
||||
return 0;
|
||||
|
||||
return lg_g13_kbd_led_write(g15, g15_led, brightness);
|
||||
}
|
||||
|
||||
static enum led_brightness lg_g13_kbd_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct led_classdev_mc const * const mc = lcdev_to_mccdev(led_cdev);
|
||||
struct lg_g15_led const *g15_led =
|
||||
container_of(mc, struct lg_g15_led, mcdev);
|
||||
|
||||
return g15_led->brightness;
|
||||
}
|
||||
|
||||
static int lg_g13_mkey_led_set(struct led_classdev *led_cdev, enum led_brightness brightness)
|
||||
{
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
|
||||
int i, ret;
|
||||
u8 * const tbuf = g15->transfer_buf;
|
||||
u8 val, mask = 0;
|
||||
|
||||
/* Ignore LED off on unregister / keyboard unplug */
|
||||
if (led_cdev->flags & LED_UNREGISTERING)
|
||||
return 0;
|
||||
|
||||
guard(mutex)(&g15->mutex);
|
||||
|
||||
for (i = LG_G15_MACRO_PRESET1; i < LG_G15_LED_MAX; ++i) {
|
||||
if (i == g15_led->led)
|
||||
val = brightness;
|
||||
else
|
||||
val = g15->leds[i].brightness;
|
||||
|
||||
if (val)
|
||||
mask |= 1 << (i - LG_G15_MACRO_PRESET1);
|
||||
}
|
||||
|
||||
tbuf[0] = 5;
|
||||
tbuf[1] = mask;
|
||||
tbuf[2] = 0;
|
||||
tbuf[3] = 0;
|
||||
tbuf[4] = 0;
|
||||
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G13_FEATURE_M_KEYS_LEDS,
|
||||
tbuf, 5,
|
||||
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
|
||||
if (ret != 5) {
|
||||
hid_err(g15->hdev, "Error setting LED brightness: %d\n", ret);
|
||||
return (ret < 0) ? ret : -EIO;
|
||||
}
|
||||
|
||||
g15_led->brightness = brightness;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static enum led_brightness lg_g13_mkey_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
/*
|
||||
* G13 doesn't change macro key LEDs behind our back, so they're
|
||||
* whatever we last set them to.
|
||||
*/
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
|
||||
return g15_led->brightness;
|
||||
}
|
||||
|
||||
/******** G15 and G15 v2 LED functions ********/
|
||||
|
||||
static int lg_g15_update_led_brightness(struct lg_g15_data *g15)
|
||||
@@ -221,6 +426,20 @@ static int lg_g510_get_initial_led_brightness(struct lg_g15_data *g15, int i)
|
||||
g15->leds[i].brightness = 0;
|
||||
}
|
||||
|
||||
if (i)
|
||||
return 0;
|
||||
|
||||
ret = hid_hw_raw_request(g15->hdev, LG_G510_INPUT_KBD_BACKLIGHT,
|
||||
g15->transfer_buf, 2,
|
||||
HID_INPUT_REPORT, HID_REQ_GET_REPORT);
|
||||
if (ret != 2) {
|
||||
/* This can happen when a KVM switch is used, so only warn. */
|
||||
hid_warn(g15->hdev, "Error getting backlight state: %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
g15->backlight_disabled = g15->transfer_buf[1] & 0x04;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -229,15 +448,15 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15,
|
||||
struct lg_g15_led *g15_led,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct mc_subled *subleds = g15_led->mcdev.subled_info;
|
||||
int ret;
|
||||
|
||||
led_mc_calc_color_components(&g15_led->mcdev, brightness);
|
||||
|
||||
g15->transfer_buf[0] = 5 + g15_led->led;
|
||||
g15->transfer_buf[1] =
|
||||
DIV_ROUND_CLOSEST(g15_led->red * brightness, 255);
|
||||
g15->transfer_buf[2] =
|
||||
DIV_ROUND_CLOSEST(g15_led->green * brightness, 255);
|
||||
g15->transfer_buf[3] =
|
||||
DIV_ROUND_CLOSEST(g15_led->blue * brightness, 255);
|
||||
g15->transfer_buf[1] = subleds[0].brightness;
|
||||
g15->transfer_buf[2] = subleds[1].brightness;
|
||||
g15->transfer_buf[3] = subleds[2].brightness;
|
||||
|
||||
ret = hid_hw_raw_request(g15->hdev,
|
||||
LG_G510_FEATURE_BACKLIGHT_RGB + g15_led->led,
|
||||
@@ -258,8 +477,9 @@ static int lg_g510_kbd_led_write(struct lg_g15_data *g15,
|
||||
static int lg_g510_kbd_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev);
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
container_of(mc, struct lg_g15_led, mcdev);
|
||||
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
|
||||
int ret;
|
||||
|
||||
@@ -276,82 +496,20 @@ static int lg_g510_kbd_led_set(struct led_classdev *led_cdev,
|
||||
|
||||
static enum led_brightness lg_g510_kbd_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct led_classdev_mc *mc = lcdev_to_mccdev(led_cdev);
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
container_of(mc, struct lg_g15_led, mcdev);
|
||||
|
||||
return g15_led->brightness;
|
||||
}
|
||||
|
||||
static ssize_t color_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
|
||||
unsigned long value;
|
||||
int ret;
|
||||
|
||||
if (count < 7 || (count == 8 && buf[7] != '\n') || count > 8)
|
||||
return -EINVAL;
|
||||
|
||||
if (buf[0] != '#')
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf + 1, 16, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&g15->mutex);
|
||||
g15_led->red = (value & 0xff0000) >> 16;
|
||||
g15_led->green = (value & 0x00ff00) >> 8;
|
||||
g15_led->blue = (value & 0x0000ff);
|
||||
ret = lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness);
|
||||
mutex_unlock(&g15->mutex);
|
||||
|
||||
return (ret < 0) ? ret : count;
|
||||
}
|
||||
|
||||
static ssize_t color_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct lg_g15_led *g15_led =
|
||||
container_of(led_cdev, struct lg_g15_led, cdev);
|
||||
struct lg_g15_data *g15 = dev_get_drvdata(led_cdev->dev->parent);
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&g15->mutex);
|
||||
ret = sprintf(buf, "#%02x%02x%02x\n",
|
||||
g15_led->red, g15_led->green, g15_led->blue);
|
||||
mutex_unlock(&g15->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(color);
|
||||
|
||||
static struct attribute *lg_g510_kbd_led_attrs[] = {
|
||||
&dev_attr_color.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group lg_g510_kbd_led_group = {
|
||||
.attrs = lg_g510_kbd_led_attrs,
|
||||
};
|
||||
|
||||
static const struct attribute_group *lg_g510_kbd_led_groups[] = {
|
||||
&lg_g510_kbd_led_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void lg_g510_leds_sync_work(struct work_struct *work)
|
||||
{
|
||||
struct lg_g15_data *g15 = container_of(work, struct lg_g15_data, work);
|
||||
struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS];
|
||||
|
||||
mutex_lock(&g15->mutex);
|
||||
lg_g510_kbd_led_write(g15, &g15->leds[LG_G15_KBD_BRIGHTNESS],
|
||||
g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
|
||||
lg_g510_kbd_led_write(g15, g15_led, g15_led->brightness);
|
||||
mutex_unlock(&g15->mutex);
|
||||
}
|
||||
|
||||
@@ -445,6 +603,8 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
|
||||
int ret;
|
||||
|
||||
switch (g15->model) {
|
||||
case LG_G13:
|
||||
return lg_g13_get_leds_state(g15);
|
||||
case LG_G15:
|
||||
case LG_G15_V2:
|
||||
return lg_g15_update_led_brightness(g15);
|
||||
@@ -472,6 +632,108 @@ static int lg_g15_get_initial_led_brightness(struct lg_g15_data *g15)
|
||||
|
||||
/******** Input functions ********/
|
||||
|
||||
/* Table mapping keybits[] bit positions to event codes. */
|
||||
/* Note: Indices are discontinuous to aid readability. */
|
||||
static const u16 g13_keys_for_bits[] = {
|
||||
/* Main keypad - keys G1 - G22 */
|
||||
[0] = KEY_MACRO1,
|
||||
[1] = KEY_MACRO2,
|
||||
[2] = KEY_MACRO3,
|
||||
[3] = KEY_MACRO4,
|
||||
[4] = KEY_MACRO5,
|
||||
[5] = KEY_MACRO6,
|
||||
[6] = KEY_MACRO7,
|
||||
[7] = KEY_MACRO8,
|
||||
[8] = KEY_MACRO9,
|
||||
[9] = KEY_MACRO10,
|
||||
[10] = KEY_MACRO11,
|
||||
[11] = KEY_MACRO12,
|
||||
[12] = KEY_MACRO13,
|
||||
[13] = KEY_MACRO14,
|
||||
[14] = KEY_MACRO15,
|
||||
[15] = KEY_MACRO16,
|
||||
[16] = KEY_MACRO17,
|
||||
[17] = KEY_MACRO18,
|
||||
[18] = KEY_MACRO19,
|
||||
[19] = KEY_MACRO20,
|
||||
[20] = KEY_MACRO21,
|
||||
[21] = KEY_MACRO22,
|
||||
|
||||
/* LCD menu buttons. */
|
||||
[24] = KEY_KBD_LCD_MENU5, /* "Next page" button */
|
||||
[25] = KEY_KBD_LCD_MENU1, /* Left-most */
|
||||
[26] = KEY_KBD_LCD_MENU2,
|
||||
[27] = KEY_KBD_LCD_MENU3,
|
||||
[28] = KEY_KBD_LCD_MENU4, /* Right-most */
|
||||
|
||||
/* Macro preset and record buttons; have red LEDs under them. */
|
||||
[29] = KEY_MACRO_PRESET1,
|
||||
[30] = KEY_MACRO_PRESET2,
|
||||
[31] = KEY_MACRO_PRESET3,
|
||||
[32] = KEY_MACRO_RECORD_START,
|
||||
|
||||
/* 33-35 handled by joystick device. */
|
||||
|
||||
/* Backlight toggle. */
|
||||
[37] = KEY_LIGHTS_TOGGLE,
|
||||
};
|
||||
|
||||
#define G13_JS_KEYBITS_OFFSET 33
|
||||
|
||||
static const u16 g13_keys_for_bits_js[] = {
|
||||
/* Joystick buttons */
|
||||
/* These keybits are at bit indices 33, 34, and 35. */
|
||||
BTN_BASE, /* Left side */
|
||||
BTN_BASE2, /* Bottom side */
|
||||
BTN_THUMB, /* Stick depress */
|
||||
};
|
||||
|
||||
static int lg_g13_event(struct lg_g15_data *g15, u8 const *data)
|
||||
{
|
||||
struct g13_input_report const * const rep = (struct g13_input_report *) data;
|
||||
int i, val;
|
||||
bool backlight_disabled;
|
||||
|
||||
/*
|
||||
* Main macropad and menu keys.
|
||||
* Emit key events defined for each bit position.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) {
|
||||
if (g13_keys_for_bits[i]) {
|
||||
val = TEST_BIT(rep->keybits, i);
|
||||
input_report_key(g15->input, g13_keys_for_bits[i], val);
|
||||
}
|
||||
}
|
||||
input_sync(g15->input);
|
||||
|
||||
/*
|
||||
* Joystick.
|
||||
* Emit button and deflection events.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i) {
|
||||
val = TEST_BIT(rep->keybits, i + G13_JS_KEYBITS_OFFSET);
|
||||
input_report_key(g15->input_js, g13_keys_for_bits_js[i], val);
|
||||
}
|
||||
input_report_abs(g15->input_js, ABS_X, rep->joy_x);
|
||||
input_report_abs(g15->input_js, ABS_Y, rep->joy_y);
|
||||
input_sync(g15->input_js);
|
||||
|
||||
/*
|
||||
* Bit 23 of keybits[] reports the current backlight on/off state. If
|
||||
* it has changed from the last cached value, apply an update.
|
||||
*/
|
||||
backlight_disabled = !TEST_BIT(rep->keybits, LG_G13_BACKLIGHT_HW_ON_BIT);
|
||||
if (backlight_disabled ^ g15->backlight_disabled) {
|
||||
led_classdev_notify_brightness_hw_changed(
|
||||
&g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev,
|
||||
backlight_disabled
|
||||
? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
|
||||
g15->backlight_disabled = backlight_disabled;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* On the G15 Mark I Logitech has been quite creative with which bit is what */
|
||||
static void lg_g15_handle_lcd_menu_keys(struct lg_g15_data *g15, u8 *data)
|
||||
{
|
||||
@@ -604,14 +866,24 @@ static int lg_g510_event(struct lg_g15_data *g15, u8 *data)
|
||||
|
||||
static int lg_g510_leds_event(struct lg_g15_data *g15, u8 *data)
|
||||
{
|
||||
struct lg_g15_led *g15_led = &g15->leds[LG_G15_KBD_BRIGHTNESS];
|
||||
bool backlight_disabled;
|
||||
|
||||
backlight_disabled = data[1] & 0x04;
|
||||
if (backlight_disabled == g15->backlight_disabled)
|
||||
return 0;
|
||||
|
||||
led_classdev_notify_brightness_hw_changed(
|
||||
&g15_led->mcdev.led_cdev,
|
||||
backlight_disabled ? 0 : g15_led->brightness);
|
||||
|
||||
g15->backlight_disabled = backlight_disabled;
|
||||
|
||||
/*
|
||||
* The G510 ignores backlight updates when the backlight is turned off
|
||||
* through the light toggle button on the keyboard, to work around this
|
||||
* we queue a workitem to sync values when the backlight is turned on.
|
||||
*/
|
||||
backlight_disabled = data[1] & 0x04;
|
||||
if (!backlight_disabled)
|
||||
schedule_work(&g15->work);
|
||||
|
||||
@@ -627,6 +899,10 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
return 0;
|
||||
|
||||
switch (g15->model) {
|
||||
case LG_G13:
|
||||
if (data[0] == 0x01 && size == sizeof(struct g13_input_report))
|
||||
return lg_g13_event(g15, data);
|
||||
break;
|
||||
case LG_G15:
|
||||
if (data[0] == 0x02 && size == 9)
|
||||
return lg_g15_event(g15, data);
|
||||
@@ -643,9 +919,9 @@ static int lg_g15_raw_event(struct hid_device *hdev, struct hid_report *report,
|
||||
break;
|
||||
case LG_G510:
|
||||
case LG_G510_USB_AUDIO:
|
||||
if (data[0] == 0x03 && size == 5)
|
||||
if (data[0] == LG_G510_INPUT_MACRO_KEYS && size == 5)
|
||||
return lg_g510_event(g15, data);
|
||||
if (data[0] == 0x04 && size == 2)
|
||||
if (data[0] == LG_G510_INPUT_KBD_BACKLIGHT && size == 2)
|
||||
return lg_g510_leds_event(g15, data);
|
||||
break;
|
||||
}
|
||||
@@ -667,12 +943,78 @@ static void lg_g15_input_close(struct input_dev *dev)
|
||||
hid_hw_close(hdev);
|
||||
}
|
||||
|
||||
static void lg_g15_setup_led_rgb(struct lg_g15_data *g15, int index)
|
||||
{
|
||||
int i;
|
||||
struct mc_subled *subled_info;
|
||||
struct lg_g15_led * const gled = &g15->leds[index];
|
||||
|
||||
if (g15->model == LG_G13) {
|
||||
gled->mcdev.led_cdev.brightness_set_blocking =
|
||||
lg_g13_kbd_led_set;
|
||||
gled->mcdev.led_cdev.brightness_get =
|
||||
lg_g13_kbd_led_get;
|
||||
gled->mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
|
||||
} else {
|
||||
gled->mcdev.led_cdev.brightness_set_blocking =
|
||||
lg_g510_kbd_led_set;
|
||||
gled->mcdev.led_cdev.brightness_get =
|
||||
lg_g510_kbd_led_get;
|
||||
if (index == LG_G15_KBD_BRIGHTNESS)
|
||||
g15->leds[index].mcdev.led_cdev.flags = LED_BRIGHT_HW_CHANGED;
|
||||
}
|
||||
gled->mcdev.led_cdev.max_brightness = 255;
|
||||
gled->mcdev.num_colors = 3;
|
||||
|
||||
subled_info = devm_kcalloc(&g15->hdev->dev, 3, sizeof(*subled_info), GFP_KERNEL);
|
||||
if (!subled_info)
|
||||
return;
|
||||
|
||||
for (i = 0; i < 3; i++) {
|
||||
switch (i + 1) {
|
||||
case LED_COLOR_ID_RED:
|
||||
subled_info[i].color_index = LED_COLOR_ID_RED;
|
||||
subled_info[i].intensity = gled->red;
|
||||
break;
|
||||
case LED_COLOR_ID_GREEN:
|
||||
subled_info[i].color_index = LED_COLOR_ID_GREEN;
|
||||
subled_info[i].intensity = gled->green;
|
||||
break;
|
||||
case LED_COLOR_ID_BLUE:
|
||||
subled_info[i].color_index = LED_COLOR_ID_BLUE;
|
||||
subled_info[i].intensity = gled->blue;
|
||||
break;
|
||||
}
|
||||
subled_info[i].channel = i;
|
||||
}
|
||||
gled->mcdev.subled_info = subled_info;
|
||||
}
|
||||
|
||||
static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
g15->leds[i].led = i;
|
||||
g15->leds[i].cdev.name = name;
|
||||
|
||||
switch (g15->model) {
|
||||
case LG_G13:
|
||||
if (i < LG_G15_BRIGHTNESS_MAX) {
|
||||
/* RGB backlight. */
|
||||
lg_g15_setup_led_rgb(g15, i);
|
||||
ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev,
|
||||
&g15->leds[i].mcdev,
|
||||
NULL);
|
||||
} else {
|
||||
/* Macro keys */
|
||||
g15->leds[i].cdev.brightness_set_blocking = lg_g13_mkey_led_set;
|
||||
g15->leds[i].cdev.brightness_get = lg_g13_mkey_led_get;
|
||||
g15->leds[i].cdev.max_brightness = 1;
|
||||
|
||||
ret = devm_led_classdev_register(&g15->hdev->dev,
|
||||
&g15->leds[i].cdev);
|
||||
}
|
||||
break;
|
||||
case LG_G15:
|
||||
case LG_G15_V2:
|
||||
g15->leds[i].cdev.brightness_get = lg_g15_led_get;
|
||||
@@ -685,6 +1027,7 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
|
||||
} else {
|
||||
g15->leds[i].cdev.max_brightness = 1;
|
||||
}
|
||||
ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
|
||||
break;
|
||||
case LG_G510:
|
||||
case LG_G510_USB_AUDIO:
|
||||
@@ -697,12 +1040,11 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
|
||||
g15->leds[i].cdev.name = "g15::power_on_backlight_val";
|
||||
fallthrough;
|
||||
case LG_G15_KBD_BRIGHTNESS:
|
||||
g15->leds[i].cdev.brightness_set_blocking =
|
||||
lg_g510_kbd_led_set;
|
||||
g15->leds[i].cdev.brightness_get =
|
||||
lg_g510_kbd_led_get;
|
||||
g15->leds[i].cdev.max_brightness = 255;
|
||||
g15->leds[i].cdev.groups = lg_g510_kbd_led_groups;
|
||||
/* register multicolor LED */
|
||||
lg_g15_setup_led_rgb(g15, i);
|
||||
ret = devm_led_classdev_multicolor_register_ext(&g15->hdev->dev,
|
||||
&g15->leds[i].mcdev,
|
||||
NULL);
|
||||
break;
|
||||
default:
|
||||
g15->leds[i].cdev.brightness_set_blocking =
|
||||
@@ -710,19 +1052,18 @@ static int lg_g15_register_led(struct lg_g15_data *g15, int i, const char *name)
|
||||
g15->leds[i].cdev.brightness_get =
|
||||
lg_g510_mkey_led_get;
|
||||
g15->leds[i].cdev.max_brightness = 1;
|
||||
ret = devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return devm_led_classdev_register(&g15->hdev->dev, &g15->leds[i].cdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Common input device init code shared between keyboards and Z-10 speaker handling */
|
||||
static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
|
||||
const char *name)
|
||||
static void lg_g15_init_input_dev_core(struct hid_device *hdev, struct input_dev *input,
|
||||
char const *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
input->name = name;
|
||||
input->phys = hdev->phys;
|
||||
input->uniq = hdev->uniq;
|
||||
@@ -733,12 +1074,42 @@ static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *inp
|
||||
input->dev.parent = &hdev->dev;
|
||||
input->open = lg_g15_input_open;
|
||||
input->close = lg_g15_input_close;
|
||||
}
|
||||
|
||||
static void lg_g15_init_input_dev(struct hid_device *hdev, struct input_dev *input,
|
||||
const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
lg_g15_init_input_dev_core(hdev, input, name);
|
||||
|
||||
/* Keys below the LCD, intended for controlling a menu on the LCD */
|
||||
for (i = 0; i < 5; i++)
|
||||
input_set_capability(input, EV_KEY, KEY_KBD_LCD_MENU1 + i);
|
||||
}
|
||||
|
||||
static void lg_g13_init_input_dev(struct hid_device *hdev,
|
||||
struct input_dev *input, const char *name,
|
||||
struct input_dev *input_js, const char *name_js)
|
||||
{
|
||||
/* Macropad. */
|
||||
lg_g15_init_input_dev_core(hdev, input, name);
|
||||
for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits); ++i) {
|
||||
if (g13_keys_for_bits[i])
|
||||
input_set_capability(input, EV_KEY, g13_keys_for_bits[i]);
|
||||
}
|
||||
|
||||
/* OBTW, we're a joystick, too... */
|
||||
lg_g15_init_input_dev_core(hdev, input_js, name_js);
|
||||
for (int i = 0; i < ARRAY_SIZE(g13_keys_for_bits_js); ++i)
|
||||
input_set_capability(input_js, EV_KEY, g13_keys_for_bits_js[i]);
|
||||
|
||||
input_set_capability(input_js, EV_ABS, ABS_X);
|
||||
input_set_abs_params(input_js, ABS_X, 0, 255, 0, 0);
|
||||
input_set_capability(input_js, EV_ABS, ABS_Y);
|
||||
input_set_abs_params(input_js, ABS_Y, 0, 255, 0, 0);
|
||||
}
|
||||
|
||||
static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
static const char * const led_names[] = {
|
||||
@@ -755,7 +1126,7 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
unsigned int connect_mask = 0;
|
||||
bool has_ff000000 = false;
|
||||
struct lg_g15_data *g15;
|
||||
struct input_dev *input;
|
||||
struct input_dev *input, *input_js;
|
||||
struct hid_report *rep;
|
||||
int ret, i, gkeys = 0;
|
||||
|
||||
@@ -794,6 +1165,25 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
hid_set_drvdata(hdev, (void *)g15);
|
||||
|
||||
switch (g15->model) {
|
||||
case LG_G13:
|
||||
/*
|
||||
* The G13 has an analog thumbstick with nearby buttons. Some
|
||||
* libraries and applications are known to ignore devices that
|
||||
* don't "look like" a joystick, and a device with two ABS axes
|
||||
* and 25+ macro keys would confuse them.
|
||||
*
|
||||
* Create an additional input device dedicated to appear as a
|
||||
* simplified joystick (two ABS axes, three BTN buttons).
|
||||
*/
|
||||
input_js = devm_input_allocate_device(&hdev->dev);
|
||||
if (!input_js)
|
||||
return -ENOMEM;
|
||||
g15->input_js = input_js;
|
||||
input_set_drvdata(input_js, hdev);
|
||||
|
||||
connect_mask = HID_CONNECT_HIDRAW;
|
||||
gkeys = 25;
|
||||
break;
|
||||
case LG_G15:
|
||||
INIT_WORK(&g15->work, lg_g15_leds_changed_work);
|
||||
/*
|
||||
@@ -875,6 +1265,38 @@ static int lg_g15_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
goto error_hw_stop;
|
||||
|
||||
return 0; /* All done */
|
||||
} else if (g15->model == LG_G13) {
|
||||
static char const * const g13_led_names[] = {
|
||||
/* Backlight is shared between LCD and keys. */
|
||||
"g13:rgb:kbd_backlight",
|
||||
NULL, /* Keep in sync with led_type enum */
|
||||
"g13:red:macro_preset_1",
|
||||
"g13:red:macro_preset_2",
|
||||
"g13:red:macro_preset_3",
|
||||
"g13:red:macro_record",
|
||||
};
|
||||
lg_g13_init_input_dev(hdev,
|
||||
input, "Logitech G13 Gaming Keypad",
|
||||
input_js, "Logitech G13 Thumbstick");
|
||||
ret = input_register_device(input);
|
||||
if (ret)
|
||||
goto error_hw_stop;
|
||||
ret = input_register_device(input_js);
|
||||
if (ret)
|
||||
goto error_hw_stop;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(g13_led_names); ++i) {
|
||||
if (g13_led_names[i]) {
|
||||
ret = lg_g15_register_led(g15, i, g13_led_names[i]);
|
||||
if (ret)
|
||||
goto error_hw_stop;
|
||||
}
|
||||
}
|
||||
led_classdev_notify_brightness_hw_changed(
|
||||
&g15->leds[LG_G15_KBD_BRIGHTNESS].mcdev.led_cdev,
|
||||
g15->backlight_disabled
|
||||
? 0 : g15->leds[LG_G15_KBD_BRIGHTNESS].brightness);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Setup and register input device */
|
||||
@@ -919,6 +1341,13 @@ error_hw_stop:
|
||||
}
|
||||
|
||||
static const struct hid_device_id lg_g15_devices[] = {
|
||||
/*
|
||||
* The G13 is a macropad-only device with an LCD, LED backlighing,
|
||||
* and joystick.
|
||||
*/
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_G13),
|
||||
.driver_data = LG_G13 },
|
||||
/* The G11 is a G15 without the LCD, treat it as a G15 */
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_G11),
|
||||
|
||||
@@ -768,7 +768,7 @@ static int lg_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
drv_data = kzalloc(sizeof(struct lg_drv_data), GFP_KERNEL);
|
||||
drv_data = kzalloc_obj(struct lg_drv_data);
|
||||
if (!drv_data) {
|
||||
hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -66,7 +66,7 @@ int lg2ff_init(struct hid_device *hid)
|
||||
if (!report)
|
||||
return -ENODEV;
|
||||
|
||||
lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL);
|
||||
lg2ff = kmalloc_obj(struct lg2ff_device);
|
||||
if (!lg2ff)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -1288,7 +1288,7 @@ int lg4ff_init(struct hid_device *hid)
|
||||
hid_err(hid, "Cannot add device, private driver data not allocated\n");
|
||||
return -1;
|
||||
}
|
||||
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
||||
entry = kzalloc_obj(*entry);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
spin_lock_init(&entry->report_lock);
|
||||
|
||||
+171
-58
@@ -116,6 +116,7 @@ enum recvr_type {
|
||||
recvr_type_dj,
|
||||
recvr_type_hidpp,
|
||||
recvr_type_gaming_hidpp,
|
||||
recvr_type_gaming_hidpp_ls_1_3,
|
||||
recvr_type_mouse_only,
|
||||
recvr_type_27mhz,
|
||||
recvr_type_bluetooth,
|
||||
@@ -148,6 +149,7 @@ struct dj_receiver_dev {
|
||||
struct kfifo notif_fifo;
|
||||
unsigned long last_query; /* in jiffies */
|
||||
bool ready;
|
||||
bool dj_mode;
|
||||
enum recvr_type type;
|
||||
unsigned int unnumbered_application;
|
||||
spinlock_t lock;
|
||||
@@ -211,6 +213,44 @@ static const char kbd_descriptor[] = {
|
||||
0xC0
|
||||
};
|
||||
|
||||
/* Gaming Keyboard descriptor (1) */
|
||||
static const char kbd_lightspeed_1_3_descriptor[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x06, /* Usage (Keyboard) */
|
||||
0xA1, 0x01, /* Collection (Application) */
|
||||
0x85, 0x01, /* Report ID (1) */
|
||||
0x05, 0x07, /* Usage Page (Kbrd/Keypad) */
|
||||
0x19, 0xE0, /* Usage Minimum (0xE0) */
|
||||
0x29, 0xE7, /* Usage Maximum (0xE7) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x95, 0x08, /* Report Count (8) */
|
||||
0x81, 0x02, /* Input (Data,Var) */
|
||||
0x95, 0x70, /* Report Count (112) */
|
||||
0x19, 0x04, /* Usage Minimum (0x04) */
|
||||
0x29, 0x73, /* Usage Maximum (0x73) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x19, 0x87, /* Usage Minimum (0x87) */
|
||||
0x29, 0x8B, /* Usage Maximum (0x8B) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x95, 0x03, /* Report Count (3) */
|
||||
0x19, 0x90, /* Usage Minimum (0x90) */
|
||||
0x29, 0x92, /* Usage Maximum (0x92) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x85, 0x0E, /* Report ID (14) */
|
||||
0x05, 0x08, /* Usage Page (LEDs) */
|
||||
0x19, 0x01, /* Usage Minimum (Num Lock) */
|
||||
0x29, 0x05, /* Usage Maximum (Kana) */
|
||||
0x91, 0x02, /* Output (Data,Var,Abs) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x03, /* Report Size (3) */
|
||||
0x91, 0x03, /* Output (Const,Var,Abs) */
|
||||
0xC0, /* End Collection */
|
||||
};
|
||||
|
||||
/* Mouse descriptor (2) */
|
||||
static const char mse_descriptor[] = {
|
||||
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
|
||||
@@ -415,6 +455,51 @@ static const char mse_high_res_descriptor[] = {
|
||||
0xC0, /* END_COLLECTION */
|
||||
};
|
||||
|
||||
/* Gaming Mouse descriptor with vendor data (2) */
|
||||
static const char mse_high_res_ls_1_3_descriptor[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x02, /* Usage (Mouse) */
|
||||
0xA1, 0x01, /* Collection (Application) */
|
||||
0x85, 0x02, /* Report ID (2) */
|
||||
0x09, 0x01, /* Usage (Pointer) */
|
||||
0xA1, 0x00, /* Collection (Physical) */
|
||||
0x95, 0x10, /* Report Count (16) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x05, 0x09, /* Usage Page (Button) */
|
||||
0x19, 0x01, /* Usage Minimum (0x01) */
|
||||
0x29, 0x10, /* Usage Maximum (0x10) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x95, 0x02, /* Report Count (2) */
|
||||
0x75, 0x10, /* Report Size (16) */
|
||||
0x16, 0x01, 0x80, /* Logical Minimum (-32767) */
|
||||
0x26, 0xFF, 0x7F, /* Logical Maximum (32767) */
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x30, /* Usage (X) */
|
||||
0x09, 0x31, /* Usage (Y) */
|
||||
0x81, 0x06, /* Input (Data,Var,Rel) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x15, 0x81, /* Logical Minimum (-127) */
|
||||
0x25, 0x7F, /* Logical Maximum (127) */
|
||||
0x09, 0x38, /* Usage (Wheel) */
|
||||
0x81, 0x06, /* Input (Data,Var,Rel) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x05, 0x0C, /* Usage Page (Consumer) */
|
||||
0x0A, 0x38, 0x02, /* Usage (AC Pan) */
|
||||
0x81, 0x06, /* Input (Data,Var,Rel) */
|
||||
0xC0, /* End Collection */
|
||||
0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */
|
||||
0x09, 0xF1, /* Usage (0xF1) */
|
||||
0x75, 0x08, /* Report Size (8) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x26, 0xFF, 0x00, /* Logical Maximum (255) */
|
||||
0x81, 0x00, /* Input (Data,Array,Abs) */
|
||||
0xC0, /* End Collection */
|
||||
};
|
||||
|
||||
/* Consumer Control descriptor (3) */
|
||||
static const char consumer_descriptor[] = {
|
||||
0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */
|
||||
@@ -520,9 +605,9 @@ static const char hidpp_descriptor[] = {
|
||||
/* Maximum size of all defined hid reports in bytes (including report id) */
|
||||
#define MAX_REPORT_SIZE 8
|
||||
|
||||
/* Make sure all descriptors are present here */
|
||||
/* Make sure the largest of each descriptor type is present here */
|
||||
#define MAX_RDESC_SIZE \
|
||||
(sizeof(kbd_descriptor) + \
|
||||
(sizeof(kbd_lightspeed_1_3_descriptor) +\
|
||||
sizeof(mse_bluetooth_descriptor) + \
|
||||
sizeof(mse5_bluetooth_descriptor) + \
|
||||
sizeof(consumer_descriptor) + \
|
||||
@@ -557,6 +642,8 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
|
||||
static const struct hid_ll_driver logi_dj_ll_driver;
|
||||
|
||||
static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);
|
||||
static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
unsigned int timeout);
|
||||
static void delayedwork_callback(struct work_struct *work);
|
||||
|
||||
static LIST_HEAD(dj_hdev_list);
|
||||
@@ -646,7 +733,7 @@ static struct dj_receiver_dev *dj_get_receiver_dev(struct hid_device *hdev,
|
||||
|
||||
djrcv_dev = dj_find_receiver_dev(hdev, type);
|
||||
if (!djrcv_dev) {
|
||||
djrcv_dev = kzalloc(sizeof(*djrcv_dev), GFP_KERNEL);
|
||||
djrcv_dev = kzalloc_obj(*djrcv_dev);
|
||||
if (!djrcv_dev)
|
||||
goto out;
|
||||
|
||||
@@ -764,7 +851,7 @@ static void logi_dj_recv_add_djhid_device(struct dj_receiver_dev *djrcv_dev,
|
||||
snprintf(tmpstr, sizeof(tmpstr), ":%d", device_index);
|
||||
strlcat(dj_hiddev->phys, tmpstr, sizeof(dj_hiddev->phys));
|
||||
|
||||
dj_dev = kzalloc(sizeof(struct dj_device), GFP_KERNEL);
|
||||
dj_dev = kzalloc_obj(struct dj_device);
|
||||
|
||||
if (!dj_dev) {
|
||||
hid_err(djrcv_hdev, "%s: failed allocating dj_dev\n", __func__);
|
||||
@@ -805,7 +892,6 @@ static void delayedwork_callback(struct work_struct *work)
|
||||
struct dj_workitem workitem;
|
||||
unsigned long flags;
|
||||
int count;
|
||||
int retval;
|
||||
|
||||
dbg_hid("%s\n", __func__);
|
||||
|
||||
@@ -842,11 +928,10 @@ static void delayedwork_callback(struct work_struct *work)
|
||||
logi_dj_recv_destroy_djhid_device(djrcv_dev, &workitem);
|
||||
break;
|
||||
case WORKITEM_TYPE_UNKNOWN:
|
||||
retval = logi_dj_recv_query_paired_devices(djrcv_dev);
|
||||
if (retval) {
|
||||
hid_err(djrcv_dev->hidpp, "%s: logi_dj_recv_query_paired_devices error: %d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
if (!djrcv_dev->dj_mode)
|
||||
logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
|
||||
logi_dj_recv_query_paired_devices(djrcv_dev);
|
||||
break;
|
||||
case WORKITEM_TYPE_EMPTY:
|
||||
dbg_hid("%s: device list is empty\n", __func__);
|
||||
@@ -1239,10 +1324,15 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
|
||||
|
||||
djrcv_dev->last_query = jiffies;
|
||||
|
||||
if (djrcv_dev->type != recvr_type_dj)
|
||||
return logi_dj_recv_query_hidpp_devices(djrcv_dev);
|
||||
if (!djrcv_dev->dj_mode)
|
||||
return 0;
|
||||
|
||||
dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
|
||||
if (djrcv_dev->type != recvr_type_dj) {
|
||||
retval = logi_dj_recv_query_hidpp_devices(djrcv_dev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dj_report = kzalloc_obj(struct dj_report);
|
||||
if (!dj_report)
|
||||
return -ENOMEM;
|
||||
dj_report->report_id = REPORT_ID_DJ_SHORT;
|
||||
@@ -1250,6 +1340,10 @@ static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev)
|
||||
dj_report->report_type = REPORT_TYPE_CMD_GET_PAIRED_DEVICES;
|
||||
retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
|
||||
kfree(dj_report);
|
||||
out:
|
||||
if (retval < 0)
|
||||
hid_err(djrcv_dev->hidpp, "%s error:%d\n", __func__, retval);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1262,7 +1356,7 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
u8 *buf;
|
||||
int retval = 0;
|
||||
|
||||
dj_report = kzalloc(sizeof(struct dj_report), GFP_KERNEL);
|
||||
dj_report = kzalloc_obj(struct dj_report);
|
||||
if (!dj_report)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -1275,6 +1369,8 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
(u8)timeout;
|
||||
|
||||
retval = logi_dj_recv_send_report(djrcv_dev, dj_report);
|
||||
if (retval)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Ugly sleep to work around a USB 3.0 bug when the receiver is
|
||||
@@ -1283,11 +1379,6 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
* 50 msec should gives enough time to the receiver to be ready.
|
||||
*/
|
||||
msleep(50);
|
||||
|
||||
if (retval) {
|
||||
kfree(dj_report);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1313,7 +1404,13 @@ static int logi_dj_recv_switch_to_dj_mode(struct dj_receiver_dev *djrcv_dev,
|
||||
HIDPP_REPORT_SHORT_LENGTH, HID_OUTPUT_REPORT,
|
||||
HID_REQ_SET_REPORT);
|
||||
|
||||
out:
|
||||
kfree(dj_report);
|
||||
|
||||
if (retval < 0)
|
||||
hid_err(hdev, "%s error:%d\n", __func__, retval);
|
||||
|
||||
djrcv_dev->dj_mode = retval >= 0;
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -1374,12 +1471,19 @@ static int logi_dj_ll_raw_request(struct hid_device *hid,
|
||||
return -EINVAL;
|
||||
|
||||
if (djrcv_dev->type != recvr_type_dj && count >= 2) {
|
||||
unsigned char led_report_id = 0;
|
||||
|
||||
if (!djrcv_dev->keyboard) {
|
||||
hid_warn(hid, "Received REPORT_TYPE_LEDS request before the keyboard interface was enumerated\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This Lightspeed receiver expects LED reports with report ID 1 */
|
||||
if (djrcv_dev->type == recvr_type_gaming_hidpp_ls_1_3)
|
||||
led_report_id = 1;
|
||||
|
||||
/* usbhid overrides the report ID and ignores the first byte */
|
||||
return hid_hw_raw_request(djrcv_dev->keyboard, 0, buf, count,
|
||||
return hid_hw_raw_request(djrcv_dev->keyboard, led_report_id, buf, count,
|
||||
report_type, reqtype);
|
||||
}
|
||||
|
||||
@@ -1426,6 +1530,10 @@ static int logi_dj_ll_parse(struct hid_device *hid)
|
||||
if (djdev->reports_supported & STD_KEYBOARD) {
|
||||
dbg_hid("%s: sending a kbd descriptor, reports_supported: %llx\n",
|
||||
__func__, djdev->reports_supported);
|
||||
if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
|
||||
rdcat(rdesc, &rsize, kbd_lightspeed_1_3_descriptor,
|
||||
sizeof(kbd_lightspeed_1_3_descriptor));
|
||||
else
|
||||
rdcat(rdesc, &rsize, kbd_descriptor, sizeof(kbd_descriptor));
|
||||
}
|
||||
|
||||
@@ -1436,6 +1544,9 @@ static int logi_dj_ll_parse(struct hid_device *hid)
|
||||
djdev->dj_receiver_dev->type == recvr_type_mouse_only)
|
||||
rdcat(rdesc, &rsize, mse_high_res_descriptor,
|
||||
sizeof(mse_high_res_descriptor));
|
||||
else if (djdev->dj_receiver_dev->type == recvr_type_gaming_hidpp_ls_1_3)
|
||||
rdcat(rdesc, &rsize, mse_high_res_ls_1_3_descriptor,
|
||||
sizeof(mse_high_res_ls_1_3_descriptor));
|
||||
else if (djdev->dj_receiver_dev->type == recvr_type_27mhz)
|
||||
rdcat(rdesc, &rsize, mse_27mhz_descriptor,
|
||||
sizeof(mse_27mhz_descriptor));
|
||||
@@ -1553,7 +1664,7 @@ static int logi_dj_dj_event(struct hid_device *hdev,
|
||||
* so ignore those reports too.
|
||||
*/
|
||||
if (dj_report->device_index != DJ_RECEIVER_INDEX)
|
||||
hid_err(hdev, "%s: invalid device index:%d\n",
|
||||
hid_err(hdev, "%s: invalid receiver index:%d\n",
|
||||
__func__, dj_report->device_index);
|
||||
return false;
|
||||
}
|
||||
@@ -1695,11 +1806,12 @@ static int logi_dj_raw_event(struct hid_device *hdev,
|
||||
}
|
||||
/*
|
||||
* Mouse-only receivers send unnumbered mouse data. The 27 MHz
|
||||
* receiver uses 6 byte packets, the nano receiver 8 bytes.
|
||||
* receiver uses 6 byte packets, the nano receiver 8 bytes,
|
||||
* the lightspeed receiver (Pro X Superlight) 13 bytes.
|
||||
*/
|
||||
if (djrcv_dev->unnumbered_application == HID_GD_MOUSE &&
|
||||
size <= 8) {
|
||||
u8 mouse_report[9];
|
||||
size <= 13){
|
||||
u8 mouse_report[14];
|
||||
|
||||
/* Prepend report id */
|
||||
mouse_report[0] = REPORT_TYPE_MOUSE;
|
||||
@@ -1746,7 +1858,8 @@ static int logi_dj_raw_event(struct hid_device *hdev,
|
||||
static int logi_dj_probe(struct hid_device *hdev,
|
||||
const struct hid_device_id *id)
|
||||
{
|
||||
struct hid_report_enum *rep_enum;
|
||||
struct hid_report_enum *input_report_enum;
|
||||
struct hid_report_enum *output_report_enum;
|
||||
struct hid_report *rep;
|
||||
struct dj_receiver_dev *djrcv_dev;
|
||||
struct usb_interface *intf;
|
||||
@@ -1776,6 +1889,7 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
case recvr_type_dj: no_dj_interfaces = 3; break;
|
||||
case recvr_type_hidpp: no_dj_interfaces = 2; break;
|
||||
case recvr_type_gaming_hidpp: no_dj_interfaces = 3; break;
|
||||
case recvr_type_gaming_hidpp_ls_1_3: no_dj_interfaces = 3; break;
|
||||
case recvr_type_mouse_only: no_dj_interfaces = 2; break;
|
||||
case recvr_type_27mhz: no_dj_interfaces = 2; break;
|
||||
case recvr_type_bluetooth: no_dj_interfaces = 2; break;
|
||||
@@ -1790,10 +1904,20 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
}
|
||||
}
|
||||
|
||||
rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
|
||||
output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT];
|
||||
rep = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT];
|
||||
|
||||
if (rep && (rep->maxfield < 1 ||
|
||||
rep->field[0]->report_count != DJREPORT_SHORT_LENGTH - 1)) {
|
||||
hid_err(hdev, "Expected size of DJ short report is %d, but got %d",
|
||||
DJREPORT_SHORT_LENGTH - 1, rep->field[0]->report_count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
input_report_enum = &hdev->report_enum[HID_INPUT_REPORT];
|
||||
|
||||
/* no input reports, bail out */
|
||||
if (list_empty(&rep_enum->report_list))
|
||||
if (list_empty(&input_report_enum->report_list))
|
||||
return -ENODEV;
|
||||
|
||||
/*
|
||||
@@ -1801,7 +1925,7 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
* Note: we should theoretically check for HID++ and DJ
|
||||
* collections, but this will do.
|
||||
*/
|
||||
list_for_each_entry(rep, &rep_enum->report_list, list) {
|
||||
list_for_each_entry(rep, &input_report_enum->report_list, list) {
|
||||
if (rep->application == 0xff000001)
|
||||
has_hidpp = true;
|
||||
}
|
||||
@@ -1814,7 +1938,7 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
return -ENODEV;
|
||||
|
||||
/* get the current application attached to the node */
|
||||
rep = list_first_entry(&rep_enum->report_list, struct hid_report, list);
|
||||
rep = list_first_entry(&input_report_enum->report_list, struct hid_report, list);
|
||||
djrcv_dev = dj_get_receiver_dev(hdev, id->driver_data,
|
||||
rep->application, has_hidpp);
|
||||
if (!djrcv_dev) {
|
||||
@@ -1822,7 +1946,7 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (!rep_enum->numbered)
|
||||
if (!input_report_enum->numbered)
|
||||
djrcv_dev->unnumbered_application = rep->application;
|
||||
|
||||
/* Starts the usb device and connects to upper interfaces hiddev and
|
||||
@@ -1834,12 +1958,11 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
}
|
||||
|
||||
if (has_hidpp) {
|
||||
retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
if (retval < 0) {
|
||||
hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n",
|
||||
__func__, retval);
|
||||
goto switch_to_dj_mode_fail;
|
||||
}
|
||||
/*
|
||||
* This can fail with a KVM. Ignore errors to let the probe
|
||||
* succeed, logi_dj_recv_queue_unknown_work will retry later.
|
||||
*/
|
||||
logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
}
|
||||
|
||||
/* This is enabling the polling urb on the IN endpoint */
|
||||
@@ -1857,21 +1980,13 @@ static int logi_dj_probe(struct hid_device *hdev,
|
||||
spin_lock_irqsave(&djrcv_dev->lock, flags);
|
||||
djrcv_dev->ready = true;
|
||||
spin_unlock_irqrestore(&djrcv_dev->lock, flags);
|
||||
retval = logi_dj_recv_query_paired_devices(djrcv_dev);
|
||||
if (retval < 0) {
|
||||
hid_err(hdev, "%s: logi_dj_recv_query_paired_devices error:%d\n",
|
||||
__func__, retval);
|
||||
/*
|
||||
* This can happen with a KVM, let the probe succeed,
|
||||
* logi_dj_recv_queue_unknown_work will retry later.
|
||||
*/
|
||||
}
|
||||
/* This too can fail with a KVM, ignore errors. */
|
||||
logi_dj_recv_query_paired_devices(djrcv_dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
llopen_failed:
|
||||
switch_to_dj_mode_fail:
|
||||
hid_hw_stop(hdev);
|
||||
|
||||
hid_hw_start_fail:
|
||||
@@ -1879,24 +1994,16 @@ hid_hw_start_fail:
|
||||
return retval;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int logi_dj_reset_resume(struct hid_device *hdev)
|
||||
{
|
||||
int retval;
|
||||
struct dj_receiver_dev *djrcv_dev = hid_get_drvdata(hdev);
|
||||
|
||||
if (!djrcv_dev || djrcv_dev->hidpp != hdev)
|
||||
return 0;
|
||||
|
||||
retval = logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
if (retval < 0) {
|
||||
hid_err(hdev, "%s: logi_dj_recv_switch_to_dj_mode returned error:%d\n",
|
||||
__func__, retval);
|
||||
}
|
||||
|
||||
logi_dj_recv_switch_to_dj_mode(djrcv_dev, 0);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void logi_dj_remove(struct hid_device *hdev)
|
||||
{
|
||||
@@ -1987,6 +2094,14 @@ static const struct hid_device_id logi_dj_receivers[] = {
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_2),
|
||||
.driver_data = recvr_type_gaming_hidpp},
|
||||
{ /* Logitech lightspeed receiver (0xc547) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_3),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
{ /* Logitech lightspeed receiver (0xc54d) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH,
|
||||
USB_DEVICE_ID_LOGITECH_NANO_RECEIVER_LIGHTSPEED_1_4),
|
||||
.driver_data = recvr_type_gaming_hidpp_ls_1_3},
|
||||
|
||||
{ /* Logitech 27 MHz HID++ 1.0 receiver (0xc513) */
|
||||
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER),
|
||||
@@ -2044,9 +2159,7 @@ static struct hid_driver logi_djreceiver_driver = {
|
||||
.probe = logi_dj_probe,
|
||||
.remove = logi_dj_remove,
|
||||
.raw_event = logi_dj_raw_event,
|
||||
#ifdef CONFIG_PM
|
||||
.reset_resume = logi_dj_reset_resume,
|
||||
#endif
|
||||
.reset_resume = pm_ptr(logi_dj_reset_resume),
|
||||
};
|
||||
|
||||
module_hid_driver(logi_djreceiver_driver);
|
||||
|
||||
@@ -306,21 +306,22 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
if (ret) {
|
||||
dbg_hid("__hidpp_send_report returned err: %d\n", ret);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
|
||||
5*HZ)) {
|
||||
dbg_hid("%s:timeout waiting for response\n", __func__);
|
||||
memset(response, 0, sizeof(struct hidpp_report));
|
||||
return -ETIMEDOUT;
|
||||
ret = -ETIMEDOUT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
response->rap.sub_id == HIDPP_ERROR) {
|
||||
ret = response->rap.params[1];
|
||||
dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
|
||||
@@ -328,10 +329,14 @@ static int __do_hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
response->fap.feature_index == HIDPP20_ERROR) {
|
||||
ret = response->fap.params[1];
|
||||
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
hidpp->send_receive_buf = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -352,10 +357,15 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
|
||||
|
||||
do {
|
||||
ret = __do_hidpp_send_message_sync(hidpp, message, response);
|
||||
if (ret != HIDPP20_ERROR_BUSY)
|
||||
if (response->report_id == REPORT_ID_HIDPP_SHORT &&
|
||||
ret != HIDPP_ERROR_BUSY)
|
||||
break;
|
||||
if ((response->report_id == REPORT_ID_HIDPP_LONG ||
|
||||
response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
|
||||
ret != HIDPP20_ERROR_BUSY)
|
||||
break;
|
||||
|
||||
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
|
||||
dbg_hid("%s:got busy hidpp error %02X, retrying\n", __func__, ret);
|
||||
} while (--max_retries);
|
||||
|
||||
mutex_unlock(&hidpp->send_mutex);
|
||||
@@ -385,7 +395,7 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
|
||||
message = kzalloc_obj(struct hidpp_report);
|
||||
if (!message)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -438,7 +448,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
|
||||
if (param_count > max_count)
|
||||
return -EINVAL;
|
||||
|
||||
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
|
||||
message = kzalloc_obj(struct hidpp_report);
|
||||
if (!message)
|
||||
return -ENOMEM;
|
||||
message->report_id = report_id;
|
||||
@@ -971,7 +981,8 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
|
||||
}
|
||||
|
||||
/* the device might not be connected */
|
||||
if (ret == HIDPP_ERROR_RESOURCE_ERROR)
|
||||
if (ret == HIDPP_ERROR_RESOURCE_ERROR ||
|
||||
ret == HIDPP_ERROR_UNKNOWN_DEVICE)
|
||||
return -EIO;
|
||||
|
||||
if (ret > 0) {
|
||||
@@ -2496,12 +2507,15 @@ static void hidpp_ff_work_handler(struct work_struct *w)
|
||||
}
|
||||
break;
|
||||
case HIDPP_FF_DESTROY_EFFECT:
|
||||
slot = wd->params[0];
|
||||
if (slot > 0 && slot <= data->num_effects) {
|
||||
if (wd->effect_id >= 0)
|
||||
/* regular effect destroyed */
|
||||
data->effect_ids[wd->params[0]-1] = -1;
|
||||
data->effect_ids[slot-1] = -1;
|
||||
else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
|
||||
/* autocenter spring destroyed */
|
||||
data->slot_autocenter = 0;
|
||||
}
|
||||
break;
|
||||
case HIDPP_FF_SET_GLOBAL_GAINS:
|
||||
data->gain = (wd->params[0] << 8) + wd->params[1];
|
||||
@@ -2521,7 +2535,7 @@ out:
|
||||
|
||||
static int hidpp_ff_queue_work(struct hidpp_ff_private_data *data, int effect_id, u8 command, u8 *params, u8 size)
|
||||
{
|
||||
struct hidpp_ff_work_data *wd = kzalloc(sizeof(*wd), GFP_KERNEL);
|
||||
struct hidpp_ff_work_data *wd = kzalloc_obj(*wd);
|
||||
int s;
|
||||
|
||||
if (!wd)
|
||||
@@ -2847,7 +2861,7 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
|
||||
data = kmemdup(data, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
data->effect_ids = kcalloc(num_slots, sizeof(int), GFP_KERNEL);
|
||||
data->effect_ids = kzalloc_objs(int, num_slots);
|
||||
if (!data->effect_ids) {
|
||||
kfree(data);
|
||||
return -ENOMEM;
|
||||
@@ -3659,7 +3673,7 @@ static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp,
|
||||
memcpy(&consumer_report[1], &data[3], 4);
|
||||
/* We are called from atomic context */
|
||||
hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT,
|
||||
consumer_report, 5, 1);
|
||||
consumer_report, sizeof(consumer_report), 5, 1);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -3834,8 +3848,7 @@ static int hidpp_input_configured(struct hid_device *hdev,
|
||||
static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
|
||||
int size)
|
||||
{
|
||||
struct hidpp_report *question = hidpp->send_receive_buf;
|
||||
struct hidpp_report *answer = hidpp->send_receive_buf;
|
||||
struct hidpp_report *question, *answer;
|
||||
struct hidpp_report *report = (struct hidpp_report *)data;
|
||||
int ret;
|
||||
int last_online;
|
||||
@@ -3845,6 +3858,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
|
||||
* previously sent command.
|
||||
*/
|
||||
if (unlikely(mutex_is_locked(&hidpp->send_mutex))) {
|
||||
question = hidpp->send_receive_buf;
|
||||
answer = hidpp->send_receive_buf;
|
||||
|
||||
if (!question)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Check for a correct hidpp20 answer or the corresponding
|
||||
* error
|
||||
@@ -4308,7 +4327,7 @@ static int hidpp_get_report_length(struct hid_device *hdev, int id)
|
||||
|
||||
re = &(hdev->report_enum[HID_OUTPUT_REPORT]);
|
||||
report = re->report_id_hash[id];
|
||||
if (!report)
|
||||
if (!report || !report->maxfield)
|
||||
return 0;
|
||||
|
||||
return report->field[0]->report_count + 1;
|
||||
@@ -4481,10 +4500,12 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
if (!ret)
|
||||
ret = hidpp_ff_init(hidpp, &data);
|
||||
|
||||
if (ret)
|
||||
if (ret) {
|
||||
hid_warn(hidpp->hid_dev,
|
||||
"Unable to initialize force feedback support, errno %d\n",
|
||||
ret);
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4656,8 +4677,52 @@ 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 3S mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb037) },
|
||||
{ /* MX Anywhere 3SB mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
|
||||
{ /* Slim Solar+ K980 Keyboard over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb391) },
|
||||
{ /* MX Master 4 mouse over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb042) },
|
||||
{ /* Logitech Signature K650 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36f) },
|
||||
{ /* Logitech Signature K650 B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb370) },
|
||||
{ /* Logitech Pebble Keys 2 K380S over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb377) },
|
||||
{ /* Logitech Casa Pop-Up Desk over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb371) },
|
||||
{ /* Logitech Casa Pop-Up Desk B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb374) },
|
||||
{ /* Logitech Wave Keys over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb383) },
|
||||
{ /* Logitech Wave Keys B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb384) },
|
||||
{ /* Logitech Signature Slim K950 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb386) },
|
||||
{ /* Logitech Signature Slim K950 B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb388) },
|
||||
{ /* Logitech MX Keys S over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb378) },
|
||||
{ /* Logitech MX Keys S B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb380) },
|
||||
{ /* Logitech Keys-To-Go 2 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38c) },
|
||||
{ /* Logitech Pop Icon Keys over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb38f) },
|
||||
{ /* Logitech MX Keys Mini over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb369) },
|
||||
{ /* Logitech MX Keys Mini B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb36e) },
|
||||
{ /* Logitech Signature Slim Solar+ K980 B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb394) },
|
||||
{ /* Logitech Bluetooth Keyboard K250/K251 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb397) },
|
||||
{ /* Logitech Signature Comfort K880 over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39c) },
|
||||
{ /* Logitech Signature Comfort K880 B2B over Bluetooth */
|
||||
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb39d) },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -390,6 +390,10 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
struct input_dev *input = msc->input;
|
||||
int x = 0, y = 0, ii, clicks = 0, npoints;
|
||||
|
||||
/* Protect against zero sized recursive calls from DOUBLE_REPORT_ID */
|
||||
if (size < 1)
|
||||
return 0;
|
||||
|
||||
switch (data[0]) {
|
||||
case TRACKPAD_REPORT_ID:
|
||||
case TRACKPAD2_BT_REPORT_ID:
|
||||
@@ -490,6 +494,18 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
||||
/* Sometimes the trackpad sends two touch reports in one
|
||||
* packet.
|
||||
*/
|
||||
|
||||
/* Ensure that we have at least 2 elements (report type and size) */
|
||||
if (size < 2)
|
||||
return 0;
|
||||
|
||||
if (size < data[1] + 2) {
|
||||
hid_warn(hdev,
|
||||
"received report length (%d) was smaller than specified (%d)",
|
||||
size, data[1] + 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
magicmouse_raw_event(hdev, report, data + 2, data[1]);
|
||||
magicmouse_raw_event(hdev, report, data + 2 + data[1],
|
||||
size - 2 - data[1]);
|
||||
@@ -725,6 +741,11 @@ static int magicmouse_input_configured(struct hid_device *hdev,
|
||||
struct magicmouse_sc *msc = hid_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
if (!msc->input) {
|
||||
hid_err(hdev, "magicmouse setup input failed (no input)");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = magicmouse_setup_input(msc->input, hdev);
|
||||
if (ret) {
|
||||
hid_err(hdev, "magicmouse setup input failed (%d)\n", ret);
|
||||
@@ -812,19 +833,21 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
|
||||
#ifdef CONFIG_HID_BATTERY_STRENGTH
|
||||
struct hid_report_enum *report_enum;
|
||||
struct hid_report *report;
|
||||
struct hid_battery *bat;
|
||||
|
||||
if (!hdev->battery ||
|
||||
bat = hid_get_battery(hdev);
|
||||
if (!bat ||
|
||||
(!is_usb_magicmouse2(hdev->vendor, hdev->product) &&
|
||||
!is_usb_magictrackpad2(hdev->vendor, hdev->product)))
|
||||
return -1;
|
||||
|
||||
report_enum = &hdev->report_enum[hdev->battery_report_type];
|
||||
report = report_enum->report_id_hash[hdev->battery_report_id];
|
||||
report_enum = &hdev->report_enum[bat->report_type];
|
||||
report = report_enum->report_id_hash[bat->report_id];
|
||||
|
||||
if (!report || report->maxfield < 1)
|
||||
return -1;
|
||||
|
||||
if (hdev->battery_capacity == hdev->battery_max)
|
||||
if (bat->capacity == bat->max)
|
||||
return -1;
|
||||
|
||||
hid_hw_request(hdev, report, HID_REQ_GET_REPORT);
|
||||
@@ -985,13 +1008,11 @@ static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
||||
*/
|
||||
if ((is_usb_magicmouse2(hdev->vendor, hdev->product) ||
|
||||
is_usb_magictrackpad2(hdev->vendor, hdev->product)) &&
|
||||
*rsize == 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) {
|
||||
*rsize >= 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) {
|
||||
hid_info(hdev,
|
||||
"fixing up magicmouse battery report descriptor\n");
|
||||
*rsize = *rsize - 1;
|
||||
rdesc = kmemdup(rdesc + 1, *rsize, GFP_KERNEL);
|
||||
if (!rdesc)
|
||||
return NULL;
|
||||
rdesc = rdesc + 1;
|
||||
|
||||
rdesc[0] = 0x05;
|
||||
rdesc[1] = 0x01;
|
||||
|
||||
+29
-10
@@ -19,8 +19,15 @@
|
||||
#include <linux/gpio/driver.h>
|
||||
#include <linux/iio/iio.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include "hid-ids.h"
|
||||
|
||||
static bool gpio_mode_enforce;
|
||||
|
||||
module_param(gpio_mode_enforce, bool, 0644);
|
||||
MODULE_PARM_DESC(gpio_mode_enforce,
|
||||
"Enforce GPIO mode for GP0 thru GP3 (default: false, will be used for IIO)");
|
||||
|
||||
/* Commands codes in a raw output report */
|
||||
enum {
|
||||
MCP2221_I2C_WR_DATA = 0x90,
|
||||
@@ -121,6 +128,7 @@ struct mcp2221 {
|
||||
u8 *rxbuf;
|
||||
u8 txbuf[64];
|
||||
int rxbuf_idx;
|
||||
int rxbuf_size;
|
||||
int status;
|
||||
u8 cur_i2c_clk_div;
|
||||
struct gpio_chip *gc;
|
||||
@@ -323,12 +331,14 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
||||
mcp->txbuf[3] = (u8)(msg->addr << 1);
|
||||
total_len = msg->len;
|
||||
mcp->rxbuf = msg->buf;
|
||||
mcp->rxbuf_size = msg->len;
|
||||
} else {
|
||||
mcp->txbuf[1] = smbus_len;
|
||||
mcp->txbuf[2] = 0;
|
||||
mcp->txbuf[3] = (u8)(smbus_addr << 1);
|
||||
total_len = smbus_len;
|
||||
mcp->rxbuf = smbus_buf;
|
||||
mcp->rxbuf_size = smbus_len;
|
||||
}
|
||||
|
||||
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 4);
|
||||
@@ -353,6 +363,8 @@ static int mcp_i2c_smbus_read(struct mcp2221 *mcp,
|
||||
usleep_range(90, 100);
|
||||
retries++;
|
||||
} else {
|
||||
usleep_range(980, 1000);
|
||||
mcp_cancel_last_cmd(mcp);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
@@ -534,10 +546,10 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mcp->rxbuf_idx = 0;
|
||||
mcp->rxbuf = data->block;
|
||||
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
||||
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
||||
ret = mcp_i2c_smbus_read(mcp, NULL,
|
||||
MCP2221_I2C_RD_RPT_START,
|
||||
addr, data->block[0] + 1,
|
||||
data->block);
|
||||
if (ret)
|
||||
goto exit;
|
||||
} else {
|
||||
@@ -553,14 +565,14 @@ static int mcp_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
|
||||
case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
if (read_write == I2C_SMBUS_READ) {
|
||||
ret = mcp_smbus_write(mcp, addr, command, NULL,
|
||||
0, MCP2221_I2C_WR_NO_STOP, 1);
|
||||
0, MCP2221_I2C_WR_NO_STOP, 0);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mcp->rxbuf_idx = 0;
|
||||
mcp->rxbuf = data->block;
|
||||
mcp->txbuf[0] = MCP2221_I2C_GET_DATA;
|
||||
ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
|
||||
ret = mcp_i2c_smbus_read(mcp, NULL,
|
||||
MCP2221_I2C_RD_RPT_START,
|
||||
addr, data->block[0],
|
||||
&data->block[1]);
|
||||
if (ret)
|
||||
goto exit;
|
||||
} else {
|
||||
@@ -648,7 +660,7 @@ static int mcp2221_check_gpio_pinfunc(struct mcp2221 *mcp)
|
||||
int needgpiofix = 0;
|
||||
int ret;
|
||||
|
||||
if (IS_ENABLED(CONFIG_IIO))
|
||||
if (IS_ENABLED(CONFIG_IIO) && !gpio_mode_enforce)
|
||||
return 0;
|
||||
|
||||
ret = mcp_gpio_read_sram(mcp);
|
||||
@@ -910,6 +922,10 @@ static int mcp2221_raw_event(struct hid_device *hdev,
|
||||
mcp->status = -EINVAL;
|
||||
break;
|
||||
}
|
||||
if (mcp->rxbuf_idx + data[3] > mcp->rxbuf_size) {
|
||||
mcp->status = -EINVAL;
|
||||
break;
|
||||
}
|
||||
buf = mcp->rxbuf;
|
||||
memcpy(&buf[mcp->rxbuf_idx], &data[4], data[3]);
|
||||
mcp->rxbuf_idx = mcp->rxbuf_idx + data[3];
|
||||
@@ -1043,6 +1059,7 @@ static void mcp2221_remove(struct hid_device *hdev)
|
||||
#if IS_REACHABLE(CONFIG_IIO)
|
||||
struct mcp2221 *mcp = hid_get_drvdata(hdev);
|
||||
|
||||
if (!gpio_mode_enforce)
|
||||
cancel_delayed_work_sync(&mcp->init_work);
|
||||
#endif
|
||||
}
|
||||
@@ -1317,8 +1334,10 @@ static int mcp2221_probe(struct hid_device *hdev,
|
||||
#endif
|
||||
|
||||
#if IS_REACHABLE(CONFIG_IIO)
|
||||
if (!gpio_mode_enforce) {
|
||||
INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
|
||||
schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -57,7 +57,7 @@ static int mwctrl_init(struct hid_device *hid)
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
mwctrl = kzalloc(sizeof(struct mwctrl_device), GFP_KERNEL);
|
||||
mwctrl = kzalloc_obj(struct mwctrl_device);
|
||||
if (!mwctrl)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ static int mf_init(struct hid_device *hid)
|
||||
input_ptr = input_ptr->next;
|
||||
input = list_entry(input_ptr, struct hid_input, list);
|
||||
|
||||
mf = kzalloc(sizeof(struct mf_device), GFP_KERNEL);
|
||||
mf = kzalloc_obj(struct mf_device);
|
||||
if (!mf)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ MODULE_LICENSE("GPL");
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#include "hid-haptic.h"
|
||||
|
||||
/* quirks to control the device */
|
||||
#define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0)
|
||||
#define MT_QUIRK_SLOT_IS_CONTACTID BIT(1)
|
||||
@@ -74,11 +76,14 @@ MODULE_LICENSE("GPL");
|
||||
#define MT_QUIRK_DISABLE_WAKEUP BIT(21)
|
||||
#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
|
||||
#define MT_QUIRK_APPLE_TOUCHBAR BIT(23)
|
||||
#define MT_QUIRK_YOGABOOK9I BIT(24)
|
||||
#define MT_QUIRK_KEEP_LATENCY_ON_CLOSE BIT(25)
|
||||
|
||||
#define MT_INPUTMODE_TOUCHSCREEN 0x02
|
||||
#define MT_INPUTMODE_TOUCHPAD 0x03
|
||||
|
||||
#define MT_BUTTONTYPE_CLICKPAD 0
|
||||
#define MT_BUTTONTYPE_PRESSUREPAD 1
|
||||
|
||||
enum latency_mode {
|
||||
HID_LATENCY_NORMAL = 0,
|
||||
@@ -167,6 +172,7 @@ struct mt_report_data {
|
||||
struct mt_device {
|
||||
struct mt_class mtclass; /* our mt device class */
|
||||
struct timer_list release_timer; /* to release sticky fingers */
|
||||
struct hid_haptic_device *haptic; /* haptic related configuration */
|
||||
struct hid_device *hdev; /* hid_device we're attached to */
|
||||
unsigned long mt_io_flags; /* mt flags (MT_IO_FLAGS_RUNNING)
|
||||
* first 8 bits are reserved for keeping the slot
|
||||
@@ -176,6 +182,8 @@ struct mt_device {
|
||||
__u8 inputmode_value; /* InputMode HID feature value */
|
||||
__u8 maxcontacts;
|
||||
bool is_buttonpad; /* is this device a button pad? */
|
||||
bool is_pressurepad; /* is this device a pressurepad? */
|
||||
bool is_haptic_touchpad; /* is this device a haptic touchpad? */
|
||||
bool serial_maybe; /* need to check for serial protocol */
|
||||
|
||||
struct list_head applications;
|
||||
@@ -207,6 +215,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
|
||||
#define MT_CLS_WIN_8_DISABLE_WAKEUP 0x0016
|
||||
#define MT_CLS_WIN_8_NO_STICKY_FINGERS 0x0017
|
||||
#define MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU 0x0018
|
||||
#define MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE 0x0019
|
||||
|
||||
/* vendor specific classes */
|
||||
#define MT_CLS_3M 0x0101
|
||||
@@ -225,6 +234,8 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
|
||||
#define MT_CLS_RAZER_BLADE_STEALTH 0x0112
|
||||
#define MT_CLS_SMART_TECH 0x0113
|
||||
#define MT_CLS_APPLE_TOUCHBAR 0x0114
|
||||
#define MT_CLS_YOGABOOK9I 0x0115
|
||||
#define MT_CLS_EGALAX_P80H84 0x0116
|
||||
#define MT_CLS_SIS 0x0457
|
||||
|
||||
#define MT_DEFAULT_MAXCONTACT 10
|
||||
@@ -326,6 +337,15 @@ static const struct mt_class mt_classes[] = {
|
||||
MT_QUIRK_CONTACT_CNT_ACCURATE |
|
||||
MT_QUIRK_WIN8_PTP_BUTTONS,
|
||||
.export_all_inputs = true },
|
||||
{ .name = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE,
|
||||
.quirks = MT_QUIRK_ALWAYS_VALID |
|
||||
MT_QUIRK_IGNORE_DUPLICATES |
|
||||
MT_QUIRK_HOVERING |
|
||||
MT_QUIRK_CONTACT_CNT_ACCURATE |
|
||||
MT_QUIRK_STICKY_FINGERS |
|
||||
MT_QUIRK_WIN8_PTP_BUTTONS |
|
||||
MT_QUIRK_KEEP_LATENCY_ON_CLOSE,
|
||||
.export_all_inputs = true },
|
||||
|
||||
/*
|
||||
* vendor specific classes
|
||||
@@ -389,6 +409,7 @@ static const struct mt_class mt_classes[] = {
|
||||
{ .name = MT_CLS_VTL,
|
||||
.quirks = MT_QUIRK_ALWAYS_VALID |
|
||||
MT_QUIRK_CONTACT_CNT_ACCURATE |
|
||||
MT_QUIRK_STICKY_FINGERS |
|
||||
MT_QUIRK_FORCE_GET_FEATURE,
|
||||
},
|
||||
{ .name = MT_CLS_GOOGLE,
|
||||
@@ -420,6 +441,19 @@ static const struct mt_class mt_classes[] = {
|
||||
.quirks = MT_QUIRK_NOT_SEEN_MEANS_UP |
|
||||
MT_QUIRK_ALWAYS_VALID |
|
||||
MT_QUIRK_CONTACT_CNT_ACCURATE,
|
||||
},
|
||||
{ .name = MT_CLS_YOGABOOK9I,
|
||||
.quirks = MT_QUIRK_ALWAYS_VALID |
|
||||
MT_QUIRK_FORCE_MULTI_INPUT |
|
||||
MT_QUIRK_SEPARATE_APP_REPORT |
|
||||
MT_QUIRK_HOVERING |
|
||||
MT_QUIRK_YOGABOOK9I,
|
||||
.export_all_inputs = true
|
||||
},
|
||||
{ .name = MT_CLS_EGALAX_P80H84,
|
||||
.quirks = MT_QUIRK_ALWAYS_VALID |
|
||||
MT_QUIRK_IGNORE_DUPLICATES |
|
||||
MT_QUIRK_CONTACT_CNT_ACCURATE,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
@@ -492,12 +526,19 @@ static void mt_get_feature(struct hid_device *hdev, struct hid_report *report)
|
||||
dev_warn(&hdev->dev, "failed to fetch feature %d\n",
|
||||
report->id);
|
||||
} else {
|
||||
/* The report ID in the request and the response should match */
|
||||
if (report->id != buf[0]) {
|
||||
hid_err(hdev, "Returned feature report did not match the request\n");
|
||||
goto free;
|
||||
}
|
||||
|
||||
ret = hid_report_raw_event(hdev, HID_FEATURE_REPORT, buf,
|
||||
size, 0);
|
||||
size, size, 0);
|
||||
if (ret)
|
||||
dev_warn(&hdev->dev, "failed to report feature\n");
|
||||
}
|
||||
|
||||
free:
|
||||
kfree(buf);
|
||||
}
|
||||
|
||||
@@ -526,8 +567,14 @@ static void mt_feature_mapping(struct hid_device *hdev,
|
||||
}
|
||||
|
||||
mt_get_feature(hdev, field->report);
|
||||
if (field->value[usage->usage_index] == MT_BUTTONTYPE_CLICKPAD)
|
||||
switch (field->value[usage->usage_index]) {
|
||||
case MT_BUTTONTYPE_CLICKPAD:
|
||||
td->is_buttonpad = true;
|
||||
break;
|
||||
case MT_BUTTONTYPE_PRESSUREPAD:
|
||||
td->is_pressurepad = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case 0xff0000c5:
|
||||
@@ -536,6 +583,8 @@ static void mt_feature_mapping(struct hid_device *hdev,
|
||||
mt_get_feature(hdev, field->report);
|
||||
break;
|
||||
}
|
||||
|
||||
hid_haptic_feature_mapping(hdev, td->haptic, field, usage);
|
||||
}
|
||||
|
||||
static void set_abs(struct input_dev *input, unsigned int code,
|
||||
@@ -824,7 +873,8 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
if ((cls->name == MT_CLS_WIN_8 ||
|
||||
cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT ||
|
||||
cls->name == MT_CLS_WIN_8_FORCE_MULTI_INPUT_NSMU ||
|
||||
cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP) &&
|
||||
cls->name == MT_CLS_WIN_8_DISABLE_WAKEUP ||
|
||||
cls->name == MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE) &&
|
||||
(field->application == HID_DG_TOUCHPAD ||
|
||||
field->application == HID_DG_TOUCHSCREEN))
|
||||
app->quirks |= MT_QUIRK_CONFIDENCE;
|
||||
@@ -891,6 +941,9 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
case HID_DG_TIPPRESSURE:
|
||||
set_abs(hi->input, ABS_MT_PRESSURE, field,
|
||||
cls->sn_pressure);
|
||||
td->is_haptic_touchpad =
|
||||
hid_haptic_check_pressure_unit(td->haptic,
|
||||
hi, field);
|
||||
MT_STORE_FIELD(p);
|
||||
return 1;
|
||||
case HID_DG_SCANTIME:
|
||||
@@ -1012,6 +1065,8 @@ static void mt_sync_frame(struct mt_device *td, struct mt_application *app,
|
||||
|
||||
app->num_received = 0;
|
||||
app->left_button_state = 0;
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_reset(td->haptic);
|
||||
}
|
||||
|
||||
static int mt_compute_timestamp(struct mt_application *app, __s32 value)
|
||||
@@ -1163,6 +1218,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
|
||||
minor = minor >> 1;
|
||||
}
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
hid_haptic_pressure_increase(td->haptic, *slot->p);
|
||||
|
||||
x = hdev->quirks & HID_QUIRK_X_INVERT ?
|
||||
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
|
||||
*slot->x;
|
||||
@@ -1366,6 +1424,9 @@ static int mt_touch_input_configured(struct hid_device *hdev,
|
||||
if (cls->is_indirect)
|
||||
app->mt_flags |= INPUT_MT_POINTER;
|
||||
|
||||
if (td->is_haptic_touchpad)
|
||||
app->mt_flags |= INPUT_MT_TOTAL_FORCE;
|
||||
|
||||
if (app->quirks & MT_QUIRK_NOT_SEEN_MEANS_UP)
|
||||
app->mt_flags |= INPUT_MT_DROP_UNUSED;
|
||||
|
||||
@@ -1376,6 +1437,8 @@ static int mt_touch_input_configured(struct hid_device *hdev,
|
||||
|
||||
if (td->is_buttonpad)
|
||||
__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
|
||||
if (td->is_pressurepad)
|
||||
__set_bit(INPUT_PROP_PRESSUREPAD, input->propbit);
|
||||
|
||||
app->pending_palm_slots = devm_kcalloc(&hi->input->dev,
|
||||
BITS_TO_LONGS(td->maxcontacts),
|
||||
@@ -1401,6 +1464,7 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
struct mt_application *application;
|
||||
struct mt_report_data *rdata;
|
||||
int ret;
|
||||
|
||||
rdata = mt_find_report_data(td, field->report);
|
||||
if (!rdata) {
|
||||
@@ -1463,6 +1527,11 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi,
|
||||
if (field->physical == HID_DG_STYLUS)
|
||||
hi->application = HID_DG_STYLUS;
|
||||
|
||||
ret = hid_haptic_input_mapping(hdev, td->haptic, hi, field, usage, bit,
|
||||
max);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* let hid-core decide for the others */
|
||||
return 0;
|
||||
}
|
||||
@@ -1542,6 +1611,38 @@ static void mt_report(struct hid_device *hid, struct hid_report *report)
|
||||
if (rdata && rdata->is_mt_collection)
|
||||
return mt_touch_report(hid, rdata);
|
||||
|
||||
/* Lenovo Yoga Book 9i requires consuming and dropping certain bogus reports */
|
||||
if (rdata && rdata->application &&
|
||||
(rdata->application->quirks & MT_QUIRK_YOGABOOK9I)) {
|
||||
|
||||
bool all_zero_report = true;
|
||||
|
||||
for (int f = 0; f < report->maxfield && all_zero_report; f++) {
|
||||
struct hid_field *fld = report->field[f];
|
||||
|
||||
for (int i = 0; i < fld->report_count; i++) {
|
||||
unsigned int usage = fld->usage[i].hid;
|
||||
|
||||
if (usage == HID_DG_INRANGE ||
|
||||
usage == HID_DG_TIPSWITCH ||
|
||||
usage == HID_DG_BARRELSWITCH ||
|
||||
usage == HID_DG_BARRELSWITCH2 ||
|
||||
usage == HID_DG_CONTACTID ||
|
||||
usage == HID_DG_TILT_X ||
|
||||
usage == HID_DG_TILT_Y) {
|
||||
|
||||
if (fld->value[i] != 0) {
|
||||
all_zero_report = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (all_zero_report)
|
||||
return;
|
||||
}
|
||||
|
||||
if (field && field->hidinput && field->hidinput->input)
|
||||
input_sync(field->hidinput->input);
|
||||
}
|
||||
@@ -1685,6 +1786,15 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
struct hid_report *report;
|
||||
int ret;
|
||||
|
||||
if (td->is_haptic_touchpad && (td->mtclass.name == MT_CLS_WIN_8 ||
|
||||
td->mtclass.name == MT_CLS_WIN_8_FORCE_MULTI_INPUT ||
|
||||
td->mtclass.name == MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE)) {
|
||||
if (hid_haptic_input_configured(hdev, td->haptic, hi) == 0)
|
||||
td->is_haptic_touchpad = false;
|
||||
} else {
|
||||
td->is_haptic_touchpad = false;
|
||||
}
|
||||
|
||||
list_for_each_entry(report, &hi->reports, hidinput_list) {
|
||||
rdata = mt_find_report_data(td, report);
|
||||
if (!rdata) {
|
||||
@@ -1730,6 +1840,30 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Lenovo Yoga Book 9i requires custom naming to allow differentiation in udev */
|
||||
if (hi->report && td->mtclass.quirks & MT_QUIRK_YOGABOOK9I) {
|
||||
switch (hi->report->id) {
|
||||
case 48:
|
||||
suffix = "Touchscreen Top";
|
||||
break;
|
||||
case 56:
|
||||
suffix = "Touchscreen Bottom";
|
||||
break;
|
||||
case 20:
|
||||
suffix = "Stylus Top";
|
||||
break;
|
||||
case 40:
|
||||
suffix = "Stylus Bottom";
|
||||
break;
|
||||
case 80:
|
||||
suffix = "Emulated Touchpad";
|
||||
break;
|
||||
default:
|
||||
suffix = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
hi->input->name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
|
||||
"%s %s", hdev->name, suffix);
|
||||
@@ -1829,6 +1963,11 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
dev_err(&hdev->dev, "cannot allocate multitouch data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
td->haptic = devm_kzalloc(&hdev->dev, sizeof(*(td->haptic)), GFP_KERNEL);
|
||||
if (!td->haptic)
|
||||
return -ENOMEM;
|
||||
|
||||
td->haptic->hdev = hdev;
|
||||
td->hdev = hdev;
|
||||
td->mtclass = *mtclass;
|
||||
td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
|
||||
@@ -1897,6 +2036,17 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
|
||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||
|
||||
if (td->is_haptic_touchpad) {
|
||||
if (hid_haptic_init(hdev, &td->haptic)) {
|
||||
dev_warn(&hdev->dev, "Cannot allocate haptic for %s\n",
|
||||
hdev->name);
|
||||
td->is_haptic_touchpad = false;
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
} else {
|
||||
devm_kfree(&hdev->dev, td->haptic);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1951,6 +2101,11 @@ static void mt_on_hid_hw_open(struct hid_device *hdev)
|
||||
|
||||
static void mt_on_hid_hw_close(struct hid_device *hdev)
|
||||
{
|
||||
struct mt_device *td = hid_get_drvdata(hdev);
|
||||
|
||||
if (td->mtclass.quirks & MT_QUIRK_KEEP_LATENCY_ON_CLOSE)
|
||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_NONE);
|
||||
else
|
||||
mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE);
|
||||
}
|
||||
|
||||
@@ -2088,8 +2243,12 @@ static const struct hid_device_id mt_devices[] = {
|
||||
{ .driver_data = MT_CLS_EGALAX_SERIAL,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
|
||||
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_A001) },
|
||||
{ .driver_data = MT_CLS_EGALAX,
|
||||
{ .driver_data = MT_CLS_EGALAX_SERIAL,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_DWAV,
|
||||
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C000) },
|
||||
{ .driver_data = MT_CLS_EGALAX_P80H84,
|
||||
HID_DEVICE(HID_BUS_ANY, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
USB_VENDOR_ID_DWAV,
|
||||
USB_DEVICE_ID_DWAV_EGALAX_MULTITOUCH_C002) },
|
||||
|
||||
/* Elan devices */
|
||||
@@ -2219,6 +2378,12 @@ static const struct hid_device_id mt_devices[] = {
|
||||
USB_VENDOR_ID_LENOVO,
|
||||
USB_DEVICE_ID_LENOVO_X12_TAB2) },
|
||||
|
||||
/* Lenovo Yoga Book 9i */
|
||||
{ .driver_data = MT_CLS_YOGABOOK9I,
|
||||
HID_DEVICE(BUS_USB, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
USB_VENDOR_ID_LENOVO,
|
||||
USB_DEVICE_ID_LENOVO_YOGABOOK9I) },
|
||||
|
||||
/* Logitech devices */
|
||||
{ .driver_data = MT_CLS_NSMU,
|
||||
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
@@ -2328,6 +2493,14 @@ static const struct hid_device_id mt_devices[] = {
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_UNITEC,
|
||||
USB_DEVICE_ID_UNITEC_USB_TOUCH_0A19) },
|
||||
|
||||
/* Uniwill touchpads */
|
||||
{ .driver_data = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE,
|
||||
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
USB_VENDOR_ID_PIXART, 0x0255) },
|
||||
{ .driver_data = MT_CLS_WIN_8_KEEP_LATENCY_ON_CLOSE,
|
||||
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
|
||||
USB_VENDOR_ID_PIXART, 0x0274) },
|
||||
|
||||
/* VTL panels */
|
||||
{ .driver_data = MT_CLS_VTL,
|
||||
MT_USB_DEVICE(USB_VENDOR_ID_VTL,
|
||||
|
||||
@@ -819,7 +819,7 @@ static void joycon_wait_for_input_report(struct joycon_ctlr *ctlr)
|
||||
#define JC_INPUT_REPORT_MAX_DELTA 17
|
||||
#define JC_SUBCMD_TX_OFFSET_MS 4
|
||||
#define JC_SUBCMD_VALID_DELTA_REQ 3
|
||||
#define JC_SUBCMD_RATE_MAX_ATTEMPTS 500
|
||||
#define JC_SUBCMD_RATE_MAX_ATTEMPTS 25
|
||||
#define JC_SUBCMD_RATE_LIMITER_USB_MS 20
|
||||
#define JC_SUBCMD_RATE_LIMITER_BT_MS 60
|
||||
#define JC_SUBCMD_RATE_LIMITER_MS(ctlr) ((ctlr)->hdev->bus == BUS_USB ? JC_SUBCMD_RATE_LIMITER_USB_MS : JC_SUBCMD_RATE_LIMITER_BT_MS)
|
||||
@@ -1455,10 +1455,10 @@ static void joycon_parse_imu_report(struct joycon_ctlr *ctlr,
|
||||
ctlr->imu_avg_delta_ms;
|
||||
ctlr->imu_timestamp_us += 1000 * ctlr->imu_avg_delta_ms;
|
||||
if (dropped_pkts > JC_IMU_DROPPED_PKT_WARNING) {
|
||||
hid_warn(ctlr->hdev,
|
||||
hid_warn_ratelimited(ctlr->hdev,
|
||||
"compensating for %u dropped IMU reports\n",
|
||||
dropped_pkts);
|
||||
hid_warn(ctlr->hdev,
|
||||
hid_warn_ratelimited(ctlr->hdev,
|
||||
"delta=%u avg_delta=%u\n",
|
||||
delta, ctlr->imu_avg_delta_ms);
|
||||
}
|
||||
@@ -2648,7 +2648,8 @@ static int nintendo_hid_probe(struct hid_device *hdev,
|
||||
init_waitqueue_head(&ctlr->wait);
|
||||
spin_lock_init(&ctlr->lock);
|
||||
ctlr->rumble_queue = alloc_workqueue("hid-nintendo-rumble_wq",
|
||||
WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
|
||||
WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU,
|
||||
0);
|
||||
if (!ctlr->rumble_queue) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
@@ -2747,8 +2748,6 @@ static void nintendo_hid_remove(struct hid_device *hdev)
|
||||
hid_hw_stop(hdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int nintendo_hid_resume(struct hid_device *hdev)
|
||||
{
|
||||
struct joycon_ctlr *ctlr = hid_get_drvdata(hdev);
|
||||
@@ -2791,8 +2790,6 @@ static int nintendo_hid_suspend(struct hid_device *hdev, pm_message_t message)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static const struct hid_device_id nintendo_hid_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO,
|
||||
USB_DEVICE_ID_NINTENDO_PROCON) },
|
||||
@@ -2826,11 +2823,8 @@ static struct hid_driver nintendo_hid_driver = {
|
||||
.probe = nintendo_hid_probe,
|
||||
.remove = nintendo_hid_remove,
|
||||
.raw_event = nintendo_hid_event,
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
.resume = nintendo_hid_resume,
|
||||
.suspend = nintendo_hid_suspend,
|
||||
#endif
|
||||
.resume = pm_ptr(nintendo_hid_resume),
|
||||
.suspend = pm_ptr(nintendo_hid_suspend),
|
||||
};
|
||||
static int __init nintendo_init(void)
|
||||
{
|
||||
|
||||
@@ -900,7 +900,7 @@ static int ntrig_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
hdev->quirks |= HID_QUIRK_MULTI_INPUT
|
||||
| HID_QUIRK_NO_INIT_REPORTS;
|
||||
|
||||
nd = kmalloc(sizeof(struct ntrig_data), GFP_KERNEL);
|
||||
nd = kmalloc_obj(struct ntrig_data);
|
||||
if (!nd) {
|
||||
hid_err(hdev, "cannot allocate N-Trig data\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -78,7 +78,7 @@ struct picolcd_pending *picolcd_send_and_wait(struct hid_device *hdev,
|
||||
return NULL;
|
||||
if (data->status & PICOLCD_FAILED)
|
||||
return NULL;
|
||||
work = kzalloc(sizeof(*work), GFP_KERNEL);
|
||||
work = kzalloc_obj(*work);
|
||||
if (!work)
|
||||
return NULL;
|
||||
|
||||
@@ -363,7 +363,6 @@ static int picolcd_raw_event(struct hid_device *hdev,
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int picolcd_suspend(struct hid_device *hdev, pm_message_t message)
|
||||
{
|
||||
if (PMSG_IS_AUTO(message))
|
||||
@@ -401,7 +400,6 @@ static int picolcd_reset_resume(struct hid_device *hdev)
|
||||
picolcd_leds_set(hid_get_drvdata(hdev));
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* initialize keypad input device */
|
||||
static int picolcd_init_keys(struct picolcd_data *data,
|
||||
@@ -530,7 +528,7 @@ static int picolcd_probe(struct hid_device *hdev,
|
||||
* Let's allocate the picolcd data structure, set some reasonable
|
||||
* defaults, and associate it with the device
|
||||
*/
|
||||
data = kzalloc(sizeof(struct picolcd_data), GFP_KERNEL);
|
||||
data = kzalloc_obj(struct picolcd_data);
|
||||
if (data == NULL) {
|
||||
hid_err(hdev, "can't allocate space for Minibox PicoLCD device data\n");
|
||||
return -ENOMEM;
|
||||
@@ -648,11 +646,9 @@ static struct hid_driver picolcd_driver = {
|
||||
.probe = picolcd_probe,
|
||||
.remove = picolcd_remove,
|
||||
.raw_event = picolcd_raw_event,
|
||||
#ifdef CONFIG_PM
|
||||
.suspend = picolcd_suspend,
|
||||
.resume = picolcd_resume,
|
||||
.reset_resume = picolcd_reset_resume,
|
||||
#endif
|
||||
.suspend = pm_ptr(picolcd_suspend),
|
||||
.resume = pm_ptr(picolcd_resume),
|
||||
.reset_resume = pm_ptr(picolcd_reset_resume),
|
||||
};
|
||||
module_hid_driver(picolcd_driver);
|
||||
|
||||
|
||||
+11
-10
@@ -24,10 +24,6 @@
|
||||
*/
|
||||
|
||||
|
||||
/* #define DEBUG */
|
||||
|
||||
#define debug(format, arg...) pr_debug("hid-plff: " format "\n" , ## arg)
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
@@ -53,14 +49,14 @@ static int hid_plff_play(struct input_dev *dev, void *data,
|
||||
|
||||
left = effect->u.rumble.strong_magnitude;
|
||||
right = effect->u.rumble.weak_magnitude;
|
||||
debug("called with 0x%04x 0x%04x", left, right);
|
||||
hid_dbg(dev, "called with 0x%04x 0x%04x", left, right);
|
||||
|
||||
left = left * plff->maxval / 0xffff;
|
||||
right = right * plff->maxval / 0xffff;
|
||||
|
||||
*plff->strong = left;
|
||||
*plff->weak = right;
|
||||
debug("running with 0x%02x 0x%02x", left, right);
|
||||
hid_dbg(dev, "running with 0x%02x 0x%02x", left, right);
|
||||
hid_hw_request(hid, plff->report, HID_REQ_SET_REPORT);
|
||||
|
||||
return 0;
|
||||
@@ -119,7 +115,7 @@ static int plff_init(struct hid_device *hid)
|
||||
report->field[0]->value[1] = 0x00;
|
||||
strong = &report->field[0]->value[2];
|
||||
weak = &report->field[0]->value[3];
|
||||
debug("detected single-field device");
|
||||
hid_dbg(hid, "detected single-field device");
|
||||
} else if (report->field[0]->maxusage == 1 &&
|
||||
report->field[0]->usage[0].hid ==
|
||||
(HID_UP_LED | 0x43) &&
|
||||
@@ -134,13 +130,13 @@ static int plff_init(struct hid_device *hid)
|
||||
weak = &report->field[3]->value[0];
|
||||
if (hid->vendor == USB_VENDOR_ID_JESS2)
|
||||
maxval = 0xff;
|
||||
debug("detected 4-field device");
|
||||
hid_dbg(hid, "detected 4-field device");
|
||||
} else {
|
||||
hid_err(hid, "not enough fields or values\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
plff = kzalloc(sizeof(struct plff_device), GFP_KERNEL);
|
||||
plff = kzalloc_obj(struct plff_device);
|
||||
if (!plff)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -194,9 +190,14 @@ static int pl_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
goto err;
|
||||
}
|
||||
|
||||
plff_init(hdev);
|
||||
ret = plff_init(hdev);
|
||||
if (ret)
|
||||
goto stop;
|
||||
|
||||
return 0;
|
||||
|
||||
stop:
|
||||
hid_hw_stop(hdev);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
+421
-275
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ static int px_raw_event(struct hid_device *hid, struct hid_report *report,
|
||||
data[0] |= (1 << (data[idx] - 0xE0));
|
||||
data[idx] = 0;
|
||||
}
|
||||
hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, 0);
|
||||
hid_report_raw_event(hid, HID_INPUT_REPORT, data, size, size, 0);
|
||||
return 1;
|
||||
|
||||
default: /* unknown report */
|
||||
|
||||
@@ -378,6 +378,10 @@ static int pcmidi_handle_report4(struct pcmidi_snd *pm, u8 *data)
|
||||
bit_mask = (bit_mask << 8) | data[2];
|
||||
bit_mask = (bit_mask << 8) | data[3];
|
||||
|
||||
/* robustness in case input_mapping hook does not get called */
|
||||
if (!pm->input_ep82)
|
||||
return 0;
|
||||
|
||||
/* break keys */
|
||||
for (bit_index = 0; bit_index < 24; bit_index++) {
|
||||
if (!((0x01 << bit_index) & bit_mask)) {
|
||||
@@ -793,7 +797,7 @@ static int pk_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
intf = to_usb_interface(hdev->dev.parent);
|
||||
ifnum = intf->cur_altsetting->desc.bInterfaceNumber;
|
||||
|
||||
pm = kzalloc(sizeof(*pm), GFP_KERNEL);
|
||||
pm = kzalloc_obj(*pm);
|
||||
if (pm == NULL) {
|
||||
hid_err(hdev, "can't alloc descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
+62
-11
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
|
||||
static const struct hid_device_id hid_quirks[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_8BITDO, USB_DEVICE_ID_8BITDO_PRO_3), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_GAMEPAD), HID_QUIRK_BADPAD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_AASHIMA, USB_DEVICE_ID_AASHIMA_PREDATOR), HID_QUIRK_BADPAD },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ADATA_XPG, USB_VENDOR_ID_ADATA_XPG_WL_GAMING_MOUSE), HID_QUIRK_ALWAYS_POLL },
|
||||
@@ -81,6 +82,7 @@ static const struct hid_device_id hid_quirks[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_PS3), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DRAGONRISE, USB_DEVICE_ID_DRAGONRISE_WIIU), HID_QUIRK_MULTI_INPUT },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_DWAV, USB_DEVICE_ID_EGALAX_TOUCHCONTROLLER), HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_EDIFIER, USB_DEVICE_ID_EDIFIER_QR30), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, HID_ANY_ID), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, USB_DEVICE_ID_ELO_TS2700), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_EMS, USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II), HID_QUIRK_MULTI_INPUT },
|
||||
@@ -132,6 +134,7 @@ static const struct hid_device_id hid_quirks[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6019), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_BOLT_RECEIVER), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C007), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_C077), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_KEYBOARD_G710_PLUS), HID_QUIRK_NOGET },
|
||||
@@ -184,6 +187,7 @@ static const struct hid_device_id hid_quirks[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SEMICO, USB_DEVICE_ID_SEMICO_USB_KEYKOARD), HID_QUIRK_NO_INIT_REPORTS },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SENNHEISER, USB_DEVICE_ID_SENNHEISER_BTD500USB), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD), HID_QUIRK_NO_INIT_REPORTS },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, USB_DEVICE_ID_SIGMA_MICRO_USB_MOUSE), HID_QUIRK_ALWAYS_POLL },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SIGMATEL, USB_DEVICE_ID_SIGMATEL_STMP3780), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS1030_TOUCH), HID_QUIRK_NOGET },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SIS_TOUCH, USB_DEVICE_ID_SIS817_TOUCH), HID_QUIRK_NOGET },
|
||||
@@ -232,6 +236,15 @@ static const struct hid_device_id hid_quirks[] = {
|
||||
* used as a driver. See hid_scan_report().
|
||||
*/
|
||||
static const struct hid_device_id hid_have_special_driver[] = {
|
||||
#if IS_ENABLED(CONFIG_USB_APPLEDISPLAY)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9218) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9219) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921c) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x921d) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9222) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9226) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, 0x9236) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_A4TECH)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_WCP32PU) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_A4TECH, USB_DEVICE_ID_A4TECH_X5_005D) },
|
||||
@@ -410,8 +423,11 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
#if IS_ENABLED(CONFIG_HID_ELECOM)
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_BM084) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XGL20DLBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AC) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_00FB) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3URBK_018F) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_00FC) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT3DRBK_018C) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_XT4DRBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
|
||||
@@ -420,6 +436,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK_019B) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1MRBK_01AB) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_ELO)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
|
||||
@@ -675,22 +693,54 @@ static const struct hid_device_id hid_have_special_driver[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_SONY)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS4_GIBSON_SG_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CRKD, USB_DEVICE_ID_CRKD_PS5_GIBSON_SG_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_DRUMS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB1_GUITAR) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_DRUMS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB2_GUITAR) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_DRUMS_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_MUSTANG_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_SQUIER_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_HARMONIX, USB_DEVICE_ID_HARMONIX_WII_RB3_MUSTANG_GUITAR) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_MADCATZ, USB_DEVICE_ID_MADCATZ_PS4_STRATOCASTER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_JAGUAR) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS4_RIFFMASTER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_PDP, USB_DEVICE_ID_PDP_PS5_RIFFMASTER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_GUITAR_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_REDOCTANE, USB_DEVICE_ID_REDOCTANE_PS4_GHLIVE_DONGLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR5U_REMOTE) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_NSG_MR7U_REMOTE) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_MOTION_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
|
||||
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGX_MOUSE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3WIIU_GHLIVE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_DJH_TURNTABLE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_DRUMS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_GH_GUITAR) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_KEYBOARD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_DRUMS_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_KEYBOARD_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_MUSTANG_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MPA_SQUIER_MODE) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB3_MUSTANG_GUITAR) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_DRUMS) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY_RHYTHM, USB_DEVICE_ID_SONY_PS3_RB_GUITAR) },
|
||||
#endif
|
||||
#if IS_ENABLED(CONFIG_HID_SPEEDLINK)
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_X_TENSIONS, USB_DEVICE_ID_SPEEDLINK_VAD_CEZANNE) },
|
||||
@@ -768,6 +818,7 @@ static const struct hid_device_id hid_ignore_list[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_BERKSHIRE, USB_DEVICE_ID_BERKSHIRE_PCWD) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA2) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_HP_5MP_CAMERA3) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CIDC, 0x0103) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI470X) },
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_CYGNAL, USB_DEVICE_ID_CYGNAL_RADIO_SI4713) },
|
||||
@@ -1148,11 +1199,11 @@ static int hid_modify_dquirk(const struct hid_device_id *id,
|
||||
int list_edited = 0;
|
||||
int ret = 0;
|
||||
|
||||
hdev = kzalloc(sizeof(*hdev), GFP_KERNEL);
|
||||
hdev = kzalloc_obj(*hdev);
|
||||
if (!hdev)
|
||||
return -ENOMEM;
|
||||
|
||||
q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
|
||||
q_new = kmalloc_obj(struct quirks_list_struct);
|
||||
if (!q_new) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <asm-generic/errno-base.h>
|
||||
#include <asm-generic/int-ll64.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/hid.h>
|
||||
#include <linux/usb.h>
|
||||
|
||||
#include "hid-ids.h"
|
||||
|
||||
#define RAPOO_BTN_BACK 0x08
|
||||
#define RAPOO_BTN_FORWARD 0x10
|
||||
|
||||
static const struct hid_device_id rapoo_devices[] = {
|
||||
{ HID_USB_DEVICE(USB_VENDOR_ID_RAPOO, USB_DEVICE_ID_RAPOO_2_4G_RECEIVER) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(hid, rapoo_devices);
|
||||
|
||||
static int rapoo_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct input_dev *input;
|
||||
|
||||
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, "start failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hdev->bus == BUS_USB) {
|
||||
struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
|
||||
return 0;
|
||||
}
|
||||
|
||||
input = devm_input_allocate_device(&hdev->dev);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input->name = "Rapoo 2.4G Wireless Mouse";
|
||||
input->phys = "rapoo/input1";
|
||||
input->id.bustype = hdev->bus;
|
||||
input->id.vendor = hdev->vendor;
|
||||
input->id.product = hdev->product;
|
||||
input->id.version = hdev->version;
|
||||
|
||||
__set_bit(EV_KEY, input->evbit);
|
||||
__set_bit(KEY_BACK, input->keybit);
|
||||
__set_bit(KEY_FORWARD, input->keybit);
|
||||
|
||||
ret = input_register_device(input);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hid_set_drvdata(hdev, input);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rapoo_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size)
|
||||
{
|
||||
struct input_dev *input = hid_get_drvdata(hdev);
|
||||
|
||||
if (!input)
|
||||
return 0;
|
||||
|
||||
if (report->id == 1 && size >= 2) {
|
||||
u8 btn = data[1];
|
||||
|
||||
input_report_key(input, KEY_BACK, btn & RAPOO_BTN_BACK);
|
||||
input_report_key(input, KEY_FORWARD, btn & RAPOO_BTN_FORWARD);
|
||||
input_sync(input);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct hid_driver rapoo_driver = {
|
||||
.name = "hid-rapoo",
|
||||
.id_table = rapoo_devices,
|
||||
.probe = rapoo_probe,
|
||||
.raw_event = rapoo_raw_event,
|
||||
};
|
||||
|
||||
module_hid_driver(rapoo_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Nguyen Dinh Dang Duong <dangduong31205@gmail.com>");
|
||||
MODULE_DESCRIPTION("RAPOO 2.4G Wireless Device Driver");
|
||||
|
||||
@@ -299,7 +299,7 @@ static int arvo_init_specials(struct hid_device *hdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
arvo = kzalloc(sizeof(*arvo), GFP_KERNEL);
|
||||
arvo = kzalloc_obj(*arvo);
|
||||
if (!arvo) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -279,7 +279,7 @@ static int isku_init_specials(struct hid_device *hdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
isku = kzalloc(sizeof(*isku), GFP_KERNEL);
|
||||
isku = kzalloc_obj(*isku);
|
||||
if (!isku) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -704,7 +704,7 @@ static int kone_init_specials(struct hid_device *hdev)
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
== USB_INTERFACE_PROTOCOL_MOUSE) {
|
||||
|
||||
kone = kzalloc(sizeof(*kone), GFP_KERNEL);
|
||||
kone = kzalloc_obj(*kone);
|
||||
if (!kone)
|
||||
return -ENOMEM;
|
||||
hid_set_drvdata(hdev, kone);
|
||||
|
||||
@@ -384,7 +384,7 @@ static int koneplus_init_specials(struct hid_device *hdev)
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
== USB_INTERFACE_PROTOCOL_MOUSE) {
|
||||
|
||||
koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL);
|
||||
koneplus = kzalloc_obj(*koneplus);
|
||||
if (!koneplus) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -88,7 +88,7 @@ static int konepure_init_specials(struct hid_device *hdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
konepure = kzalloc(sizeof(*konepure), GFP_KERNEL);
|
||||
konepure = kzalloc_obj(*konepure);
|
||||
if (!konepure) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -453,7 +453,7 @@ static int kovaplus_init_specials(struct hid_device *hdev)
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
== USB_INTERFACE_PROTOCOL_MOUSE) {
|
||||
|
||||
kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL);
|
||||
kovaplus = kzalloc_obj(*kovaplus);
|
||||
if (!kovaplus) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -119,7 +119,7 @@ static int lua_init_specials(struct hid_device *hdev)
|
||||
struct lua_device *lua;
|
||||
int retval;
|
||||
|
||||
lua = kzalloc(sizeof(*lua), GFP_KERNEL);
|
||||
lua = kzalloc_obj(*lua);
|
||||
if (!lua) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -403,7 +403,7 @@ static int pyra_init_specials(struct hid_device *hdev)
|
||||
if (intf->cur_altsetting->desc.bInterfaceProtocol
|
||||
== USB_INTERFACE_PROTOCOL_MOUSE) {
|
||||
|
||||
pyra = kzalloc(sizeof(*pyra), GFP_KERNEL);
|
||||
pyra = kzalloc_obj(*pyra);
|
||||
if (!pyra) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -96,7 +96,7 @@ static int ryos_init_specials(struct hid_device *hdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
ryos = kzalloc(sizeof(*ryos), GFP_KERNEL);
|
||||
ryos = kzalloc_obj(*ryos);
|
||||
if (!ryos) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -68,7 +68,7 @@ static int savu_init_specials(struct hid_device *hdev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
savu = kzalloc(sizeof(*savu), GFP_KERNEL);
|
||||
savu = kzalloc_obj(*savu);
|
||||
if (!savu) {
|
||||
hid_err(hdev, "can't alloc device descriptor\n");
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -152,7 +152,7 @@ static int roccat_open(struct inode *inode, struct file *file)
|
||||
struct roccat_device *device;
|
||||
int error = 0;
|
||||
|
||||
reader = kzalloc(sizeof(struct roccat_reader), GFP_KERNEL);
|
||||
reader = kzalloc_obj(struct roccat_reader);
|
||||
if (!reader)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -257,6 +257,7 @@ int roccat_report_event(int minor, u8 const *data)
|
||||
if (!new_value)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&device->readers_lock);
|
||||
mutex_lock(&device->cbuf_lock);
|
||||
|
||||
report = &device->cbuf[device->cbuf_end];
|
||||
@@ -279,6 +280,7 @@ int roccat_report_event(int minor, u8 const *data)
|
||||
}
|
||||
|
||||
mutex_unlock(&device->cbuf_lock);
|
||||
mutex_unlock(&device->readers_lock);
|
||||
|
||||
wake_up_interruptible(&device->wait);
|
||||
return 0;
|
||||
@@ -301,7 +303,7 @@ int roccat_connect(const struct class *klass, struct hid_device *hid, int report
|
||||
struct roccat_device *device;
|
||||
int temp;
|
||||
|
||||
device = kzalloc(sizeof(struct roccat_device), GFP_KERNEL);
|
||||
device = kzalloc_obj(struct roccat_device);
|
||||
if (!device)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user