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:
Linus Torvalds 2024-11-20 13:57:40 -08:00
commit b57807cbbf
50 changed files with 2287 additions and 411 deletions

View 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

View File

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

View File

@ -213,13 +213,16 @@ config HID_CHICONY
config HID_CORSAIR
tristate "Corsair devices"
depends on USB_HID && LEDS_CLASS
select POWER_SUPPLY
help
Support for Corsair devices that are not fully compliant with the
HID standard.
Support for Corsair Void headsets.
Supported devices:
- Vengeance K90
- Scimitar PRO RGB
- Corsair Void headsets
config HID_COUGAR
tristate "Cougar devices"
@ -465,6 +468,15 @@ config HID_KYE
- MousePen i608X 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
tristate "UC-Logic"
depends on USB_HID
@ -1096,6 +1108,7 @@ config HID_RMI
select RMI4_F11
select RMI4_F12
select RMI4_F30
select RMI4_F3A
help
Support for Synaptics RMI4 touchpads.
Say Y here if you have a Synaptics RMI4 touchpads over i2c-hid or usbhid

View File

@ -38,7 +38,7 @@ obj-$(CONFIG_HID_BIGBEN_FF) += hid-bigbenff.o
obj-$(CONFIG_HID_CHERRY) += hid-cherry.o
obj-$(CONFIG_HID_CHICONY) += hid-chicony.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_CP2112) += hid-cp2112.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_KEYTOUCH) += hid-keytouch.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_LENOVO) += hid-lenovo.o
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o

View File

@ -148,7 +148,7 @@ out:
}
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;
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:
kfree(ctx_kern.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
return rdesc;
}
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)
{
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 0;
}

View File

@ -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, uniq, true),
WRITE_RANGE(hid_device, phys, true),
WRITE_RANGE(hid_device, quirks, false),
};
#undef WRITE_RANGE
const struct btf_type *state = NULL;

View File

