mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 05:02:12 +00:00
hid-for-linus-2024111801
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIVAwUAZzusZKZi849r7WBJAQKH9Q/+Llff1j+bP/W2E987vDy8zfFrMHR1nTrB nJb1mXKCp1AG+HcrrJh+9nVZXaXJtoduJ83wufxZoeK2jT06ij+TXPRW3JJcYooO C1UK8CI6fQBzTzKtDLRdBxv8MVTyuFI8KkBasDQQEJafRSN4rW007zA5B+EftcXe N9eb1+BiSbIMDtFdLw6N7kvDwfUj9iWO34cmH7ahv1Y0mj94OCRylN3p3fsbvkjG N5VP0Dm47X5H3OhS85HuVVskpFeMWBnNnLBFCxSoSELhl9pbXcriNQtUrk+uyHfL ObGrUajJlw6rrlCwugonJ5a6HmpWcfZBaXlvkP1b/q4oAePQ+Zuv0RStvPeqiM07 3m4XJf8kcRhMnUVYKo4nv9kVedK+EKu/HBZQsgTdDlTKV3BXpktt7PRD/WUKZ7yG kr/JWOlJho2Lzg7ky/47aJOgRfaLJzhPx1vSoSHrglB4L+4b+J0wZassc/iTkA0Q 9JS1Jci41oAcMzDW4KMkE2GIyXeTeSUTJH54DAm4+SXwkR1Sgn0K+ogceRc86ooA ZQu8touqvHBrVekC6jaFAA92IekLO5UFmiUcbQ1MHFdjx5Y9nruwKd2DulLV9cb9 XdHZMzT2RSdL1DLh5LlXRhLOSplhg0sUj9/XkCHIMX2GXa+ZNgFEA9VxDYBnB+dJ XZsGAHtgWQg= =J6g1 -----END PGP SIGNATURE----- Merge tag 'hid-for-linus-2024111801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid Pull HID updates from Jiri Kosina: - improvement in the way hid-bpf coexists with specific drivers (others than hid-generic) that are already bound to devices (Benjamin Tissoires) - removal of three way-too-aggressive BUG_ON()s from HID drivers (He Lugang) - assorted cleanups and small code fixes to HID core (Dmitry Torokhov, Yan Zhen, Nathan Chancellor, Andy Shevchenko) - support for Corsair Void headset family (Stuart Hayhurst) - Support for Goodix GT7986U SPI (Charles Wang) - initial vendor-specific driver for Kysona, currently adding support for Kysona M600 (Lode Willems) - other assorted code cleanups and small bugfixes all over the place * tag 'hid-for-linus-2024111801' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (40 commits) HID: multitouch: make mt_set_mode() less cryptic HID: hid-goodix-spi: Add OF supports dt-bindings: input: Goodix GT7986U SPI HID Touchscreen HID: hyperv: streamline driver probe to avoid devres issues HID: magicmouse: Apple Magic Trackpad 2 USB-C driver support HID: rmi: Add select RMI4_F3A in Kconfig HID: wacom: Interpret tilt data from Intuos Pro BT as signed values HID: steelseries: Add capacity_level mapping HID: steelseries: Fix battery requests stopping after some time HID: hid-goodix: Fix HID get/set feature operation overwritten problem HID: hid-goodix: Return 0 when receiving an empty HID feature package HID: bpf: drop use of Logical|Physical|UsageRange HID: bpf: Fix Rapoo M50 Plus Silent side buttons HID: bpf: Fix NKRO on Mistel MD770 HID: replace BUG_ON() with WARN_ON() HID: wacom: Set eraser status when either 'Eraser' or 'Invert' usage is set HID: Kysona: add basic online status HID: Kysona: check battery status every 5s using a workqueue HID: Kysona: Add basic battery reporting for Kysona M600 HID: Add IDs for Kysona ...
This commit is contained in:
commit
b57807cbbf
38
Documentation/ABI/testing/sysfs-driver-hid-corsair-void
Normal file
38
Documentation/ABI/testing/sysfs-driver-hid-corsair-void
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_headset
|
||||||
|
Date: January 2024
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (R) The firmware version of the headset
|
||||||
|
* Returns -ENODATA if no version was reported
|
||||||
|
|
||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/fw_version_receiver
|
||||||
|
Date: January 2024
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (R) The firmware version of the receiver
|
||||||
|
|
||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/microphone_up
|
||||||
|
Date: July 2023
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (R) Get the physical position of the microphone
|
||||||
|
* 1 -> Microphone up
|
||||||
|
* 0 -> Microphone down
|
||||||
|
|
||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/send_alert
|
||||||
|
Date: July 2023
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (W) Play a built-in notification from the headset (0 / 1)
|
||||||
|
|
||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/set_sidetone
|
||||||
|
Date: December 2023
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (W) Set the sidetone volume (0 - sidetone_max)
|
||||||
|
|
||||||
|
What: /sys/bus/hid/drivers/hid-corsair-void/<dev>/sidetone_max
|
||||||
|
Date: July 2024
|
||||||
|
KernelVersion: 6.13
|
||||||
|
Contact: Stuart Hayhurst <stuart.a.hayhurst@gmail.com>
|
||||||
|
Description: (R) Report the maximum sidetone volume
|
@ -0,0 +1,69 @@
|
|||||||
|
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
$id: http://devicetree.org/schemas/input/goodix,gt7986u-spifw.yaml#
|
||||||
|
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||||
|
|
||||||
|
title: Goodix GT7986U SPI HID Touchscreen
|
||||||
|
|
||||||
|
maintainers:
|
||||||
|
- Charles Wang <charles.goodix@gmail.com>
|
||||||
|
|
||||||
|
description: |
|
||||||
|
Supports the Goodix GT7986U touchscreen.
|
||||||
|
This touch controller reports data packaged according to the HID protocol
|
||||||
|
over the SPI bus, but it is incompatible with Microsoft's HID-over-SPI protocol.
|
||||||
|
|
||||||
|
NOTE: these bindings are distinct from the bindings used with the
|
||||||
|
GT7986U when the chip is running I2C firmware. This is because there's
|
||||||
|
not a single device that talks over both I2C and SPI but rather
|
||||||
|
distinct touchscreens that happen to be built with the same ASIC but
|
||||||
|
that are distinct products running distinct firmware.
|
||||||
|
|
||||||
|
allOf:
|
||||||
|
- $ref: /schemas/spi/spi-peripheral-props.yaml#
|
||||||
|
|
||||||
|
properties:
|
||||||
|
compatible:
|
||||||
|
enum:
|
||||||
|
- goodix,gt7986u-spifw
|
||||||
|
|
||||||
|
reg:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
interrupts:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
reset-gpios:
|
||||||
|
maxItems: 1
|
||||||
|
|
||||||
|
spi-max-frequency: true
|
||||||
|
|
||||||
|
required:
|
||||||
|
- compatible
|
||||||
|
- reg
|
||||||
|
- interrupts
|
||||||
|
- reset-gpios
|
||||||
|
|
||||||
|
unevaluatedProperties: false
|
||||||
|
|
||||||
|
examples:
|
||||||
|
- |
|
||||||
|
#include <dt-bindings/interrupt-controller/irq.h>
|
||||||
|
#include <dt-bindings/gpio/gpio.h>
|
||||||
|
|
||||||
|
spi {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
|
||||||
|
touchscreen@0 {
|
||||||
|
compatible = "goodix,gt7986u-spifw";
|
||||||
|
reg = <0>;
|
||||||
|
interrupt-parent = <&gpio>;
|
||||||
|
interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
|
||||||
|
reset-gpios = <&gpio1 1 GPIO_ACTIVE_LOW>;
|
||||||
|
spi-max-frequency = <10000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
...
|
@ -213,13 +213,16 @@ config HID_CHICONY
|
|||||||
config HID_CORSAIR
|
config HID_CORSAIR
|
||||||
tristate "Corsair devices"
|
tristate "Corsair devices"
|
||||||
depends on USB_HID && LEDS_CLASS
|
depends on USB_HID && LEDS_CLASS
|
||||||
|
select POWER_SUPPLY
|
||||||
help
|
help
|
||||||
Support for Corsair devices that are not fully compliant with the
|
Support for Corsair devices that are not fully compliant with the
|
||||||
HID standard.
|
HID standard.
|
||||||
|
Support for Corsair Void headsets.
|
||||||
|
|
||||||
Supported devices:
|
Supported devices:
|
||||||
- Vengeance K90
|
- Vengeance K90
|
||||||
- Scimitar PRO RGB
|
- Scimitar PRO RGB
|
||||||
|
- Corsair Void headsets
|
||||||
|
|
||||||
config HID_COUGAR
|
config HID_COUGAR
|
||||||
tristate "Cougar devices"
|
tristate "Cougar devices"
|
||||||
@ -465,6 +468,15 @@ config HID_KYE
|
|||||||
- MousePen i608X tablet
|
- MousePen i608X tablet
|
||||||
- EasyPen M610X tablet
|
- EasyPen M610X tablet
|
||||||
|
|
||||||
|
config HID_KYSONA
|
||||||
|
tristate "Kysona devices"
|
||||||
|
depends on USB_HID
|
||||||
|
help
|
||||||
|
Support for Kysona mice.
|
||||||
|
|
||||||
|
Say Y here if you have a Kysona M600 mouse
|
||||||
|
and want to be able to read its battery capacity.
|
||||||
|
|
||||||
config HID_UCLOGIC
|
config HID_UCLOGIC
|
||||||
tristate "UC-Logic"
|
tristate "UC-Logic"
|
||||||
depends on USB_HID
|
depends on USB_HID
|
||||||
@ -1096,6 +1108,7 @@ config HID_RMI
|
|||||||
select RMI4_F11
|
select RMI4_F11
|
||||||
select RMI4_F12
|
select RMI4_F12
|
||||||
select RMI4_F30
|
select RMI4_F30
|
||||||
|
select RMI4_F3A
|
||||||
help
|
help
|
||||||
Support for Synaptics RMI4 touchpads.
|
Support for Synaptics RMI4 touchpads.
|
||||||
Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
|
Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid
|
||||||
|
@ -38,7 +38,7 @@ obj-$(CONFIG_HID_BIGBEN_FF) += hid-bigbenff.o
|
|||||||
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
|
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
|
||||||
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
|
obj-$(CONFIG_HID_CHICONY) += hid-chicony.o
|
||||||
obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o
|
obj-$(CONFIG_HID_CMEDIA) += hid-cmedia.o
|
||||||
obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o
|
obj-$(CONFIG_HID_CORSAIR) += hid-corsair.o hid-corsair-void.o
|
||||||
obj-$(CONFIG_HID_COUGAR) += hid-cougar.o
|
obj-$(CONFIG_HID_COUGAR) += hid-cougar.o
|
||||||
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
|
obj-$(CONFIG_HID_CP2112) += hid-cp2112.o
|
||||||
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
|
obj-$(CONFIG_HID_CYPRESS) += hid-cypress.o
|
||||||
@ -70,6 +70,7 @@ obj-$(CONFIG_HID_JABRA) += hid-jabra.o
|
|||||||
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
|
obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o
|
||||||
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
|
obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
|
||||||
obj-$(CONFIG_HID_KYE) += hid-kye.o
|
obj-$(CONFIG_HID_KYE) += hid-kye.o
|
||||||
|
obj-$(CONFIG_HID_KYSONA) += hid-kysona.o
|
||||||
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
|
||||||
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
|
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
|
||||||
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
|
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
|
||||||
|
@ -148,7 +148,7 @@ out:
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_output_report);
|
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_output_report);
|
||||||
|
|
||||||
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size)
|
const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct hid_bpf_ctx_kern ctx_kern = {
|
struct hid_bpf_ctx_kern ctx_kern = {
|
||||||
@ -183,7 +183,7 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned
|
|||||||
|
|
||||||
ignore_bpf:
|
ignore_bpf:
|
||||||
kfree(ctx_kern.data);
|
kfree(ctx_kern.data);
|
||||||
return kmemdup(rdesc, *size, GFP_KERNEL);
|
return rdesc;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
|
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
|
||||||
|
|
||||||
@ -260,8 +260,11 @@ int hid_bpf_allocate_event_data(struct hid_device *hdev)
|
|||||||
|
|
||||||
int hid_bpf_reconnect(struct hid_device *hdev)
|
int hid_bpf_reconnect(struct hid_device *hdev)
|
||||||
{
|
{
|
||||||
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
|
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status)) {
|
||||||
|
/* trigger call to call_hid_bpf_rdesc_fixup() during the next probe */
|
||||||
|
hdev->bpf_rsize = 0;
|
||||||
return device_reprobe(&hdev->dev);
|
return device_reprobe(&hdev->dev);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log,
|
|||||||
WRITE_RANGE(hid_device, name, true),
|
WRITE_RANGE(hid_device, name, true),
|
||||||
WRITE_RANGE(hid_device, uniq, true),
|
WRITE_RANGE(hid_device, uniq, true),
|
||||||
WRITE_RANGE(hid_device, phys, true),
|
WRITE_RANGE(hid_device, phys, true),
|
||||||
|
WRITE_RANGE(hid_device, quirks, false),
|
||||||
};
|
};
|
||||||
#undef WRITE_RANGE
|
#undef WRITE_RANGE
|
||||||
const struct btf_type *state = NULL;
|
const struct btf_type *state = NULL;
|
||||||
|
@ -214,7 +214,8 @@ static const __u8 fixed_rdesc_pad[] = {
|
|||||||
CollectionApplication(
|
CollectionApplication(
|
||||||
// -- Byte 0 in report
|
// -- Byte 0 in report
|
||||||
ReportId(PAD_REPORT_ID)
|
ReportId(PAD_REPORT_ID)
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMaximum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TabletFunctionKeys
|
Usage_Dig_TabletFunctionKeys
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
@ -234,14 +235,17 @@ static const __u8 fixed_rdesc_pad[] = {
|
|||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Byte 4 in report is the dial
|
// Byte 4 in report is the dial
|
||||||
Usage_GD_Wheel
|
Usage_GD_Wheel
|
||||||
LogicalRange_i8(-1, 1)
|
LogicalMinimum_i8(-1)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportCount(1)
|
ReportCount(1)
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
Input(Var|Rel)
|
Input(Var|Rel)
|
||||||
// Byte 5 is the button state
|
// Byte 5 is the button state
|
||||||
UsagePage_Button
|
UsagePage_Button
|
||||||
UsageRange_i8(0x01, 0x8)
|
UsageMinimum_i8(0x01)
|
||||||
LogicalRange_i8(0x0, 0x1)
|
UsageMaximum_i8(0x08)
|
||||||
|
LogicalMinimum_i8(0x0)
|
||||||
|
LogicalMaximum_i8(0x1)
|
||||||
ReportCount(7)
|
ReportCount(7)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
@ -265,7 +269,8 @@ static const __u8 fixed_rdesc_pen[] = {
|
|||||||
Usage_Dig_TipSwitch
|
Usage_Dig_TipSwitch
|
||||||
Usage_Dig_BarrelSwitch
|
Usage_Dig_BarrelSwitch
|
||||||
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
|
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
ReportCount(3)
|
ReportCount(3)
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
@ -280,22 +285,28 @@ static const __u8 fixed_rdesc_pen[] = {
|
|||||||
UsagePage_GenericDesktop
|
UsagePage_GenericDesktop
|
||||||
Unit(cm)
|
Unit(cm)
|
||||||
UnitExponent(-1)
|
UnitExponent(-1)
|
||||||
PhysicalRange_i16(0, 266)
|
PhysicalMinimum_i16(0)
|
||||||
LogicalRange_i16(0, 32767)
|
PhysicalMaximum_i16(266)
|
||||||
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(32767)
|
||||||
Usage_GD_X
|
Usage_GD_X
|
||||||
Input(Var|Abs) // Bytes 2+3
|
Input(Var|Abs) // Bytes 2+3
|
||||||
PhysicalRange_i16(0, 166)
|
PhysicalMinimum_i16(0)
|
||||||
LogicalRange_i16(0, 32767)
|
PhysicalMaximum_i16(166)
|
||||||
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(32767)
|
||||||
Usage_GD_Y
|
Usage_GD_Y
|
||||||
Input(Var|Abs) // Bytes 4+5
|
Input(Var|Abs) // Bytes 4+5
|
||||||
)
|
)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TipPressure
|
Usage_Dig_TipPressure
|
||||||
LogicalRange_i16(0, 8191)
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(8191)
|
||||||
Input(Var|Abs) // Byte 6+7
|
Input(Var|Abs) // Byte 6+7
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
ReportCount(2)
|
ReportCount(2)
|
||||||
LogicalRange_i8(-60, 60)
|
LogicalMinimum_i8(-60)
|
||||||
|
LogicalMaximum_i8(60)
|
||||||
Usage_Dig_XTilt
|
Usage_Dig_XTilt
|
||||||
Usage_Dig_YTilt
|
Usage_Dig_YTilt
|
||||||
Input(Var|Abs) // Byte 8+9
|
Input(Var|Abs) // Byte 8+9
|
||||||
@ -313,7 +324,8 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
Usage_Dig_Pen
|
Usage_Dig_Pen
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
// Byte 1 are the buttons
|
// Byte 1 are the buttons
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
Usage_Dig_TipSwitch
|
Usage_Dig_TipSwitch
|
||||||
Usage_Dig_BarrelSwitch
|
Usage_Dig_BarrelSwitch
|
||||||
@ -333,25 +345,31 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
UnitExponent(-1)
|
UnitExponent(-1)
|
||||||
// Note: reported logical range differs
|
// Note: reported logical range differs
|
||||||
// from the pen report ID for x and y
|
// from the pen report ID for x and y
|
||||||
LogicalRange_i16(0, 53340)
|
LogicalMinimum_i16(0)
|
||||||
PhysicalRange_i16(0, 266)
|
LogicalMaximum_i16(53340)
|
||||||
|
PhysicalMinimum_i16(0)
|
||||||
|
PhysicalMaximum_i16(266)
|
||||||
// Bytes 2/3 in report
|
// Bytes 2/3 in report
|
||||||
Usage_GD_X
|
Usage_GD_X
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
LogicalRange_i16(0, 33340)
|
LogicalMinimum_i16(0)
|
||||||
PhysicalRange_i16(0, 166)
|
LogicalMaximum_i16(33340)
|
||||||
|
PhysicalMinimum_i16(0)
|
||||||
|
PhysicalMaximum_i16(166)
|
||||||
// Bytes 4/5 in report
|
// Bytes 4/5 in report
|
||||||
Usage_GD_Y
|
Usage_GD_Y
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
)
|
)
|
||||||
// Bytes 6/7 in report
|
// Bytes 6/7 in report
|
||||||
LogicalRange_i16(0, 8191)
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(8191)
|
||||||
Usage_Dig_TipPressure
|
Usage_Dig_TipPressure
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Bytes 8/9 in report
|
// Bytes 8/9 in report
|
||||||
ReportCount(1) // Padding
|
ReportCount(1) // Padding
|
||||||
Input(Const)
|
Input(Const)
|
||||||
LogicalRange_i8(-60, 60)
|
LogicalMinimum_i8(-60)
|
||||||
|
LogicalMaximum_i8(60)
|
||||||
// Byte 10 in report
|
// Byte 10 in report
|
||||||
Usage_Dig_XTilt
|
Usage_Dig_XTilt
|
||||||
// Byte 11 in report
|
// Byte 11 in report
|
||||||
@ -366,7 +384,8 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
CollectionApplication(
|
CollectionApplication(
|
||||||
// Byte 0
|
// Byte 0
|
||||||
ReportId(PAD_REPORT_ID)
|
ReportId(PAD_REPORT_ID)
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TabletFunctionKeys
|
Usage_Dig_TabletFunctionKeys
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
@ -386,15 +405,18 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Byte 4 is the button state
|
// Byte 4 is the button state
|
||||||
UsagePage_Button
|
UsagePage_Button
|
||||||
UsageRange_i8(0x01, 0x8)
|
UsageMinimum_i8(0x1)
|
||||||
LogicalRange_i8(0x0, 0x1)
|
UsageMaximum_i8(0x8)
|
||||||
|
LogicalMinimum_i8(0x0)
|
||||||
|
LogicalMaximum_i8(0x1)
|
||||||
ReportCount(8)
|
ReportCount(8)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Byte 5 is the top dial
|
// Byte 5 is the top dial
|
||||||
UsagePage_GenericDesktop
|
UsagePage_GenericDesktop
|
||||||
Usage_GD_Wheel
|
Usage_GD_Wheel
|
||||||
LogicalRange_i8(-1, 1)
|
LogicalMinimum_i8(-1)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportCount(1)
|
ReportCount(1)
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
Input(Var|Rel)
|
Input(Var|Rel)
|
||||||
|
@ -170,7 +170,8 @@ static const __u8 fixed_rdesc_pad[] = {
|
|||||||
CollectionApplication(
|
CollectionApplication(
|
||||||
// -- Byte 0 in report
|
// -- Byte 0 in report
|
||||||
ReportId(PAD_REPORT_ID)
|
ReportId(PAD_REPORT_ID)
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TabletFunctionKeys
|
Usage_Dig_TabletFunctionKeys
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
@ -190,14 +191,17 @@ static const __u8 fixed_rdesc_pad[] = {
|
|||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Byte 4 in report is the wheel
|
// Byte 4 in report is the wheel
|
||||||
Usage_GD_Wheel
|
Usage_GD_Wheel
|
||||||
LogicalRange_i8(-1, 1)
|
LogicalMinimum_i8(-1)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportCount(1)
|
ReportCount(1)
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
Input(Var|Rel)
|
Input(Var|Rel)
|
||||||
// Byte 5 is the button state
|
// Byte 5 is the button state
|
||||||
UsagePage_Button
|
UsagePage_Button
|
||||||
UsageRange_i8(0x01, 0x6)
|
UsageMinimum_i8(0x1)
|
||||||
LogicalRange_i8(0x01, 0x6)
|
UsageMaximum_i8(0x6)
|
||||||
|
LogicalMinimum_i8(0x1)
|
||||||
|
LogicalMaximum_i8(0x6)
|
||||||
ReportCount(1)
|
ReportCount(1)
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
Input(Arr|Abs)
|
Input(Arr|Abs)
|
||||||
@ -219,7 +223,8 @@ static const __u8 fixed_rdesc_pen[] = {
|
|||||||
Usage_Dig_TipSwitch
|
Usage_Dig_TipSwitch
|
||||||
Usage_Dig_BarrelSwitch
|
Usage_Dig_BarrelSwitch
|
||||||
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
|
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
ReportCount(3)
|
ReportCount(3)
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
@ -234,18 +239,23 @@ static const __u8 fixed_rdesc_pen[] = {
|
|||||||
UsagePage_GenericDesktop
|
UsagePage_GenericDesktop
|
||||||
Unit(cm)
|
Unit(cm)
|
||||||
UnitExponent(-1)
|
UnitExponent(-1)
|
||||||
PhysicalRange_i16(0, 160)
|
PhysicalMinimum_i16(0)
|
||||||
LogicalRange_i16(0, 32767)
|
PhysicalMaximum_i16(160)
|
||||||
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(32767)
|
||||||
Usage_GD_X
|
Usage_GD_X
|
||||||
Input(Var|Abs) // Bytes 2+3
|
Input(Var|Abs) // Bytes 2+3
|
||||||
PhysicalRange_i16(0, 100)
|
PhysicalMinimum_i16(0)
|
||||||
LogicalRange_i16(0, 32767)
|
PhysicalMaximum_i16(100)
|
||||||
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(32767)
|
||||||
Usage_GD_Y
|
Usage_GD_Y
|
||||||
Input(Var|Abs) // Bytes 4+5
|
Input(Var|Abs) // Bytes 4+5
|
||||||
)
|
)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TipPressure
|
Usage_Dig_TipPressure
|
||||||
LogicalRange_i16(0, 8191)
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(8191)
|
||||||
Input(Var|Abs) // Byte 6+7
|
Input(Var|Abs) // Byte 6+7
|
||||||
// Two bytes padding so we don't need to change the report at all
|
// Two bytes padding so we don't need to change the report at all
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
@ -265,7 +275,8 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
Usage_Dig_Pen
|
Usage_Dig_Pen
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
// Byte 1 are the buttons
|
// Byte 1 are the buttons
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
Usage_Dig_TipSwitch
|
Usage_Dig_TipSwitch
|
||||||
Usage_Dig_BarrelSwitch
|
Usage_Dig_BarrelSwitch
|
||||||
@ -285,19 +296,24 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
UnitExponent(-1)
|
UnitExponent(-1)
|
||||||
// Note: reported logical range differs
|
// Note: reported logical range differs
|
||||||
// from the pen report ID for x and y
|
// from the pen report ID for x and y
|
||||||
LogicalRange_i16(0, 32000)
|
LogicalMinimum_i16(0)
|
||||||
PhysicalRange_i16(0, 160)
|
LogicalMaximum_i16(32000)
|
||||||
|
PhysicalMinimum_i16(0)
|
||||||
|
PhysicalMaximum_i16(160)
|
||||||
// Bytes 2/3 in report
|
// Bytes 2/3 in report
|
||||||
Usage_GD_X
|
Usage_GD_X
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
LogicalRange_i16(0, 20000)
|
LogicalMinimum_i16(0)
|
||||||
PhysicalRange_i16(0, 100)
|
LogicalMaximum_i16(20000)
|
||||||
|
PhysicalMinimum_i16(0)
|
||||||
|
PhysicalMaximum_i16(100)
|
||||||
// Bytes 4/5 in report
|
// Bytes 4/5 in report
|
||||||
Usage_GD_Y
|
Usage_GD_Y
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
)
|
)
|
||||||
// Bytes 6/7 in report
|
// Bytes 6/7 in report
|
||||||
LogicalRange_i16(0, 8192)
|
LogicalMinimum_i16(0)
|
||||||
|
LogicalMaximum_i16(8192)
|
||||||
Usage_Dig_TipPressure
|
Usage_Dig_TipPressure
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
)
|
)
|
||||||
@ -307,7 +323,8 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
CollectionApplication(
|
CollectionApplication(
|
||||||
// Byte 0
|
// Byte 0
|
||||||
ReportId(PAD_REPORT_ID)
|
ReportId(PAD_REPORT_ID)
|
||||||
LogicalRange_i8(0, 1)
|
LogicalMinimum_i8(0)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
UsagePage_Digitizers
|
UsagePage_Digitizers
|
||||||
Usage_Dig_TabletFunctionKeys
|
Usage_Dig_TabletFunctionKeys
|
||||||
CollectionPhysical(
|
CollectionPhysical(
|
||||||
@ -327,8 +344,10 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
// Byte 4 is the button state
|
// Byte 4 is the button state
|
||||||
UsagePage_Button
|
UsagePage_Button
|
||||||
UsageRange_i8(0x01, 0x6)
|
UsageMinimum_i8(0x1)
|
||||||
LogicalRange_i8(0x0, 0x1)
|
UsageMaximum_i8(0x6)
|
||||||
|
LogicalMinimum_i8(0x0)
|
||||||
|
LogicalMaximum_i8(0x1)
|
||||||
ReportCount(6)
|
ReportCount(6)
|
||||||
ReportSize(1)
|
ReportSize(1)
|
||||||
Input(Var|Abs)
|
Input(Var|Abs)
|
||||||
@ -337,7 +356,8 @@ static const __u8 fixed_rdesc_vendor[] = {
|
|||||||
// Byte 5 is the wheel
|
// Byte 5 is the wheel
|
||||||
UsagePage_GenericDesktop
|
UsagePage_GenericDesktop
|
||||||
Usage_GD_Wheel
|
Usage_GD_Wheel
|
||||||
LogicalRange_i8(-1, 1)
|
LogicalMinimum_i8(-1)
|
||||||
|
LogicalMaximum_i8(1)
|
||||||
ReportCount(1)
|
ReportCount(1)
|
||||||
ReportSize(8)
|
ReportSize(8)
|
||||||
Input(Var|Rel)
|
Input(Var|Rel)
|
||||||
|
154
drivers/hid/bpf/progs/Mistel__MD770.bpf.c
Normal file
154
drivers/hid/bpf/progs/Mistel__MD770.bpf.c
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/* Copyright (c) 2024 Tatsuyuki Ishi
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vmlinux.h"
|
||||||
|
#include "hid_bpf.h"
|
||||||
|
#include "hid_bpf_helpers.h"
|
||||||
|
#include <bpf/bpf_tracing.h>
|
||||||
|
|
||||||
|
#define VID_HOLTEK 0x04D9
|
||||||
|
#define PID_MD770 0x0339
|
||||||
|
#define RDESC_SIZE 203
|
||||||
|
|
||||||
|
HID_BPF_CONFIG(
|
||||||
|
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_HOLTEK, PID_MD770)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Mistel MD770 keyboard reports the first 6 simultaneous key presses
|
||||||
|
* through the first interface, and anything beyond that through a second
|
||||||
|
* interface. Unfortunately, the second interface's report descriptor has an
|
||||||
|
* error, causing events to be malformed and ignored. This HID-BPF driver
|
||||||
|
* fixes the descriptor to allow NKRO to work again.
|
||||||
|
*
|
||||||
|
* For reference, this is the original report descriptor:
|
||||||
|
*
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||||
|
* 0x09, 0x80, // Usage (System Control) 2
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 4
|
||||||
|
* 0x85, 0x01, // Report ID (1) 6
|
||||||
|
* 0x19, 0x81, // Usage Minimum (129) 8
|
||||||
|
* 0x29, 0x83, // Usage Maximum (131) 10
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 12
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 14
|
||||||
|
* 0x95, 0x03, // Report Count (3) 16
|
||||||
|
* 0x75, 0x01, // Report Size (1) 18
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 20
|
||||||
|
* 0x95, 0x01, // Report Count (1) 22
|
||||||
|
* 0x75, 0x05, // Report Size (5) 24
|
||||||
|
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 26
|
||||||
|
* 0xc0, // End Collection 28
|
||||||
|
* 0x05, 0x0c, // Usage Page (Consumer Devices) 29
|
||||||
|
* 0x09, 0x01, // Usage (Consumer Control) 31
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 33
|
||||||
|
* 0x85, 0x02, // Report ID (2) 35
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 37
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 39
|
||||||
|
* 0x95, 0x12, // Report Count (18) 41
|
||||||
|
* 0x75, 0x01, // Report Size (1) 43
|
||||||
|
* 0x0a, 0x83, 0x01, // Usage (AL Consumer Control Config) 45
|
||||||
|
* 0x0a, 0x8a, 0x01, // Usage (AL Email Reader) 48
|
||||||
|
* 0x0a, 0x92, 0x01, // Usage (AL Calculator) 51
|
||||||
|
* 0x0a, 0x94, 0x01, // Usage (AL Local Machine Browser) 54
|
||||||
|
* 0x09, 0xcd, // Usage (Play/Pause) 57
|
||||||
|
* 0x09, 0xb7, // Usage (Stop) 59
|
||||||
|
* 0x09, 0xb6, // Usage (Scan Previous Track) 61
|
||||||
|
* 0x09, 0xb5, // Usage (Scan Next Track) 63
|
||||||
|
* 0x09, 0xe2, // Usage (Mute) 65
|
||||||
|
* 0x09, 0xea, // Usage (Volume Down) 67
|
||||||
|
* 0x09, 0xe9, // Usage (Volume Up) 69
|
||||||
|
* 0x0a, 0x21, 0x02, // Usage (AC Search) 71
|
||||||
|
* 0x0a, 0x23, 0x02, // Usage (AC Home) 74
|
||||||
|
* 0x0a, 0x24, 0x02, // Usage (AC Back) 77
|
||||||
|
* 0x0a, 0x25, 0x02, // Usage (AC Forward) 80
|
||||||
|
* 0x0a, 0x26, 0x02, // Usage (AC Stop) 83
|
||||||
|
* 0x0a, 0x27, 0x02, // Usage (AC Refresh) 86
|
||||||
|
* 0x0a, 0x2a, 0x02, // Usage (AC Bookmarks) 89
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 92
|
||||||
|
* 0x95, 0x01, // Report Count (1) 94
|
||||||
|
* 0x75, 0x0e, // Report Size (14) 96
|
||||||
|
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 98
|
||||||
|
* 0xc0, // End Collection 100
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 101
|
||||||
|
* 0x09, 0x02, // Usage (Mouse) 103
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 105
|
||||||
|
* 0x09, 0x01, // Usage (Pointer) 107
|
||||||
|
* 0xa1, 0x00, // Collection (Physical) 109
|
||||||
|
* 0x85, 0x03, // Report ID (3) 111
|
||||||
|
* 0x05, 0x09, // Usage Page (Button) 113
|
||||||
|
* 0x19, 0x01, // Usage Minimum (1) 115
|
||||||
|
* 0x29, 0x08, // Usage Maximum (8) 117
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 119
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 121
|
||||||
|
* 0x75, 0x01, // Report Size (1) 123
|
||||||
|
* 0x95, 0x08, // Report Count (8) 125
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 127
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 129
|
||||||
|
* 0x09, 0x30, // Usage (X) 131
|
||||||
|
* 0x09, 0x31, // Usage (Y) 133
|
||||||
|
* 0x16, 0x01, 0x80, // Logical Minimum (-32767) 135
|
||||||
|
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 138
|
||||||
|
* 0x75, 0x10, // Report Size (16) 141
|
||||||
|
* 0x95, 0x02, // Report Count (2) 143
|
||||||
|
* 0x81, 0x06, // Input (Data,Var,Rel) 145
|
||||||
|
* 0x09, 0x38, // Usage (Wheel) 147
|
||||||
|
* 0x15, 0x81, // Logical Minimum (-127) 149
|
||||||
|
* 0x25, 0x7f, // Logical Maximum (127) 151
|
||||||
|
* 0x75, 0x08, // Report Size (8) 153
|
||||||
|
* 0x95, 0x01, // Report Count (1) 155
|
||||||
|
* 0x81, 0x06, // Input (Data,Var,Rel) 157
|
||||||
|
* 0x05, 0x0c, // Usage Page (Consumer Devices) 159
|
||||||
|
* 0x0a, 0x38, 0x02, // Usage (AC Pan) 161
|
||||||
|
* 0x95, 0x01, // Report Count (1) 164
|
||||||
|
* 0x81, 0x06, // Input (Data,Var,Rel) 166
|
||||||
|
* 0xc0, // End Collection 168
|
||||||
|
* 0xc0, // End Collection 169
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 170
|
||||||
|
* 0x09, 0x06, // Usage (Keyboard) 172
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 174
|
||||||
|
* 0x85, 0x04, // Report ID (4) 176
|
||||||
|
* 0x05, 0x07, // Usage Page (Keyboard) 178
|
||||||
|
* 0x95, 0x01, // Report Count (1) 180
|
||||||
|
* 0x75, 0x08, // Report Size (8) 182
|
||||||
|
* 0x81, 0x03, // Input (Cnst,Var,Abs) 184
|
||||||
|
* 0x95, 0xe8, // Report Count (232) 186
|
||||||
|
* 0x75, 0x01, // Report Size (1) 188
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 190
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 192
|
||||||
|
* 0x05, 0x07, // Usage Page (Keyboard) 194
|
||||||
|
* 0x19, 0x00, // Usage Minimum (0) 196
|
||||||
|
* 0x29, 0xe7, // Usage Maximum (231) 198
|
||||||
|
* 0x81, 0x00, // Input (Data,Arr,Abs) 200 <- change to 0x81, 0x02 (Data,Var,Abs)
|
||||||
|
* 0xc0, // End Collection 202
|
||||||
|
*/
|
||||||
|
|
||||||
|
SEC(HID_BPF_RDESC_FIXUP)
|
||||||
|
int BPF_PROG(hid_rdesc_fixup_mistel_md770, struct hid_bpf_ctx *hctx)
|
||||||
|
{
|
||||||
|
__u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return 0; /* EPERM check */
|
||||||
|
|
||||||
|
if (data[201] == 0x00)
|
||||||
|
data[201] = 0x02;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HID_BPF_OPS(mistel_md770) = {
|
||||||
|
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_mistel_md770,
|
||||||
|
};
|
||||||
|
|
||||||
|
SEC("syscall")
|
||||||
|
int probe(struct hid_bpf_probe_args *ctx)
|
||||||
|
{
|
||||||
|
ctx->retval = ctx->rdesc_size != RDESC_SIZE;
|
||||||
|
if (ctx->retval)
|
||||||
|
ctx->retval = -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
148
drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c
Normal file
148
drivers/hid/bpf/progs/Rapoo__M50-Plus-Silent.bpf.c
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/* Copyright (c) 2024 José Expósito
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vmlinux.h"
|
||||||
|
#include "hid_bpf.h"
|
||||||
|
#include "hid_bpf_helpers.h"
|
||||||
|
#include <bpf/bpf_tracing.h>
|
||||||
|
|
||||||
|
#define VID_RAPOO 0x24AE
|
||||||
|
#define PID_M50 0x2015
|
||||||
|
#define RDESC_SIZE 186
|
||||||
|
|
||||||
|
HID_BPF_CONFIG(
|
||||||
|
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_RAPOO, PID_M50)
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Rapoo M50 Plus Silent mouse has 2 side buttons in addition to the left,
|
||||||
|
* right and middle buttons. However, its original HID descriptor has a Usage
|
||||||
|
* Maximum of 3, preventing the side buttons to work. This HID-BPF driver
|
||||||
|
* changes that usage to 5.
|
||||||
|
*
|
||||||
|
* For reference, this is the original report descriptor:
|
||||||
|
*
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 0
|
||||||
|
* 0x09, 0x02, // Usage (Mouse) 2
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 4
|
||||||
|
* 0x85, 0x01, // Report ID (1) 6
|
||||||
|
* 0x09, 0x01, // Usage (Pointer) 8
|
||||||
|
* 0xa1, 0x00, // Collection (Physical) 10
|
||||||
|
* 0x05, 0x09, // Usage Page (Button) 12
|
||||||
|
* 0x19, 0x01, // Usage Minimum (1) 14
|
||||||
|
* 0x29, 0x03, // Usage Maximum (3) 16 <- change to 0x05
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 18
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 20
|
||||||
|
* 0x75, 0x01, // Report Size (1) 22
|
||||||
|
* 0x95, 0x05, // Report Count (5) 24
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 26
|
||||||
|
* 0x75, 0x03, // Report Size (3) 28
|
||||||
|
* 0x95, 0x01, // Report Count (1) 30
|
||||||
|
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 32
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 34
|
||||||
|
* 0x09, 0x30, // Usage (X) 36
|
||||||
|
* 0x09, 0x31, // Usage (Y) 38
|
||||||
|
* 0x16, 0x01, 0x80, // Logical Minimum (-32767) 40
|
||||||
|
* 0x26, 0xff, 0x7f, // Logical Maximum (32767) 43
|
||||||
|
* 0x75, 0x10, // Report Size (16) 46
|
||||||
|
* 0x95, 0x02, // Report Count (2) 48
|
||||||
|
* 0x81, 0x06, // Input (Data,Var,Rel) 50
|
||||||
|
* 0x09, 0x38, // Usage (Wheel) 52
|
||||||
|
* 0x15, 0x81, // Logical Minimum (-127) 54
|
||||||
|
* 0x25, 0x7f, // Logical Maximum (127) 56
|
||||||
|
* 0x75, 0x08, // Report Size (8) 58
|
||||||
|
* 0x95, 0x01, // Report Count (1) 60
|
||||||
|
* 0x81, 0x06, // Input (Data,Var,Rel) 62
|
||||||
|
* 0xc0, // End Collection 64
|
||||||
|
* 0xc0, // End Collection 65
|
||||||
|
* 0x05, 0x0c, // Usage Page (Consumer Devices) 66
|
||||||
|
* 0x09, 0x01, // Usage (Consumer Control) 68
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 70
|
||||||
|
* 0x85, 0x02, // Report ID (2) 72
|
||||||
|
* 0x75, 0x10, // Report Size (16) 74
|
||||||
|
* 0x95, 0x01, // Report Count (1) 76
|
||||||
|
* 0x15, 0x01, // Logical Minimum (1) 78
|
||||||
|
* 0x26, 0x8c, 0x02, // Logical Maximum (652) 80
|
||||||
|
* 0x19, 0x01, // Usage Minimum (1) 83
|
||||||
|
* 0x2a, 0x8c, 0x02, // Usage Maximum (652) 85
|
||||||
|
* 0x81, 0x00, // Input (Data,Arr,Abs) 88
|
||||||
|
* 0xc0, // End Collection 90
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 91
|
||||||
|
* 0x09, 0x80, // Usage (System Control) 93
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 95
|
||||||
|
* 0x85, 0x03, // Report ID (3) 97
|
||||||
|
* 0x09, 0x82, // Usage (System Sleep) 99
|
||||||
|
* 0x09, 0x81, // Usage (System Power Down) 101
|
||||||
|
* 0x09, 0x83, // Usage (System Wake Up) 103
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 105
|
||||||
|
* 0x25, 0x01, // Logical Maximum (1) 107
|
||||||
|
* 0x19, 0x01, // Usage Minimum (1) 109
|
||||||
|
* 0x29, 0x03, // Usage Maximum (3) 111
|
||||||
|
* 0x75, 0x01, // Report Size (1) 113
|
||||||
|
* 0x95, 0x03, // Report Count (3) 115
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 117
|
||||||
|
* 0x95, 0x05, // Report Count (5) 119
|
||||||
|
* 0x81, 0x01, // Input (Cnst,Arr,Abs) 121
|
||||||
|
* 0xc0, // End Collection 123
|
||||||
|
* 0x05, 0x01, // Usage Page (Generic Desktop) 124
|
||||||
|
* 0x09, 0x00, // Usage (Undefined) 126
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 128
|
||||||
|
* 0x85, 0x05, // Report ID (5) 130
|
||||||
|
* 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 132
|
||||||
|
* 0x09, 0x01, // Usage (Vendor Usage 1) 135
|
||||||
|
* 0x15, 0x81, // Logical Minimum (-127) 137
|
||||||
|
* 0x25, 0x7f, // Logical Maximum (127) 139
|
||||||
|
* 0x75, 0x08, // Report Size (8) 141
|
||||||
|
* 0x95, 0x07, // Report Count (7) 143
|
||||||
|
* 0xb1, 0x02, // Feature (Data,Var,Abs) 145
|
||||||
|
* 0xc0, // End Collection 147
|
||||||
|
* 0x06, 0x00, 0xff, // Usage Page (Vendor Defined Page 1) 148
|
||||||
|
* 0x09, 0x0e, // Usage (Vendor Usage 0x0e) 151
|
||||||
|
* 0xa1, 0x01, // Collection (Application) 153
|
||||||
|
* 0x85, 0xba, // Report ID (186) 155
|
||||||
|
* 0x95, 0x1f, // Report Count (31) 157
|
||||||
|
* 0x75, 0x08, // Report Size (8) 159
|
||||||
|
* 0x26, 0xff, 0x00, // Logical Maximum (255) 161
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 164
|
||||||
|
* 0x09, 0x01, // Usage (Vendor Usage 1) 166
|
||||||
|
* 0x91, 0x02, // Output (Data,Var,Abs) 168
|
||||||
|
* 0x85, 0xba, // Report ID (186) 170
|
||||||
|
* 0x95, 0x1f, // Report Count (31) 172
|
||||||
|
* 0x75, 0x08, // Report Size (8) 174
|
||||||
|
* 0x26, 0xff, 0x00, // Logical Maximum (255) 176
|
||||||
|
* 0x15, 0x00, // Logical Minimum (0) 179
|
||||||
|
* 0x09, 0x01, // Usage (Vendor Usage 1) 181
|
||||||
|
* 0x81, 0x02, // Input (Data,Var,Abs) 183
|
||||||
|
* 0xc0, // End Collection 185
|
||||||
|
*/
|
||||||
|
|
||||||
|
SEC(HID_BPF_RDESC_FIXUP)
|
||||||
|
int BPF_PROG(hid_rdesc_fixup_rapoo_m50, struct hid_bpf_ctx *hctx)
|
||||||
|
{
|
||||||
|
__u8 *data = hid_bpf_get_data(hctx, 0, HID_MAX_DESCRIPTOR_SIZE);
|
||||||
|
|
||||||
|
if (!data)
|
||||||
|
return 0; /* EPERM check */
|
||||||
|
|
||||||
|
if (data[17] == 0x03)
|
||||||
|
data[17] = 0x05;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HID_BPF_OPS(rapoo_m50) = {
|
||||||
|
.hid_rdesc_fixup = (void *)hid_rdesc_fixup_rapoo_m50,
|
||||||
|
};
|
||||||
|
|
||||||
|
SEC("syscall")
|
||||||
|
int probe(struct hid_bpf_probe_args *ctx)
|
||||||
|
{
|
||||||
|
ctx->retval = ctx->rdesc_size != RDESC_SIZE;
|
||||||
|
if (ctx->retval)
|
||||||
|
ctx->retval = -EINVAL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char _license[] SEC("license") = "GPL";
|
@ -52,7 +52,8 @@
|
|||||||
* Usage_GD_Keyboard
|
* Usage_GD_Keyboard
|
||||||
* CollectionApplication( ← Open the collection
|
* CollectionApplication( ← Open the collection
|
||||||
* ReportId(3)
|
* ReportId(3)
|
||||||
* LogicalRange_i8(0, 1)
|
* LogicalMinimum_i8(0)
|
||||||
|
* LogicalMaximum_i8(1)
|
||||||
* // other fields
|
* // other fields
|
||||||
* ) ← End EndCollection
|
* ) ← End EndCollection
|
||||||
*
|
*
|
||||||
@ -74,26 +75,43 @@
|
|||||||
#define Arr 0x0
|
#define Arr 0x0
|
||||||
#define Abs 0x0
|
#define Abs 0x0
|
||||||
#define Rel 0x4
|
#define Rel 0x4
|
||||||
|
#define Null 0x40
|
||||||
|
#define Buff 0x0100
|
||||||
|
|
||||||
/* Use like this: Input(Var|Abs) */
|
/* Use like this: Input(Var|Abs) */
|
||||||
#define Input(i_) 0x081, i8(i_),
|
#define Input(i_) 0x081, i8(i_),
|
||||||
#define Output(i_) 0x091, i8(i_),
|
#define Output(i_) 0x091, i8(i_),
|
||||||
#define Feature(i_) 0x0b1, i8(i_),
|
#define Feature(i_) 0x0b1, i8(i_),
|
||||||
|
|
||||||
|
#define Input_i16(i_) 0x082, LE16(i_),
|
||||||
|
#define Output_i16(i_) 0x092, LE16(i_),
|
||||||
|
#define Feature_i16(i_) 0x0b2, LE16(i_),
|
||||||
|
|
||||||
#define ReportId(id_) 0x85, i8(id_),
|
#define ReportId(id_) 0x85, i8(id_),
|
||||||
#define ReportSize(sz_) 0x75, i8(sz_),
|
#define ReportSize(sz_) 0x75, i8(sz_),
|
||||||
#define ReportCount(cnt_) 0x95, i8(cnt_),
|
#define ReportCount(cnt_) 0x95, i8(cnt_),
|
||||||
|
|
||||||
#define LogicalRange_i8(min_, max_) 0x15, i8(min_), 0x25, i8(max_),
|
#define LogicalMinimum_i8(min_) 0x15, i8(min_),
|
||||||
#define LogicalRange_i16(min_, max_) 0x16, LE16(min_), 0x26, LE16(max_),
|
#define LogicalMinimum_i16(min_) 0x16, LE16(min_),
|
||||||
#define LogicalRange_i32(min_, max_) 0x17, LE32(min_), 0x27, LE32(max_),
|
#define LogicalMinimum_i32(min_) 0x17, LE32(min_),
|
||||||
|
|
||||||
#define PhysicalRange_i8(min_, max_) 0x35, i8(min_), 0x45, i8(max_),
|
#define LogicalMaximum_i8(max_) 0x25, i8(max_),
|
||||||
#define PhysicalRange_i16(min_, max_) 0x36, LE16(min_), 0x46, LE16(max_),
|
#define LogicalMaximum_i16(max_) 0x26, LE16(max_),
|
||||||
#define PhysicalRange_i32(min_, max_) 0x37, LE32(min_), 0x47, LE32(max_),
|
#define LogicalMaximum_i32(max_) 0x27, LE32(max_),
|
||||||
|
|
||||||
#define UsageRange_i8(min_, max_) 0x19, i8(min_), 0x29, i8(max_),
|
#define PhysicalMinimum_i8(min_) 0x35, i8(min_),
|
||||||
#define UsageRange_i16(min_, max_) 0x1a, LE16(min_), 0x2a, LE16(max_),
|
#define PhysicalMinimum_i16(min_) 0x36, LE16(min_),
|
||||||
|
#define PhysicalMinimum_i32(min_) 0x37, LE32(min_),
|
||||||
|
|
||||||
|
#define PhysicalMaximum_i8(max_) 0x45, i8(max_),
|
||||||
|
#define PhysicalMaximum_i16(max_) 0x46, LE16(max_),
|
||||||
|
#define PhysicalMaximum_i32(max_) 0x47, LE32(max_),
|
||||||
|
|
||||||
|
#define UsageMinimum_i8(min_) 0x19, i8(min_),
|
||||||
|
#define UsageMinimum_i16(min_) 0x1a, LE16(min_),
|
||||||
|
|
||||||
|
#define UsageMaximum_i8(max_) 0x29, i8(max_),
|
||||||
|
#define UsageMaximum_i16(max_) 0x2a, LE16(max_),
|
||||||
|
|
||||||
#define UsagePage_i8(p_) 0x05, i8(p_),
|
#define UsagePage_i8(p_) 0x05, i8(p_),
|
||||||
#define UsagePage_i16(p_) 0x06, LE16(p_),
|
#define UsagePage_i16(p_) 0x06, LE16(p_),
|
||||||
|
@ -1183,7 +1183,7 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
|||||||
|
|
||||||
if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
|
if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
|
||||||
*rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
|
*rsize == 75 && rdesc[61] == 0x15 && rdesc[62] == 0x00) {
|
||||||
/* report is missing usage mninum and maximum */
|
/* report is missing usage minimum and maximum */
|
||||||
__u8 *new_rdesc;
|
__u8 *new_rdesc;
|
||||||
size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);
|
size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);
|
||||||
|
|
||||||
|
@ -45,6 +45,34 @@ static int hid_ignore_special_drivers = 0;
|
|||||||
module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600);
|
module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600);
|
||||||
MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
|
MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a signed n-bit integer to signed 32-bit integer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static s32 snto32(__u32 value, unsigned int n)
|
||||||
|
{
|
||||||
|
if (!value || !n)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (n > 32)
|
||||||
|
n = 32;
|
||||||
|
|
||||||
|
return sign_extend32(value, n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert a signed 32-bit integer to a signed n-bit integer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static u32 s32ton(__s32 value, unsigned int n)
|
||||||
|
{
|
||||||
|
s32 a = value >> (n - 1);
|
||||||
|
|
||||||
|
if (a && a != -1)
|
||||||
|
return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
|
||||||
|
return value & ((1 << n) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register a new report for a device.
|
* Register a new report for a device.
|
||||||
*/
|
*/
|
||||||
@ -425,7 +453,7 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
|
|||||||
* both this and the standard encoding. */
|
* both this and the standard encoding. */
|
||||||
raw_value = item_sdata(item);
|
raw_value = item_sdata(item);
|
||||||
if (!(raw_value & 0xfffffff0))
|
if (!(raw_value & 0xfffffff0))
|
||||||
parser->global.unit_exponent = hid_snto32(raw_value, 4);
|
parser->global.unit_exponent = snto32(raw_value, 4);
|
||||||
else
|
else
|
||||||
parser->global.unit_exponent = raw_value;
|
parser->global.unit_exponent = raw_value;
|
||||||
return 0;
|
return 0;
|
||||||
@ -685,6 +713,13 @@ static void hid_close_report(struct hid_device *device)
|
|||||||
INIT_LIST_HEAD(&report_enum->report_list);
|
INIT_LIST_HEAD(&report_enum->report_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the HID driver had a rdesc_fixup() callback, dev->rdesc
|
||||||
|
* will be allocated by hid-core and needs to be freed.
|
||||||
|
* Otherwise, it is either equal to dev_rdesc or bpf_rdesc, in
|
||||||
|
* which cases it'll be freed later on device removal or destroy.
|
||||||
|
*/
|
||||||
|
if (device->rdesc != device->dev_rdesc && device->rdesc != device->bpf_rdesc)
|
||||||
kfree(device->rdesc);
|
kfree(device->rdesc);
|
||||||
device->rdesc = NULL;
|
device->rdesc = NULL;
|
||||||
device->rsize = 0;
|
device->rsize = 0;
|
||||||
@ -698,6 +733,14 @@ static void hid_close_report(struct hid_device *device)
|
|||||||
device->status &= ~HID_STAT_PARSED;
|
device->status &= ~HID_STAT_PARSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void hid_free_bpf_rdesc(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
/* bpf_rdesc is either equal to dev_rdesc or allocated by call_hid_bpf_rdesc_fixup() */
|
||||||
|
if (hdev->bpf_rdesc != hdev->dev_rdesc)
|
||||||
|
kfree(hdev->bpf_rdesc);
|
||||||
|
hdev->bpf_rdesc = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free a device structure, all reports, and all fields.
|
* Free a device structure, all reports, and all fields.
|
||||||
*/
|
*/
|
||||||
@ -707,6 +750,7 @@ void hiddev_free(struct kref *ref)
|
|||||||
struct hid_device *hid = container_of(ref, struct hid_device, ref);
|
struct hid_device *hid = container_of(ref, struct hid_device, ref);
|
||||||
|
|
||||||
hid_close_report(hid);
|
hid_close_report(hid);
|
||||||
|
hid_free_bpf_rdesc(hid);
|
||||||
kfree(hid->dev_rdesc);
|
kfree(hid->dev_rdesc);
|
||||||
kfree(hid);
|
kfree(hid);
|
||||||
}
|
}
|
||||||
@ -754,35 +798,29 @@ static const u8 *fetch_item(const __u8 *start, const __u8 *end, struct hid_item
|
|||||||
}
|
}
|
||||||
|
|
||||||
item->format = HID_ITEM_FORMAT_SHORT;
|
item->format = HID_ITEM_FORMAT_SHORT;
|
||||||
item->size = b & 3;
|
item->size = BIT(b & 3) >> 1; /* 0, 1, 2, 3 -> 0, 1, 2, 4 */
|
||||||
|
|
||||||
|
if (end - start < item->size)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
switch (item->size) {
|
switch (item->size) {
|
||||||
case 0:
|
case 0:
|
||||||
return start;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
if ((end - start) < 1)
|
item->data.u8 = *start;
|
||||||
return NULL;
|
break;
|
||||||
item->data.u8 = *start++;
|
|
||||||
return start;
|
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if ((end - start) < 2)
|
|
||||||
return NULL;
|
|
||||||
item->data.u16 = get_unaligned_le16(start);
|
item->data.u16 = get_unaligned_le16(start);
|
||||||
start = (__u8 *)((__le16 *)start + 1);
|
break;
|
||||||
return start;
|
|
||||||
|
|
||||||
case 3:
|
case 4:
|
||||||
item->size++;
|
|
||||||
if ((end - start) < 4)
|
|
||||||
return NULL;
|
|
||||||
item->data.u32 = get_unaligned_le32(start);
|
item->data.u32 = get_unaligned_le32(start);
|
||||||
start = (__u8 *)((__le32 *)start + 1);
|
break;
|
||||||
return start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return start + item->size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hid_scan_input_usage(struct hid_parser *parser, u32 usage)
|
static void hid_scan_input_usage(struct hid_parser *parser, u32 usage)
|
||||||
@ -1205,7 +1243,6 @@ int hid_open_report(struct hid_device *device)
|
|||||||
struct hid_item item;
|
struct hid_item item;
|
||||||
unsigned int size;
|
unsigned int size;
|
||||||
const __u8 *start;
|
const __u8 *start;
|
||||||
__u8 *buf;
|
|
||||||
const __u8 *end;
|
const __u8 *end;
|
||||||
const __u8 *next;
|
const __u8 *next;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1221,25 +1258,34 @@ int hid_open_report(struct hid_device *device)
|
|||||||
if (WARN_ON(device->status & HID_STAT_PARSED))
|
if (WARN_ON(device->status & HID_STAT_PARSED))
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
|
||||||
start = device->dev_rdesc;
|
start = device->bpf_rdesc;
|
||||||
if (WARN_ON(!start))
|
if (WARN_ON(!start))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
size = device->dev_rsize;
|
size = device->bpf_rsize;
|
||||||
|
|
||||||
|
if (device->driver->report_fixup) {
|
||||||
|
/*
|
||||||
|
* device->driver->report_fixup() needs to work
|
||||||
|
* on a copy of our report descriptor so it can
|
||||||
|
* change it.
|
||||||
|
*/
|
||||||
|
__u8 *buf = kmemdup(start, size, GFP_KERNEL);
|
||||||
|
|
||||||
/* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
|
|
||||||
buf = call_hid_bpf_rdesc_fixup(device, start, &size);
|
|
||||||
if (buf == NULL)
|
if (buf == NULL)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
if (device->driver->report_fixup)
|
|
||||||
start = device->driver->report_fixup(device, buf, &size);
|
start = device->driver->report_fixup(device, buf, &size);
|
||||||
else
|
|
||||||
start = buf;
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The second kmemdup is required in case report_fixup() returns
|
||||||
|
* a static read-only memory, but we have no idea if that memory
|
||||||
|
* needs to be cleaned up or not at the end.
|
||||||
|
*/
|
||||||
start = kmemdup(start, size, GFP_KERNEL);
|
start = kmemdup(start, size, GFP_KERNEL);
|
||||||
kfree(buf);
|
kfree(buf);
|
||||||
if (start == NULL)
|
if (start == NULL)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
device->rdesc = start;
|
device->rdesc = start;
|
||||||
device->rsize = size;
|
device->rsize = size;
|
||||||
@ -1315,46 +1361,6 @@ alloc_err:
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hid_open_report);
|
EXPORT_SYMBOL_GPL(hid_open_report);
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a signed n-bit integer to signed 32-bit integer. Common
|
|
||||||
* cases are done through the compiler, the screwed things has to be
|
|
||||||
* done by hand.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static s32 snto32(__u32 value, unsigned n)
|
|
||||||
{
|
|
||||||
if (!value || !n)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (n > 32)
|
|
||||||
n = 32;
|
|
||||||
|
|
||||||
switch (n) {
|
|
||||||
case 8: return ((__s8)value);
|
|
||||||
case 16: return ((__s16)value);
|
|
||||||
case 32: return ((__s32)value);
|
|
||||||
}
|
|
||||||
return value & (1 << (n - 1)) ? value | (~0U << n) : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 hid_snto32(__u32 value, unsigned n)
|
|
||||||
{
|
|
||||||
return snto32(value, n);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(hid_snto32);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert a signed 32-bit integer to a signed n-bit integer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static u32 s32ton(__s32 value, unsigned n)
|
|
||||||
{
|
|
||||||
s32 a = value >> (n - 1);
|
|
||||||
if (a && a != -1)
|
|
||||||
return value < 0 ? 1 << (n - 1) : (1 << (n - 1)) - 1;
|
|
||||||
return value & ((1 << n) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extract/implement a data field from/to a little endian report (bit array).
|
* Extract/implement a data field from/to a little endian report (bit array).
|
||||||
*
|
*
|
||||||
@ -2674,9 +2680,10 @@ static bool hid_check_device_match(struct hid_device *hdev,
|
|||||||
/*
|
/*
|
||||||
* hid-generic implements .match(), so we must be dealing with a
|
* hid-generic implements .match(), so we must be dealing with a
|
||||||
* different HID driver here, and can simply check if
|
* different HID driver here, and can simply check if
|
||||||
* hid_ignore_special_drivers is set or not.
|
* hid_ignore_special_drivers or HID_QUIRK_IGNORE_SPECIAL_DRIVER
|
||||||
|
* are set or not.
|
||||||
*/
|
*/
|
||||||
return !hid_ignore_special_drivers;
|
return !hid_ignore_special_drivers && !(hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
||||||
@ -2684,6 +2691,27 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
|||||||
const struct hid_device_id *id;
|
const struct hid_device_id *id;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!hdev->bpf_rsize) {
|
||||||
|
unsigned int quirks;
|
||||||
|
|
||||||
|
/* reset the quirks that has been previously set */
|
||||||
|
quirks = hid_lookup_quirk(hdev);
|
||||||
|
hdev->quirks = quirks;
|
||||||
|
|
||||||
|
/* in case a bpf program gets detached, we need to free the old one */
|
||||||
|
hid_free_bpf_rdesc(hdev);
|
||||||
|
|
||||||
|
/* keep this around so we know we called it once */
|
||||||
|
hdev->bpf_rsize = hdev->dev_rsize;
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
if (quirks ^ hdev->quirks)
|
||||||
|
hid_info(hdev, "HID-BPF toggled quirks on the device: %04x",
|
||||||
|
quirks ^ hdev->quirks);
|
||||||
|
}
|
||||||
|
|
||||||
if (!hid_check_device_match(hdev, hdrv, &id))
|
if (!hid_check_device_match(hdev, hdrv, &id))
|
||||||
return -ENODEV;
|
return -ENODEV;
|
||||||
|
|
||||||
@ -2691,8 +2719,6 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
|
|||||||
if (!hdev->devres_group_id)
|
if (!hdev->devres_group_id)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
/* reset the quirks that has been previously set */
|
|
||||||
hdev->quirks = hid_lookup_quirk(hdev);
|
|
||||||
hdev->driver = hdrv;
|
hdev->driver = hdrv;
|
||||||
|
|
||||||
if (hdrv->probe) {
|
if (hdrv->probe) {
|
||||||
@ -2940,9 +2966,11 @@ static void hid_remove_device(struct hid_device *hdev)
|
|||||||
hid_debug_unregister(hdev);
|
hid_debug_unregister(hdev);
|
||||||
hdev->status &= ~HID_STAT_ADDED;
|
hdev->status &= ~HID_STAT_ADDED;
|
||||||
}
|
}
|
||||||
|
hid_free_bpf_rdesc(hdev);
|
||||||
kfree(hdev->dev_rdesc);
|
kfree(hdev->dev_rdesc);
|
||||||
hdev->dev_rdesc = NULL;
|
hdev->dev_rdesc = NULL;
|
||||||
hdev->dev_rsize = 0;
|
hdev->dev_rsize = 0;
|
||||||
|
hdev->bpf_rsize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
829
drivers/hid/hid-corsair-void.c
Normal file
829
drivers/hid/hid-corsair-void.c
Normal file
@ -0,0 +1,829 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* HID driver for Corsair Void headsets
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023-2024 Stuart Hayhurst
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Receiver report information: (ID 100) */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/*
|
||||||
|
* When queried, the receiver reponds with 5 bytes to describe the battery
|
||||||
|
* The power button, mute button and moving the mic also trigger this report
|
||||||
|
* This includes power button + mic + connection + battery status and capacity
|
||||||
|
* The information below may not be perfect, it's been gathered through guesses
|
||||||
|
*
|
||||||
|
* 0: REPORT ID
|
||||||
|
* 100 for the battery packet
|
||||||
|
*
|
||||||
|
* 1: POWER BUTTON + (?)
|
||||||
|
* Largest bit is 1 when power button pressed
|
||||||
|
*
|
||||||
|
* 2: BATTERY CAPACITY + MIC STATUS
|
||||||
|
* Battery capacity:
|
||||||
|
* Seems to report ~54 higher than reality when charging
|
||||||
|
* Capped at 100, charging or not
|
||||||
|
* Microphone status:
|
||||||
|
* Largest bit is set to 1 when the mic is physically up
|
||||||
|
* No bits change when the mic is muted, only when physically moved
|
||||||
|
* This report is sent every time the mic is moved, no polling required
|
||||||
|
*
|
||||||
|
* 3: CONNECTION STATUS
|
||||||
|
* 16: Wired headset
|
||||||
|
* 38: Initialising
|
||||||
|
* 49: Lost connection
|
||||||
|
* 51: Disconnected, searching
|
||||||
|
* 52: Disconnected, not searching
|
||||||
|
* 177: Normal
|
||||||
|
*
|
||||||
|
* 4: BATTERY STATUS
|
||||||
|
* 0: Disconnected
|
||||||
|
* 1: Normal
|
||||||
|
* 2: Low
|
||||||
|
* 3: Critical - sent during shutdown
|
||||||
|
* 4: Fully charged
|
||||||
|
* 5: Charging
|
||||||
|
*/
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Receiver report information: (ID 102) */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/*
|
||||||
|
* When queried, the recevier responds with 4 bytes to describe the firmware
|
||||||
|
* The first 2 bytes are for the receiver, the second 2 are the headset
|
||||||
|
* The headset firmware version will be 0 if no headset is connected
|
||||||
|
*
|
||||||
|
* 0: Recevier firmware major version
|
||||||
|
* Major version of the receiver's firmware
|
||||||
|
*
|
||||||
|
* 1: Recevier firmware minor version
|
||||||
|
* Minor version of the receiver's firmware
|
||||||
|
*
|
||||||
|
* 2: Headset firmware major version
|
||||||
|
* Major version of the headset's firmware
|
||||||
|
*
|
||||||
|
* 3: Headset firmware minor version
|
||||||
|
* Minor version of the headset's firmware
|
||||||
|
*/
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#include <linux/bitfield.h>
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/cleanup.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/hid.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/power_supply.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
#include <asm/byteorder.h>
|
||||||
|
|
||||||
|
#include "hid-ids.h"
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_DEVICE(id, type) { HID_USB_DEVICE(USB_VENDOR_ID_CORSAIR, (id)), \
|
||||||
|
.driver_data = (type) }
|
||||||
|
#define CORSAIR_VOID_WIRELESS_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRELESS)
|
||||||
|
#define CORSAIR_VOID_WIRED_DEVICE(id) CORSAIR_VOID_DEVICE((id), CORSAIR_VOID_WIRED)
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_STATUS_REQUEST_ID 0xC9
|
||||||
|
#define CORSAIR_VOID_NOTIF_REQUEST_ID 0xCA
|
||||||
|
#define CORSAIR_VOID_SIDETONE_REQUEST_ID 0xFF
|
||||||
|
#define CORSAIR_VOID_STATUS_REPORT_ID 0x64
|
||||||
|
#define CORSAIR_VOID_FIRMWARE_REPORT_ID 0x66
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_USB_SIDETONE_REQUEST 0x1
|
||||||
|
#define CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE 0x21
|
||||||
|
#define CORSAIR_VOID_USB_SIDETONE_VALUE 0x200
|
||||||
|
#define CORSAIR_VOID_USB_SIDETONE_INDEX 0xB00
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_MIC_MASK GENMASK(7, 7)
|
||||||
|
#define CORSAIR_VOID_CAPACITY_MASK GENMASK(6, 0)
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_WIRELESS_CONNECTED 177
|
||||||
|
|
||||||
|
#define CORSAIR_VOID_SIDETONE_MAX_WIRELESS 55
|
||||||
|
#define CORSAIR_VOID_SIDETONE_MAX_WIRED 4096
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CORSAIR_VOID_WIRELESS,
|
||||||
|
CORSAIR_VOID_WIRED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CORSAIR_VOID_BATTERY_NORMAL = 1,
|
||||||
|
CORSAIR_VOID_BATTERY_LOW = 2,
|
||||||
|
CORSAIR_VOID_BATTERY_CRITICAL = 3,
|
||||||
|
CORSAIR_VOID_BATTERY_CHARGED = 4,
|
||||||
|
CORSAIR_VOID_BATTERY_CHARGING = 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum power_supply_property corsair_void_battery_props[] = {
|
||||||
|
POWER_SUPPLY_PROP_STATUS,
|
||||||
|
POWER_SUPPLY_PROP_PRESENT,
|
||||||
|
POWER_SUPPLY_PROP_CAPACITY,
|
||||||
|
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
||||||
|
POWER_SUPPLY_PROP_SCOPE,
|
||||||
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||||
|
POWER_SUPPLY_PROP_MANUFACTURER,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct corsair_void_battery_data {
|
||||||
|
int status;
|
||||||
|
bool present;
|
||||||
|
int capacity;
|
||||||
|
int capacity_level;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct corsair_void_drvdata {
|
||||||
|
struct hid_device *hid_dev;
|
||||||
|
struct device *dev;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
bool is_wired;
|
||||||
|
unsigned int sidetone_max;
|
||||||
|
|
||||||
|
struct corsair_void_battery_data battery_data;
|
||||||
|
bool mic_up;
|
||||||
|
bool connected;
|
||||||
|
int fw_receiver_major;
|
||||||
|
int fw_receiver_minor;
|
||||||
|
int fw_headset_major;
|
||||||
|
int fw_headset_minor;
|
||||||
|
|
||||||
|
struct power_supply *battery;
|
||||||
|
struct power_supply_desc battery_desc;
|
||||||
|
struct mutex battery_mutex;
|
||||||
|
|
||||||
|
struct delayed_work delayed_status_work;
|
||||||
|
struct delayed_work delayed_firmware_work;
|
||||||
|
struct work_struct battery_remove_work;
|
||||||
|
struct work_struct battery_add_work;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions to process receiver data
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void corsair_void_set_wireless_status(struct corsair_void_drvdata *drvdata)
|
||||||
|
{
|
||||||
|
struct usb_interface *usb_if = to_usb_interface(drvdata->dev->parent);
|
||||||
|
|
||||||
|
if (drvdata->is_wired)
|
||||||
|
return;
|
||||||
|
|
||||||
|
usb_set_wireless_status(usb_if, drvdata->connected ?
|
||||||
|
USB_WIRELESS_STATUS_CONNECTED :
|
||||||
|
USB_WIRELESS_STATUS_DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_set_unknown_batt(struct corsair_void_drvdata *drvdata)
|
||||||
|
{
|
||||||
|
struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
|
||||||
|
|
||||||
|
battery_data->status = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||||
|
battery_data->present = false;
|
||||||
|
battery_data->capacity = 0;
|
||||||
|
battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset data that may change between wireless connections */
|
||||||
|
static void corsair_void_set_unknown_wireless_data(struct corsair_void_drvdata *drvdata)
|
||||||
|
{
|
||||||
|
/* Only 0 out headset, receiver is always known if relevant */
|
||||||
|
drvdata->fw_headset_major = 0;
|
||||||
|
drvdata->fw_headset_minor = 0;
|
||||||
|
|
||||||
|
drvdata->connected = false;
|
||||||
|
drvdata->mic_up = false;
|
||||||
|
|
||||||
|
corsair_void_set_wireless_status(drvdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_process_receiver(struct corsair_void_drvdata *drvdata,
|
||||||
|
int raw_battery_capacity,
|
||||||
|
int raw_connection_status,
|
||||||
|
int raw_battery_status)
|
||||||
|
{
|
||||||
|
struct corsair_void_battery_data *battery_data = &drvdata->battery_data;
|
||||||
|
struct corsair_void_battery_data orig_battery_data;
|
||||||
|
|
||||||
|
/* Save initial battery data, to compare later */
|
||||||
|
orig_battery_data = *battery_data;
|
||||||
|
|
||||||
|
/* Headset not connected, or it's wired */
|
||||||
|
if (raw_connection_status != CORSAIR_VOID_WIRELESS_CONNECTED)
|
||||||
|
goto unknown_battery;
|
||||||
|
|
||||||
|
/* Battery information unavailable */
|
||||||
|
if (raw_battery_status == 0)
|
||||||
|
goto unknown_battery;
|
||||||
|
|
||||||
|
/* Battery must be connected then */
|
||||||
|
battery_data->present = true;
|
||||||
|
battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
||||||
|
|
||||||
|
/* Set battery status */
|
||||||
|
switch (raw_battery_status) {
|
||||||
|
case CORSAIR_VOID_BATTERY_NORMAL:
|
||||||
|
case CORSAIR_VOID_BATTERY_LOW:
|
||||||
|
case CORSAIR_VOID_BATTERY_CRITICAL:
|
||||||
|
battery_data->status = POWER_SUPPLY_STATUS_DISCHARGING;
|
||||||
|
if (raw_battery_status == CORSAIR_VOID_BATTERY_LOW)
|
||||||
|
battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
||||||
|
else if (raw_battery_status == CORSAIR_VOID_BATTERY_CRITICAL)
|
||||||
|
battery_data->capacity_level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case CORSAIR_VOID_BATTERY_CHARGED:
|
||||||
|
battery_data->status = POWER_SUPPLY_STATUS_FULL;
|
||||||
|
break;
|
||||||
|
case CORSAIR_VOID_BATTERY_CHARGING:
|
||||||
|
battery_data->status = POWER_SUPPLY_STATUS_CHARGING;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hid_warn(drvdata->hid_dev, "unknown battery status '%d'",
|
||||||
|
raw_battery_status);
|
||||||
|
goto unknown_battery;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
battery_data->capacity = raw_battery_capacity;
|
||||||
|
corsair_void_set_wireless_status(drvdata);
|
||||||
|
|
||||||
|
goto success;
|
||||||
|
unknown_battery:
|
||||||
|
corsair_void_set_unknown_batt(drvdata);
|
||||||
|
success:
|
||||||
|
|
||||||
|
/* Inform power supply if battery values changed */
|
||||||
|
if (memcmp(&orig_battery_data, battery_data, sizeof(*battery_data))) {
|
||||||
|
scoped_guard(mutex, &drvdata->battery_mutex) {
|
||||||
|
if (drvdata->battery) {
|
||||||
|
power_supply_changed(drvdata->battery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions to report stored data
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int corsair_void_battery_get_property(struct power_supply *psy,
|
||||||
|
enum power_supply_property prop,
|
||||||
|
union power_supply_propval *val)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = power_supply_get_drvdata(psy);
|
||||||
|
|
||||||
|
switch (prop) {
|
||||||
|
case POWER_SUPPLY_PROP_SCOPE:
|
||||||
|
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||||
|
if (!strncmp(drvdata->hid_dev->name, "Corsair ", 8))
|
||||||
|
val->strval = drvdata->hid_dev->name + 8;
|
||||||
|
else
|
||||||
|
val->strval = drvdata->hid_dev->name;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_MANUFACTURER:
|
||||||
|
val->strval = "Corsair";
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_STATUS:
|
||||||
|
val->intval = drvdata->battery_data.status;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_PRESENT:
|
||||||
|
val->intval = drvdata->battery_data.present;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_CAPACITY:
|
||||||
|
val->intval = drvdata->battery_data.capacity;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||||
|
val->intval = drvdata->battery_data.capacity_level;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t microphone_up_show(struct device *dev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (!drvdata->connected)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%d\n", drvdata->mic_up);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t fw_version_receiver_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (drvdata->fw_receiver_major == 0 && drvdata->fw_receiver_minor == 0)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_receiver_major,
|
||||||
|
drvdata->fw_receiver_minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static ssize_t fw_version_headset_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (drvdata->fw_headset_major == 0 && drvdata->fw_headset_minor == 0)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%d.%02d\n", drvdata->fw_headset_major,
|
||||||
|
drvdata->fw_headset_minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t sidetone_max_show(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%d\n", drvdata->sidetone_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions to send data to headset
|
||||||
|
*/
|
||||||
|
|
||||||
|
static ssize_t send_alert_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
struct hid_device *hid_dev = drvdata->hid_dev;
|
||||||
|
unsigned char alert_id;
|
||||||
|
unsigned char *send_buf __free(kfree) = NULL;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!drvdata->connected || drvdata->is_wired)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* Only accept 0 or 1 for alert ID */
|
||||||
|
if (kstrtou8(buf, 10, &alert_id) || alert_id >= 2)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
send_buf = kmalloc(3, GFP_KERNEL);
|
||||||
|
if (!send_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Packet format to send alert with ID alert_id */
|
||||||
|
send_buf[0] = CORSAIR_VOID_NOTIF_REQUEST_ID;
|
||||||
|
send_buf[1] = 0x02;
|
||||||
|
send_buf[2] = alert_id;
|
||||||
|
|
||||||
|
ret = hid_hw_raw_request(hid_dev, CORSAIR_VOID_NOTIF_REQUEST_ID,
|
||||||
|
send_buf, 3, HID_OUTPUT_REPORT,
|
||||||
|
HID_REQ_SET_REPORT);
|
||||||
|
if (ret < 0)
|
||||||
|
hid_warn(hid_dev, "failed to send alert request (reason: %d)",
|
||||||
|
ret);
|
||||||
|
else
|
||||||
|
ret = count;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int corsair_void_set_sidetone_wired(struct device *dev, const char *buf,
|
||||||
|
unsigned int sidetone)
|
||||||
|
{
|
||||||
|
struct usb_interface *usb_if = to_usb_interface(dev->parent);
|
||||||
|
struct usb_device *usb_dev = interface_to_usbdev(usb_if);
|
||||||
|
|
||||||
|
/* Packet format to set sidetone for wired headsets */
|
||||||
|
__le16 sidetone_le = cpu_to_le16(sidetone);
|
||||||
|
|
||||||
|
return usb_control_msg_send(usb_dev, 0,
|
||||||
|
CORSAIR_VOID_USB_SIDETONE_REQUEST,
|
||||||
|
CORSAIR_VOID_USB_SIDETONE_REQUEST_TYPE,
|
||||||
|
CORSAIR_VOID_USB_SIDETONE_VALUE,
|
||||||
|
CORSAIR_VOID_USB_SIDETONE_INDEX,
|
||||||
|
&sidetone_le, 2, USB_CTRL_SET_TIMEOUT,
|
||||||
|
GFP_KERNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int corsair_void_set_sidetone_wireless(struct device *dev,
|
||||||
|
const char *buf,
|
||||||
|
unsigned char sidetone)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
struct hid_device *hid_dev = drvdata->hid_dev;
|
||||||
|
unsigned char *send_buf __free(kfree) = NULL;
|
||||||
|
|
||||||
|
send_buf = kmalloc(12, GFP_KERNEL);
|
||||||
|
if (!send_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Packet format to set sidetone for wireless headsets */
|
||||||
|
send_buf[0] = CORSAIR_VOID_SIDETONE_REQUEST_ID;
|
||||||
|
send_buf[1] = 0x0B;
|
||||||
|
send_buf[2] = 0x00;
|
||||||
|
send_buf[3] = 0xFF;
|
||||||
|
send_buf[4] = 0x04;
|
||||||
|
send_buf[5] = 0x0E;
|
||||||
|
send_buf[6] = 0xFF;
|
||||||
|
send_buf[7] = 0x05;
|
||||||
|
send_buf[8] = 0x01;
|
||||||
|
send_buf[9] = 0x04;
|
||||||
|
send_buf[10] = 0x00;
|
||||||
|
send_buf[11] = sidetone + 200;
|
||||||
|
|
||||||
|
return hid_hw_raw_request(hid_dev, CORSAIR_VOID_SIDETONE_REQUEST_ID,
|
||||||
|
send_buf, 12, HID_FEATURE_REPORT,
|
||||||
|
HID_REQ_SET_REPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t set_sidetone_store(struct device *dev,
|
||||||
|
struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = dev_get_drvdata(dev);
|
||||||
|
struct hid_device *hid_dev = drvdata->hid_dev;
|
||||||
|
unsigned int sidetone;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!drvdata->connected)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
/* sidetone must be between 0 and drvdata->sidetone_max inclusive */
|
||||||
|
if (kstrtouint(buf, 10, &sidetone) || sidetone > drvdata->sidetone_max)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (drvdata->is_wired)
|
||||||
|
ret = corsair_void_set_sidetone_wired(dev, buf, sidetone);
|
||||||
|
else
|
||||||
|
ret = corsair_void_set_sidetone_wireless(dev, buf, sidetone);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
hid_warn(hid_dev, "failed to send sidetone (reason: %d)", ret);
|
||||||
|
else
|
||||||
|
ret = count;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int corsair_void_request_status(struct hid_device *hid_dev, int id)
|
||||||
|
{
|
||||||
|
unsigned char *send_buf __free(kfree) = NULL;
|
||||||
|
|
||||||
|
send_buf = kmalloc(2, GFP_KERNEL);
|
||||||
|
if (!send_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Packet format to request data item (status / firmware) refresh */
|
||||||
|
send_buf[0] = CORSAIR_VOID_STATUS_REQUEST_ID;
|
||||||
|
send_buf[1] = id;
|
||||||
|
|
||||||
|
/* Send request for data refresh */
|
||||||
|
return hid_hw_raw_request(hid_dev, CORSAIR_VOID_STATUS_REQUEST_ID,
|
||||||
|
send_buf, 2, HID_OUTPUT_REPORT,
|
||||||
|
HID_REQ_SET_REPORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Headset connect / disconnect handlers and work handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void corsair_void_status_work_handler(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata;
|
||||||
|
struct delayed_work *delayed_work;
|
||||||
|
int battery_ret;
|
||||||
|
|
||||||
|
delayed_work = container_of(work, struct delayed_work, work);
|
||||||
|
drvdata = container_of(delayed_work, struct corsair_void_drvdata,
|
||||||
|
delayed_status_work);
|
||||||
|
|
||||||
|
battery_ret = corsair_void_request_status(drvdata->hid_dev,
|
||||||
|
CORSAIR_VOID_STATUS_REPORT_ID);
|
||||||
|
if (battery_ret < 0) {
|
||||||
|
hid_warn(drvdata->hid_dev,
|
||||||
|
"failed to request battery (reason: %d)", battery_ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_firmware_work_handler(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata;
|
||||||
|
struct delayed_work *delayed_work;
|
||||||
|
int firmware_ret;
|
||||||
|
|
||||||
|
delayed_work = container_of(work, struct delayed_work, work);
|
||||||
|
drvdata = container_of(delayed_work, struct corsair_void_drvdata,
|
||||||
|
delayed_firmware_work);
|
||||||
|
|
||||||
|
firmware_ret = corsair_void_request_status(drvdata->hid_dev,
|
||||||
|
CORSAIR_VOID_FIRMWARE_REPORT_ID);
|
||||||
|
if (firmware_ret < 0) {
|
||||||
|
hid_warn(drvdata->hid_dev,
|
||||||
|
"failed to request firmware (reason: %d)", firmware_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_battery_remove_work_handler(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata;
|
||||||
|
|
||||||
|
drvdata = container_of(work, struct corsair_void_drvdata,
|
||||||
|
battery_remove_work);
|
||||||
|
scoped_guard(mutex, &drvdata->battery_mutex) {
|
||||||
|
if (drvdata->battery) {
|
||||||
|
power_supply_unregister(drvdata->battery);
|
||||||
|
drvdata->battery = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_battery_add_work_handler(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata;
|
||||||
|
struct power_supply_config psy_cfg;
|
||||||
|
struct power_supply *new_supply;
|
||||||
|
|
||||||
|
drvdata = container_of(work, struct corsair_void_drvdata,
|
||||||
|
battery_add_work);
|
||||||
|
guard(mutex)(&drvdata->battery_mutex);
|
||||||
|
if (drvdata->battery)
|
||||||
|
return;
|
||||||
|
|
||||||
|
psy_cfg.drv_data = drvdata;
|
||||||
|
new_supply = power_supply_register(drvdata->dev,
|
||||||
|
&drvdata->battery_desc,
|
||||||
|
&psy_cfg);
|
||||||
|
|
||||||
|
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));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (power_supply_powers(new_supply, drvdata->dev)) {
|
||||||
|
power_supply_unregister(new_supply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drvdata->battery = new_supply;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_headset_connected(struct corsair_void_drvdata *drvdata)
|
||||||
|
{
|
||||||
|
schedule_work(&drvdata->battery_add_work);
|
||||||
|
schedule_delayed_work(&drvdata->delayed_firmware_work,
|
||||||
|
msecs_to_jiffies(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_headset_disconnected(struct corsair_void_drvdata *drvdata)
|
||||||
|
{
|
||||||
|
schedule_work(&drvdata->battery_remove_work);
|
||||||
|
|
||||||
|
corsair_void_set_unknown_wireless_data(drvdata);
|
||||||
|
corsair_void_set_unknown_batt(drvdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Driver setup, probing and HID event handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
static DEVICE_ATTR_RO(fw_version_receiver);
|
||||||
|
static DEVICE_ATTR_RO(fw_version_headset);
|
||||||
|
static DEVICE_ATTR_RO(microphone_up);
|
||||||
|
static DEVICE_ATTR_RO(sidetone_max);
|
||||||
|
|
||||||
|
static DEVICE_ATTR_WO(send_alert);
|
||||||
|
static DEVICE_ATTR_WO(set_sidetone);
|
||||||
|
|
||||||
|
static struct attribute *corsair_void_attrs[] = {
|
||||||
|
&dev_attr_fw_version_receiver.attr,
|
||||||
|
&dev_attr_fw_version_headset.attr,
|
||||||
|
&dev_attr_microphone_up.attr,
|
||||||
|
&dev_attr_send_alert.attr,
|
||||||
|
&dev_attr_set_sidetone.attr,
|
||||||
|
&dev_attr_sidetone_max.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct attribute_group corsair_void_attr_group = {
|
||||||
|
.attrs = corsair_void_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int corsair_void_probe(struct hid_device *hid_dev,
|
||||||
|
const struct hid_device_id *hid_id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct corsair_void_drvdata *drvdata;
|
||||||
|
char *name;
|
||||||
|
|
||||||
|
if (!hid_is_usb(hid_dev))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
drvdata = devm_kzalloc(&hid_dev->dev, sizeof(*drvdata),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!drvdata)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
hid_set_drvdata(hid_dev, drvdata);
|
||||||
|
dev_set_drvdata(&hid_dev->dev, drvdata);
|
||||||
|
|
||||||
|
drvdata->dev = &hid_dev->dev;
|
||||||
|
drvdata->hid_dev = hid_dev;
|
||||||
|
drvdata->is_wired = hid_id->driver_data == CORSAIR_VOID_WIRED;
|
||||||
|
|
||||||
|
drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRELESS;
|
||||||
|
if (drvdata->is_wired)
|
||||||
|
drvdata->sidetone_max = CORSAIR_VOID_SIDETONE_MAX_WIRED;
|
||||||
|
|
||||||
|
/* Set initial values for no wireless headset attached */
|
||||||
|
/* If a headset is attached, it'll be prompted later */
|
||||||
|
corsair_void_set_unknown_wireless_data(drvdata);
|
||||||
|
corsair_void_set_unknown_batt(drvdata);
|
||||||
|
|
||||||
|
/* Receiver version won't be reset after init */
|
||||||
|
/* Headset version already set via set_unknown_wireless_data */
|
||||||
|
drvdata->fw_receiver_major = 0;
|
||||||
|
drvdata->fw_receiver_minor = 0;
|
||||||
|
|
||||||
|
ret = hid_parse(hid_dev);
|
||||||
|
if (ret) {
|
||||||
|
hid_err(hid_dev, "parse failed (reason: %d)\n", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
name = devm_kasprintf(drvdata->dev, GFP_KERNEL,
|
||||||
|
"corsair-void-%d-battery", hid_dev->id);
|
||||||
|
if (!name)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
drvdata->battery_desc.name = name;
|
||||||
|
drvdata->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||||
|
drvdata->battery_desc.properties = corsair_void_battery_props;
|
||||||
|
drvdata->battery_desc.num_properties = ARRAY_SIZE(corsair_void_battery_props);
|
||||||
|
drvdata->battery_desc.get_property = corsair_void_battery_get_property;
|
||||||
|
|
||||||
|
drvdata->battery = NULL;
|
||||||
|
INIT_WORK(&drvdata->battery_remove_work,
|
||||||
|
corsair_void_battery_remove_work_handler);
|
||||||
|
INIT_WORK(&drvdata->battery_add_work,
|
||||||
|
corsair_void_battery_add_work_handler);
|
||||||
|
ret = devm_mutex_init(drvdata->dev, &drvdata->battery_mutex);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = sysfs_create_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* Any failures after here will need to call hid_hw_stop */
|
||||||
|
ret = hid_hw_start(hid_dev, HID_CONNECT_DEFAULT);
|
||||||
|
if (ret) {
|
||||||
|
hid_err(hid_dev, "hid_hw_start failed (reason: %d)\n", ret);
|
||||||
|
goto failed_after_sysfs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Refresh battery data, in case wireless headset is already connected */
|
||||||
|
INIT_DELAYED_WORK(&drvdata->delayed_status_work,
|
||||||
|
corsair_void_status_work_handler);
|
||||||
|
schedule_delayed_work(&drvdata->delayed_status_work,
|
||||||
|
msecs_to_jiffies(100));
|
||||||
|
|
||||||
|
/* Refresh firmware versions */
|
||||||
|
INIT_DELAYED_WORK(&drvdata->delayed_firmware_work,
|
||||||
|
corsair_void_firmware_work_handler);
|
||||||
|
schedule_delayed_work(&drvdata->delayed_firmware_work,
|
||||||
|
msecs_to_jiffies(100));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
failed_after_sysfs:
|
||||||
|
sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void corsair_void_remove(struct hid_device *hid_dev)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
|
||||||
|
|
||||||
|
hid_hw_stop(hid_dev);
|
||||||
|
cancel_work_sync(&drvdata->battery_remove_work);
|
||||||
|
cancel_work_sync(&drvdata->battery_add_work);
|
||||||
|
if (drvdata->battery)
|
||||||
|
power_supply_unregister(drvdata->battery);
|
||||||
|
|
||||||
|
cancel_delayed_work_sync(&drvdata->delayed_firmware_work);
|
||||||
|
sysfs_remove_group(&hid_dev->dev.kobj, &corsair_void_attr_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int corsair_void_raw_event(struct hid_device *hid_dev,
|
||||||
|
struct hid_report *hid_report,
|
||||||
|
u8 *data, int size)
|
||||||
|
{
|
||||||
|
struct corsair_void_drvdata *drvdata = hid_get_drvdata(hid_dev);
|
||||||
|
bool was_connected = drvdata->connected;
|
||||||
|
|
||||||
|
/* Description of packets are documented at the top of this file */
|
||||||
|
if (hid_report->id == CORSAIR_VOID_STATUS_REPORT_ID) {
|
||||||
|
drvdata->mic_up = FIELD_GET(CORSAIR_VOID_MIC_MASK, data[2]);
|
||||||
|
drvdata->connected = (data[3] == CORSAIR_VOID_WIRELESS_CONNECTED) ||
|
||||||
|
drvdata->is_wired;
|
||||||
|
|
||||||
|
corsair_void_process_receiver(drvdata,
|
||||||
|
FIELD_GET(CORSAIR_VOID_CAPACITY_MASK, data[2]),
|
||||||
|
data[3], data[4]);
|
||||||
|
} else if (hid_report->id == CORSAIR_VOID_FIRMWARE_REPORT_ID) {
|
||||||
|
drvdata->fw_receiver_major = data[1];
|
||||||
|
drvdata->fw_receiver_minor = data[2];
|
||||||
|
drvdata->fw_headset_major = data[3];
|
||||||
|
drvdata->fw_headset_minor = data[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle wireless headset connect / disconnect */
|
||||||
|
if ((was_connected != drvdata->connected) && !drvdata->is_wired) {
|
||||||
|
if (drvdata->connected)
|
||||||
|
corsair_void_headset_connected(drvdata);
|
||||||
|
else
|
||||||
|
corsair_void_headset_disconnected(drvdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hid_device_id corsair_void_devices[] = {
|
||||||
|
/* Corsair Void Wireless */
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a0c),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a2b),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x1b23),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x1b25),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x1b27),
|
||||||
|
|
||||||
|
/* Corsair Void USB */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a0f),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x1b1c),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x1b29),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x1b2a),
|
||||||
|
|
||||||
|
/* Corsair Void Surround */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a30),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a31),
|
||||||
|
|
||||||
|
/* Corsair Void Pro Wireless */
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a14),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a16),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a1a),
|
||||||
|
|
||||||
|
/* Corsair Void Pro USB */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a17),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a1d),
|
||||||
|
|
||||||
|
/* Corsair Void Pro Surround */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a18),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a1e),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a1f),
|
||||||
|
|
||||||
|
/* Corsair Void Elite Wireless */
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a51),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a55),
|
||||||
|
CORSAIR_VOID_WIRELESS_DEVICE(0x0a75),
|
||||||
|
|
||||||
|
/* Corsair Void Elite USB */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a52),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a56),
|
||||||
|
|
||||||
|
/* Corsair Void Elite Surround */
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a53),
|
||||||
|
CORSAIR_VOID_WIRED_DEVICE(0x0a57),
|
||||||
|
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
MODULE_DEVICE_TABLE(hid, corsair_void_devices);
|
||||||
|
|
||||||
|
static struct hid_driver corsair_void_driver = {
|
||||||
|
.name = "hid-corsair-void",
|
||||||
|
.id_table = corsair_void_devices,
|
||||||
|
.probe = corsair_void_probe,
|
||||||
|
.remove = corsair_void_remove,
|
||||||
|
.raw_event = corsair_void_raw_event,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_hid_driver(corsair_void_driver);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Stuart Hayhurst <stuart.a.hayhurst@gmail.com>");
|
||||||
|
MODULE_DESCRIPTION("HID driver for Corsair Void headsets");
|
@ -852,7 +852,8 @@ static int cp2112_set_usb_config(struct hid_device *hdev,
|
|||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
BUG_ON(cfg->report != CP2112_USB_CONFIG);
|
if (WARN_ON(cfg->report != CP2112_USB_CONFIG))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
ret = cp2112_hid_output(hdev, (u8 *)cfg, sizeof(*cfg),
|
ret = cp2112_hid_output(hdev, (u8 *)cfg, sizeof(*cfg),
|
||||||
HID_FEATURE_REPORT);
|
HID_FEATURE_REPORT);
|
||||||
|
@ -3309,9 +3309,9 @@ static const char *keys[KEY_MAX + 1] = {
|
|||||||
[KEY_EPG] = "EPG", [KEY_PVR] = "PVR",
|
[KEY_EPG] = "EPG", [KEY_PVR] = "PVR",
|
||||||
[KEY_MHP] = "MHP", [KEY_LANGUAGE] = "Language",
|
[KEY_MHP] = "MHP", [KEY_LANGUAGE] = "Language",
|
||||||
[KEY_TITLE] = "Title", [KEY_SUBTITLE] = "Subtitle",
|
[KEY_TITLE] = "Title", [KEY_SUBTITLE] = "Subtitle",
|
||||||
[KEY_ANGLE] = "Angle", [KEY_ZOOM] = "Zoom",
|
[KEY_ANGLE] = "Angle",
|
||||||
[KEY_MODE] = "Mode", [KEY_KEYBOARD] = "Keyboard",
|
[KEY_MODE] = "Mode", [KEY_KEYBOARD] = "Keyboard",
|
||||||
[KEY_SCREEN] = "Screen", [KEY_PC] = "PC",
|
[KEY_PC] = "PC",
|
||||||
[KEY_TV] = "TV", [KEY_TV2] = "TV2",
|
[KEY_TV] = "TV", [KEY_TV2] = "TV2",
|
||||||
[KEY_VCR] = "VCR", [KEY_VCR2] = "VCR2",
|
[KEY_VCR] = "VCR", [KEY_VCR2] = "VCR2",
|
||||||
[KEY_SAT] = "Sat", [KEY_SAT2] = "Sat2",
|
[KEY_SAT] = "Sat", [KEY_SAT2] = "Sat2",
|
||||||
@ -3409,8 +3409,7 @@ static const char *keys[KEY_MAX + 1] = {
|
|||||||
[BTN_TRIGGER_HAPPY35] = "TriggerHappy35", [BTN_TRIGGER_HAPPY36] = "TriggerHappy36",
|
[BTN_TRIGGER_HAPPY35] = "TriggerHappy35", [BTN_TRIGGER_HAPPY36] = "TriggerHappy36",
|
||||||
[BTN_TRIGGER_HAPPY37] = "TriggerHappy37", [BTN_TRIGGER_HAPPY38] = "TriggerHappy38",
|
[BTN_TRIGGER_HAPPY37] = "TriggerHappy37", [BTN_TRIGGER_HAPPY38] = "TriggerHappy38",
|
||||||
[BTN_TRIGGER_HAPPY39] = "TriggerHappy39", [BTN_TRIGGER_HAPPY40] = "TriggerHappy40",
|
[BTN_TRIGGER_HAPPY39] = "TriggerHappy39", [BTN_TRIGGER_HAPPY40] = "TriggerHappy40",
|
||||||
[BTN_DIGI] = "Digi", [BTN_STYLUS3] = "Stylus3",
|
[BTN_STYLUS3] = "Stylus3", [BTN_TOOL_QUINTTAP] = "ToolQuintTap",
|
||||||
[BTN_TOOL_QUINTTAP] = "ToolQuintTap", [BTN_WHEEL] = "Wheel",
|
|
||||||
[KEY_10CHANNELSDOWN] = "10ChannelsDown",
|
[KEY_10CHANNELSDOWN] = "10ChannelsDown",
|
||||||
[KEY_10CHANNELSUP] = "10ChannelsUp",
|
[KEY_10CHANNELSUP] = "10ChannelsUp",
|
||||||
[KEY_3D_MODE] = "3DMode", [KEY_ADDRESSBOOK] = "Addressbook",
|
[KEY_3D_MODE] = "3DMode", [KEY_ADDRESSBOOK] = "Addressbook",
|
||||||
@ -3440,7 +3439,7 @@ static const char *keys[KEY_MAX + 1] = {
|
|||||||
[KEY_FN_RIGHT_SHIFT] = "FnRightShift", [KEY_FRAMEBACK] = "FrameBack",
|
[KEY_FN_RIGHT_SHIFT] = "FnRightShift", [KEY_FRAMEBACK] = "FrameBack",
|
||||||
[KEY_FRAMEFORWARD] = "FrameForward", [KEY_FULL_SCREEN] = "FullScreen",
|
[KEY_FRAMEFORWARD] = "FrameForward", [KEY_FULL_SCREEN] = "FullScreen",
|
||||||
[KEY_GAMES] = "Games", [KEY_GRAPHICSEDITOR] = "GraphicsEditor",
|
[KEY_GAMES] = "Games", [KEY_GRAPHICSEDITOR] = "GraphicsEditor",
|
||||||
[KEY_HANGEUL] = "HanGeul", [KEY_HANGUP_PHONE] = "HangUpPhone",
|
[KEY_HANGUP_PHONE] = "HangUpPhone",
|
||||||
[KEY_IMAGES] = "Images", [KEY_KBD_LCD_MENU1] = "KbdLcdMenu1",
|
[KEY_IMAGES] = "Images", [KEY_KBD_LCD_MENU1] = "KbdLcdMenu1",
|
||||||
[KEY_KBD_LCD_MENU2] = "KbdLcdMenu2", [KEY_KBD_LCD_MENU3] = "KbdLcdMenu3",
|
[KEY_KBD_LCD_MENU2] = "KbdLcdMenu2", [KEY_KBD_LCD_MENU3] = "KbdLcdMenu3",
|
||||||
[KEY_KBD_LCD_MENU4] = "KbdLcdMenu4", [KEY_KBD_LCD_MENU5] = "KbdLcdMenu5",
|
[KEY_KBD_LCD_MENU4] = "KbdLcdMenu4", [KEY_KBD_LCD_MENU5] = "KbdLcdMenu5",
|
||||||
|
@ -40,6 +40,9 @@ static bool hid_generic_match(struct hid_device *hdev,
|
|||||||
if (ignore_special_driver)
|
if (ignore_special_driver)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)
|
if (hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
#define GOODIX_HID_DESC_ADDR 0x1058C
|
#define GOODIX_HID_DESC_ADDR 0x1058C
|
||||||
#define GOODIX_HID_REPORT_DESC_ADDR 0x105AA
|
#define GOODIX_HID_REPORT_DESC_ADDR 0x105AA
|
||||||
#define GOODIX_HID_SIGN_ADDR 0x10D32
|
#define GOODIX_HID_SIGN_ADDR 0x10D32
|
||||||
|
#define GOODIX_HID_CMD_ADDR 0x10364
|
||||||
|
#define GOODIX_HID_REPORT_ADDR 0x22C8C
|
||||||
|
|
||||||
#define GOODIX_HID_GET_REPORT_CMD 0x02
|
#define GOODIX_HID_GET_REPORT_CMD 0x02
|
||||||
#define GOODIX_HID_SET_REPORT_CMD 0x03
|
#define GOODIX_HID_SET_REPORT_CMD 0x03
|
||||||
@ -348,7 +350,7 @@ static int goodix_hid_check_ack_status(struct goodix_ts_data *ts, u32 *resp_len)
|
|||||||
* - byte 0: Ack flag, value of 1 for data ready
|
* - byte 0: Ack flag, value of 1 for data ready
|
||||||
* - bytes 1-2: Response data length
|
* - bytes 1-2: Response data length
|
||||||
*/
|
*/
|
||||||
error = goodix_spi_read(ts, ts->hid_report_addr,
|
error = goodix_spi_read(ts, GOODIX_HID_CMD_ADDR,
|
||||||
&hdr, sizeof(hdr));
|
&hdr, sizeof(hdr));
|
||||||
if (!error && (hdr.flag & GOODIX_HID_ACK_READY_FLAG)) {
|
if (!error && (hdr.flag & GOODIX_HID_ACK_READY_FLAG)) {
|
||||||
len = le16_to_cpu(hdr.size);
|
len = le16_to_cpu(hdr.size);
|
||||||
@ -356,7 +358,7 @@ static int goodix_hid_check_ack_status(struct goodix_ts_data *ts, u32 *resp_len)
|
|||||||
dev_err(ts->dev, "hrd.size too short: %d", len);
|
dev_err(ts->dev, "hrd.size too short: %d", len);
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
*resp_len = len;
|
*resp_len = len - GOODIX_HID_PKG_LEN_SIZE;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,7 +433,7 @@ static int goodix_hid_get_raw_report(struct hid_device *hid,
|
|||||||
tx_len += args_len;
|
tx_len += args_len;
|
||||||
|
|
||||||
/* Step1: write report request info */
|
/* Step1: write report request info */
|
||||||
error = goodix_spi_write(ts, ts->hid_report_addr, tmp_buf, tx_len);
|
error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, tmp_buf, tx_len);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(ts->dev, "failed send read feature cmd, %d", error);
|
dev_err(ts->dev, "failed send read feature cmd, %d", error);
|
||||||
return error;
|
return error;
|
||||||
@ -446,9 +448,12 @@ static int goodix_hid_get_raw_report(struct hid_device *hid,
|
|||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
len = min(len, response_data_len - GOODIX_HID_PKG_LEN_SIZE);
|
/* Empty reprot response */
|
||||||
|
if (!response_data_len)
|
||||||
|
return 0;
|
||||||
|
len = min(len, response_data_len);
|
||||||
/* Step3: read response data(skip 2bytes of hid pkg length) */
|
/* Step3: read response data(skip 2bytes of hid pkg length) */
|
||||||
error = goodix_spi_read(ts, ts->hid_report_addr +
|
error = goodix_spi_read(ts, GOODIX_HID_CMD_ADDR +
|
||||||
GOODIX_HID_ACK_HEADER_SIZE +
|
GOODIX_HID_ACK_HEADER_SIZE +
|
||||||
GOODIX_HID_PKG_LEN_SIZE, buf, len);
|
GOODIX_HID_PKG_LEN_SIZE, buf, len);
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -518,7 +523,7 @@ static int goodix_hid_set_raw_report(struct hid_device *hid,
|
|||||||
memcpy(tmp_buf + tx_len, buf, len);
|
memcpy(tmp_buf + tx_len, buf, len);
|
||||||
tx_len += len;
|
tx_len += len;
|
||||||
|
|
||||||
error = goodix_spi_write(ts, ts->hid_report_addr, tmp_buf, tx_len);
|
error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, tmp_buf, tx_len);
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(ts->dev, "failed send report: %*ph", tx_len, tmp_buf);
|
dev_err(ts->dev, "failed send report: %*ph", tx_len, tmp_buf);
|
||||||
return error;
|
return error;
|
||||||
@ -697,12 +702,7 @@ static int goodix_spi_probe(struct spi_device *spi)
|
|||||||
return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
|
return dev_err_probe(dev, PTR_ERR(ts->reset_gpio),
|
||||||
"failed to request reset gpio\n");
|
"failed to request reset gpio\n");
|
||||||
|
|
||||||
error = device_property_read_u32(dev, "goodix,hid-report-addr",
|
ts->hid_report_addr = GOODIX_HID_REPORT_ADDR;
|
||||||
&ts->hid_report_addr);
|
|
||||||
if (error)
|
|
||||||
return dev_err_probe(dev, error,
|
|
||||||
"failed get hid report addr\n");
|
|
||||||
|
|
||||||
error = goodix_dev_confirm(ts);
|
error = goodix_dev_confirm(ts);
|
||||||
if (error)
|
if (error)
|
||||||
return error;
|
return error;
|
||||||
@ -749,7 +749,7 @@ static int goodix_spi_set_power(struct goodix_ts_data *ts, int power_state)
|
|||||||
power_control_cmd[5] = power_state;
|
power_control_cmd[5] = power_state;
|
||||||
|
|
||||||
guard(mutex)(&ts->hid_request_lock);
|
guard(mutex)(&ts->hid_request_lock);
|
||||||
error = goodix_spi_write(ts, ts->hid_report_addr, power_control_cmd,
|
error = goodix_spi_write(ts, GOODIX_HID_CMD_ADDR, power_control_cmd,
|
||||||
sizeof(power_control_cmd));
|
sizeof(power_control_cmd));
|
||||||
if (error) {
|
if (error) {
|
||||||
dev_err(ts->dev, "failed set power mode: %s",
|
dev_err(ts->dev, "failed set power mode: %s",
|
||||||
@ -786,6 +786,14 @@ static const struct acpi_device_id goodix_spi_acpi_match[] = {
|
|||||||
MODULE_DEVICE_TABLE(acpi, goodix_spi_acpi_match);
|
MODULE_DEVICE_TABLE(acpi, goodix_spi_acpi_match);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id goodix_spi_of_match[] = {
|
||||||
|
{ .compatible = "goodix,gt7986u-spifw", },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, goodix_spi_of_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
static const struct spi_device_id goodix_spi_ids[] = {
|
static const struct spi_device_id goodix_spi_ids[] = {
|
||||||
{ "gt7986u" },
|
{ "gt7986u" },
|
||||||
{ },
|
{ },
|
||||||
@ -796,6 +804,7 @@ static struct spi_driver goodix_spi_driver = {
|
|||||||
.driver = {
|
.driver = {
|
||||||
.name = "goodix-spi-hid",
|
.name = "goodix-spi-hid",
|
||||||
.acpi_match_table = ACPI_PTR(goodix_spi_acpi_match),
|
.acpi_match_table = ACPI_PTR(goodix_spi_acpi_match),
|
||||||
|
.of_match_table = of_match_ptr(goodix_spi_of_match),
|
||||||
.pm = pm_sleep_ptr(&goodix_spi_pm_ops),
|
.pm = pm_sleep_ptr(&goodix_spi_pm_ops),
|
||||||
},
|
},
|
||||||
.probe = goodix_spi_probe,
|
.probe = goodix_spi_probe,
|
||||||
|
@ -422,6 +422,25 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int mousevsc_hid_probe(struct hid_device *hid_dev, const struct hid_device_id *id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = hid_parse(hid_dev);
|
||||||
|
if (ret) {
|
||||||
|
hid_err(hid_dev, "parse failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
|
||||||
|
if (ret) {
|
||||||
|
hid_err(hid_dev, "hw start failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct hid_ll_driver mousevsc_ll_driver = {
|
static const struct hid_ll_driver mousevsc_ll_driver = {
|
||||||
.parse = mousevsc_hid_parse,
|
.parse = mousevsc_hid_parse,
|
||||||
.open = mousevsc_hid_open,
|
.open = mousevsc_hid_open,
|
||||||
@ -431,7 +450,16 @@ static const struct hid_ll_driver mousevsc_ll_driver = {
|
|||||||
.raw_request = mousevsc_hid_raw_request,
|
.raw_request = mousevsc_hid_raw_request,
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct hid_driver mousevsc_hid_driver;
|
static const struct hid_device_id mousevsc_devices[] = {
|
||||||
|
{ HID_DEVICE(BUS_VIRTUAL, HID_GROUP_ANY, 0x045E, 0x0621) },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct hid_driver mousevsc_hid_driver = {
|
||||||
|
.name = "hid-hyperv",
|
||||||
|
.id_table = mousevsc_devices,
|
||||||
|
.probe = mousevsc_hid_probe,
|
||||||
|
};
|
||||||
|
|
||||||
static int mousevsc_probe(struct hv_device *device,
|
static int mousevsc_probe(struct hv_device *device,
|
||||||
const struct hv_vmbus_device_id *dev_id)
|
const struct hv_vmbus_device_id *dev_id)
|
||||||
@ -473,7 +501,6 @@ static int mousevsc_probe(struct hv_device *device,
|
|||||||
}
|
}
|
||||||
|
|
||||||
hid_dev->ll_driver = &mousevsc_ll_driver;
|
hid_dev->ll_driver = &mousevsc_ll_driver;
|
||||||
hid_dev->driver = &mousevsc_hid_driver;
|
|
||||||
hid_dev->bus = BUS_VIRTUAL;
|
hid_dev->bus = BUS_VIRTUAL;
|
||||||
hid_dev->vendor = input_dev->hid_dev_info.vendor;
|
hid_dev->vendor = input_dev->hid_dev_info.vendor;
|
||||||
hid_dev->product = input_dev->hid_dev_info.product;
|
hid_dev->product = input_dev->hid_dev_info.product;
|
||||||
@ -488,20 +515,6 @@ static int mousevsc_probe(struct hv_device *device,
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto probe_err2;
|
goto probe_err2;
|
||||||
|
|
||||||
|
|
||||||
ret = hid_parse(hid_dev);
|
|
||||||
if (ret) {
|
|
||||||
hid_err(hid_dev, "parse failed\n");
|
|
||||||
goto probe_err2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = hid_hw_start(hid_dev, HID_CONNECT_HIDINPUT | HID_CONNECT_HIDDEV);
|
|
||||||
|
|
||||||
if (ret) {
|
|
||||||
hid_err(hid_dev, "hw start failed\n");
|
|
||||||
goto probe_err2;
|
|
||||||
}
|
|
||||||
|
|
||||||
device_init_wakeup(&device->device, true);
|
device_init_wakeup(&device->device, true);
|
||||||
|
|
||||||
input_dev->connected = true;
|
input_dev->connected = true;
|
||||||
@ -579,12 +592,23 @@ static struct hv_driver mousevsc_drv = {
|
|||||||
|
|
||||||
static int __init mousevsc_init(void)
|
static int __init mousevsc_init(void)
|
||||||
{
|
{
|
||||||
return vmbus_driver_register(&mousevsc_drv);
|
int ret;
|
||||||
|
|
||||||
|
ret = hid_register_driver(&mousevsc_hid_driver);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = vmbus_driver_register(&mousevsc_drv);
|
||||||
|
if (ret)
|
||||||
|
hid_unregister_driver(&mousevsc_hid_driver);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __exit mousevsc_exit(void)
|
static void __exit mousevsc_exit(void)
|
||||||
{
|
{
|
||||||
vmbus_driver_unregister(&mousevsc_drv);
|
vmbus_driver_unregister(&mousevsc_drv);
|
||||||
|
hid_unregister_driver(&mousevsc_hid_driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
|
@ -94,6 +94,7 @@
|
|||||||
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
|
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
|
||||||
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
|
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
|
||||||
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265
|
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 0x0265
|
||||||
|
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC 0x0324
|
||||||
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
|
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ANSI 0x020e
|
||||||
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f
|
#define USB_DEVICE_ID_APPLE_FOUNTAIN_ISO 0x020f
|
||||||
#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214
|
#define USB_DEVICE_ID_APPLE_GEYSER_ANSI 0x0214
|
||||||
@ -735,6 +736,10 @@
|
|||||||
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501A
|
#define USB_DEVICE_ID_KYE_MOUSEPEN_I608X_V2 0x501A
|
||||||
#define USB_DEVICE_ID_KYE_PENSKETCH_T609A 0x501B
|
#define USB_DEVICE_ID_KYE_PENSKETCH_T609A 0x501B
|
||||||
|
|
||||||
|
#define USB_VENDOR_ID_KYSONA 0x3554
|
||||||
|
#define USB_DEVICE_ID_KYSONA_M600_DONGLE 0xF57C
|
||||||
|
#define USB_DEVICE_ID_KYSONA_M600_WIRED 0xF57D
|
||||||
|
|
||||||
#define USB_VENDOR_ID_LABTEC 0x1020
|
#define USB_VENDOR_ID_LABTEC 0x1020
|
||||||
#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
|
#define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
|
||||||
#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE 0x8888
|
#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE 0x8888
|
||||||
|
248
drivers/hid/hid-kysona.c
Normal file
248
drivers/hid/hid-kysona.c
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* USB HID driver for Kysona
|
||||||
|
* Kysona M600 mice.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2024 Lode Willems <me@lodewillems.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/hid.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
|
||||||
|
#include "hid-ids.h"
|
||||||
|
|
||||||
|
#define BATTERY_TIMEOUT_MS 5000
|
||||||
|
|
||||||
|
#define BATTERY_REPORT_ID 4
|
||||||
|
|
||||||
|
struct kysona_drvdata {
|
||||||
|
struct hid_device *hdev;
|
||||||
|
bool online;
|
||||||
|
|
||||||
|
struct power_supply_desc battery_desc;
|
||||||
|
struct power_supply *battery;
|
||||||
|
u8 battery_capacity;
|
||||||
|
bool battery_charging;
|
||||||
|
u16 battery_voltage;
|
||||||
|
struct delayed_work battery_work;
|
||||||
|
};
|
||||||
|
|
||||||
|
static enum power_supply_property kysona_battery_props[] = {
|
||||||
|
POWER_SUPPLY_PROP_STATUS,
|
||||||
|
POWER_SUPPLY_PROP_PRESENT,
|
||||||
|
POWER_SUPPLY_PROP_CAPACITY,
|
||||||
|
POWER_SUPPLY_PROP_SCOPE,
|
||||||
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
||||||
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
||||||
|
POWER_SUPPLY_PROP_ONLINE
|
||||||
|
};
|
||||||
|
|
||||||
|
static int kysona_battery_get_property(struct power_supply *psy,
|
||||||
|
enum power_supply_property psp,
|
||||||
|
union power_supply_propval *val)
|
||||||
|
{
|
||||||
|
struct kysona_drvdata *drv_data = power_supply_get_drvdata(psy);
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
switch (psp) {
|
||||||
|
case POWER_SUPPLY_PROP_PRESENT:
|
||||||
|
val->intval = 1;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_ONLINE:
|
||||||
|
val->intval = drv_data->online;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_STATUS:
|
||||||
|
if (drv_data->online)
|
||||||
|
val->intval = drv_data->battery_charging ?
|
||||||
|
POWER_SUPPLY_STATUS_CHARGING :
|
||||||
|
POWER_SUPPLY_STATUS_DISCHARGING;
|
||||||
|
else
|
||||||
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_SCOPE:
|
||||||
|
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_CAPACITY:
|
||||||
|
val->intval = drv_data->battery_capacity;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
||||||
|
/* hardware reports voltage in mV. sysfs expects uV */
|
||||||
|
val->intval = drv_data->battery_voltage * 1000;
|
||||||
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
||||||
|
val->strval = drv_data->hdev->name;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EINVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char kysona_battery_request[] = {
|
||||||
|
0x08, BATTERY_REPORT_ID, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49
|
||||||
|
};
|
||||||
|
|
||||||
|
static int kysona_m600_fetch_battery(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
u8 *write_buf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Request battery information */
|
||||||
|
write_buf = kmemdup(kysona_battery_request, sizeof(kysona_battery_request), GFP_KERNEL);
|
||||||
|
if (!write_buf)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = hid_hw_raw_request(hdev, kysona_battery_request[0],
|
||||||
|
write_buf, sizeof(kysona_battery_request),
|
||||||
|
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
|
||||||
|
if (ret < (int)sizeof(kysona_battery_request)) {
|
||||||
|
hid_err(hdev, "hid_hw_raw_request() failed with %d\n", ret);
|
||||||
|
ret = -ENODATA;
|
||||||
|
}
|
||||||
|
kfree(write_buf);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kysona_fetch_battery(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
int ret = kysona_m600_fetch_battery(hdev);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
hid_dbg(hdev,
|
||||||
|
"Battery query failed (err: %d)\n", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kysona_battery_timer_tick(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct kysona_drvdata *drv_data = container_of(work,
|
||||||
|
struct kysona_drvdata, battery_work.work);
|
||||||
|
struct hid_device *hdev = drv_data->hdev;
|
||||||
|
|
||||||
|
kysona_fetch_battery(hdev);
|
||||||
|
schedule_delayed_work(&drv_data->battery_work,
|
||||||
|
msecs_to_jiffies(BATTERY_TIMEOUT_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kysona_battery_probe(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
|
||||||
|
struct power_supply_config pscfg = { .drv_data = drv_data };
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
drv_data->online = false;
|
||||||
|
drv_data->battery_capacity = 100;
|
||||||
|
drv_data->battery_voltage = 4200;
|
||||||
|
|
||||||
|
drv_data->battery_desc.properties = kysona_battery_props;
|
||||||
|
drv_data->battery_desc.num_properties = ARRAY_SIZE(kysona_battery_props);
|
||||||
|
drv_data->battery_desc.get_property = kysona_battery_get_property;
|
||||||
|
drv_data->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
||||||
|
drv_data->battery_desc.use_for_apm = 0;
|
||||||
|
drv_data->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
|
||||||
|
"kysona-%s-battery",
|
||||||
|
strlen(hdev->uniq) ?
|
||||||
|
hdev->uniq : dev_name(&hdev->dev));
|
||||||
|
if (!drv_data->battery_desc.name)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
drv_data->battery = devm_power_supply_register(&hdev->dev,
|
||||||
|
&drv_data->battery_desc, &pscfg);
|
||||||
|
if (IS_ERR(drv_data->battery)) {
|
||||||
|
ret = PTR_ERR(drv_data->battery);
|
||||||
|
drv_data->battery = NULL;
|
||||||
|
hid_err(hdev, "Unable to register battery device\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
power_supply_powers(drv_data->battery, &hdev->dev);
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&drv_data->battery_work, kysona_battery_timer_tick);
|
||||||
|
kysona_fetch_battery(hdev);
|
||||||
|
schedule_delayed_work(&drv_data->battery_work,
|
||||||
|
msecs_to_jiffies(BATTERY_TIMEOUT_MS));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kysona_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct kysona_drvdata *drv_data;
|
||||||
|
struct usb_interface *usbif;
|
||||||
|
|
||||||
|
if (!hid_is_usb(hdev))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
usbif = to_usb_interface(hdev->dev.parent);
|
||||||
|
|
||||||
|
drv_data = devm_kzalloc(&hdev->dev, sizeof(*drv_data), GFP_KERNEL);
|
||||||
|
if (!drv_data)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
hid_set_drvdata(hdev, drv_data);
|
||||||
|
drv_data->hdev = hdev;
|
||||||
|
|
||||||
|
ret = hid_parse(hdev);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (usbif->cur_altsetting->desc.bInterfaceNumber == 1) {
|
||||||
|
if (kysona_battery_probe(hdev) < 0)
|
||||||
|
hid_err(hdev, "Kysona hid battery_probe failed: %d\n", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kysona_raw_event(struct hid_device *hdev,
|
||||||
|
struct hid_report *report, u8 *data, int size)
|
||||||
|
{
|
||||||
|
struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
|
||||||
|
|
||||||
|
if (drv_data->battery && size == sizeof(kysona_battery_request) &&
|
||||||
|
data[0] == 8 && data[1] == BATTERY_REPORT_ID) {
|
||||||
|
drv_data->battery_capacity = data[6];
|
||||||
|
drv_data->battery_charging = data[7];
|
||||||
|
drv_data->battery_voltage = (data[8] << 8) | data[9];
|
||||||
|
drv_data->online = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kysona_remove(struct hid_device *hdev)
|
||||||
|
{
|
||||||
|
struct kysona_drvdata *drv_data = hid_get_drvdata(hdev);
|
||||||
|
|
||||||
|
if (drv_data->battery)
|
||||||
|
cancel_delayed_work_sync(&drv_data->battery_work);
|
||||||
|
|
||||||
|
hid_hw_stop(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) },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(hid, kysona_devices);
|
||||||
|
|
||||||
|
static struct hid_driver kysona_driver = {
|
||||||
|
.name = "kysona",
|
||||||
|
.id_table = kysona_devices,
|
||||||
|
.probe = kysona_probe,
|
||||||
|
.raw_event = kysona_raw_event,
|
||||||
|
.remove = kysona_remove
|
||||||
|
};
|
||||||
|
module_hid_driver(kysona_driver);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_DESCRIPTION("HID driver for Kysona devices");
|
||||||
|
MODULE_AUTHOR("Lode Willems <me@lodewillems.com>");
|
@ -1350,7 +1350,8 @@ int lg4ff_init(struct hid_device *hid)
|
|||||||
|
|
||||||
/* Initialize device properties */
|
/* Initialize device properties */
|
||||||
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
|
if (mmode_ret == LG4FF_MMODE_IS_MULTIMODE) {
|
||||||
BUG_ON(mmode_idx == -1);
|
if (WARN_ON(mmode_idx == -1))
|
||||||
|
return -EINVAL;
|
||||||
mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
|
mmode_wheel = &lg4ff_multimode_wheels[mmode_idx];
|
||||||
}
|
}
|
||||||
lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
|
lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);
|
||||||
|
@ -928,7 +928,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
|
|||||||
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
|
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
|
||||||
|
|
||||||
static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
|
static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
|
||||||
u8 *feature_index, u8 *feature_type)
|
u8 *feature_index)
|
||||||
{
|
{
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
int ret;
|
int ret;
|
||||||
@ -945,7 +945,6 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
|
|||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
|
|
||||||
*feature_index = response.fap.params[0];
|
*feature_index = response.fap.params[0];
|
||||||
*feature_type = response.fap.params[1];
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1012,13 +1011,11 @@ print_version:
|
|||||||
static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
|
static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
|
||||||
{
|
{
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
u8 feature_type;
|
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
|
||||||
&feature_index,
|
&feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -1125,7 +1122,6 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp,
|
|||||||
|
|
||||||
static char *hidpp_get_device_name(struct hidpp_device *hidpp)
|
static char *hidpp_get_device_name(struct hidpp_device *hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
u8 __name_length;
|
u8 __name_length;
|
||||||
char *name;
|
char *name;
|
||||||
@ -1133,7 +1129,7 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
|
||||||
&feature_index, &feature_type);
|
&feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
@ -1300,15 +1296,13 @@ static int hidpp20_batterylevel_get_battery_info(struct hidpp_device *hidpp,
|
|||||||
|
|
||||||
static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
|
static int hidpp20_query_battery_info_1000(struct hidpp_device *hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
int status, capacity, next_capacity, level;
|
int status, capacity, next_capacity, level;
|
||||||
|
|
||||||
if (hidpp->battery.feature_index == 0xff) {
|
if (hidpp->battery.feature_index == 0xff) {
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
ret = hidpp_root_get_feature(hidpp,
|
||||||
HIDPP_PAGE_BATTERY_LEVEL_STATUS,
|
HIDPP_PAGE_BATTERY_LEVEL_STATUS,
|
||||||
&hidpp->battery.feature_index,
|
&hidpp->battery.feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1489,14 +1483,12 @@ static int hidpp20_map_battery_capacity(struct hid_device *hid_dev, int voltage)
|
|||||||
|
|
||||||
static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
|
static int hidpp20_query_battery_voltage_info(struct hidpp_device *hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
int status, voltage, level, charge_type;
|
int status, voltage, level, charge_type;
|
||||||
|
|
||||||
if (hidpp->battery.voltage_feature_index == 0xff) {
|
if (hidpp->battery.voltage_feature_index == 0xff) {
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
|
||||||
&hidpp->battery.voltage_feature_index,
|
&hidpp->battery.voltage_feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1692,7 +1684,6 @@ static int hidpp20_unifiedbattery_get_status(struct hidpp_device *hidpp,
|
|||||||
|
|
||||||
static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
|
static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 state_of_charge;
|
u8 state_of_charge;
|
||||||
int status, level;
|
int status, level;
|
||||||
@ -1700,8 +1691,7 @@ static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
|
|||||||
if (hidpp->battery.feature_index == 0xff) {
|
if (hidpp->battery.feature_index == 0xff) {
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
ret = hidpp_root_get_feature(hidpp,
|
||||||
HIDPP_PAGE_UNIFIED_BATTERY,
|
HIDPP_PAGE_UNIFIED_BATTERY,
|
||||||
&hidpp->battery.feature_index,
|
&hidpp->battery.feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -1834,14 +1824,9 @@ static int hidpp_battery_get_property(struct power_supply *psy,
|
|||||||
|
|
||||||
static int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index)
|
static int hidpp_get_wireless_feature_index(struct hidpp_device *hidpp, u8 *feature_index)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
return hidpp_root_get_feature(hidpp,
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
|
||||||
HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
|
HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
|
||||||
feature_index, &feature_type);
|
feature_index);
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -1952,14 +1937,11 @@ static bool hidpp20_get_adc_measurement_1f20(struct hidpp_device *hidpp,
|
|||||||
|
|
||||||
static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
|
static int hidpp20_query_adc_measurement_info_1f20(struct hidpp_device *hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_type;
|
|
||||||
|
|
||||||
if (hidpp->battery.adc_measurement_feature_index == 0xff) {
|
if (hidpp->battery.adc_measurement_feature_index == 0xff) {
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
|
||||||
&hidpp->battery.adc_measurement_feature_index,
|
&hidpp->battery.adc_measurement_feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -2014,15 +1996,13 @@ static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
|
|||||||
bool enabled, u8 *multiplier)
|
bool enabled, u8 *multiplier)
|
||||||
{
|
{
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 params[1];
|
u8 params[1];
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
ret = hidpp_root_get_feature(hidpp,
|
||||||
HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
|
HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
|
||||||
&feature_index,
|
&feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -2049,12 +2029,11 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
|
|||||||
u8 *multiplier)
|
u8 *multiplier)
|
||||||
{
|
{
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
||||||
&feature_index, &feature_type);
|
&feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto return_default;
|
goto return_default;
|
||||||
|
|
||||||
@ -2076,13 +2055,12 @@ static int hidpp_hrw_set_wheel_mode(struct hidpp_device *hidpp, bool invert,
|
|||||||
bool high_resolution, bool use_hidpp)
|
bool high_resolution, bool use_hidpp)
|
||||||
{
|
{
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
u8 params[1];
|
u8 params[1];
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
||||||
&feature_index, &feature_type);
|
&feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -2111,14 +2089,12 @@ static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
|
|||||||
{
|
{
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
u8 params[2] = { 1, 1 };
|
u8 params[2] = { 1, 1 };
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (hidpp->battery.feature_index == 0xff) {
|
if (hidpp->battery.feature_index == 0xff) {
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
ret = hidpp_root_get_feature(hidpp,
|
||||||
HIDPP_PAGE_SOLAR_KEYBOARD,
|
HIDPP_PAGE_SOLAR_KEYBOARD,
|
||||||
&hidpp->battery.solar_feature_index,
|
&hidpp->battery.solar_feature_index);
|
||||||
&feature_type);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -2522,7 +2498,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
|
|||||||
/* regular effect destroyed */
|
/* regular effect destroyed */
|
||||||
data->effect_ids[wd->params[0]-1] = -1;
|
data->effect_ids[wd->params[0]-1] = -1;
|
||||||
else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
|
else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
|
||||||
/* autocenter spring destoyed */
|
/* autocenter spring destroyed */
|
||||||
data->slot_autocenter = 0;
|
data->slot_autocenter = 0;
|
||||||
break;
|
break;
|
||||||
case HIDPP_FF_SET_GLOBAL_GAINS:
|
case HIDPP_FF_SET_GLOBAL_GAINS:
|
||||||
@ -3098,11 +3074,10 @@ static int wtp_get_config(struct hidpp_device *hidpp)
|
|||||||
{
|
{
|
||||||
struct wtp_data *wd = hidpp->private_data;
|
struct wtp_data *wd = hidpp->private_data;
|
||||||
struct hidpp_touchpad_raw_info raw_info = {0};
|
struct hidpp_touchpad_raw_info raw_info = {0};
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
|
||||||
&wd->mt_feature_index, &feature_type);
|
&wd->mt_feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
/* means that the device is not powered up */
|
/* means that the device is not powered up */
|
||||||
return ret;
|
return ret;
|
||||||
@ -3296,13 +3271,13 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
|
|||||||
120);
|
120);
|
||||||
}
|
}
|
||||||
|
|
||||||
v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12);
|
v = sign_extend32(hid_field_extract(hdev, data + 3, 0, 12), 11);
|
||||||
input_report_rel(hidpp->input, REL_X, v);
|
input_report_rel(hidpp->input, REL_X, v);
|
||||||
|
|
||||||
v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12);
|
v = sign_extend32(hid_field_extract(hdev, data + 3, 12, 12), 11);
|
||||||
input_report_rel(hidpp->input, REL_Y, v);
|
input_report_rel(hidpp->input, REL_Y, v);
|
||||||
|
|
||||||
v = hid_snto32(data[6], 8);
|
v = sign_extend32(data[6], 7);
|
||||||
if (v != 0)
|
if (v != 0)
|
||||||
hidpp_scroll_counter_handle_scroll(hidpp->input,
|
hidpp_scroll_counter_handle_scroll(hidpp->input,
|
||||||
&hidpp->vertical_wheel_counter, v);
|
&hidpp->vertical_wheel_counter, v);
|
||||||
@ -3362,12 +3337,11 @@ static int k400_disable_tap_to_click(struct hidpp_device *hidpp)
|
|||||||
struct k400_private_data *k400 = hidpp->private_data;
|
struct k400_private_data *k400 = hidpp->private_data;
|
||||||
struct hidpp_touchpad_fw_items items = {};
|
struct hidpp_touchpad_fw_items items = {};
|
||||||
int ret;
|
int ret;
|
||||||
u8 feature_type;
|
|
||||||
|
|
||||||
if (!k400->feature_index) {
|
if (!k400->feature_index) {
|
||||||
ret = hidpp_root_get_feature(hidpp,
|
ret = hidpp_root_get_feature(hidpp,
|
||||||
HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
|
HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
|
||||||
&k400->feature_index, &feature_type);
|
&k400->feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
/* means that the device is not powered up */
|
/* means that the device is not powered up */
|
||||||
return ret;
|
return ret;
|
||||||
@ -3439,14 +3413,13 @@ static int g920_get_config(struct hidpp_device *hidpp,
|
|||||||
struct hidpp_ff_private_data *data)
|
struct hidpp_ff_private_data *data)
|
||||||
{
|
{
|
||||||
struct hidpp_report response;
|
struct hidpp_report response;
|
||||||
u8 feature_type;
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
memset(data, 0, sizeof(*data));
|
memset(data, 0, sizeof(*data));
|
||||||
|
|
||||||
/* Find feature and store for later use */
|
/* Find feature and store for later use */
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
|
||||||
&data->feature_index, &feature_type);
|
&data->feature_index);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@ -3735,17 +3708,16 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
|
|||||||
|
|
||||||
if (hidpp->protocol_major >= 2) {
|
if (hidpp->protocol_major >= 2) {
|
||||||
u8 feature_index;
|
u8 feature_index;
|
||||||
u8 feature_type;
|
|
||||||
|
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
|
||||||
&feature_index, &feature_type);
|
&feature_index);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL;
|
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL;
|
||||||
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n");
|
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
|
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
|
||||||
&feature_index, &feature_type);
|
&feature_index);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL;
|
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL;
|
||||||
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
|
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
|
||||||
|
@ -227,7 +227,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
|||||||
touch_minor = tdata[4];
|
touch_minor = tdata[4];
|
||||||
state = tdata[7] & TOUCH_STATE_MASK;
|
state = tdata[7] & TOUCH_STATE_MASK;
|
||||||
down = state != TOUCH_STATE_NONE;
|
down = state != TOUCH_STATE_NONE;
|
||||||
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
id = tdata[8] & 0xf;
|
id = tdata[8] & 0xf;
|
||||||
x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
|
x = (tdata[1] << 27 | tdata[0] << 19) >> 19;
|
||||||
y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
|
y = -((tdata[3] << 30 | tdata[2] << 22 | tdata[1] << 14) >> 19);
|
||||||
@ -259,8 +261,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
|||||||
/* If requested, emulate a scroll wheel by detecting small
|
/* If requested, emulate a scroll wheel by detecting small
|
||||||
* vertical touch motions.
|
* vertical touch motions.
|
||||||
*/
|
*/
|
||||||
if (emulate_scroll_wheel && (input->id.product !=
|
if (emulate_scroll_wheel &&
|
||||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
|
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
|
||||||
|
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
unsigned long now = jiffies;
|
unsigned long now = jiffies;
|
||||||
int step_x = msc->touches[id].scroll_x - x;
|
int step_x = msc->touches[id].scroll_x - x;
|
||||||
int step_y = msc->touches[id].scroll_y - y;
|
int step_y = msc->touches[id].scroll_y - y;
|
||||||
@ -359,7 +362,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
|||||||
input_report_abs(input, ABS_MT_POSITION_X, x);
|
input_report_abs(input, ABS_MT_POSITION_X, x);
|
||||||
input_report_abs(input, ABS_MT_POSITION_Y, y);
|
input_report_abs(input, ABS_MT_POSITION_Y, y);
|
||||||
|
|
||||||
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
|
if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)
|
||||||
input_report_abs(input, ABS_MT_PRESSURE, pressure);
|
input_report_abs(input, ABS_MT_PRESSURE, pressure);
|
||||||
|
|
||||||
if (report_undeciphered) {
|
if (report_undeciphered) {
|
||||||
@ -367,7 +372,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
|
|||||||
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
|
input->id.product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
|
||||||
input_event(input, EV_MSC, MSC_RAW, tdata[7]);
|
input_event(input, EV_MSC, MSC_RAW, tdata[7]);
|
||||||
else if (input->id.product !=
|
else if (input->id.product !=
|
||||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
|
||||||
|
input->id.product !=
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC)
|
||||||
input_event(input, EV_MSC, MSC_RAW, tdata[8]);
|
input_event(input, EV_MSC, MSC_RAW, tdata[8]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,7 +500,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
|
|||||||
magicmouse_emit_buttons(msc, clicks & 3);
|
magicmouse_emit_buttons(msc, clicks & 3);
|
||||||
input_report_rel(input, REL_X, x);
|
input_report_rel(input, REL_X, x);
|
||||||
input_report_rel(input, REL_Y, y);
|
input_report_rel(input, REL_Y, y);
|
||||||
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
input_mt_sync_frame(input);
|
input_mt_sync_frame(input);
|
||||||
input_report_key(input, BTN_MOUSE, clicks & 1);
|
input_report_key(input, BTN_MOUSE, clicks & 1);
|
||||||
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
|
||||||
@ -545,7 +554,9 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
|
|||||||
__set_bit(REL_WHEEL_HI_RES, input->relbit);
|
__set_bit(REL_WHEEL_HI_RES, input->relbit);
|
||||||
__set_bit(REL_HWHEEL_HI_RES, input->relbit);
|
__set_bit(REL_HWHEEL_HI_RES, input->relbit);
|
||||||
}
|
}
|
||||||
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
/* If the trackpad has been connected to a Mac, the name is
|
/* If the trackpad has been connected to a Mac, the name is
|
||||||
* automatically personalized, e.g., "José Expósito's Trackpad".
|
* automatically personalized, e.g., "José Expósito's Trackpad".
|
||||||
* When connected through Bluetooth, the personalized name is
|
* When connected through Bluetooth, the personalized name is
|
||||||
@ -621,7 +632,9 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
|
|||||||
MOUSE_RES_X);
|
MOUSE_RES_X);
|
||||||
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
input_abs_set_res(input, ABS_MT_POSITION_Y,
|
||||||
MOUSE_RES_Y);
|
MOUSE_RES_Y);
|
||||||
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
} else if (input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0);
|
input_set_abs_params(input, ABS_MT_PRESSURE, 0, 253, 0, 0);
|
||||||
input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0);
|
input_set_abs_params(input, ABS_PRESSURE, 0, 253, 0, 0);
|
||||||
input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
|
input_set_abs_params(input, ABS_MT_ORIENTATION, -3, 4, 0, 0);
|
||||||
@ -660,7 +673,8 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
|
|||||||
input_set_events_per_packet(input, 60);
|
input_set_events_per_packet(input, 60);
|
||||||
|
|
||||||
if (report_undeciphered &&
|
if (report_undeciphered &&
|
||||||
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
|
||||||
|
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
__set_bit(EV_MSC, input->evbit);
|
__set_bit(EV_MSC, input->evbit);
|
||||||
__set_bit(MSC_RAW, input->mscbit);
|
__set_bit(MSC_RAW, input->mscbit);
|
||||||
}
|
}
|
||||||
@ -685,7 +699,9 @@ static int magicmouse_input_mapping(struct hid_device *hdev,
|
|||||||
|
|
||||||
/* Magic Trackpad does not give relative data after switching to MT */
|
/* Magic Trackpad does not give relative data after switching to MT */
|
||||||
if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
|
if ((hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD ||
|
||||||
hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
|
hi->input->id.product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
hi->input->id.product ==
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) &&
|
||||||
field->flags & HID_MAIN_ITEM_RELATIVE)
|
field->flags & HID_MAIN_ITEM_RELATIVE)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -721,7 +737,8 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
|
|||||||
int ret;
|
int ret;
|
||||||
int feature_size;
|
int feature_size;
|
||||||
|
|
||||||
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
|
if (hdev->vendor == BT_VENDOR_ID_APPLE) {
|
||||||
feature_size = sizeof(feature_mt_trackpad2_bt);
|
feature_size = sizeof(feature_mt_trackpad2_bt);
|
||||||
feature = feature_mt_trackpad2_bt;
|
feature = feature_mt_trackpad2_bt;
|
||||||
@ -766,7 +783,8 @@ static int magicmouse_fetch_battery(struct hid_device *hdev)
|
|||||||
|
|
||||||
if (!hdev->battery || hdev->vendor != USB_VENDOR_ID_APPLE ||
|
if (!hdev->battery || hdev->vendor != USB_VENDOR_ID_APPLE ||
|
||||||
(hdev->product != USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
|
(hdev->product != USB_DEVICE_ID_APPLE_MAGICMOUSE2 &&
|
||||||
hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2))
|
hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
|
||||||
|
hdev->product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
report_enum = &hdev->report_enum[hdev->battery_report_type];
|
report_enum = &hdev->report_enum[hdev->battery_report_type];
|
||||||
@ -835,7 +853,9 @@ static int magicmouse_probe(struct hid_device *hdev,
|
|||||||
|
|
||||||
if (id->vendor == USB_VENDOR_ID_APPLE &&
|
if (id->vendor == USB_VENDOR_ID_APPLE &&
|
||||||
(id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
|
(id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
|
||||||
(id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 && hdev->type != HID_TYPE_USBMOUSE)))
|
((id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) &&
|
||||||
|
hdev->type != HID_TYPE_USBMOUSE)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!msc->input) {
|
if (!msc->input) {
|
||||||
@ -850,7 +870,8 @@ static int magicmouse_probe(struct hid_device *hdev,
|
|||||||
else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
|
else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
|
||||||
report = hid_register_report(hdev, HID_INPUT_REPORT,
|
report = hid_register_report(hdev, HID_INPUT_REPORT,
|
||||||
MOUSE2_REPORT_ID, 0);
|
MOUSE2_REPORT_ID, 0);
|
||||||
else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
|
else if (id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
id->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
|
||||||
if (id->vendor == BT_VENDOR_ID_APPLE)
|
if (id->vendor == BT_VENDOR_ID_APPLE)
|
||||||
report = hid_register_report(hdev, HID_INPUT_REPORT,
|
report = hid_register_report(hdev, HID_INPUT_REPORT,
|
||||||
TRACKPAD2_BT_REPORT_ID, 0);
|
TRACKPAD2_BT_REPORT_ID, 0);
|
||||||
@ -920,7 +941,8 @@ static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,
|
|||||||
*/
|
*/
|
||||||
if (hdev->vendor == USB_VENDOR_ID_APPLE &&
|
if (hdev->vendor == USB_VENDOR_ID_APPLE &&
|
||||||
(hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
|
(hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2 ||
|
||||||
hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) &&
|
hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 ||
|
||||||
|
hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) &&
|
||||||
*rsize == 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) {
|
*rsize == 83 && rdesc[46] == 0x84 && rdesc[58] == 0x85) {
|
||||||
hid_info(hdev,
|
hid_info(hdev,
|
||||||
"fixing up magicmouse battery report descriptor\n");
|
"fixing up magicmouse battery report descriptor\n");
|
||||||
@ -951,6 +973,10 @@ static const struct hid_device_id magic_mice[] = {
|
|||||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
|
||||||
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
|
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
|
||||||
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
|
||||||
|
{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE,
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 },
|
||||||
|
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
|
||||||
|
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC), .driver_data = 0 },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(hid, magic_mice);
|
MODULE_DEVICE_TABLE(hid, magic_mice);
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
* [1] https://gitlab.freedesktop.org/libevdev/hid-tools
|
* [1] https://gitlab.freedesktop.org/libevdev/hid-tools
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/bits.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/hid.h>
|
#include <linux/hid.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
@ -83,6 +84,13 @@ enum latency_mode {
|
|||||||
HID_LATENCY_HIGH = 1,
|
HID_LATENCY_HIGH = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum report_mode {
|
||||||
|
TOUCHPAD_REPORT_NONE = 0,
|
||||||
|
TOUCHPAD_REPORT_BUTTONS = BIT(0),
|
||||||
|
TOUCHPAD_REPORT_CONTACTS = BIT(1),
|
||||||
|
TOUCHPAD_REPORT_ALL = TOUCHPAD_REPORT_BUTTONS | TOUCHPAD_REPORT_CONTACTS,
|
||||||
|
};
|
||||||
|
|
||||||
#define MT_IO_FLAGS_RUNNING 0
|
#define MT_IO_FLAGS_RUNNING 0
|
||||||
#define MT_IO_FLAGS_ACTIVE_SLOTS 1
|
#define MT_IO_FLAGS_ACTIVE_SLOTS 1
|
||||||
#define MT_IO_FLAGS_PENDING_SLOTS 2
|
#define MT_IO_FLAGS_PENDING_SLOTS 2
|
||||||
@ -1493,8 +1501,7 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
|
|||||||
struct hid_field *field,
|
struct hid_field *field,
|
||||||
struct hid_usage *usage,
|
struct hid_usage *usage,
|
||||||
enum latency_mode latency,
|
enum latency_mode latency,
|
||||||
bool surface_switch,
|
enum report_mode report_mode,
|
||||||
bool button_switch,
|
|
||||||
bool *inputmode_found)
|
bool *inputmode_found)
|
||||||
{
|
{
|
||||||
struct mt_device *td = hid_get_drvdata(hdev);
|
struct mt_device *td = hid_get_drvdata(hdev);
|
||||||
@ -1549,11 +1556,11 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HID_DG_SURFACESWITCH:
|
case HID_DG_SURFACESWITCH:
|
||||||
field->value[index] = surface_switch;
|
field->value[index] = !!(report_mode & TOUCHPAD_REPORT_CONTACTS);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case HID_DG_BUTTONSWITCH:
|
case HID_DG_BUTTONSWITCH:
|
||||||
field->value[index] = button_switch;
|
field->value[index] = !!(report_mode & TOUCHPAD_REPORT_BUTTONS);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1561,7 +1568,7 @@ static bool mt_need_to_apply_feature(struct hid_device *hdev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
|
static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
|
||||||
bool surface_switch, bool button_switch)
|
enum report_mode report_mode)
|
||||||
{
|
{
|
||||||
struct hid_report_enum *rep_enum;
|
struct hid_report_enum *rep_enum;
|
||||||
struct hid_report *rep;
|
struct hid_report *rep;
|
||||||
@ -1586,8 +1593,7 @@ static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
|
|||||||
rep->field[i],
|
rep->field[i],
|
||||||
usage,
|
usage,
|
||||||
latency,
|
latency,
|
||||||
surface_switch,
|
report_mode,
|
||||||
button_switch,
|
|
||||||
&inputmode_found))
|
&inputmode_found))
|
||||||
update_report = true;
|
update_report = true;
|
||||||
}
|
}
|
||||||
@ -1830,7 +1836,7 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
|
|||||||
dev_warn(&hdev->dev, "Cannot allocate sysfs group for %s\n",
|
dev_warn(&hdev->dev, "Cannot allocate sysfs group for %s\n",
|
||||||
hdev->name);
|
hdev->name);
|
||||||
|
|
||||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
|
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1842,9 +1848,9 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state)
|
|||||||
/* High latency is desirable for power savings during S3/S0ix */
|
/* High latency is desirable for power savings during S3/S0ix */
|
||||||
if ((td->mtclass.quirks & MT_QUIRK_DISABLE_WAKEUP) ||
|
if ((td->mtclass.quirks & MT_QUIRK_DISABLE_WAKEUP) ||
|
||||||
!hid_hw_may_wakeup(hdev))
|
!hid_hw_may_wakeup(hdev))
|
||||||
mt_set_modes(hdev, HID_LATENCY_HIGH, false, false);
|
mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_NONE);
|
||||||
else
|
else
|
||||||
mt_set_modes(hdev, HID_LATENCY_HIGH, true, true);
|
mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_ALL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1852,7 +1858,7 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state)
|
|||||||
static int mt_reset_resume(struct hid_device *hdev)
|
static int mt_reset_resume(struct hid_device *hdev)
|
||||||
{
|
{
|
||||||
mt_release_contacts(hdev);
|
mt_release_contacts(hdev);
|
||||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
|
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1864,7 +1870,7 @@ static int mt_resume(struct hid_device *hdev)
|
|||||||
|
|
||||||
hid_hw_idle(hdev, 0, 0, HID_REQ_SET_IDLE);
|
hid_hw_idle(hdev, 0, 0, HID_REQ_SET_IDLE);
|
||||||
|
|
||||||
mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
|
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -296,7 +296,7 @@ static void picolcd_fb_destroy(struct fb_info *info)
|
|||||||
/* make sure no work is deferred */
|
/* make sure no work is deferred */
|
||||||
fb_deferred_io_cleanup(info);
|
fb_deferred_io_cleanup(info);
|
||||||
|
|
||||||
/* No thridparty should ever unregister our framebuffer! */
|
/* No thirdparty should ever unregister our framebuffer! */
|
||||||
WARN_ON(fbdata->picolcd != NULL);
|
WARN_ON(fbdata->picolcd != NULL);
|
||||||
|
|
||||||
vfree((u8 *)info->fix.smem_start);
|
vfree((u8 *)info->fix.smem_start);
|
||||||
|
@ -946,7 +946,7 @@ hid_sensor_register_platform_device(struct platform_device *pdev,
|
|||||||
|
|
||||||
memcpy(real_usage, match->luid, 4);
|
memcpy(real_usage, match->luid, 4);
|
||||||
|
|
||||||
/* usage id are all lowcase */
|
/* usage id are all lowercase */
|
||||||
for (c = real_usage; *c != '\0'; c++)
|
for (c = real_usage; *c != '\0'; c++)
|
||||||
*c = tolower(*c);
|
*c = tolower(*c);
|
||||||
|
|
||||||
|
@ -1379,7 +1379,8 @@ static int sony_leds_init(struct sony_sc *sc)
|
|||||||
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
|
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
|
||||||
u8 use_hw_blink[MAX_LEDS] = { 0 };
|
u8 use_hw_blink[MAX_LEDS] = { 0 };
|
||||||
|
|
||||||
BUG_ON(!(sc->quirks & SONY_LED_SUPPORT));
|
if (WARN_ON(!(sc->quirks & SONY_LED_SUPPORT)))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
if (sc->quirks & BUZZ_CONTROLLER) {
|
if (sc->quirks & BUZZ_CONTROLLER) {
|
||||||
sc->led_count = 4;
|
sc->led_count = 4;
|
||||||
|
@ -253,7 +253,7 @@ enum
|
|||||||
ID_CONTROLLER_DECK_STATE = 9
|
ID_CONTROLLER_DECK_STATE = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
/* String attribute idenitifiers */
|
/* String attribute identifiers */
|
||||||
enum {
|
enum {
|
||||||
ATTRIB_STR_BOARD_SERIAL,
|
ATTRIB_STR_BOARD_SERIAL,
|
||||||
ATTRIB_STR_UNIT_SERIAL,
|
ATTRIB_STR_UNIT_SERIAL,
|
||||||
|
@ -411,6 +411,15 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
|
|||||||
"Battery query failed (err: %d)\n", ret);
|
"Battery query failed (err: %d)\n", ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int battery_capacity_to_level(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity >= 50)
|
||||||
|
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
||||||
|
if (capacity >= 20)
|
||||||
|
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
||||||
|
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
||||||
|
}
|
||||||
|
|
||||||
static void steelseries_headset_battery_timer_tick(struct work_struct *work)
|
static void steelseries_headset_battery_timer_tick(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct steelseries_device *sd = container_of(work,
|
struct steelseries_device *sd = container_of(work,
|
||||||
@ -442,6 +451,9 @@ static int steelseries_headset_battery_get_property(struct power_supply *psy,
|
|||||||
case POWER_SUPPLY_PROP_CAPACITY:
|
case POWER_SUPPLY_PROP_CAPACITY:
|
||||||
val->intval = sd->battery_capacity;
|
val->intval = sd->battery_capacity;
|
||||||
break;
|
break;
|
||||||
|
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
||||||
|
val->intval = battery_capacity_to_level(sd->battery_capacity);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ret = -EINVAL;
|
ret = -EINVAL;
|
||||||
break;
|
break;
|
||||||
@ -469,6 +481,7 @@ static enum power_supply_property steelseries_headset_battery_props[] = {
|
|||||||
POWER_SUPPLY_PROP_STATUS,
|
POWER_SUPPLY_PROP_STATUS,
|
||||||
POWER_SUPPLY_PROP_SCOPE,
|
POWER_SUPPLY_PROP_SCOPE,
|
||||||
POWER_SUPPLY_PROP_CAPACITY,
|
POWER_SUPPLY_PROP_CAPACITY,
|
||||||
|
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int steelseries_headset_battery_register(struct steelseries_device *sd)
|
static int steelseries_headset_battery_register(struct steelseries_device *sd)
|
||||||
@ -603,8 +616,11 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
|
|||||||
hid_dbg(sd->hdev,
|
hid_dbg(sd->hdev,
|
||||||
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
|
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
|
||||||
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
|
if (size < ARCTIS_1_BATTERY_RESPONSE_LEN ||
|
||||||
memcmp (read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request)))
|
memcmp(read_buf, arctis_1_battery_request, sizeof(arctis_1_battery_request))) {
|
||||||
|
if (!delayed_work_pending(&sd->battery_work))
|
||||||
|
goto request_battery;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
if (read_buf[2] == 0x01) {
|
if (read_buf[2] == 0x01) {
|
||||||
connected = false;
|
connected = false;
|
||||||
capacity = 100;
|
capacity = 100;
|
||||||
@ -631,6 +647,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
|
|||||||
power_supply_changed(sd->battery);
|
power_supply_changed(sd->battery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request_battery:
|
||||||
spin_lock_irqsave(&sd->lock, flags);
|
spin_lock_irqsave(&sd->lock, flags);
|
||||||
if (!sd->removed)
|
if (!sd->removed)
|
||||||
schedule_delayed_work(&sd->battery_work,
|
schedule_delayed_work(&sd->battery_work,
|
||||||
|
@ -144,9 +144,9 @@ MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const struct i2c_device_id i2c_hid_of_id_table[] = {
|
static const struct i2c_device_id i2c_hid_of_id_table[] = {
|
||||||
{ "hid", 0 },
|
{ "hid" },
|
||||||
{ "hid-over-i2c", 0 },
|
{ "hid-over-i2c" },
|
||||||
{ },
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table);
|
MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table);
|
||||||
|
|
||||||
|
@ -381,6 +381,50 @@ static int __maybe_unused ish_resume(struct device *device)
|
|||||||
|
|
||||||
static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
|
static SIMPLE_DEV_PM_OPS(ish_pm_ops, ish_suspend, ish_resume);
|
||||||
|
|
||||||
|
static ssize_t base_version_show(struct device *cdev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct ishtp_device *dev = dev_get_drvdata(cdev);
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->base_ver.major,
|
||||||
|
dev->base_ver.minor, dev->base_ver.hotfix,
|
||||||
|
dev->base_ver.build);
|
||||||
|
}
|
||||||
|
static DEVICE_ATTR_RO(base_version);
|
||||||
|
|
||||||
|
static ssize_t project_version_show(struct device *cdev,
|
||||||
|
struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct ishtp_device *dev = dev_get_drvdata(cdev);
|
||||||
|
|
||||||
|
return sysfs_emit(buf, "%u.%u.%u.%u\n", dev->prj_ver.major,
|
||||||
|
dev->prj_ver.minor, dev->prj_ver.hotfix,
|
||||||
|
dev->prj_ver.build);
|
||||||
|
}
|
||||||
|
static DEVICE_ATTR_RO(project_version);
|
||||||
|
|
||||||
|
static struct attribute *ish_firmware_attrs[] = {
|
||||||
|
&dev_attr_base_version.attr,
|
||||||
|
&dev_attr_project_version.attr,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static umode_t firmware_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||||
|
int i)
|
||||||
|
{
|
||||||
|
struct ishtp_device *dev = dev_get_drvdata(kobj_to_dev(kobj));
|
||||||
|
|
||||||
|
return dev->driver_data->fw_generation ? attr->mode : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct attribute_group ish_firmware_group = {
|
||||||
|
.name = "firmware",
|
||||||
|
.attrs = ish_firmware_attrs,
|
||||||
|
.is_visible = firmware_is_visible,
|
||||||
|
};
|
||||||
|
|
||||||
|
__ATTRIBUTE_GROUPS(ish_firmware);
|
||||||
|
|
||||||
static struct pci_driver ish_driver = {
|
static struct pci_driver ish_driver = {
|
||||||
.name = KBUILD_MODNAME,
|
.name = KBUILD_MODNAME,
|
||||||
.id_table = ish_pci_tbl,
|
.id_table = ish_pci_tbl,
|
||||||
@ -388,6 +432,7 @@ static struct pci_driver ish_driver = {
|
|||||||
.remove = ish_remove,
|
.remove = ish_remove,
|
||||||
.shutdown = ish_shutdown,
|
.shutdown = ish_shutdown,
|
||||||
.driver.pm = &ish_pm_ops,
|
.driver.pm = &ish_pm_ops,
|
||||||
|
.dev_groups = ish_firmware_groups,
|
||||||
};
|
};
|
||||||
|
|
||||||
module_pci_driver(ish_driver);
|
module_pci_driver(ish_driver);
|
||||||
|
@ -793,7 +793,7 @@ static int load_fw_from_host(struct ishtp_cl_data *client_data)
|
|||||||
if (rv < 0)
|
if (rv < 0)
|
||||||
goto end_err_fw_release;
|
goto end_err_fw_release;
|
||||||
|
|
||||||
/* Step 3: Start ISH main firmware exeuction */
|
/* Step 3: Start ISH main firmware execution */
|
||||||
|
|
||||||
rv = ish_fw_start(client_data);
|
rv = ish_fw_start(client_data);
|
||||||
if (rv < 0)
|
if (rv < 0)
|
||||||
|
@ -70,10 +70,10 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
|
|||||||
unsigned char *payload;
|
unsigned char *payload;
|
||||||
struct device_info *dev_info;
|
struct device_info *dev_info;
|
||||||
int i, j;
|
int i, j;
|
||||||
size_t payload_len, total_len, cur_pos, raw_len;
|
size_t payload_len, total_len, cur_pos, raw_len, msg_len;
|
||||||
int report_type;
|
int report_type;
|
||||||
struct report_list *reports_list;
|
struct report_list *reports_list;
|
||||||
char *reports;
|
struct report *report;
|
||||||
size_t report_len;
|
size_t report_len;
|
||||||
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
|
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
|
||||||
int curr_hid_dev = client_data->cur_hid_dev;
|
int curr_hid_dev = client_data->cur_hid_dev;
|
||||||
@ -280,14 +280,13 @@ do_get_report:
|
|||||||
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
|
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
|
||||||
report_type = HID_INPUT_REPORT;
|
report_type = HID_INPUT_REPORT;
|
||||||
reports_list = (struct report_list *)payload;
|
reports_list = (struct report_list *)payload;
|
||||||
reports = (char *)reports_list->reports;
|
report = reports_list->reports;
|
||||||
|
|
||||||
for (j = 0; j < reports_list->num_of_reports; j++) {
|
for (j = 0; j < reports_list->num_of_reports; j++) {
|
||||||
recv_msg = (struct hostif_msg *)(reports +
|
recv_msg = container_of(&report->msg,
|
||||||
sizeof(uint16_t));
|
struct hostif_msg, hdr);
|
||||||
report_len = *(uint16_t *)reports;
|
report_len = report->size;
|
||||||
payload = reports + sizeof(uint16_t) +
|
payload = recv_msg->payload;
|
||||||
sizeof(struct hostif_msg_hdr);
|
|
||||||
payload_len = report_len -
|
payload_len = report_len -
|
||||||
sizeof(struct hostif_msg_hdr);
|
sizeof(struct hostif_msg_hdr);
|
||||||
|
|
||||||
@ -304,7 +303,7 @@ do_get_report:
|
|||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
|
|
||||||
reports += sizeof(uint16_t) + report_len;
|
report += sizeof(*report) + payload_len;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -316,12 +315,12 @@ do_get_report:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cur_pos && cur_pos + payload_len +
|
msg_len = payload_len + sizeof(struct hostif_msg);
|
||||||
sizeof(struct hostif_msg) < total_len)
|
if (!cur_pos && cur_pos + msg_len < total_len)
|
||||||
++client_data->multi_packet_cnt;
|
++client_data->multi_packet_cnt;
|
||||||
|
|
||||||
cur_pos += payload_len + sizeof(struct hostif_msg);
|
cur_pos += msg_len;
|
||||||
payload += payload_len + sizeof(struct hostif_msg);
|
payload += msg_len;
|
||||||
|
|
||||||
} while (cur_pos < total_len);
|
} while (cur_pos < total_len);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ struct hostif_msg_hdr {
|
|||||||
|
|
||||||
struct hostif_msg {
|
struct hostif_msg {
|
||||||
struct hostif_msg_hdr hdr;
|
struct hostif_msg_hdr hdr;
|
||||||
|
uint8_t payload[];
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
struct hostif_msg_to_sensor {
|
struct hostif_msg_to_sensor {
|
||||||
@ -52,15 +53,17 @@ struct ishtp_version {
|
|||||||
uint16_t build;
|
uint16_t build;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
|
struct report {
|
||||||
|
uint16_t size;
|
||||||
|
struct hostif_msg_hdr msg;
|
||||||
|
} __packed;
|
||||||
|
|
||||||
/* struct for ISHTP aggregated input data */
|
/* struct for ISHTP aggregated input data */
|
||||||
struct report_list {
|
struct report_list {
|
||||||
uint16_t total_size;
|
uint16_t total_size;
|
||||||
uint8_t num_of_reports;
|
uint8_t num_of_reports;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
struct {
|
struct report reports[];
|
||||||
uint16_t size_of_report;
|
|
||||||
uint8_t report[1];
|
|
||||||
} __packed reports[1];
|
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
/* HOSTIF commands */
|
/* HOSTIF commands */
|
||||||
|
@ -863,7 +863,7 @@ static void ipc_tx_send(void *prm)
|
|||||||
/* Send ipc fragment */
|
/* Send ipc fragment */
|
||||||
ishtp_hdr.length = dev->mtu;
|
ishtp_hdr.length = dev->mtu;
|
||||||
ishtp_hdr.msg_complete = 0;
|
ishtp_hdr.msg_complete = 0;
|
||||||
/* All fregments submitted to IPC queue with no callback */
|
/* All fragments submitted to IPC queue with no callback */
|
||||||
ishtp_write_message(dev, &ishtp_hdr, pmsg);
|
ishtp_write_message(dev, &ishtp_hdr, pmsg);
|
||||||
cl->tx_offs += dev->mtu;
|
cl->tx_offs += dev->mtu;
|
||||||
rem = cl_msg->send_buf.size - cl->tx_offs;
|
rem = cl_msg->send_buf.size - cl->tx_offs;
|
||||||
|
@ -140,6 +140,13 @@ struct ishtp_driver_data {
|
|||||||
char *fw_generation;
|
char *fw_generation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ish_version {
|
||||||
|
u16 major;
|
||||||
|
u16 minor;
|
||||||
|
u16 hotfix;
|
||||||
|
u16 build;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct ishtp_device - ISHTP private device struct
|
* struct ishtp_device - ISHTP private device struct
|
||||||
*/
|
*/
|
||||||
@ -236,6 +243,11 @@ struct ishtp_device {
|
|||||||
/* Dump to trace buffers if enabled*/
|
/* Dump to trace buffers if enabled*/
|
||||||
ishtp_print_log print_log;
|
ishtp_print_log print_log;
|
||||||
|
|
||||||
|
/* Base version of Intel's released firmware */
|
||||||
|
struct ish_version base_ver;
|
||||||
|
/* Vendor-customized project version */
|
||||||
|
struct ish_version prj_ver;
|
||||||
|
|
||||||
/* Debug stats */
|
/* Debug stats */
|
||||||
unsigned int ipc_rx_cnt;
|
unsigned int ipc_rx_cnt;
|
||||||
unsigned long long ipc_rx_bytes_cnt;
|
unsigned long long ipc_rx_bytes_cnt;
|
||||||
|
@ -308,6 +308,28 @@ static int request_ish_firmware(const struct firmware **firmware_p,
|
|||||||
return _request_ish_firmware(firmware_p, filename, dev);
|
return _request_ish_firmware(firmware_p, filename, dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int copy_manifest(const struct firmware *fw, struct ish_global_manifest *manifest)
|
||||||
|
{
|
||||||
|
u32 offset;
|
||||||
|
|
||||||
|
for (offset = 0; offset + sizeof(*manifest) < fw->size; offset += ISH_MANIFEST_ALIGNMENT) {
|
||||||
|
memcpy(manifest, fw->data + offset, sizeof(*manifest));
|
||||||
|
|
||||||
|
if (le32_to_cpu(manifest->sig_fourcc) == ISH_GLOBAL_SIG)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_ish_version(struct version_in_manifest *src, struct ish_version *dst)
|
||||||
|
{
|
||||||
|
dst->major = le16_to_cpu(src->major);
|
||||||
|
dst->minor = le16_to_cpu(src->minor);
|
||||||
|
dst->hotfix = le16_to_cpu(src->hotfix);
|
||||||
|
dst->build = le16_to_cpu(src->build);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ishtp_loader_work() - Load the ISHTP firmware
|
* ishtp_loader_work() - Load the ISHTP firmware
|
||||||
* @work: The work structure
|
* @work: The work structure
|
||||||
@ -336,6 +358,7 @@ void ishtp_loader_work(struct work_struct *work)
|
|||||||
struct loader_xfer_query query = { .header = cpu_to_le32(query_hdr.val32), };
|
struct loader_xfer_query query = { .header = cpu_to_le32(query_hdr.val32), };
|
||||||
struct loader_start start = { .header = cpu_to_le32(start_hdr.val32), };
|
struct loader_start start = { .header = cpu_to_le32(start_hdr.val32), };
|
||||||
union loader_recv_message recv_msg;
|
union loader_recv_message recv_msg;
|
||||||
|
struct ish_global_manifest manifest;
|
||||||
const struct firmware *ish_fw;
|
const struct firmware *ish_fw;
|
||||||
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
|
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
|
||||||
u32 fragment_size;
|
u32 fragment_size;
|
||||||
@ -372,7 +395,7 @@ void ishtp_loader_work(struct work_struct *work)
|
|||||||
if (rv)
|
if (rv)
|
||||||
continue; /* try again if failed */
|
continue; /* try again if failed */
|
||||||
|
|
||||||
dev_dbg(dev->devc, "ISH Version %u.%u.%u.%u\n",
|
dev_dbg(dev->devc, "ISH Bootloader Version %u.%u.%u.%u\n",
|
||||||
recv_msg.query_ack.version_major,
|
recv_msg.query_ack.version_major,
|
||||||
recv_msg.query_ack.version_minor,
|
recv_msg.query_ack.version_minor,
|
||||||
recv_msg.query_ack.version_hotfix,
|
recv_msg.query_ack.version_hotfix,
|
||||||
@ -390,6 +413,16 @@ void ishtp_loader_work(struct work_struct *work)
|
|||||||
continue; /* try again if failed */
|
continue; /* try again if failed */
|
||||||
|
|
||||||
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
|
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
|
||||||
|
if (!copy_manifest(ish_fw, &manifest)) {
|
||||||
|
copy_ish_version(&manifest.base_ver, &dev->base_ver);
|
||||||
|
copy_ish_version(&manifest.prj_ver, &dev->prj_ver);
|
||||||
|
dev_info(dev->devc, "FW base version: %u.%u.%u.%u\n",
|
||||||
|
dev->base_ver.major, dev->base_ver.minor,
|
||||||
|
dev->base_ver.hotfix, dev->base_ver.build);
|
||||||
|
dev_info(dev->devc, "FW project version: %u.%u.%u.%u\n",
|
||||||
|
dev->prj_ver.major, dev->prj_ver.minor,
|
||||||
|
dev->prj_ver.hotfix, dev->prj_ver.build);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} while (--retry);
|
} while (--retry);
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <linux/bits.h>
|
#include <linux/bits.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
|
#include <linux/sizes.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
|
|
||||||
#include "ishtp-dev.h"
|
#include "ishtp-dev.h"
|
||||||
@ -228,4 +229,37 @@ struct ish_firmware_variant {
|
|||||||
*/
|
*/
|
||||||
void ishtp_loader_work(struct work_struct *work);
|
void ishtp_loader_work(struct work_struct *work);
|
||||||
|
|
||||||
|
/* ISH Manifest alignment in binary is 4KB aligned */
|
||||||
|
#define ISH_MANIFEST_ALIGNMENT SZ_4K
|
||||||
|
|
||||||
|
/* Signature for ISH global manifest */
|
||||||
|
#define ISH_GLOBAL_SIG 0x47485349 /* FourCC 'I', 'S', 'H', 'G' */
|
||||||
|
|
||||||
|
struct version_in_manifest {
|
||||||
|
__le16 major;
|
||||||
|
__le16 minor;
|
||||||
|
__le16 hotfix;
|
||||||
|
__le16 build;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct ish_global_manifest - global manifest for ISH
|
||||||
|
* @sig_fourcc: Signature FourCC, should be 'I', 'S', 'H', 'G'.
|
||||||
|
* @len: Length of the manifest.
|
||||||
|
* @header_version: Version of the manifest header.
|
||||||
|
* @flags: Flags for additional information.
|
||||||
|
* @base_ver: Base version of Intel's released firmware.
|
||||||
|
* @reserved: Reserved space for future use.
|
||||||
|
* @prj_ver: Vendor-customized project version.
|
||||||
|
*/
|
||||||
|
struct ish_global_manifest {
|
||||||
|
__le32 sig_fourcc;
|
||||||
|
__le32 len;
|
||||||
|
__le32 header_version;
|
||||||
|
__le32 flags;
|
||||||
|
struct version_in_manifest base_ver;
|
||||||
|
__le32 reserved[13];
|
||||||
|
struct version_in_manifest prj_ver;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* _ISHTP_LOADER_H_ */
|
#endif /* _ISHTP_LOADER_H_ */
|
||||||
|
@ -1100,7 +1100,7 @@ static int usbhid_start(struct hid_device *hid)
|
|||||||
|
|
||||||
interval = endpoint->bInterval;
|
interval = endpoint->bInterval;
|
||||||
|
|
||||||
/* Some vendors give fullspeed interval on highspeed devides */
|
/* Some vendors give fullspeed interval on highspeed devices */
|
||||||
if (hid->quirks & HID_QUIRK_FULLSPEED_INTERVAL &&
|
if (hid->quirks & HID_QUIRK_FULLSPEED_INTERVAL &&
|
||||||
dev->speed == USB_SPEED_HIGH) {
|
dev->speed == USB_SPEED_HIGH) {
|
||||||
interval = fls(endpoint->bInterval*8);
|
interval = fls(endpoint->bInterval*8);
|
||||||
|
@ -1353,9 +1353,9 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
|
|||||||
rotation -= 1800;
|
rotation -= 1800;
|
||||||
|
|
||||||
input_report_abs(pen_input, ABS_TILT_X,
|
input_report_abs(pen_input, ABS_TILT_X,
|
||||||
(char)frame[7]);
|
(signed char)frame[7]);
|
||||||
input_report_abs(pen_input, ABS_TILT_Y,
|
input_report_abs(pen_input, ABS_TILT_Y,
|
||||||
(char)frame[8]);
|
(signed char)frame[8]);
|
||||||
input_report_abs(pen_input, ABS_Z, rotation);
|
input_report_abs(pen_input, ABS_Z, rotation);
|
||||||
input_report_abs(pen_input, ABS_WHEEL,
|
input_report_abs(pen_input, ABS_WHEEL,
|
||||||
get_unaligned_le16(&frame[11]));
|
get_unaligned_le16(&frame[11]));
|
||||||
@ -2422,9 +2422,11 @@ static void wacom_wac_pen_event(struct hid_device *hdev, struct hid_field *field
|
|||||||
wacom_wac->hid_data.sense_state = value;
|
wacom_wac->hid_data.sense_state = value;
|
||||||
return;
|
return;
|
||||||
case HID_DG_INVERT:
|
case HID_DG_INVERT:
|
||||||
wacom_wac->hid_data.invert_state = value;
|
wacom_wac->hid_data.eraser |= value;
|
||||||
return;
|
return;
|
||||||
case HID_DG_ERASER:
|
case HID_DG_ERASER:
|
||||||
|
wacom_wac->hid_data.eraser |= value;
|
||||||
|
fallthrough;
|
||||||
case HID_DG_TIPSWITCH:
|
case HID_DG_TIPSWITCH:
|
||||||
wacom_wac->hid_data.tipswitch |= value;
|
wacom_wac->hid_data.tipswitch |= value;
|
||||||
return;
|
return;
|
||||||
@ -2565,7 +2567,7 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
|
|||||||
|
|
||||||
if (entering_range) { /* first in range */
|
if (entering_range) { /* first in range */
|
||||||
/* Going into range select tool */
|
/* Going into range select tool */
|
||||||
if (wacom_wac->hid_data.invert_state)
|
if (wacom_wac->hid_data.eraser)
|
||||||
wacom_wac->tool[0] = BTN_TOOL_RUBBER;
|
wacom_wac->tool[0] = BTN_TOOL_RUBBER;
|
||||||
else if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN)
|
else if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN)
|
||||||
wacom_wac->tool[0] = BTN_TOOL_PEN;
|
wacom_wac->tool[0] = BTN_TOOL_PEN;
|
||||||
@ -2619,6 +2621,7 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
|
|||||||
}
|
}
|
||||||
|
|
||||||
wacom_wac->hid_data.tipswitch = false;
|
wacom_wac->hid_data.tipswitch = false;
|
||||||
|
wacom_wac->hid_data.eraser = false;
|
||||||
|
|
||||||
input_sync(input);
|
input_sync(input);
|
||||||
}
|
}
|
||||||
|
@ -300,7 +300,7 @@ struct hid_data {
|
|||||||
__s16 inputmode_index; /* InputMode HID feature index in the report */
|
__s16 inputmode_index; /* InputMode HID feature index in the report */
|
||||||
bool sense_state;
|
bool sense_state;
|
||||||
bool inrange_state;
|
bool inrange_state;
|
||||||
bool invert_state;
|
bool eraser;
|
||||||
bool tipswitch;
|
bool tipswitch;
|
||||||
bool barrelswitch;
|
bool barrelswitch;
|
||||||
bool barrelswitch2;
|
bool barrelswitch2;
|
||||||
|
@ -359,6 +359,7 @@ struct hid_item {
|
|||||||
* | @HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP:
|
* | @HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP:
|
||||||
* | @HID_QUIRK_HAVE_SPECIAL_DRIVER:
|
* | @HID_QUIRK_HAVE_SPECIAL_DRIVER:
|
||||||
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
|
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
|
||||||
|
* | @HID_QUIRK_IGNORE_SPECIAL_DRIVER
|
||||||
* | @HID_QUIRK_FULLSPEED_INTERVAL:
|
* | @HID_QUIRK_FULLSPEED_INTERVAL:
|
||||||
* | @HID_QUIRK_NO_INIT_REPORTS:
|
* | @HID_QUIRK_NO_INIT_REPORTS:
|
||||||
* | @HID_QUIRK_NO_IGNORE:
|
* | @HID_QUIRK_NO_IGNORE:
|
||||||
@ -384,6 +385,7 @@ struct hid_item {
|
|||||||
#define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19)
|
#define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19)
|
||||||
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20)
|
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20)
|
||||||
#define HID_QUIRK_NOINVERT BIT(21)
|
#define HID_QUIRK_NOINVERT BIT(21)
|
||||||
|
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
|
||||||
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
|
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
|
||||||
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
|
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
|
||||||
#define HID_QUIRK_NO_IGNORE BIT(30)
|
#define HID_QUIRK_NO_IGNORE BIT(30)
|
||||||
@ -599,15 +601,17 @@ enum hid_battery_status {
|
|||||||
struct hid_driver;
|
struct hid_driver;
|
||||||
struct hid_ll_driver;
|
struct hid_ll_driver;
|
||||||
|
|
||||||
struct hid_device { /* device report descriptor */
|
struct hid_device {
|
||||||
const __u8 *dev_rdesc;
|
const __u8 *dev_rdesc; /* device report descriptor */
|
||||||
unsigned dev_rsize;
|
const __u8 *bpf_rdesc; /* bpf modified report descriptor, if any */
|
||||||
const __u8 *rdesc;
|
const __u8 *rdesc; /* currently used report descriptor */
|
||||||
unsigned rsize;
|
unsigned int dev_rsize;
|
||||||
|
unsigned int bpf_rsize;
|
||||||
|
unsigned int rsize;
|
||||||
|
unsigned int collection_size; /* Number of allocated hid_collections */
|
||||||
struct hid_collection *collection; /* List of HID collections */
|
struct hid_collection *collection; /* List of HID collections */
|
||||||
unsigned collection_size; /* Number of allocated hid_collections */
|
unsigned int maxcollection; /* Number of parsed collections */
|
||||||
unsigned maxcollection; /* Number of parsed collections */
|
unsigned int maxapplication; /* Number of applications */
|
||||||
unsigned maxapplication; /* Number of applications */
|
|
||||||
__u16 bus; /* BUS ID */
|
__u16 bus; /* BUS ID */
|
||||||
__u16 group; /* Report group */
|
__u16 group; /* Report group */
|
||||||
__u32 vendor; /* Vendor ID */
|
__u32 vendor; /* Vendor ID */
|
||||||
@ -974,7 +978,6 @@ const struct hid_device_id *hid_match_device(struct hid_device *hdev,
|
|||||||
struct hid_driver *hdrv);
|
struct hid_driver *hdrv);
|
||||||
bool hid_compare_device_paths(struct hid_device *hdev_a,
|
bool hid_compare_device_paths(struct hid_device *hdev_a,
|
||||||
struct hid_device *hdev_b, char separator);
|
struct hid_device *hdev_b, char separator);
|
||||||
s32 hid_snto32(__u32 value, unsigned n);
|
|
||||||
__u32 hid_field_extract(const struct hid_device *hid, __u8 *report,
|
__u32 hid_field_extract(const struct hid_device *hid, __u8 *report,
|
||||||
unsigned offset, unsigned n);
|
unsigned offset, unsigned n);
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ int hid_bpf_connect_device(struct hid_device *hdev);
|
|||||||
void hid_bpf_disconnect_device(struct hid_device *hdev);
|
void hid_bpf_disconnect_device(struct hid_device *hdev);
|
||||||
void hid_bpf_destroy_device(struct hid_device *hid);
|
void hid_bpf_destroy_device(struct hid_device *hid);
|
||||||
int hid_bpf_device_init(struct hid_device *hid);
|
int hid_bpf_device_init(struct hid_device *hid);
|
||||||
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size);
|
const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc, unsigned int *size);
|
||||||
#else /* CONFIG_HID_BPF */
|
#else /* CONFIG_HID_BPF */
|
||||||
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
|
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
|
||||||
u8 *data, u32 *size, int interrupt,
|
u8 *data, u32 *size, int interrupt,
|
||||||
@ -228,13 +228,8 @@ static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
|
|||||||
static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
|
static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
|
||||||
static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
|
static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
|
||||||
static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
|
static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
|
||||||
/*
|
static inline const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc,
|
||||||
* This specialized allocator has to be a macro for its allocations to be
|
unsigned int *size) { return rdesc; }
|
||||||
* accounted separately (to have a separate alloc_tag). The typecast is
|
|
||||||
* intentional to enforce typesafety.
|
|
||||||
*/
|
|
||||||
#define call_hid_bpf_rdesc_fixup(_hdev, _rdesc, _size) \
|
|
||||||
((u8 *)kmemdup(_rdesc, *(_size), GFP_KERNEL))
|
|
||||||
|
|
||||||
#endif /* CONFIG_HID_BPF */
|
#endif /* CONFIG_HID_BPF */
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ $(BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(OUTPUT)
|
|||||||
$(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $<
|
$(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $<
|
||||||
$(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked1.o) name $(notdir $(<:.bpf.o=)) > $@
|
$(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked1.o) name $(notdir $(<:.bpf.o=)) > $@
|
||||||
|
|
||||||
$(OUTPUT)/%.o: %.c $(BPF_SKELS)
|
$(OUTPUT)/%.o: %.c $(BPF_SKELS) hid_common.h
|
||||||
$(call msg,CC,,$@)
|
$(call msg,CC,,$@)
|
||||||
$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
|
$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
@ -4,13 +4,6 @@
|
|||||||
#include "hid_common.h"
|
#include "hid_common.h"
|
||||||
#include <bpf/bpf.h>
|
#include <bpf/bpf.h>
|
||||||
|
|
||||||
struct attach_prog_args {
|
|
||||||
int prog_fd;
|
|
||||||
unsigned int hid;
|
|
||||||
int retval;
|
|
||||||
int insert_head;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct hid_hw_request_syscall_args {
|
struct hid_hw_request_syscall_args {
|
||||||
__u8 data[10];
|
__u8 data[10];
|
||||||
unsigned int hid;
|
unsigned int hid;
|
||||||
@ -21,11 +14,8 @@ struct hid_hw_request_syscall_args {
|
|||||||
};
|
};
|
||||||
|
|
||||||
FIXTURE(hid_bpf) {
|
FIXTURE(hid_bpf) {
|
||||||
int dev_id;
|
struct uhid_device hid;
|
||||||
int uhid_fd;
|
|
||||||
int hidraw_fd;
|
int hidraw_fd;
|
||||||
int hid_id;
|
|
||||||
pthread_t tid;
|
|
||||||
struct hid *skel;
|
struct hid *skel;
|
||||||
struct bpf_link *hid_links[3]; /* max number of programs loaded in a single test */
|
struct bpf_link *hid_links[3]; /* max number of programs loaded in a single test */
|
||||||
};
|
};
|
||||||
@ -54,35 +44,52 @@ static void detach_bpf(FIXTURE_DATA(hid_bpf) * self)
|
|||||||
FIXTURE_TEARDOWN(hid_bpf) {
|
FIXTURE_TEARDOWN(hid_bpf) {
|
||||||
void *uhid_err;
|
void *uhid_err;
|
||||||
|
|
||||||
uhid_destroy(_metadata, self->uhid_fd);
|
uhid_destroy(_metadata, &self->hid);
|
||||||
|
|
||||||
detach_bpf(self);
|
detach_bpf(self);
|
||||||
pthread_join(self->tid, &uhid_err);
|
pthread_join(self->hid.tid, &uhid_err);
|
||||||
}
|
}
|
||||||
#define TEARDOWN_LOG(fmt, ...) do { \
|
#define TEARDOWN_LOG(fmt, ...) do { \
|
||||||
TH_LOG(fmt, ##__VA_ARGS__); \
|
TH_LOG(fmt, ##__VA_ARGS__); \
|
||||||
hid_bpf_teardown(_metadata, self, variant); \
|
hid_bpf_teardown(_metadata, self, variant); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
struct specific_device {
|
||||||
|
const char test_name[64];
|
||||||
|
__u16 bus;
|
||||||
|
__u32 vid;
|
||||||
|
__u32 pid;
|
||||||
|
};
|
||||||
|
|
||||||
FIXTURE_SETUP(hid_bpf)
|
FIXTURE_SETUP(hid_bpf)
|
||||||
{
|
{
|
||||||
time_t t;
|
const struct specific_device *match = NULL;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* initialize random number generator */
|
const struct specific_device devices[] = {
|
||||||
srand((unsigned int)time(&t));
|
{
|
||||||
|
.test_name = "test_hid_driver_probe",
|
||||||
|
.bus = BUS_BLUETOOTH,
|
||||||
|
.vid = 0x05ac, /* USB_VENDOR_ID_APPLE */
|
||||||
|
.pid = 0x022c, /* USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI */
|
||||||
|
}, {
|
||||||
|
.test_name = "*",
|
||||||
|
.bus = BUS_USB,
|
||||||
|
.vid = 0x0001,
|
||||||
|
.pid = 0x0a36,
|
||||||
|
}};
|
||||||
|
|
||||||
self->dev_id = rand() % 1024;
|
for (int i = 0; i < ARRAY_SIZE(devices); i++) {
|
||||||
|
match = &devices[i];
|
||||||
|
if (!strncmp(_metadata->name, devices[i].test_name, sizeof(devices[i].test_name)))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
self->uhid_fd = setup_uhid(_metadata, self->dev_id);
|
ASSERT_OK_PTR(match);
|
||||||
|
|
||||||
/* locate the uev, self, variant);ent file of the created device */
|
err = setup_uhid(_metadata, &self->hid, match->bus, match->vid, match->pid,
|
||||||
self->hid_id = get_hid_id(self->dev_id);
|
rdesc, sizeof(rdesc));
|
||||||
ASSERT_GT(self->hid_id, 0)
|
ASSERT_OK(err);
|
||||||
TEARDOWN_LOG("Could not locate uhid device id: %d", self->hid_id);
|
|
||||||
|
|
||||||
err = uhid_start_listener(_metadata, &self->tid, self->uhid_fd);
|
|
||||||
ASSERT_EQ(0, err) TEARDOWN_LOG("could not start udev listener: %d", err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct test_program {
|
struct test_program {
|
||||||
@ -129,7 +136,7 @@ static void load_programs(const struct test_program programs[],
|
|||||||
ops_hid_id = bpf_map__initial_value(map, NULL);
|
ops_hid_id = bpf_map__initial_value(map, NULL);
|
||||||
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
|
ASSERT_OK_PTR(ops_hid_id) TH_LOG("unable to retrieve struct_ops data");
|
||||||
|
|
||||||
*ops_hid_id = self->hid_id;
|
*ops_hid_id = self->hid.hid_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we disable the auto-attach feature of all maps because we
|
/* we disable the auto-attach feature of all maps because we
|
||||||
@ -157,7 +164,7 @@ static void load_programs(const struct test_program programs[],
|
|||||||
|
|
||||||
hid__attach(self->skel);
|
hid__attach(self->skel);
|
||||||
|
|
||||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
self->hidraw_fd = open_hidraw(&self->hid);
|
||||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +199,7 @@ TEST_F(hid_bpf, raw_event)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* check that hid_first_event() was executed */
|
/* check that hid_first_event() was executed */
|
||||||
ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1");
|
ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1");
|
||||||
@ -208,7 +215,7 @@ TEST_F(hid_bpf, raw_event)
|
|||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 47;
|
buf[1] = 47;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* check that hid_first_event() was executed */
|
/* check that hid_first_event() was executed */
|
||||||
ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1");
|
ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1");
|
||||||
@ -239,7 +246,7 @@ TEST_F(hid_bpf, subprog_raw_event)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -252,7 +259,7 @@ TEST_F(hid_bpf, subprog_raw_event)
|
|||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 47;
|
buf[1] = 47;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -303,7 +310,7 @@ TEST_F(hid_bpf, test_attach_detach)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -326,14 +333,14 @@ TEST_F(hid_bpf, test_attach_detach)
|
|||||||
/* detach the program */
|
/* detach the program */
|
||||||
detach_bpf(self);
|
detach_bpf(self);
|
||||||
|
|
||||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
self->hidraw_fd = open_hidraw(&self->hid);
|
||||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||||
|
|
||||||
/* inject another event */
|
/* inject another event */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 47;
|
buf[1] = 47;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -352,7 +359,7 @@ TEST_F(hid_bpf, test_attach_detach)
|
|||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -382,7 +389,7 @@ TEST_F(hid_bpf, test_hid_change_report)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -412,7 +419,7 @@ TEST_F(hid_bpf, test_hid_user_input_report_call)
|
|||||||
|
|
||||||
LOAD_BPF;
|
LOAD_BPF;
|
||||||
|
|
||||||
args.hid = self->hid_id;
|
args.hid = self->hid.hid_id;
|
||||||
args.data[0] = 1; /* report ID */
|
args.data[0] = 1; /* report ID */
|
||||||
args.data[1] = 2; /* report ID */
|
args.data[1] = 2; /* report ID */
|
||||||
args.data[2] = 42; /* report ID */
|
args.data[2] = 42; /* report ID */
|
||||||
@ -458,7 +465,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call)
|
|||||||
|
|
||||||
LOAD_BPF;
|
LOAD_BPF;
|
||||||
|
|
||||||
args.hid = self->hid_id;
|
args.hid = self->hid.hid_id;
|
||||||
args.data[0] = 1; /* report ID */
|
args.data[0] = 1; /* report ID */
|
||||||
args.data[1] = 2; /* report ID */
|
args.data[1] = 2; /* report ID */
|
||||||
args.data[2] = 42; /* report ID */
|
args.data[2] = 42; /* report ID */
|
||||||
@ -506,7 +513,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call)
|
|||||||
|
|
||||||
LOAD_BPF;
|
LOAD_BPF;
|
||||||
|
|
||||||
args.hid = self->hid_id;
|
args.hid = self->hid.hid_id;
|
||||||
args.data[0] = 1; /* report ID */
|
args.data[0] = 1; /* report ID */
|
||||||
|
|
||||||
prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request);
|
prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request);
|
||||||
@ -539,7 +546,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -565,7 +572,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call)
|
|||||||
/* detach the program */
|
/* detach the program */
|
||||||
detach_bpf(self);
|
detach_bpf(self);
|
||||||
|
|
||||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
self->hidraw_fd = open_hidraw(&self->hid);
|
||||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||||
|
|
||||||
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
|
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
|
||||||
@ -641,7 +648,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -667,7 +674,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call)
|
|||||||
/* detach the program */
|
/* detach the program */
|
||||||
detach_bpf(self);
|
detach_bpf(self);
|
||||||
|
|
||||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
self->hidraw_fd = open_hidraw(&self->hid);
|
||||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||||
|
|
||||||
err = write(self->hidraw_fd, buf, 3);
|
err = write(self->hidraw_fd, buf, 3);
|
||||||
@ -742,7 +749,7 @@ TEST_F(hid_bpf, test_multiply_events_wq)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -780,7 +787,7 @@ TEST_F(hid_bpf, test_multiply_events)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -816,7 +823,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call)
|
|||||||
buf[1] = 2;
|
buf[1] = 2;
|
||||||
buf[2] = 42;
|
buf[2] = 42;
|
||||||
|
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -867,7 +874,7 @@ TEST_F(hid_bpf, test_hid_attach_flags)
|
|||||||
|
|
||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -878,6 +885,54 @@ TEST_F(hid_bpf, test_hid_attach_flags)
|
|||||||
ASSERT_EQ(buf[3], 3);
|
ASSERT_EQ(buf[3], 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool is_using_driver(struct __test_metadata *_metadata, struct uhid_device *hid,
|
||||||
|
const char *driver)
|
||||||
|
{
|
||||||
|
char driver_line[512];
|
||||||
|
char uevent[1024];
|
||||||
|
char temp[512];
|
||||||
|
int fd, nread;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
sprintf(uevent, "/sys/bus/hid/devices/%04X:%04X:%04X.%04X/uevent",
|
||||||
|
hid->bus, hid->vid, hid->pid, hid->hid_id);
|
||||||
|
|
||||||
|
fd = open(uevent, O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd < 0) {
|
||||||
|
TH_LOG("couldn't open '%s': %d, %d", uevent, fd, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(driver_line, "DRIVER=%s", driver);
|
||||||
|
|
||||||
|
nread = read(fd, temp, ARRAY_SIZE(temp));
|
||||||
|
if (nread > 0 && (strstr(temp, driver_line)) != NULL)
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attach hid_driver_probe to the given uhid device,
|
||||||
|
* check that the device is now using hid-generic.
|
||||||
|
*/
|
||||||
|
TEST_F(hid_bpf, test_hid_driver_probe)
|
||||||
|
{
|
||||||
|
const struct test_program progs[] = {
|
||||||
|
{
|
||||||
|
.name = "hid_test_driver_probe",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_using_driver(_metadata, &self->hid, "apple"));
|
||||||
|
|
||||||
|
LOAD_PROGRAMS(progs);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_using_driver(_metadata, &self->hid, "hid-generic"));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Attach hid_rdesc_fixup to the given uhid device,
|
* Attach hid_rdesc_fixup to the given uhid device,
|
||||||
* retrieve and open the matching hidraw node,
|
* retrieve and open the matching hidraw node,
|
||||||
|
@ -19,6 +19,16 @@
|
|||||||
__typeof__(b) _b = (b); \
|
__typeof__(b) _b = (b); \
|
||||||
_a < _b ? _a : _b; })
|
_a < _b ? _a : _b; })
|
||||||
|
|
||||||
|
struct uhid_device {
|
||||||
|
int dev_id; /* uniq (random) number to identify the device */
|
||||||
|
int uhid_fd;
|
||||||
|
int hid_id; /* HID device id in the system */
|
||||||
|
__u16 bus;
|
||||||
|
__u32 vid;
|
||||||
|
__u32 pid;
|
||||||
|
pthread_t tid; /* thread for reading uhid events */
|
||||||
|
};
|
||||||
|
|
||||||
static unsigned char rdesc[] = {
|
static unsigned char rdesc[] = {
|
||||||
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
|
||||||
0x09, 0x21, /* Usage (Vendor Usage 0x21) */
|
0x09, 0x21, /* Usage (Vendor Usage 0x21) */
|
||||||
@ -122,7 +132,9 @@ static int uhid_write(struct __test_metadata *_metadata, int fd, const struct uh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
|
static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb,
|
||||||
|
__u16 bus, __u32 vid, __u32 pid, __u8 *rdesc,
|
||||||
|
size_t rdesc_size)
|
||||||
{
|
{
|
||||||
struct uhid_event ev;
|
struct uhid_event ev;
|
||||||
char buf[25];
|
char buf[25];
|
||||||
@ -133,10 +145,10 @@ static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
|
|||||||
ev.type = UHID_CREATE;
|
ev.type = UHID_CREATE;
|
||||||
strcpy((char *)ev.u.create.name, buf);
|
strcpy((char *)ev.u.create.name, buf);
|
||||||
ev.u.create.rd_data = rdesc;
|
ev.u.create.rd_data = rdesc;
|
||||||
ev.u.create.rd_size = sizeof(rdesc);
|
ev.u.create.rd_size = rdesc_size;
|
||||||
ev.u.create.bus = BUS_USB;
|
ev.u.create.bus = bus;
|
||||||
ev.u.create.vendor = 0x0001;
|
ev.u.create.vendor = vid;
|
||||||
ev.u.create.product = 0x0a37;
|
ev.u.create.product = pid;
|
||||||
ev.u.create.version = 0;
|
ev.u.create.version = 0;
|
||||||
ev.u.create.country = 0;
|
ev.u.create.country = 0;
|
||||||
|
|
||||||
@ -146,14 +158,14 @@ static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
|
|||||||
return uhid_write(_metadata, fd, &ev);
|
return uhid_write(_metadata, fd, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void uhid_destroy(struct __test_metadata *_metadata, int fd)
|
static void uhid_destroy(struct __test_metadata *_metadata, struct uhid_device *hid)
|
||||||
{
|
{
|
||||||
struct uhid_event ev;
|
struct uhid_event ev;
|
||||||
|
|
||||||
memset(&ev, 0, sizeof(ev));
|
memset(&ev, 0, sizeof(ev));
|
||||||
ev.type = UHID_DESTROY;
|
ev.type = UHID_DESTROY;
|
||||||
|
|
||||||
uhid_write(_metadata, fd, &ev);
|
uhid_write(_metadata, hid->uhid_fd, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int uhid_event(struct __test_metadata *_metadata, int fd)
|
static int uhid_event(struct __test_metadata *_metadata, int fd)
|
||||||
@ -281,7 +293,8 @@ static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf, size_t size)
|
static int uhid_send_event(struct __test_metadata *_metadata, struct uhid_device *hid,
|
||||||
|
__u8 *buf, size_t size)
|
||||||
{
|
{
|
||||||
struct uhid_event ev;
|
struct uhid_event ev;
|
||||||
|
|
||||||
@ -294,36 +307,20 @@ static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf,
|
|||||||
|
|
||||||
memcpy(ev.u.input2.data, buf, size);
|
memcpy(ev.u.input2.data, buf, size);
|
||||||
|
|
||||||
return uhid_write(_metadata, fd, &ev);
|
return uhid_write(_metadata, hid->uhid_fd, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int setup_uhid(struct __test_metadata *_metadata, int rand_nb)
|
static bool match_sysfs_device(struct uhid_device *hid, const char *workdir, struct dirent *dir)
|
||||||
{
|
{
|
||||||
int fd;
|
char target[20] = "";
|
||||||
const char *path = "/dev/uhid";
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
fd = open(path, O_RDWR | O_CLOEXEC);
|
|
||||||
ASSERT_GE(fd, 0) TH_LOG("open uhid-cdev failed; %d", fd);
|
|
||||||
|
|
||||||
ret = uhid_create(_metadata, fd, rand_nb);
|
|
||||||
ASSERT_EQ(0, ret) {
|
|
||||||
TH_LOG("create uhid device failed: %d", ret);
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *dir)
|
|
||||||
{
|
|
||||||
const char *target = "0003:0001:0A37.*";
|
|
||||||
char phys[512];
|
char phys[512];
|
||||||
char uevent[1024];
|
char uevent[1024];
|
||||||
char temp[512];
|
char temp[512];
|
||||||
int fd, nread;
|
int fd, nread;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
|
snprintf(target, sizeof(target), "%04X:%04X:%04X.*", hid->bus, hid->vid, hid->pid);
|
||||||
|
|
||||||
if (fnmatch(target, dir->d_name, 0))
|
if (fnmatch(target, dir->d_name, 0))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -334,7 +331,7 @@ static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *d
|
|||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
sprintf(phys, "PHYS=%d", dev_id);
|
sprintf(phys, "PHYS=%d", hid->dev_id);
|
||||||
|
|
||||||
nread = read(fd, temp, ARRAY_SIZE(temp));
|
nread = read(fd, temp, ARRAY_SIZE(temp));
|
||||||
if (nread > 0 && (strstr(temp, phys)) != NULL)
|
if (nread > 0 && (strstr(temp, phys)) != NULL)
|
||||||
@ -345,7 +342,7 @@ static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *d
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_hid_id(int dev_id)
|
static int get_hid_id(struct uhid_device *hid)
|
||||||
{
|
{
|
||||||
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
||||||
const char *str_id;
|
const char *str_id;
|
||||||
@ -360,10 +357,10 @@ static int get_hid_id(int dev_id)
|
|||||||
d = opendir(workdir);
|
d = opendir(workdir);
|
||||||
if (d) {
|
if (d) {
|
||||||
while ((dir = readdir(d)) != NULL) {
|
while ((dir = readdir(d)) != NULL) {
|
||||||
if (!match_sysfs_device(dev_id, workdir, dir))
|
if (!match_sysfs_device(hid, workdir, dir))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
str_id = dir->d_name + sizeof("0003:0001:0A37.");
|
str_id = dir->d_name + sizeof("0000:0000:0000.");
|
||||||
found = (int)strtol(str_id, NULL, 16);
|
found = (int)strtol(str_id, NULL, 16);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -377,7 +374,7 @@ static int get_hid_id(int dev_id)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_hidraw(int dev_id)
|
static int get_hidraw(struct uhid_device *hid)
|
||||||
{
|
{
|
||||||
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
const char *workdir = "/sys/devices/virtual/misc/uhid";
|
||||||
char sysfs[1024];
|
char sysfs[1024];
|
||||||
@ -394,7 +391,7 @@ static int get_hidraw(int dev_id)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
while ((dir = readdir(d)) != NULL) {
|
while ((dir = readdir(d)) != NULL) {
|
||||||
if (!match_sysfs_device(dev_id, workdir, dir))
|
if (!match_sysfs_device(hid, workdir, dir))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
|
sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
|
||||||
@ -421,12 +418,12 @@ static int get_hidraw(int dev_id)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int open_hidraw(int dev_id)
|
static int open_hidraw(struct uhid_device *hid)
|
||||||
{
|
{
|
||||||
int hidraw_number;
|
int hidraw_number;
|
||||||
char hidraw_path[64] = { 0 };
|
char hidraw_path[64] = { 0 };
|
||||||
|
|
||||||
hidraw_number = get_hidraw(dev_id);
|
hidraw_number = get_hidraw(hid);
|
||||||
if (hidraw_number < 0)
|
if (hidraw_number < 0)
|
||||||
return hidraw_number;
|
return hidraw_number;
|
||||||
|
|
||||||
@ -434,3 +431,44 @@ static int open_hidraw(int dev_id)
|
|||||||
sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
|
sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
|
||||||
return open(hidraw_path, O_RDWR | O_NONBLOCK);
|
return open(hidraw_path, O_RDWR | O_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int setup_uhid(struct __test_metadata *_metadata, struct uhid_device *hid,
|
||||||
|
__u16 bus, __u32 vid, __u32 pid, const __u8 *rdesc, size_t rdesc_size)
|
||||||
|
{
|
||||||
|
const char *path = "/dev/uhid";
|
||||||
|
time_t t;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* initialize random number generator */
|
||||||
|
srand((unsigned int)time(&t));
|
||||||
|
|
||||||
|
hid->dev_id = rand() % 1024;
|
||||||
|
hid->bus = bus;
|
||||||
|
hid->vid = vid;
|
||||||
|
hid->pid = pid;
|
||||||
|
|
||||||
|
hid->uhid_fd = open(path, O_RDWR | O_CLOEXEC);
|
||||||
|
ASSERT_GE(hid->uhid_fd, 0) TH_LOG("open uhid-cdev failed; %d", hid->uhid_fd);
|
||||||
|
|
||||||
|
ret = uhid_create(_metadata, hid->uhid_fd, hid->dev_id, bus, vid, pid,
|
||||||
|
(__u8 *)rdesc, rdesc_size);
|
||||||
|
ASSERT_EQ(0, ret) {
|
||||||
|
TH_LOG("create uhid device failed: %d", ret);
|
||||||
|
close(hid->uhid_fd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* locate the uevent file of the created device */
|
||||||
|
hid->hid_id = get_hid_id(hid);
|
||||||
|
ASSERT_GT(hid->hid_id, 0)
|
||||||
|
TH_LOG("Could not locate uhid device id: %d", hid->hid_id);
|
||||||
|
|
||||||
|
ret = uhid_start_listener(_metadata, &hid->tid, hid->uhid_fd);
|
||||||
|
ASSERT_EQ(0, ret) {
|
||||||
|
TH_LOG("could not start udev listener: %d", ret);
|
||||||
|
close(hid->uhid_fd);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -9,11 +9,8 @@
|
|||||||
#endif /* HIDIOCREVOKE */
|
#endif /* HIDIOCREVOKE */
|
||||||
|
|
||||||
FIXTURE(hidraw) {
|
FIXTURE(hidraw) {
|
||||||
int dev_id;
|
struct uhid_device hid;
|
||||||
int uhid_fd;
|
|
||||||
int hidraw_fd;
|
int hidraw_fd;
|
||||||
int hid_id;
|
|
||||||
pthread_t tid;
|
|
||||||
};
|
};
|
||||||
static void close_hidraw(FIXTURE_DATA(hidraw) * self)
|
static void close_hidraw(FIXTURE_DATA(hidraw) * self)
|
||||||
{
|
{
|
||||||
@ -25,10 +22,10 @@ static void close_hidraw(FIXTURE_DATA(hidraw) * self)
|
|||||||
FIXTURE_TEARDOWN(hidraw) {
|
FIXTURE_TEARDOWN(hidraw) {
|
||||||
void *uhid_err;
|
void *uhid_err;
|
||||||
|
|
||||||
uhid_destroy(_metadata, self->uhid_fd);
|
uhid_destroy(_metadata, &self->hid);
|
||||||
|
|
||||||
close_hidraw(self);
|
close_hidraw(self);
|
||||||
pthread_join(self->tid, &uhid_err);
|
pthread_join(self->hid.tid, &uhid_err);
|
||||||
}
|
}
|
||||||
#define TEARDOWN_LOG(fmt, ...) do { \
|
#define TEARDOWN_LOG(fmt, ...) do { \
|
||||||
TH_LOG(fmt, ##__VA_ARGS__); \
|
TH_LOG(fmt, ##__VA_ARGS__); \
|
||||||
@ -37,25 +34,12 @@ FIXTURE_TEARDOWN(hidraw) {
|
|||||||
|
|
||||||
FIXTURE_SETUP(hidraw)
|
FIXTURE_SETUP(hidraw)
|
||||||
{
|
{
|
||||||
time_t t;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
/* initialize random number generator */
|
err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a37, rdesc, sizeof(rdesc));
|
||||||
srand((unsigned int)time(&t));
|
ASSERT_OK(err);
|
||||||
|
|
||||||
self->dev_id = rand() % 1024;
|
self->hidraw_fd = open_hidraw(&self->hid);
|
||||||
|
|
||||||
self->uhid_fd = setup_uhid(_metadata, self->dev_id);
|
|
||||||
|
|
||||||
/* locate the uev, self, variant);ent file of the created device */
|
|
||||||
self->hid_id = get_hid_id(self->dev_id);
|
|
||||||
ASSERT_GT(self->hid_id, 0)
|
|
||||||
TEARDOWN_LOG("Could not locate uhid device id: %d", self->hid_id);
|
|
||||||
|
|
||||||
err = uhid_start_listener(_metadata, &self->tid, self->uhid_fd);
|
|
||||||
ASSERT_EQ(0, err) TEARDOWN_LOG("could not start udev listener: %d", err);
|
|
||||||
|
|
||||||
self->hidraw_fd = open_hidraw(self->dev_id);
|
|
||||||
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +63,7 @@ TEST_F(hidraw, raw_event)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -101,7 +85,7 @@ TEST_F(hidraw, raw_event_revoked)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -117,7 +101,7 @@ TEST_F(hidraw, raw_event_revoked)
|
|||||||
/* inject one other event */
|
/* inject one other event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 43;
|
buf[1] = 43;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
/* read the data from hidraw */
|
/* read the data from hidraw */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
@ -161,7 +145,7 @@ TEST_F(hidraw, poll_revoked)
|
|||||||
/* inject one event */
|
/* inject one event */
|
||||||
buf[0] = 1;
|
buf[0] = 1;
|
||||||
buf[1] = 42;
|
buf[1] = 42;
|
||||||
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
|
uhid_send_event(_metadata, &self->hid, buf, 6);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ready = poll(pfds, 1, 5000);
|
ready = poll(pfds, 1, 5000);
|
||||||
|
@ -598,3 +598,15 @@ SEC(".struct_ops.link")
|
|||||||
struct hid_bpf_ops test_infinite_loop_input_report = {
|
struct hid_bpf_ops test_infinite_loop_input_report = {
|
||||||
.hid_device_event = (void *)hid_test_infinite_loop_input_report,
|
.hid_device_event = (void *)hid_test_infinite_loop_input_report,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SEC("?struct_ops.s/hid_rdesc_fixup")
|
||||||
|
int BPF_PROG(hid_test_driver_probe, struct hid_bpf_ctx *hid_ctx)
|
||||||
|
{
|
||||||
|
hid_ctx->hid->quirks |= HID_QUIRK_IGNORE_SPECIAL_DRIVER;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SEC(".struct_ops.link")
|
||||||
|
struct hid_bpf_ops test_driver_probe = {
|
||||||
|
.hid_rdesc_fixup = (void *)hid_test_driver_probe,
|
||||||
|
};
|
||||||
|
@ -84,10 +84,14 @@ struct hid_bpf_ops {
|
|||||||
struct hid_device *hdev;
|
struct hid_device *hdev;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define BIT(n) (1U << n)
|
||||||
|
|
||||||
#ifndef BPF_F_BEFORE
|
#ifndef BPF_F_BEFORE
|
||||||
#define BPF_F_BEFORE (1U << 3)
|
#define BPF_F_BEFORE BIT(3)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
|
||||||
|
|
||||||
/* following are kfuncs exported by HID for HID-BPF */
|
/* following are kfuncs exported by HID for HID-BPF */
|
||||||
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
|
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
|
||||||
unsigned int offset,
|
unsigned int offset,
|
||||||
|
Loading…
Reference in New Issue
Block a user