@ -214,7 +214,8 @@ static const __u8 fixed_rdesc_pad[] = {
CollectionApplication(
// -- Byte 0 in report
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
LogicalMaximum_i8(0)
LogicalMaximum_i8(1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
@ -234,14 +235,17 @@ static const __u8 fixed_rdesc_pad[] = {
Input(Var|Abs)
// Byte 4 in report is the dial
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
// Byte 5 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x8)
LogicalRange_i8(0x0, 0x1)
UsageMinimum_i8(0x01)
UsageMaximum_i8(0x08)
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(7)
ReportSize(1)
Input(Var|Abs)
@ -265,7 +269,8 @@ static const __u8 fixed_rdesc_pen[] = {
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
ReportSize(1)
ReportCount(3)
Input(Var|Abs)
@ -280,22 +285,28 @@ static const __u8 fixed_rdesc_pen[] = {
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
PhysicalRange_i16(0, 266)
LogicalRange_i16(0, 32767)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(266)
LogicalMinimum_i16(0)
LogicalMaximum_i16(32767)
Usage_GD_X
Input(Var|Abs) // Bytes 2+3
PhysicalRange_i16(0, 166)
LogicalRange_i16(0, 32767)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(166)
LogicalMinimum_i16(0)
LogicalMaximum_i16(32767)
Usage_GD_Y
Input(Var|Abs) // Bytes 4+5
)
UsagePage_Digitizers
Usage_Dig_TipPressure
LogicalRange_i16(0, 8191)
LogicalMinimum_i16(0)
LogicalMaximum_i16(8191)
Input(Var|Abs) // Byte 6+7
ReportSize(8)
ReportCount(2)
LogicalRange_i8(-60, 60)
LogicalMinimum_i8(-60)
LogicalMaximum_i8(60)
Usage_Dig_XTilt
Usage_Dig_YTilt
Input(Var|Abs) // Byte 8+9
@ -313,7 +324,8 @@ static const __u8 fixed_rdesc_vendor[] = {
Usage_Dig_Pen
CollectionPhysical(
// Byte 1 are the buttons
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
ReportSize(1)
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
@ -333,25 +345,31 @@ static const __u8 fixed_rdesc_vendor[] = {
UnitExponent(-1)
// Note: reported logical range differs
// from the pen report ID for x and y
LogicalRange_i16(0, 53340)
PhysicalRange_i16(0, 266)
LogicalMinimum_i16(0)
LogicalMaximum_i16(53340)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(266)
// Bytes 2/3 in report
Usage_GD_X
Input(Var|Abs)
LogicalRange_i16(0, 33340)
PhysicalRange_i16(0, 166)
LogicalMinimum_i16(0)
LogicalMaximum_i16(33340)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(166)
// Bytes 4/5 in report
Usage_GD_Y
Input(Var|Abs)
)
// Bytes 6/7 in report
LogicalRange_i16(0, 8191)
LogicalMinimum_i16(0)
LogicalMaximum_i16(8191)
Usage_Dig_TipPressure
Input(Var|Abs)
// Bytes 8/9 in report
ReportCount(1) // Padding
Input(Const)
LogicalRange_i8(-60, 60)
LogicalMinimum_i8(-60)
LogicalMaximum_i8(60)
// Byte 10 in report
Usage_Dig_XTilt
// Byte 11 in report
@ -366,7 +384,8 @@ static const __u8 fixed_rdesc_vendor[] = {
CollectionApplication(
// Byte 0
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
@ -386,15 +405,18 @@ static const __u8 fixed_rdesc_vendor[] = {
Input(Var|Abs)
// Byte 4 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x8)
LogicalRange_i8(0x0, 0x1)
UsageMinimum_i8(0x1)
UsageMaximum_i8(0x8)
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(8)
ReportSize(1)
Input(Var|Abs)
// Byte 5 is the top dial
UsagePage_GenericDesktop
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)

View File

@ -170,7 +170,8 @@ static const __u8 fixed_rdesc_pad[] = {
CollectionApplication(
// -- Byte 0 in report
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
@ -190,14 +191,17 @@ static const __u8 fixed_rdesc_pad[] = {
Input(Var|Abs)
// Byte 4 in report is the wheel
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)
// Byte 5 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x6)
LogicalRange_i8(0x01, 0x6)
UsageMinimum_i8(0x1)
UsageMaximum_i8(0x6)
LogicalMinimum_i8(0x1)
LogicalMaximum_i8(0x6)
ReportCount(1)
ReportSize(8)
Input(Arr|Abs)
@ -219,7 +223,8 @@ static const __u8 fixed_rdesc_pen[] = {
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
Usage_Dig_SecondaryBarrelSwitch // maps eraser to BTN_STYLUS2
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
ReportSize(1)
ReportCount(3)
Input(Var|Abs)
@ -234,18 +239,23 @@ static const __u8 fixed_rdesc_pen[] = {
UsagePage_GenericDesktop
Unit(cm)
UnitExponent(-1)
PhysicalRange_i16(0, 160)
LogicalRange_i16(0, 32767)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(160)
LogicalMinimum_i16(0)
LogicalMaximum_i16(32767)
Usage_GD_X
Input(Var|Abs) // Bytes 2+3
PhysicalRange_i16(0, 100)
LogicalRange_i16(0, 32767)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(100)
LogicalMinimum_i16(0)
LogicalMaximum_i16(32767)
Usage_GD_Y
Input(Var|Abs) // Bytes 4+5
)
UsagePage_Digitizers
Usage_Dig_TipPressure
LogicalRange_i16(0, 8191)
LogicalMinimum_i16(0)
LogicalMaximum_i16(8191)
Input(Var|Abs) // Byte 6+7
// Two bytes padding so we don't need to change the report at all
ReportSize(8)
@ -265,7 +275,8 @@ static const __u8 fixed_rdesc_vendor[] = {
Usage_Dig_Pen
CollectionPhysical(
// Byte 1 are the buttons
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
ReportSize(1)
Usage_Dig_TipSwitch
Usage_Dig_BarrelSwitch
@ -285,19 +296,24 @@ static const __u8 fixed_rdesc_vendor[] = {
UnitExponent(-1)
// Note: reported logical range differs
// from the pen report ID for x and y
LogicalRange_i16(0, 32000)
PhysicalRange_i16(0, 160)
LogicalMinimum_i16(0)
LogicalMaximum_i16(32000)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(160)
// Bytes 2/3 in report
Usage_GD_X
Input(Var|Abs)
LogicalRange_i16(0, 20000)
PhysicalRange_i16(0, 100)
LogicalMinimum_i16(0)
LogicalMaximum_i16(20000)
PhysicalMinimum_i16(0)
PhysicalMaximum_i16(100)
// Bytes 4/5 in report
Usage_GD_Y
Input(Var|Abs)
)
// Bytes 6/7 in report
LogicalRange_i16(0, 8192)
LogicalMinimum_i16(0)
LogicalMaximum_i16(8192)
Usage_Dig_TipPressure
Input(Var|Abs)
)
@ -307,7 +323,8 @@ static const __u8 fixed_rdesc_vendor[] = {
CollectionApplication(
// Byte 0
ReportId(PAD_REPORT_ID)
LogicalRange_i8(0, 1)
LogicalMinimum_i8(0)
LogicalMaximum_i8(1)
UsagePage_Digitizers
Usage_Dig_TabletFunctionKeys
CollectionPhysical(
@ -327,8 +344,10 @@ static const __u8 fixed_rdesc_vendor[] = {
Input(Var|Abs)
// Byte 4 is the button state
UsagePage_Button
UsageRange_i8(0x01, 0x6)
LogicalRange_i8(0x0, 0x1)
UsageMinimum_i8(0x1)
UsageMaximum_i8(0x6)
LogicalMinimum_i8(0x0)
LogicalMaximum_i8(0x1)
ReportCount(6)
ReportSize(1)
Input(Var|Abs)
@ -337,7 +356,8 @@ static const __u8 fixed_rdesc_vendor[] = {
// Byte 5 is the wheel
UsagePage_GenericDesktop
Usage_GD_Wheel
LogicalRange_i8(-1, 1)
LogicalMinimum_i8(-1)
LogicalMaximum_i8(1)
ReportCount(1)
ReportSize(8)
Input(Var|Rel)

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

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

View File

@ -52,7 +52,8 @@
* Usage_GD_Keyboard
* CollectionApplication( Open the collection
* ReportId(3)
* LogicalRange_i8(0, 1)
* LogicalMinimum_i8(0)
* LogicalMaximum_i8(1)
* // other fields
* ) End EndCollection
*
@ -74,26 +75,43 @@
#define Arr 0x0
#define Abs 0x0
#define Rel 0x4
#define Null 0x40
#define Buff 0x0100
/* Use like this: Input(Var|Abs) */
#define Input(i_) 0x081, i8(i_),
#define Output(i_) 0x091, 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 ReportSize(sz_) 0x75, i8(sz_),
#define ReportCount(cnt_) 0x95, i8(cnt_),
#define LogicalRange_i8(min_, max_) 0x15, i8(min_), 0x25, i8(max_),
#define LogicalRange_i16(min_, max_) 0x16, LE16(min_), 0x26, LE16(max_),
#define LogicalRange_i32(min_, max_) 0x17, LE32(min_), 0x27, LE32(max_),
#define LogicalMinimum_i8(min_) 0x15, i8(min_),
#define LogicalMinimum_i16(min_) 0x16, LE16(min_),
#define LogicalMinimum_i32(min_) 0x17, LE32(min_),
#define PhysicalRange_i8(min_, max_) 0x35, i8(min_), 0x45, i8(max_),
#define PhysicalRange_i16(min_, max_) 0x36, LE16(min_), 0x46, LE16(max_),
#define PhysicalRange_i32(min_, max_) 0x37, LE32(min_), 0x47, LE32(max_),
#define LogicalMaximum_i8(max_) 0x25, i8(max_),
#define LogicalMaximum_i16(max_) 0x26, LE16(max_),
#define LogicalMaximum_i32(max_) 0x27, LE32(max_),
#define UsageRange_i8(min_, max_) 0x19, i8(min_), 0x29, i8(max_),
#define UsageRange_i16(min_, max_) 0x1a, LE16(min_), 0x2a, LE16(max_),
#define PhysicalMinimum_i8(min_) 0x35, i8(min_),
#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_i16(p_) 0x06, LE16(p_),

View File

@ -1183,7 +1183,7 @@ static const __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
if (drvdata->quirks & QUIRK_G752_KEYBOARD &&
*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;
size_t new_size = *rsize + sizeof(asus_g752_fixed_rdesc);

View File

@ -45,6 +45,34 @@ static int hid_ignore_special_drivers = 0;
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");
/*
* 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.
*/
@ -425,7 +453,7 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
* both this and the standard encoding. */
raw_value = item_sdata(item);
if (!(raw_value & 0xfffffff0))
parser->global.unit_exponent = hid_snto32(raw_value, 4);
parser->global.unit_exponent = snto32(raw_value, 4);
else
parser->global.unit_exponent = raw_value;
return 0;
@ -685,6 +713,13 @@ static void hid_close_report(struct hid_device *device)
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);
device->rdesc = NULL;
device->rsize = 0;
@ -698,6 +733,14 @@ static void hid_close_report(struct hid_device *device)
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.
*/
@ -707,6 +750,7 @@ void hiddev_free(struct kref *ref)
struct hid_device *hid = container_of(ref, struct hid_device, ref);
hid_close_report(hid);
hid_free_bpf_rdesc(hid);
kfree(hid->dev_rdesc);
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->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) {
case 0:
return start;
break;
case 1:
if ((end - start) < 1)
return NULL;
item->data.u8 = *start++;
return start;
item->data.u8 = *start;
break;
case 2:
if ((end - start) < 2)
return NULL;
item->data.u16 = get_unaligned_le16(start);
start = (__u8 *)((__le16 *)start + 1);
return start;
break;
case 3:
item->size++;
if ((end - start) < 4)
return NULL;
case 4:
item->data.u32 = get_unaligned_le32(start);
start = (__u8 *)((__le32 *)start + 1);
return start;
break;
}
return NULL;
return start + item->size;
}
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;
unsigned int size;
const __u8 *start;
__u8 *buf;
const __u8 *end;
const __u8 *next;
int ret;
@ -1221,25 +1258,34 @@ int hid_open_report(struct hid_device *device)
if (WARN_ON(device->status & HID_STAT_PARSED))
return -EBUSY;
start = device->dev_rdesc;
start = device->bpf_rdesc;
if (WARN_ON(!start))
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)
return -ENOMEM;
if (device->driver->report_fixup)
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);
kfree(buf);
if (start == NULL)
return -ENOMEM;
}
device->rdesc = start;
device->rsize = size;
@ -1315,46 +1361,6 @@ alloc_err:
}
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).
*
@ -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
* 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)
@ -2684,6 +2691,27 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
const struct hid_device_id *id;
int ret;
if (!hdev->bpf_rsize) {
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))
return -ENODEV;
@ -2691,8 +2719,6 @@ static int __hid_device_probe(struct hid_device *hdev, struct hid_driver *hdrv)
if (!hdev->devres_group_id)
return -ENOMEM;
/* reset the quirks that has been previously set */
hdev->quirks = hid_lookup_quirk(hdev);
hdev->driver = hdrv;
if (hdrv->probe) {
@ -2940,9 +2966,11 @@ static void hid_remove_device(struct hid_device *hdev)
hid_debug_unregister(hdev);
hdev->status &= ~HID_STAT_ADDED;
}
hid_free_bpf_rdesc(hdev);
kfree(hdev->dev_rdesc);
hdev->dev_rdesc = NULL;
hdev->dev_rsize = 0;
hdev->bpf_rsize = 0;
}
/**

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

View File

@ -852,7 +852,8 @@ static int cp2112_set_usb_config(struct hid_device *hdev,
{
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),
HID_FEATURE_REPORT);

View File

@ -3309,9 +3309,9 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_EPG] = "EPG", [KEY_PVR] = "PVR",
[KEY_MHP] = "MHP", [KEY_LANGUAGE] = "Language",
[KEY_TITLE] = "Title", [KEY_SUBTITLE] = "Subtitle",
[KEY_ANGLE] = "Angle", [KEY_ZOOM] = "Zoom",
[KEY_ANGLE] = "Angle",
[KEY_MODE] = "Mode", [KEY_KEYBOARD] = "Keyboard",
[KEY_SCREEN] = "Screen", [KEY_PC] = "PC",
[KEY_PC] = "PC",
[KEY_TV] = "TV", [KEY_TV2] = "TV2",
[KEY_VCR] = "VCR", [KEY_VCR2] = "VCR2",
[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_HAPPY37] = "TriggerHappy37", [BTN_TRIGGER_HAPPY38] = "TriggerHappy38",
[BTN_TRIGGER_HAPPY39] = "TriggerHappy39", [BTN_TRIGGER_HAPPY40] = "TriggerHappy40",
[BTN_DIGI] = "Digi", [BTN_STYLUS3] = "Stylus3",
[BTN_TOOL_QUINTTAP] = "ToolQuintTap", [BTN_WHEEL] = "Wheel",
[BTN_STYLUS3] = "Stylus3", [BTN_TOOL_QUINTTAP] = "ToolQuintTap",
[KEY_10CHANNELSDOWN] = "10ChannelsDown",
[KEY_10CHANNELSUP] = "10ChannelsUp",
[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_FRAMEFORWARD] = "FrameForward", [KEY_FULL_SCREEN] = "FullScreen",
[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_KBD_LCD_MENU2] = "KbdLcdMenu2", [KEY_KBD_LCD_MENU3] = "KbdLcdMenu3",
[KEY_KBD_LCD_MENU4] = "KbdLcdMenu4", [KEY_KBD_LCD_MENU5] = "KbdLcdMenu5",

View File

@ -40,6 +40,9 @@ static bool hid_generic_match(struct hid_device *hdev,
if (ignore_special_driver)
return true;
if (hdev->quirks & HID_QUIRK_IGNORE_SPECIAL_DRIVER)
return true;
if (hdev->quirks & HID_QUIRK_HAVE_SPECIAL_DRIVER)
return false;

View File

@ -19,6 +19,8 @@
#define GOODIX_HID_DESC_ADDR 0x1058C
#define GOODIX_HID_REPORT_DESC_ADDR 0x105AA
#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_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
* - 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));
if (!error && (hdr.flag & GOODIX_HID_ACK_READY_FLAG)) {
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);
return -EINVAL;
}
*resp_len = len;
*resp_len = len - GOODIX_HID_PKG_LEN_SIZE;
return 0;
}
@ -431,7 +433,7 @@ static int goodix_hid_get_raw_report(struct hid_device *hid,
tx_len += args_len;
/* 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) {
dev_err(ts->dev, "failed send read feature cmd, %d", error);
return error;
@ -446,9 +448,12 @@ static int goodix_hid_get_raw_report(struct hid_device *hid,
if (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) */
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_PKG_LEN_SIZE, buf, len);
if (error) {
@ -518,7 +523,7 @@ static int goodix_hid_set_raw_report(struct hid_device *hid,
memcpy(tmp_buf + tx_len, buf, 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) {
dev_err(ts->dev, "failed send report: %*ph", tx_len, tmp_buf);
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),
"failed to request reset gpio\n");
error = device_property_read_u32(dev, "goodix,hid-report-addr",
&ts->hid_report_addr);
if (error)
return dev_err_probe(dev, error,
"failed get hid report addr\n");
ts->hid_report_addr = GOODIX_HID_REPORT_ADDR;
error = goodix_dev_confirm(ts);
if (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;
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));
if (error) {
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);
#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[] = {
{ "gt7986u" },
{ },
@ -796,6 +804,7 @@ static struct spi_driver goodix_spi_driver = {
.driver = {
.name = "goodix-spi-hid",
.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),
},
.probe = goodix_spi_probe,

View File

@ -422,6 +422,25 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
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 = {
.parse = mousevsc_hid_parse,
.open = mousevsc_hid_open,
@ -431,7 +450,16 @@ static const struct hid_ll_driver mousevsc_ll_driver = {
.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,
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->driver = &mousevsc_hid_driver;
hid_dev->bus = BUS_VIRTUAL;
hid_dev->vendor = input_dev->hid_dev_info.vendor;
hid_dev->product = input_dev->hid_dev_info.product;
@ -488,20 +515,6 @@ static int mousevsc_probe(struct hv_device *device,
if (ret)
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);
input_dev->connected = true;
@ -579,12 +592,23 @@ static struct hv_driver mousevsc_drv = {
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)
{
vmbus_driver_unregister(&mousevsc_drv);
hid_unregister_driver(&mousevsc_hid_driver);
}
MODULE_LICENSE("GPL");

View File

@ -94,6 +94,7 @@
#define USB_DEVICE_ID_APPLE_MAGICMOUSE2 0x0269
#define USB_DEVICE_ID_APPLE_MAGICTRACKPAD 0x030e
#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_ISO 0x020f
#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_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_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006
#define USB_DEVICE_ID_LABTEC_ODDOR_HANDBRAKE 0x8888

248
drivers/hid/hid-kysona.c Normal file
View 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>");

View File

@ -1350,7 +1350,8 @@ int lg4ff_init(struct hid_device *hid)
/* Initialize device properties */
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];
}
lg4ff_init_wheel_data(&entry->wdata, &lg4ff_devices[i], mmode_wheel, real_product_id);

View File

@ -928,7 +928,7 @@ static int hidpp_unifying_init(struct hidpp_device *hidpp)
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
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;
int ret;
@ -945,7 +945,6 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature,
return -ENOENT;
*feature_index = response.fap.params[0];
*feature_type = response.fap.params[1];
return ret;
}
@ -1012,13 +1011,11 @@ print_version:
static int hidpp_get_serial(struct hidpp_device *hidpp, u32 *serial)
{
struct hidpp_report response;
u8 feature_type;
u8 feature_index;
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_DEVICE_INFORMATION,
&feature_index,
&feature_type);
&feature_index);
if (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)
{
u8 feature_type;
u8 feature_index;
u8 __name_length;
char *name;
@ -1133,7 +1129,7 @@ static char *hidpp_get_device_name(struct hidpp_device *hidpp)
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_GET_DEVICE_NAME_TYPE,
&feature_index, &feature_type);
&feature_index);
if (ret)
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)
{
u8 feature_type;
int ret;
int status, capacity, next_capacity, level;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_BATTERY_LEVEL_STATUS,
&hidpp->battery.feature_index,
&feature_type);
&hidpp->battery.feature_index);
if (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)
{
u8 feature_type;
int ret;
int status, voltage, level, charge_type;
if (hidpp->battery.voltage_feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_BATTERY_VOLTAGE,
&hidpp->battery.voltage_feature_index,
&feature_type);
&hidpp->battery.voltage_feature_index);
if (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)
{
u8 feature_type;
int ret;
u8 state_of_charge;
int status, level;
@ -1700,8 +1691,7 @@ static int hidpp20_query_battery_info_1004(struct hidpp_device *hidpp)
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_UNIFIED_BATTERY,
&hidpp->battery.feature_index,
&feature_type);
&hidpp->battery.feature_index);
if (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)
{
u8 feature_type;
int ret;
ret = hidpp_root_get_feature(hidpp,
return hidpp_root_get_feature(hidpp,
HIDPP_PAGE_WIRELESS_DEVICE_STATUS,
feature_index, &feature_type);
return ret;
feature_index);
}
/* -------------------------------------------------------------------------- */
@ -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)
{
u8 feature_type;
if (hidpp->battery.adc_measurement_feature_index == 0xff) {
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_ADC_MEASUREMENT,
&hidpp->battery.adc_measurement_feature_index,
&feature_type);
&hidpp->battery.adc_measurement_feature_index);
if (ret)
return ret;
@ -2014,15 +1996,13 @@ static int hidpp_hrs_set_highres_scrolling_mode(struct hidpp_device *hidpp,
bool enabled, u8 *multiplier)
{
u8 feature_index;
u8 feature_type;
int ret;
u8 params[1];
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
&feature_index,
&feature_type);
&feature_index);
if (ret)
return ret;
@ -2049,12 +2029,11 @@ static int hidpp_hrw_get_wheel_capability(struct hidpp_device *hidpp,
u8 *multiplier)
{
u8 feature_index;
u8 feature_type;
int ret;
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
&feature_index, &feature_type);
&feature_index);
if (ret)
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)
{
u8 feature_index;
u8 feature_type;
int ret;
u8 params[1];
struct hidpp_report response;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
&feature_index, &feature_type);
&feature_index);
if (ret)
return ret;
@ -2111,14 +2089,12 @@ static int hidpp_solar_request_battery_event(struct hidpp_device *hidpp)
{
struct hidpp_report response;
u8 params[2] = { 1, 1 };
u8 feature_type;
int ret;
if (hidpp->battery.feature_index == 0xff) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_SOLAR_KEYBOARD,
&hidpp->battery.solar_feature_index,
&feature_type);
&hidpp->battery.solar_feature_index);
if (ret)
return ret;
}
@ -2522,7 +2498,7 @@ static void hidpp_ff_work_handler(struct work_struct *w)
/* regular effect destroyed */
data->effect_ids[wd->params[0]-1] = -1;
else if (wd->effect_id >= HIDPP_FF_EFFECTID_AUTOCENTER)
/* autocenter spring destoyed */
/* autocenter spring destroyed */
data->slot_autocenter = 0;
break;
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 hidpp_touchpad_raw_info raw_info = {0};
u8 feature_type;
int ret;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_TOUCHPAD_RAW_XY,
&wd->mt_feature_index, &feature_type);
&wd->mt_feature_index);
if (ret)
/* means that the device is not powered up */
return ret;
@ -3296,13 +3271,13 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size)
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);
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);
v = hid_snto32(data[6], 8);
v = sign_extend32(data[6], 7);
if (v != 0)
hidpp_scroll_counter_handle_scroll(hidpp->input,
&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 hidpp_touchpad_fw_items items = {};
int ret;
u8 feature_type;
if (!k400->feature_index) {
ret = hidpp_root_get_feature(hidpp,
HIDPP_PAGE_TOUCHPAD_FW_ITEMS,
&k400->feature_index, &feature_type);
&k400->feature_index);
if (ret)
/* means that the device is not powered up */
return ret;
@ -3439,14 +3413,13 @@ static int g920_get_config(struct hidpp_device *hidpp,
struct hidpp_ff_private_data *data)
{
struct hidpp_report response;
u8 feature_type;
int ret;
memset(data, 0, sizeof(*data));
/* Find feature and store for later use */
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
&data->feature_index, &feature_type);
&data->feature_index);
if (ret)
return ret;
@ -3735,17 +3708,16 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
if (hidpp->protocol_major >= 2) {
u8 feature_index;
u8 feature_type;
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HIRES_WHEEL,
&feature_index, &feature_type);
&feature_index);
if (!ret) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scroll wheel\n");
return 0;
}
ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_HI_RESOLUTION_SCROLLING,
&feature_index, &feature_type);
&feature_index);
if (!ret) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");

View File

@ -227,7 +227,9 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
touch_minor = tdata[4];
state = tdata[7] & TOUCH_STATE_MASK;
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;
x = (tdata[1] << 27 | tdata[0] << 19) >> 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
* vertical touch motions.
*/
if (emulate_scroll_wheel && (input->id.product !=
USB_DEVICE_ID_APPLE_MAGICTRACKPAD2)) {
if (emulate_scroll_wheel &&
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2 &&
input->id.product != USB_DEVICE_ID_APPLE_MAGICTRACKPAD2_USBC) {
unsigned long now = jiffies;
int step_x = msc->touches[id].scroll_x - x;
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_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);
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_event(input, EV_MSC, MSC_RAW, tdata[7]);
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]);
}
}
@ -493,7 +500,9 @@ static int magicmouse_raw_event(struct hid_device *hdev,
magicmouse_emit_buttons(msc, clicks & 3);
input_report_rel(input, REL_X, x);
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_report_key(input, BTN_MOUSE, clicks & 1);
} 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_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
* automatically personalized, e.g., "José Expósito's Trackpad".
* 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);
input_abs_set_res(input, ABS_MT_POSITION_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_PRESSURE, 0, 253, 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);
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(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 */
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)
return -1;
@ -721,7 +737,8 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
int ret;
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) {
feature_size = sizeof(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 ||
(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;
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 &&
(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;
if (!msc->input) {
@ -850,7 +870,8 @@ static int magicmouse_probe(struct hid_device *hdev,
else if (id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2)
report = hid_register_report(hdev, HID_INPUT_REPORT,
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)
report = hid_register_report(hdev, HID_INPUT_REPORT,
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 &&
(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) {
hid_info(hdev,
"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 },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
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);

View File

@ -31,6 +31,7 @@
* [1] https://gitlab.freedesktop.org/libevdev/hid-tools
*/
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
@ -83,6 +84,13 @@ enum latency_mode {
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_ACTIVE_SLOTS 1
#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_usage *usage,
enum latency_mode latency,
bool surface_switch,
bool button_switch,
enum report_mode report_mode,
bool *inputmode_found)
{
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;
case HID_DG_SURFACESWITCH:
field->value[index] = surface_switch;
field->value[index] = !!(report_mode & TOUCHPAD_REPORT_CONTACTS);
return true;
case HID_DG_BUTTONSWITCH:
field->value[index] = button_switch;
field->value[index] = !!(report_mode & TOUCHPAD_REPORT_BUTTONS);
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,
bool surface_switch, bool button_switch)
enum report_mode report_mode)
{
struct hid_report_enum *rep_enum;
struct hid_report *rep;
@ -1586,8 +1593,7 @@ static void mt_set_modes(struct hid_device *hdev, enum latency_mode latency,
rep->field[i],
usage,
latency,
surface_switch,
button_switch,
report_mode,
&inputmode_found))
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",
hdev->name);
mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true);
mt_set_modes(hdev, HID_LATENCY_NORMAL, TOUCHPAD_REPORT_ALL);
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 */
if ((td->mtclass.quirks & MT_QUIRK_DISABLE_WAKEUP) ||
!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
mt_set_modes(hdev, HID_LATENCY_HIGH, true, true);
mt_set_modes(hdev, HID_LATENCY_HIGH, TOUCHPAD_REPORT_ALL);
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)
{
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;
}
@ -1864,7 +1870,7 @@ static int mt_resume(struct hid_device *hdev)
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;
}

View File

@ -296,7 +296,7 @@ static void picolcd_fb_destroy(struct fb_info *info)
/* make sure no work is deferred */
fb_deferred_io_cleanup(info);
/* No thridparty should ever unregister our framebuffer! */
/* No thirdparty should ever unregister our framebuffer! */
WARN_ON(fbdata->picolcd != NULL);
vfree((u8 *)info->fix.smem_start);

View File

@ -946,7 +946,7 @@ hid_sensor_register_platform_device(struct platform_device *pdev,
memcpy(real_usage, match->luid, 4);
/* usage id are all lowcase */
/* usage id are all lowercase */
for (c = real_usage; *c != '\0'; c++)
*c = tolower(*c);

View File

@ -1379,7 +1379,8 @@ static int sony_leds_init(struct sony_sc *sc)
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
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) {
sc->led_count = 4;

View File

@ -253,7 +253,7 @@ enum
ID_CONTROLLER_DECK_STATE = 9
};
/* String attribute idenitifiers */
/* String attribute identifiers */
enum {
ATTRIB_STR_BOARD_SERIAL,
ATTRIB_STR_UNIT_SERIAL,

View File

@ -411,6 +411,15 @@ static void steelseries_headset_fetch_battery(struct hid_device *hdev)
"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)
{
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:
val->intval = sd->battery_capacity;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = battery_capacity_to_level(sd->battery_capacity);
break;
default:
ret = -EINVAL;
break;
@ -469,6 +481,7 @@ static enum power_supply_property steelseries_headset_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
};
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,
"Parsing raw event for Arctis 1 headset (%*ph)\n", size, read_buf);
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;
}
if (read_buf[2] == 0x01) {
connected = false;
capacity = 100;
@ -631,6 +647,7 @@ static int steelseries_headset_raw_event(struct hid_device *hdev,
power_supply_changed(sd->battery);
}
request_battery:
spin_lock_irqsave(&sd->lock, flags);
if (!sd->removed)
schedule_delayed_work(&sd->battery_work,

View File

@ -144,9 +144,9 @@ MODULE_DEVICE_TABLE(of, i2c_hid_of_match);
#endif
static const struct i2c_device_id i2c_hid_of_id_table[] = {
{ "hid", 0 },
{ "hid-over-i2c", 0 },
{ },
{ "hid" },
{ "hid-over-i2c" },
{ }
};
MODULE_DEVICE_TABLE(i2c, i2c_hid_of_id_table);

View File

@ -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 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 = {
.name = KBUILD_MODNAME,
.id_table = ish_pci_tbl,
@ -388,6 +432,7 @@ static struct pci_driver ish_driver = {
.remove = ish_remove,
.shutdown = ish_shutdown,
.driver.pm = &ish_pm_ops,
.dev_groups = ish_firmware_groups,
};
module_pci_driver(ish_driver);

View File

@ -793,7 +793,7 @@ static int load_fw_from_host(struct ishtp_cl_data *client_data)
if (rv < 0)
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);
if (rv < 0)

View File

@ -70,10 +70,10 @@ static void process_recv(struct ishtp_cl *hid_ishtp_cl, void *recv_buf,
unsigned char *payload;
struct device_info *dev_info;
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;
struct report_list *reports_list;
char *reports;
struct report *report;
size_t report_len;
struct ishtp_cl_data *client_data = ishtp_get_client_data(hid_ishtp_cl);
int curr_hid_dev = client_data->cur_hid_dev;
@ -280,14 +280,13 @@ do_get_report:
case HOSTIF_PUBLISH_INPUT_REPORT_LIST:
report_type = HID_INPUT_REPORT;
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++) {
recv_msg = (struct hostif_msg *)(reports +
sizeof(uint16_t));
report_len = *(uint16_t *)reports;
payload = reports + sizeof(uint16_t) +
sizeof(struct hostif_msg_hdr);
recv_msg = container_of(&report->msg,
struct hostif_msg, hdr);
report_len = report->size;
payload = recv_msg->payload;
payload_len = report_len -
sizeof(struct hostif_msg_hdr);
@ -304,7 +303,7 @@ do_get_report:
0);
}
reports += sizeof(uint16_t) + report_len;
report += sizeof(*report) + payload_len;
}
break;
default:
@ -316,12 +315,12 @@ do_get_report:
}
if (!cur_pos && cur_pos + payload_len +
sizeof(struct hostif_msg) < total_len)
msg_len = payload_len + sizeof(struct hostif_msg);
if (!cur_pos && cur_pos + msg_len < total_len)
++client_data->multi_packet_cnt;
cur_pos += payload_len + sizeof(struct hostif_msg);
payload += payload_len + sizeof(struct hostif_msg);
cur_pos += msg_len;
payload += msg_len;
} while (cur_pos < total_len);
}

View File

@ -31,6 +31,7 @@ struct hostif_msg_hdr {
struct hostif_msg {
struct hostif_msg_hdr hdr;
uint8_t payload[];
} __packed;
struct hostif_msg_to_sensor {
@ -52,15 +53,17 @@ struct ishtp_version {
uint16_t build;
} __packed;
struct report {
uint16_t size;
struct hostif_msg_hdr msg;
} __packed;
/* struct for ISHTP aggregated input data */
struct report_list {
uint16_t total_size;
uint8_t num_of_reports;
uint8_t flags;
struct {
uint16_t size_of_report;
uint8_t report[1];
} __packed reports[1];
struct report reports[];
} __packed;
/* HOSTIF commands */

View File

@ -863,7 +863,7 @@ static void ipc_tx_send(void *prm)
/* Send ipc fragment */
ishtp_hdr.length = dev->mtu;
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);
cl->tx_offs += dev->mtu;
rem = cl_msg->send_buf.size - cl->tx_offs;

View File

@ -140,6 +140,13 @@ struct ishtp_driver_data {
char *fw_generation;
};
struct ish_version {
u16 major;
u16 minor;
u16 hotfix;
u16 build;
};
/**
* struct ishtp_device - ISHTP private device struct
*/
@ -236,6 +243,11 @@ struct ishtp_device {
/* Dump to trace buffers if enabled*/
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 */
unsigned int ipc_rx_cnt;
unsigned long long ipc_rx_bytes_cnt;

View File

@ -308,6 +308,28 @@ static int request_ish_firmware(const struct firmware **firmware_p,
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
* @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_start start = { .header = cpu_to_le32(start_hdr.val32), };
union loader_recv_message recv_msg;
struct ish_global_manifest manifest;
const struct firmware *ish_fw;
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
u32 fragment_size;
@ -372,7 +395,7 @@ void ishtp_loader_work(struct work_struct *work)
if (rv)
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_minor,
recv_msg.query_ack.version_hotfix,
@ -390,6 +413,16 @@ void ishtp_loader_work(struct work_struct *work)
continue; /* try again if failed */
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;
} while (--retry);

View File

@ -10,6 +10,7 @@
#include <linux/bits.h>
#include <linux/jiffies.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include "ishtp-dev.h"
@ -228,4 +229,37 @@ struct ish_firmware_variant {
*/
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_ */

View File

@ -1100,7 +1100,7 @@ static int usbhid_start(struct hid_device *hid)
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 &&
dev->speed == USB_SPEED_HIGH) {
interval = fls(endpoint->bInterval*8);

View File

@ -1353,9 +1353,9 @@ static void wacom_intuos_pro2_bt_pen(struct wacom_wac *wacom)
rotation -= 1800;
input_report_abs(pen_input, ABS_TILT_X,
(char)frame[7]);
(signed char)frame[7]);
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_WHEEL,
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;
return;
case HID_DG_INVERT:
wacom_wac->hid_data.invert_state = value;
wacom_wac->hid_data.eraser |= value;
return;
case HID_DG_ERASER:
wacom_wac->hid_data.eraser |= value;
fallthrough;
case HID_DG_TIPSWITCH:
wacom_wac->hid_data.tipswitch |= value;
return;
@ -2565,7 +2567,7 @@ static void wacom_wac_pen_report(struct hid_device *hdev,
if (entering_range) { /* first in range */
/* 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;
else if (wacom_wac->features.quirks & WACOM_QUIRK_AESPEN)
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.eraser = false;
input_sync(input);
}

View File

@ -300,7 +300,7 @@ struct hid_data {
__s16 inputmode_index; /* InputMode HID feature index in the report */
bool sense_state;
bool inrange_state;
bool invert_state;
bool eraser;
bool tipswitch;
bool barrelswitch;
bool barrelswitch2;

View File

@ -359,6 +359,7 @@ struct hid_item {
* | @HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP:
* | @HID_QUIRK_HAVE_SPECIAL_DRIVER:
* | @HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE:
* | @HID_QUIRK_IGNORE_SPECIAL_DRIVER
* | @HID_QUIRK_FULLSPEED_INTERVAL:
* | @HID_QUIRK_NO_INIT_REPORTS:
* | @HID_QUIRK_NO_IGNORE:
@ -384,6 +385,7 @@ struct hid_item {
#define HID_QUIRK_HAVE_SPECIAL_DRIVER BIT(19)
#define HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE BIT(20)
#define HID_QUIRK_NOINVERT BIT(21)
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
#define HID_QUIRK_FULLSPEED_INTERVAL BIT(28)
#define HID_QUIRK_NO_INIT_REPORTS BIT(29)
#define HID_QUIRK_NO_IGNORE BIT(30)
@ -599,15 +601,17 @@ enum hid_battery_status {
struct hid_driver;
struct hid_ll_driver;
struct hid_device { /* device report descriptor */
const __u8 *dev_rdesc;
unsigned dev_rsize;
const __u8 *rdesc;
unsigned rsize;
struct hid_device {
const __u8 *dev_rdesc; /* device report descriptor */
const __u8 *bpf_rdesc; /* bpf modified report descriptor, if any */
const __u8 *rdesc; /* currently used report descriptor */
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 */
unsigned collection_size; /* Number of allocated hid_collections */
unsigned maxcollection; /* Number of parsed collections */
unsigned maxapplication; /* Number of applications */
unsigned int maxcollection; /* Number of parsed collections */
unsigned int maxapplication; /* Number of applications */
__u16 bus; /* BUS ID */
__u16 group; /* Report group */
__u32 vendor; /* Vendor ID */
@ -974,7 +978,6 @@ const struct hid_device_id *hid_match_device(struct hid_device *hdev,
struct hid_driver *hdrv);
bool hid_compare_device_paths(struct hid_device *hdev_a,
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,
unsigned offset, unsigned n);

View File

@ -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_destroy_device(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 */
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
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_destroy_device(struct hid_device *hid) {}
static inline int hid_bpf_device_init(struct hid_device *hid) { return 0; }
/*
* This specialized allocator has to be a macro for its allocations to be
* 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))
static inline const u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, const u8 *rdesc,
unsigned int *size) { return rdesc; }
#endif /* CONFIG_HID_BPF */

View File

@ -232,7 +232,7 @@ $(BPF_SKELS): %.skel.h: %.bpf.o $(BPFTOOL) | $(OUTPUT)
$(Q)$(BPFTOOL) gen object $(<:.o=.linked1.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,,$@)
$(Q)$(CC) $(CFLAGS) -c $(filter %.c,$^) $(LDLIBS) -o $@

View File

@ -4,13 +4,6 @@
#include "hid_common.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 {
__u8 data[10];
unsigned int hid;
@ -21,11 +14,8 @@ struct hid_hw_request_syscall_args {
};
FIXTURE(hid_bpf) {
int dev_id;
int uhid_fd;
struct uhid_device hid;
int hidraw_fd;
int hid_id;
pthread_t tid;
struct hid *skel;
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) {
void *uhid_err;
uhid_destroy(_metadata, self->uhid_fd);
uhid_destroy(_metadata, &self->hid);
detach_bpf(self);
pthread_join(self->tid, &uhid_err);
pthread_join(self->hid.tid, &uhid_err);
}
#define TEARDOWN_LOG(fmt, ...) do { \
TH_LOG(fmt, ##__VA_ARGS__); \
hid_bpf_teardown(_metadata, self, variant); \
} while (0)
struct specific_device {
const char test_name[64];
__u16 bus;
__u32 vid;
__u32 pid;
};
FIXTURE_SETUP(hid_bpf)
{
time_t t;
const struct specific_device *match = NULL;
int err;
/* initialize random number generator */
srand((unsigned int)time(&t));
const struct specific_device devices[] = {
{
.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 */
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);
err = setup_uhid(_metadata, &self->hid, match->bus, match->vid, match->pid,
rdesc, sizeof(rdesc));
ASSERT_OK(err);
}
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);
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
@ -157,7 +164,7 @@ static void load_programs(const struct test_program programs[],
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");
}
@ -192,7 +199,7 @@ TEST_F(hid_bpf, raw_event)
/* inject one event */
buf[0] = 1;
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 */
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));
buf[0] = 1;
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 */
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 */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -252,7 +259,7 @@ TEST_F(hid_bpf, subprog_raw_event)
memset(buf, 0, sizeof(buf));
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -303,7 +310,7 @@ TEST_F(hid_bpf, test_attach_detach)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -326,14 +333,14 @@ TEST_F(hid_bpf, test_attach_detach)
/* detach the program */
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");
/* inject another event */
memset(buf, 0, sizeof(buf));
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -352,7 +359,7 @@ TEST_F(hid_bpf, test_attach_detach)
memset(buf, 0, sizeof(buf));
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -382,7 +389,7 @@ TEST_F(hid_bpf, test_hid_change_report)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -412,7 +419,7 @@ TEST_F(hid_bpf, test_hid_user_input_report_call)
LOAD_BPF;
args.hid = self->hid_id;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
args.data[1] = 2; /* report ID */
args.data[2] = 42; /* report ID */
@ -458,7 +465,7 @@ TEST_F(hid_bpf, test_hid_user_output_report_call)
LOAD_BPF;
args.hid = self->hid_id;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
args.data[1] = 2; /* report ID */
args.data[2] = 42; /* report ID */
@ -506,7 +513,7 @@ TEST_F(hid_bpf, test_hid_user_raw_request_call)
LOAD_BPF;
args.hid = self->hid_id;
args.hid = self->hid.hid_id;
args.data[0] = 1; /* report ID */
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 */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -565,7 +572,7 @@ TEST_F(hid_bpf, test_hid_filter_raw_request_call)
/* detach the program */
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");
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 */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -667,7 +674,7 @@ TEST_F(hid_bpf, test_hid_filter_output_report_call)
/* detach the program */
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");
err = write(self->hidraw_fd, buf, 3);
@ -742,7 +749,7 @@ TEST_F(hid_bpf, test_multiply_events_wq)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -780,7 +787,7 @@ TEST_F(hid_bpf, test_multiply_events)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -816,7 +823,7 @@ TEST_F(hid_bpf, test_hid_infinite_loop_input_report_call)
buf[1] = 2;
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 */
memset(buf, 0, sizeof(buf));
@ -867,7 +874,7 @@ TEST_F(hid_bpf, test_hid_attach_flags)
/* inject one event */
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 */
memset(buf, 0, sizeof(buf));
@ -878,6 +885,54 @@ TEST_F(hid_bpf, test_hid_attach_flags)
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,
* retrieve and open the matching hidraw node,

View File

@ -19,6 +19,16 @@
__typeof__(b) _b = (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[] = {
0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */
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;
char buf[25];
@ -133,10 +145,10 @@ static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb)
ev.type = UHID_CREATE;
strcpy((char *)ev.u.create.name, buf);
ev.u.create.rd_data = rdesc;
ev.u.create.rd_size = sizeof(rdesc);
ev.u.create.bus = BUS_USB;
ev.u.create.vendor = 0x0001;
ev.u.create.product = 0x0a37;
ev.u.create.rd_size = rdesc_size;
ev.u.create.bus = bus;
ev.u.create.vendor = vid;
ev.u.create.product = pid;
ev.u.create.version = 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);
}
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;
memset(&ev, 0, sizeof(ev));
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)
@ -281,7 +293,8 @@ static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid
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;
@ -294,36 +307,20 @@ static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf,
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;
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 target[20] = "";
char phys[512];
char uevent[1024];
char temp[512];
int fd, nread;
bool found = false;
snprintf(target, sizeof(target), "%04X:%04X:%04X.*", hid->bus, hid->vid, hid->pid);
if (fnmatch(target, dir->d_name, 0))
return false;
@ -334,7 +331,7 @@ static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *d
if (fd < 0)
return false;
sprintf(phys, "PHYS=%d", dev_id);
sprintf(phys, "PHYS=%d", hid->dev_id);
nread = read(fd, temp, ARRAY_SIZE(temp));
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;
}
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 *str_id;
@ -360,10 +357,10 @@ static int get_hid_id(int dev_id)
d = opendir(workdir);
if (d) {
while ((dir = readdir(d)) != NULL) {
if (!match_sysfs_device(dev_id, workdir, dir))
if (!match_sysfs_device(hid, workdir, dir))
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);
break;
@ -377,7 +374,7 @@ static int get_hid_id(int dev_id)
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";
char sysfs[1024];
@ -394,7 +391,7 @@ static int get_hidraw(int dev_id)
continue;
while ((dir = readdir(d)) != NULL) {
if (!match_sysfs_device(dev_id, workdir, dir))
if (!match_sysfs_device(hid, workdir, dir))
continue;
sprintf(sysfs, "%s/%s/hidraw", workdir, dir->d_name);
@ -421,12 +418,12 @@ static int get_hidraw(int dev_id)
return found;
}
static int open_hidraw(int dev_id)
static int open_hidraw(struct uhid_device *hid)
{
int hidraw_number;
char hidraw_path[64] = { 0 };
hidraw_number = get_hidraw(dev_id);
hidraw_number = get_hidraw(hid);
if (hidraw_number < 0)
return hidraw_number;
@ -434,3 +431,44 @@ static int open_hidraw(int dev_id)
sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number);
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;
}

View File

@ -9,11 +9,8 @@
#endif /* HIDIOCREVOKE */
FIXTURE(hidraw) {
int dev_id;
int uhid_fd;
struct uhid_device hid;
int hidraw_fd;
int hid_id;
pthread_t tid;
};
static void close_hidraw(FIXTURE_DATA(hidraw) * self)
{
@ -25,10 +22,10 @@ static void close_hidraw(FIXTURE_DATA(hidraw) * self)
FIXTURE_TEARDOWN(hidraw) {
void *uhid_err;
uhid_destroy(_metadata, self->uhid_fd);
uhid_destroy(_metadata, &self->hid);
close_hidraw(self);
pthread_join(self->tid, &uhid_err);
pthread_join(self->hid.tid, &uhid_err);
}
#define TEARDOWN_LOG(fmt, ...) do { \
TH_LOG(fmt, ##__VA_ARGS__); \
@ -37,25 +34,12 @@ FIXTURE_TEARDOWN(hidraw) {
FIXTURE_SETUP(hidraw)
{
time_t t;
int err;
/* initialize random number generator */
srand((unsigned int)time(&t));
err = setup_uhid(_metadata, &self->hid, BUS_USB, 0x0001, 0x0a37, rdesc, sizeof(rdesc));
ASSERT_OK(err);
self->dev_id = rand() % 1024;
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);
self->hidraw_fd = open_hidraw(&self->hid);
ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw");
}
@ -79,7 +63,7 @@ TEST_F(hidraw, raw_event)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -101,7 +85,7 @@ TEST_F(hidraw, raw_event_revoked)
/* inject one event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -117,7 +101,7 @@ TEST_F(hidraw, raw_event_revoked)
/* inject one other event */
buf[0] = 1;
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 */
memset(buf, 0, sizeof(buf));
@ -161,7 +145,7 @@ TEST_F(hidraw, poll_revoked)
/* inject one event */
buf[0] = 1;
buf[1] = 42;
uhid_send_event(_metadata, self->uhid_fd, buf, 6);
uhid_send_event(_metadata, &self->hid, buf, 6);
while (true) {
ready = poll(pfds, 1, 5000);

View File

@ -598,3 +598,15 @@ SEC(".struct_ops.link")
struct hid_bpf_ops 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,
};

View File

@ -84,10 +84,14 @@ struct hid_bpf_ops {
struct hid_device *hdev;
};
#define BIT(n) (1U << n)
#ifndef BPF_F_BEFORE
#define BPF_F_BEFORE (1U << 3)
#define BPF_F_BEFORE BIT(3)
#endif
#define HID_QUIRK_IGNORE_SPECIAL_DRIVER BIT(22)
/* following are kfuncs exported by HID for HID-BPF */
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,