Commit 6c71297e authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-linus-2023022201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid

Pull HID updates from Benjamin Tissoires:

 - HID-BPF infrastructure: this allows to start using HID-BPF. Note that
   the mechanism to ship HID-BPF program through the kernel tree is
   still not implemented yet (but is planned).

   This should be a no-op for 99% of users. Also we are gaining
   kselftests for the HID tree (Benjamin Tissoires)

 - Some UAF fixes in workers when using uhid (Pietro Borrello & Benjamin
   Tissoires)

 - Constify hid_ll_driver (Thomas Weißschuh)

 - Allow more custom IIO sensors through HID (Philipp Jungkamp)

 - Logitech HID++ fixes for scroll wheel, protocol and debug (Bastien
   Nocera)

 - Some new device support: Steam Deck (Vicki Pfau), UClogic (José
   Expósito), Logitech G923 Xbox Edition steering wheel (Walt Holman),
   EVision keyboards (Philippe Valembois)

 - other assorted code cleanups and fixes

* tag 'for-linus-2023022201' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (99 commits)
  HID: mcp-2221: prevent UAF in delayed work
  hid: bigben_probe(): validate report count
  HID: asus: use spinlock to safely schedule workers
  HID: asus: use spinlock to protect concurrent accesses
  HID: bigben: use spinlock to safely schedule workers
  HID: bigben_worker() remove unneeded check on report_field
  HID: bigben: use spinlock to protect concurrent accesses
  HID: logitech-hidpp: Add myself to authors
  HID: logitech-hidpp: Retry commands when device is busy
  HID: logitech-hidpp: Add more debug statements
  HID: Add support for Logitech G923 Xbox Edition steering wheel
  HID: logitech-hidpp: Add Signature M650
  HID: logitech-hidpp: Remove HIDPP_QUIRK_NO_HIDINPUT quirk
  HID: logitech-hidpp: Don't restart communication if not necessary
  HID: logitech-hidpp: Add constants for HID++ 2.0 error codes
  Revert "HID: logitech-hidpp: add a module parameter to keep firmware gestures"
  HID: logitech-hidpp: Hard-code HID++ 1.0 fast scroll support
  HID: i2c-hid: goodix: Add mainboard-vddio-supply
  dt-bindings: HID: i2c-hid: goodix: Add mainboard-vddio-supply
  HID: i2c-hid: goodix: Stop tying the reset line to the regulator
  ...
parents d5176cdb 904e28c6
...@@ -36,6 +36,13 @@ properties: ...@@ -36,6 +36,13 @@ properties:
vdd-supply: vdd-supply:
description: The 3.3V supply to the touchscreen. description: The 3.3V supply to the touchscreen.
mainboard-vddio-supply:
description:
The supply on the main board needed to power up IO signals going
to the touchscreen. This supply need not go to the touchscreen
itself as long as it allows the main board to make signals compatible
with what the touchscreen is expecting for its IO rails.
required: required:
- compatible - compatible
- reg - reg
......
...@@ -9,7 +9,7 @@ Currently ALPS HID driver supports U1 Touchpad device. ...@@ -9,7 +9,7 @@ Currently ALPS HID driver supports U1 Touchpad device.
U1 device basic information. U1 device basic information.
========== ====== ========== ======
Vender ID 0x044E Vendor ID 0x044E
Product ID 0x120B Product ID 0x120B
Version ID 0x0121 Version ID 0x0121
========== ====== ========== ======
......
This diff is collapsed.
...@@ -8,7 +8,7 @@ Introduction ...@@ -8,7 +8,7 @@ Introduction
In addition to the normal input type HID devices, USB also uses the In addition to the normal input type HID devices, USB also uses the
human interface device protocols for things that are not really human human interface device protocols for things that are not really human
interfaces, but have similar sorts of communication needs. The two big interfaces, but have similar sorts of communication needs. The two big
examples for this are power devices (especially uninterruptable power examples for this are power devices (especially uninterruptible power
supplies) and monitor control on higher end monitors. supplies) and monitor control on higher end monitors.
To support these disparate requirements, the Linux USB system provides To support these disparate requirements, the Linux USB system provides
......
...@@ -163,7 +163,7 @@ HIDIOCGOUTPUT(len): ...@@ -163,7 +163,7 @@ HIDIOCGOUTPUT(len):
Get an Output Report Get an Output Report
This ioctl will request an output report from the device using the control This ioctl will request an output report from the device using the control
endpoint. Typically, this is used to retrive the initial state of endpoint. Typically, this is used to retrieve the initial state of
an output report of a device, before an application updates it as necessary either an output report of a device, before an application updates it as necessary either
via a HIDIOCSOUTPUT request, or the regular device write() interface. The format via a HIDIOCSOUTPUT request, or the regular device write() interface. The format
of the buffer issued with this report is identical to that of HIDIOCGFEATURE. of the buffer issued with this report is identical to that of HIDIOCGFEATURE.
......
...@@ -11,6 +11,7 @@ Human Interface Devices (HID) ...@@ -11,6 +11,7 @@ Human Interface Devices (HID)
hidraw hidraw
hid-sensor hid-sensor
hid-transport hid-transport
hid-bpf
uhid uhid
......
...@@ -199,7 +199,7 @@ the sender that the memory region for that message may be reused. ...@@ -199,7 +199,7 @@ the sender that the memory region for that message may be reused.
DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message DMA initialization is started with host sending DMA_ALLOC_NOTIFY bus message
(that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK. (that includes RX buffer) and FW responds with DMA_ALLOC_NOTIFY_ACK.
Additionally to DMA address communication, this sequence checks capabilities: Additionally to DMA address communication, this sequence checks capabilities:
if thw host doesn't support DMA, then it won't send DMA allocation, so FW can't if the host doesn't support DMA, then it won't send DMA allocation, so FW can't
send DMA; if FW doesn't support DMA then it won't respond with send DMA; if FW doesn't support DMA then it won't respond with
DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers. DMA_ALLOC_NOTIFY_ACK, in which case host will not use DMA transfers.
Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER, Here ISH acts as busmaster DMA controller. Hence when host sends DMA_XFER,
......
...@@ -9069,9 +9069,12 @@ M: Benjamin Tissoires <benjamin.tissoires@redhat.com> ...@@ -9069,9 +9069,12 @@ M: Benjamin Tissoires <benjamin.tissoires@redhat.com>
L: linux-input@vger.kernel.org L: linux-input@vger.kernel.org
S: Maintained S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
F: Documentation/hid/
F: drivers/hid/ F: drivers/hid/
F: include/linux/hid* F: include/linux/hid*
F: include/uapi/linux/hid* F: include/uapi/linux/hid*
F: samples/hid/
F: tools/testing/selftests/hid/
HID LOGITECH DRIVERS HID LOGITECH DRIVERS
R: Filipe Laíns <lains@riseup.net> R: Filipe Laíns <lains@riseup.net>
...@@ -9079,6 +9082,13 @@ L: linux-input@vger.kernel.org ...@@ -9079,6 +9082,13 @@ L: linux-input@vger.kernel.org
S: Maintained S: Maintained
F: drivers/hid/hid-logitech-* F: drivers/hid/hid-logitech-*
HID++ LOGITECH DRIVERS
R: Filipe Laíns <lains@riseup.net>
R: Bastien Nocera <hadess@hadess.net>
L: linux-input@vger.kernel.org
S: Maintained
F: drivers/hid/hid-logitech-hidpp.c
HID PLAYSTATION DRIVER HID PLAYSTATION DRIVER
M: Roderick Colenbrander <roderick.colenbrander@sony.com> M: Roderick Colenbrander <roderick.colenbrander@sony.com>
L: linux-input@vger.kernel.org L: linux-input@vger.kernel.org
......
...@@ -137,7 +137,7 @@ obj-$(CONFIG_CRYPTO) += crypto/ ...@@ -137,7 +137,7 @@ obj-$(CONFIG_CRYPTO) += crypto/
obj-$(CONFIG_SUPERH) += sh/ obj-$(CONFIG_SUPERH) += sh/
obj-y += clocksource/ obj-y += clocksource/
obj-$(CONFIG_DCA) += dca/ obj-$(CONFIG_DCA) += dca/
obj-$(CONFIG_HID) += hid/ obj-$(CONFIG_HID_SUPPORT) += hid/
obj-$(CONFIG_PPC_PS3) += ps3/ obj-$(CONFIG_PPC_PS3) += ps3/
obj-$(CONFIG_OF) += of/ obj-$(CONFIG_OF) += of/
obj-$(CONFIG_SSB) += ssb/ obj-$(CONFIG_SSB) += ssb/
......
CONFIG_KUNIT=y CONFIG_KUNIT=y
CONFIG_USB=y CONFIG_USB=y
CONFIG_USB_HID=y CONFIG_USB_HID=y
CONFIG_HID_BATTERY_STRENGTH=y
CONFIG_HID_UCLOGIC=y CONFIG_HID_UCLOGIC=y
CONFIG_HID_KUNIT_TEST=y CONFIG_HID_KUNIT_TEST=y
...@@ -2,13 +2,20 @@ ...@@ -2,13 +2,20 @@
# #
# HID driver configuration # HID driver configuration
# #
menu "HID support" menuconfig HID_SUPPORT
bool "HID bus support"
default y
depends on INPUT depends on INPUT
help
This option adds core support for human interface device (HID).
You will also need drivers from the following menu to make use of it.
if HID_SUPPORT
config HID config HID
tristate "HID bus support" tristate "HID bus core support"
depends on INPUT
default y default y
depends on INPUT
help help
A human interface device (HID) is a type of computer device that A human interface device (HID) is a type of computer device that
interacts directly with and takes input from humans. The term "HID" interacts directly with and takes input from humans. The term "HID"
...@@ -329,6 +336,13 @@ config HID_ELO ...@@ -329,6 +336,13 @@ config HID_ELO
Support for the ELO USB 4000/4500 touchscreens. Note that this is for Support for the ELO USB 4000/4500 touchscreens. Note that this is for
different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO. different devices than those handled by CONFIG_TOUCHSCREEN_USB_ELO.
config HID_EVISION
tristate "EVision Keyboards Support"
depends on HID
help
Support for some EVision keyboards. Note that this is needed only when
applying customization using userspace programs.
config HID_EZKEY config HID_EZKEY
tristate "Ezkey BTC 8193 keyboard" tristate "Ezkey BTC 8193 keyboard"
default !EXPERT default !EXPERT
...@@ -1018,13 +1032,21 @@ config HID_SPEEDLINK ...@@ -1018,13 +1032,21 @@ config HID_SPEEDLINK
Support for Speedlink Vicious and Divine Cezanne mouse. Support for Speedlink Vicious and Divine Cezanne mouse.
config HID_STEAM config HID_STEAM
tristate "Steam Controller support" tristate "Steam Controller/Deck support"
select POWER_SUPPLY select POWER_SUPPLY
help help
Say Y here if you have a Steam Controller if you want to use it Say Y here if you have a Steam Controller or Deck if you want to use it
without running the Steam Client. It supports both the wired and without running the Steam Client. It supports both the wired and
the wireless adaptor. the wireless adaptor.
config STEAM_FF
bool "Steam Deck force feedback support"
depends on HID_STEAM
select INPUT_FF_MEMLESS
help
Say Y here if you want to enable force feedback support for the Steam
Deck.
config HID_STEELSERIES config HID_STEELSERIES
tristate "Steelseries SRW-S1 steering wheel support" tristate "Steelseries SRW-S1 steering wheel support"
help help
...@@ -1264,6 +1286,7 @@ config HID_MCP2221 ...@@ -1264,6 +1286,7 @@ config HID_MCP2221
config HID_KUNIT_TEST config HID_KUNIT_TEST
tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS
depends on KUNIT=y depends on KUNIT=y
depends on HID_BATTERY_STRENGTH
depends on HID_UCLOGIC depends on HID_UCLOGIC
default KUNIT_ALL_TESTS default KUNIT_ALL_TESTS
help help
...@@ -1279,6 +1302,8 @@ config HID_KUNIT_TEST ...@@ -1279,6 +1302,8 @@ config HID_KUNIT_TEST
endmenu endmenu
source "drivers/hid/bpf/Kconfig"
endif # HID endif # HID
source "drivers/hid/usbhid/Kconfig" source "drivers/hid/usbhid/Kconfig"
...@@ -1291,4 +1316,4 @@ source "drivers/hid/amd-sfh-hid/Kconfig" ...@@ -1291,4 +1316,4 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig"
endmenu endif # HID_SUPPORT
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
hid-y := hid-core.o hid-input.o hid-quirks.o hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o
obj-$(CONFIG_HID_BPF) += bpf/
obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o obj-$(CONFIG_UHID) += uhid.o
...@@ -45,6 +47,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o ...@@ -45,6 +47,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELAN) += hid-elan.o obj-$(CONFIG_HID_ELAN) += hid-elan.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.o obj-$(CONFIG_HID_ELECOM) += hid-elecom.o
obj-$(CONFIG_HID_ELO) += hid-elo.o obj-$(CONFIG_HID_ELO) += hid-elo.o
obj-$(CONFIG_HID_EVISION) += hid-evision.o
obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o
obj-$(CONFIG_HID_FT260) += hid-ft260.o obj-$(CONFIG_HID_FT260) += hid-ft260.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
menu "AMD SFH HID Support" menu "AMD SFH HID Support"
depends on X86_64 || COMPILE_TEST depends on X86_64 || COMPILE_TEST
depends on PCI depends on PCI
depends on HID
config AMD_SFH_HID config AMD_SFH_HID
tristate "AMD Sensor Fusion Hub" tristate "AMD Sensor Fusion Hub"
depends on HID
help help
If you say yes to this option, support will be included for the If you say yes to this option, support will be included for the
AMD Sensor Fusion Hub. AMD Sensor Fusion Hub.
......
...@@ -112,7 +112,7 @@ void amdtp_hid_wakeup(struct hid_device *hid) ...@@ -112,7 +112,7 @@ void amdtp_hid_wakeup(struct hid_device *hid)
} }
} }
static struct hid_ll_driver amdtp_hid_ll_driver = { static const struct hid_ll_driver amdtp_hid_ll_driver = {
.parse = amdtp_hid_parse, .parse = amdtp_hid_parse,
.start = amdtp_hid_start, .start = amdtp_hid_start,
.stop = amdtp_hid_stop, .stop = amdtp_hid_stop,
......
# SPDX-License-Identifier: GPL-2.0-only
menu "HID-BPF support"
config HID_BPF
bool "HID-BPF support"
depends on BPF
depends on BPF_SYSCALL
depends on DYNAMIC_FTRACE_WITH_DIRECT_CALLS
help
This option allows to support eBPF programs on the HID subsystem.
eBPF programs can fix HID devices in a lighter way than a full
kernel patch and allow a lot more flexibility.
For documentation, see Documentation/hid/hid-bpf.rst
endmenu
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for HID-BPF
#
LIBBPF_INCLUDE = $(srctree)/tools/lib
obj-$(CONFIG_HID_BPF) += hid_bpf.o
CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
# SPDX-License-Identifier: GPL-2.0
OUTPUT := .output
abs_out := $(abspath $(OUTPUT))
CLANG ?= clang
LLC ?= llc
LLVM_STRIP ?= llvm-strip
TOOLS_PATH := $(abspath ../../../../tools)
BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
BPFTOOL_OUTPUT := $(abs_out)/bpftool
DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
BPFTOOL ?= $(DEFAULT_BPFTOOL)
LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
LIBBPF_OUTPUT := $(abs_out)/libbpf
LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a
INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
CFLAGS := -g -Wall
VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
$(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
../../../../vmlinux \
/sys/kernel/btf/vmlinux \
/boot/vmlinux-$(shell uname -r)
VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
ifeq ($(VMLINUX_BTF),)
$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
endif
ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
MAKEFLAGS += --no-print-directory
submake_extras := feature_display=0
endif
.DELETE_ON_ERROR:
.PHONY: all clean
all: entrypoints.lskel.h
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) entrypoints
entrypoints.lskel.h: $(OUTPUT)/entrypoints.bpf.o | $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton -L $< > $@
$(OUTPUT)/entrypoints.bpf.o: entrypoints.bpf.c $(OUTPUT)/vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
ifeq ($(VMLINUX_H),)
$(call msg,GEN,,$@)
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
else
$(call msg,CP,,$@)
$(Q)cp "$(VMLINUX_H)" $@
endif
$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
OUTPUT=$(abspath $(dir $@))/ prefix= \
DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers
ifeq ($(CROSS_COMPILE),)
$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ \
LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \
LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
else
$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
endif
WARNING:
If you change "entrypoints.bpf.c" do "make -j" in this directory to rebuild "entrypoints.skel.h".
Make sure to have clang 10 installed.
See Documentation/bpf/bpf_devel_QA.rst
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Benjamin Tissoires */
#include ".output/vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#define HID_BPF_MAX_PROGS 1024
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, HID_BPF_MAX_PROGS);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} hid_jmp_table SEC(".maps");
SEC("fmod_ret/__hid_bpf_tail_call")
int BPF_PROG(hid_tail_call, struct hid_bpf_ctx *hctx)
{
bpf_tail_call(ctx, &hid_jmp_table, hctx->index);
return 0;
}
char LICENSE[] SEC("license") = "GPL";
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _BPF_HID_BPF_DISPATCH_H
#define _BPF_HID_BPF_DISPATCH_H
#include <linux/hid.h>
struct hid_bpf_ctx_kern {
struct hid_bpf_ctx ctx;
u8 *data;
};
int hid_bpf_preload_skel(void);
void hid_bpf_free_links_and_skel(void);
int hid_bpf_get_prog_attach_type(int prog_fd);
int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type, int prog_fd,
__u32 flags);
void __hid_bpf_destroy_device(struct hid_device *hdev);
int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
struct hid_bpf_ctx_kern *ctx_kern);
int hid_bpf_reconnect(struct hid_device *hdev);
struct bpf_prog;
#endif
This diff is collapsed.
...@@ -98,6 +98,7 @@ struct asus_kbd_leds { ...@@ -98,6 +98,7 @@ struct asus_kbd_leds {
struct hid_device *hdev; struct hid_device *hdev;
struct work_struct work; struct work_struct work;
unsigned int brightness; unsigned int brightness;
spinlock_t lock;
bool removed; bool removed;
}; };
...@@ -490,21 +491,42 @@ static int rog_nkey_led_init(struct hid_device *hdev) ...@@ -490,21 +491,42 @@ static int rog_nkey_led_init(struct hid_device *hdev)
return ret; return ret;
} }
static void asus_schedule_work(struct asus_kbd_leds *led)
{
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
if (!led->removed)
schedule_work(&led->work);
spin_unlock_irqrestore(&led->lock, flags);
}
static void asus_kbd_backlight_set(struct led_classdev *led_cdev, static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
enum led_brightness brightness) enum led_brightness brightness)
{ {
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev); cdev);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
led->brightness = brightness; led->brightness = brightness;
schedule_work(&led->work); spin_unlock_irqrestore(&led->lock, flags);
asus_schedule_work(led);
} }
static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev) static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
{ {
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds, struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev); cdev);
enum led_brightness brightness;
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
brightness = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
return led->brightness; return brightness;
} }
static void asus_kbd_backlight_work(struct work_struct *work) static void asus_kbd_backlight_work(struct work_struct *work)
...@@ -512,11 +534,11 @@ static void asus_kbd_backlight_work(struct work_struct *work) ...@@ -512,11 +534,11 @@ static void asus_kbd_backlight_work(struct work_struct *work)
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work); struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 }; u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
int ret; int ret;
unsigned long flags;
if (led->removed) spin_lock_irqsave(&led->lock, flags);
return;
buf[4] = led->brightness; buf[4] = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf)); ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
if (ret < 0) if (ret < 0)
...@@ -584,6 +606,7 @@ static int asus_kbd_register_leds(struct hid_device *hdev) ...@@ -584,6 +606,7 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set; drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get; drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work); INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev); ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
if (ret < 0) { if (ret < 0) {
...@@ -1119,9 +1142,13 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -1119,9 +1142,13 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
static void asus_remove(struct hid_device *hdev) static void asus_remove(struct hid_device *hdev)
{ {
struct asus_drvdata *drvdata = hid_get_drvdata(hdev); struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
unsigned long flags;
if (drvdata->kbd_backlight) { if (drvdata->kbd_backlight) {
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true; drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
cancel_work_sync(&drvdata->kbd_backlight->work); cancel_work_sync(&drvdata->kbd_backlight->work);
} }
......
...@@ -174,6 +174,7 @@ static __u8 pid0902_rdesc_fixed[] = { ...@@ -174,6 +174,7 @@ static __u8 pid0902_rdesc_fixed[] = {
struct bigben_device { struct bigben_device {
struct hid_device *hid; struct hid_device *hid;
struct hid_report *report; struct hid_report *report;
spinlock_t lock;
bool removed; bool removed;
u8 led_state; /* LED1 = 1 .. LED4 = 8 */ u8 led_state; /* LED1 = 1 .. LED4 = 8 */
u8 right_motor_on; /* right motor off/on 0/1 */ u8 right_motor_on; /* right motor off/on 0/1 */
...@@ -184,18 +185,39 @@ struct bigben_device { ...@@ -184,18 +185,39 @@ struct bigben_device {
struct work_struct worker; struct work_struct worker;
}; };
static inline void bigben_schedule_work(struct bigben_device *bigben)
{
unsigned long flags;
spin_lock_irqsave(&bigben->lock, flags);
if (!bigben->removed)
schedule_work(&bigben->worker);
spin_unlock_irqrestore(&bigben->lock, flags);
}
static void bigben_worker(struct work_struct *work) static void bigben_worker(struct work_struct *work)
{ {
struct bigben_device *bigben = container_of(work, struct bigben_device *bigben = container_of(work,
struct bigben_device, worker); struct bigben_device, worker);
struct hid_field *report_field = bigben->report->field[0]; struct hid_field *report_field = bigben->report->field[0];
bool do_work_led = false;
if (bigben->removed || !report_field) bool do_work_ff = false;
u8 *buf;
u32 len;
unsigned long flags;
buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL);
if (!buf)
return; return;
len = hid_report_len(bigben->report);
/* LED work */
spin_lock_irqsave(&bigben->lock, flags);
if (bigben->work_led) { if (bigben->work_led) {
bigben->work_led = false; bigben->work_led = false;
do_work_led = true;
report_field->value[0] = 0x01; /* 1 = led message */ report_field->value[0] = 0x01; /* 1 = led message */
report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->led_state; report_field->value[2] = bigben->led_state;
...@@ -204,11 +226,22 @@ static void bigben_worker(struct work_struct *work) ...@@ -204,11 +226,22 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */ report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */
report_field->value[7] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); hid_output_report(bigben->report, buf);
}
spin_unlock_irqrestore(&bigben->lock, flags);
if (do_work_led) {
hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len,
bigben->report->type, HID_REQ_SET_REPORT);
} }
/* FF work */
spin_lock_irqsave(&bigben->lock, flags);
if (bigben->work_ff) { if (bigben->work_ff) {
bigben->work_ff = false; bigben->work_ff = false;
do_work_ff = true;
report_field->value[0] = 0x02; /* 2 = rumble effect message */ report_field->value[0] = 0x02; /* 2 = rumble effect message */
report_field->value[1] = 0x08; /* reserved value, always 8 */ report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->right_motor_on; report_field->value[2] = bigben->right_motor_on;
...@@ -217,8 +250,17 @@ static void bigben_worker(struct work_struct *work) ...@@ -217,8 +250,17 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */ report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 0x00; /* padding */ report_field->value[6] = 0x00; /* padding */
report_field->value[7] = 0x00; /* padding */ report_field->value[7] = 0x00; /* padding */
hid_hw_request(bigben->hid, bigben->report, HID_REQ_SET_REPORT); hid_output_report(bigben->report, buf);
} }
spin_unlock_irqrestore(&bigben->lock, flags);
if (do_work_ff) {
hid_hw_raw_request(bigben->hid, bigben->report->id, buf, len,
bigben->report->type, HID_REQ_SET_REPORT);
}
kfree(buf);
} }
static int hid_bigben_play_effect(struct input_dev *dev, void *data, static int hid_bigben_play_effect(struct input_dev *dev, void *data,
...@@ -228,6 +270,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data, ...@@ -228,6 +270,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
struct bigben_device *bigben = hid_get_drvdata(hid); struct bigben_device *bigben = hid_get_drvdata(hid);
u8 right_motor_on; u8 right_motor_on;
u8 left_motor_force; u8 left_motor_force;
unsigned long flags;
if (!bigben) { if (!bigben) {
hid_err(hid, "no device data\n"); hid_err(hid, "no device data\n");
...@@ -242,10 +285,13 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data, ...@@ -242,10 +285,13 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
if (right_motor_on != bigben->right_motor_on || if (right_motor_on != bigben->right_motor_on ||
left_motor_force != bigben->left_motor_force) { left_motor_force != bigben->left_motor_force) {
spin_lock_irqsave(&bigben->lock, flags);
bigben->right_motor_on = right_motor_on; bigben->right_motor_on = right_motor_on;
bigben->left_motor_force = left_motor_force; bigben->left_motor_force = left_motor_force;
bigben->work_ff = true; bigben->work_ff = true;
schedule_work(&bigben->worker); spin_unlock_irqrestore(&bigben->lock, flags);
bigben_schedule_work(bigben);
} }
return 0; return 0;
...@@ -259,6 +305,7 @@ static void bigben_set_led(struct led_classdev *led, ...@@ -259,6 +305,7 @@ static void bigben_set_led(struct led_classdev *led,
struct bigben_device *bigben = hid_get_drvdata(hid); struct bigben_device *bigben = hid_get_drvdata(hid);
int n; int n;
bool work; bool work;
unsigned long flags;
if (!bigben) { if (!bigben) {
hid_err(hid, "no device data\n"); hid_err(hid, "no device data\n");
...@@ -267,6 +314,7 @@ static void bigben_set_led(struct led_classdev *led, ...@@ -267,6 +314,7 @@ static void bigben_set_led(struct led_classdev *led,
for (n = 0; n < NUM_LEDS; n++) { for (n = 0; n < NUM_LEDS; n++) {
if (led == bigben->leds[n]) { if (led == bigben->leds[n]) {
spin_lock_irqsave(&bigben->lock, flags);
if (value == LED_OFF) { if (value == LED_OFF) {
work = (bigben->led_state & BIT(n)); work = (bigben->led_state & BIT(n));
bigben->led_state &= ~BIT(n); bigben->led_state &= ~BIT(n);
...@@ -274,10 +322,11 @@ static void bigben_set_led(struct led_classdev *led, ...@@ -274,10 +322,11 @@ static void bigben_set_led(struct led_classdev *led,
work = !(bigben->led_state & BIT(n)); work = !(bigben->led_state & BIT(n));
bigben->led_state |= BIT(n); bigben->led_state |= BIT(n);
} }
spin_unlock_irqrestore(&bigben->lock, flags);
if (work) { if (work) {
bigben->work_led = true; bigben->work_led = true;
schedule_work(&bigben->worker); bigben_schedule_work(bigben);
} }
return; return;
} }
...@@ -307,8 +356,12 @@ static enum led_brightness bigben_get_led(struct led_classdev *led) ...@@ -307,8 +356,12 @@ static enum led_brightness bigben_get_led(struct led_classdev *led)
static void bigben_remove(struct hid_device *hid) static void bigben_remove(struct hid_device *hid)
{ {
struct bigben_device *bigben = hid_get_drvdata(hid); struct bigben_device *bigben = hid_get_drvdata(hid);
unsigned long flags;
spin_lock_irqsave(&bigben->lock, flags);
bigben->removed = true; bigben->removed = true;
spin_unlock_irqrestore(&bigben->lock, flags);
cancel_work_sync(&bigben->worker); cancel_work_sync(&bigben->worker);
hid_hw_stop(hid); hid_hw_stop(hid);
} }
...@@ -318,7 +371,6 @@ static int bigben_probe(struct hid_device *hid, ...@@ -318,7 +371,6 @@ static int bigben_probe(struct hid_device *hid,
{ {
struct bigben_device *bigben; struct bigben_device *bigben;
struct hid_input *hidinput; struct hid_input *hidinput;
struct list_head *report_list;
struct led_classdev *led; struct led_classdev *led;
char *name; char *name;
size_t name_sz; size_t name_sz;
...@@ -343,14 +395,12 @@ static int bigben_probe(struct hid_device *hid, ...@@ -343,14 +395,12 @@ static int bigben_probe(struct hid_device *hid,
return error; return error;
} }
report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
if (list_empty(report_list)) { if (!bigben->report) {
hid_err(hid, "no output report found\n"); hid_err(hid, "no output report found\n");
error = -ENODEV; error = -ENODEV;
goto error_hw_stop; goto error_hw_stop;
} }
bigben->report = list_entry(report_list->next,
struct hid_report, list);
if (list_empty(&hid->inputs)) { if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n"); hid_err(hid, "no inputs found\n");
...@@ -362,6 +412,7 @@ static int bigben_probe(struct hid_device *hid, ...@@ -362,6 +412,7 @@ static int bigben_probe(struct hid_device *hid,
set_bit(FF_RUMBLE, hidinput->input->ffbit); set_bit(FF_RUMBLE, hidinput->input->ffbit);
INIT_WORK(&bigben->worker, bigben_worker); INIT_WORK(&bigben->worker, bigben_worker);
spin_lock_init(&bigben->lock);
error = input_ff_create_memless(hidinput->input, NULL, error = input_ff_create_memless(hidinput->input, NULL,
hid_bigben_play_effect); hid_bigben_play_effect);
...@@ -402,7 +453,7 @@ static int bigben_probe(struct hid_device *hid, ...@@ -402,7 +453,7 @@ static int bigben_probe(struct hid_device *hid,
bigben->left_motor_force = 0; bigben->left_motor_force = 0;
bigben->work_led = true; bigben->work_led = true;
bigben->work_ff = true; bigben->work_ff = true;
schedule_work(&bigben->worker); bigben_schedule_work(bigben);
hid_info(hid, "LED and force feedback support for BigBen gamepad\n"); hid_info(hid, "LED and force feedback support for BigBen gamepad\n");
......
...@@ -41,11 +41,6 @@ ...@@ -41,11 +41,6 @@
#define DRIVER_DESC "HID core driver" #define DRIVER_DESC "HID core driver"
int hid_debug = 0;
module_param_named(debug, hid_debug, int, 0600);
MODULE_PARM_DESC(debug, "toggle HID debugging messages");
EXPORT_SYMBOL_GPL(hid_debug);
static int hid_ignore_special_drivers = 0; static int hid_ignore_special_drivers = 0;
module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600); module_param_named(ignore_special_drivers, hid_ignore_special_drivers, int, 0600);
MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver"); MODULE_PARM_DESC(ignore_special_drivers, "Ignore any special drivers and handle all devices by generic driver");
...@@ -804,7 +799,8 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type) ...@@ -804,7 +799,8 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type)
int i; int i;
if (((parser->global.usage_page << 16) == HID_UP_SENSOR) && if (((parser->global.usage_page << 16) == HID_UP_SENSOR) &&
type == HID_COLLECTION_PHYSICAL) (type == HID_COLLECTION_PHYSICAL ||
type == HID_COLLECTION_APPLICATION))
hid->group = HID_GROUP_SENSOR_HUB; hid->group = HID_GROUP_SENSOR_HUB;
if (hid->vendor == USB_VENDOR_ID_MICROSOFT && if (hid->vendor == USB_VENDOR_ID_MICROSOFT &&
...@@ -1219,7 +1215,8 @@ int hid_open_report(struct hid_device *device) ...@@ -1219,7 +1215,8 @@ int hid_open_report(struct hid_device *device)
return -ENODEV; return -ENODEV;
size = device->dev_rsize; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL); /* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
buf = call_hid_bpf_rdesc_fixup(device, start, &size);
if (buf == NULL) if (buf == NULL)
return -ENOMEM; return -ENOMEM;
...@@ -2046,6 +2043,12 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data ...@@ -2046,6 +2043,12 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
report_enum = hid->report_enum + type; report_enum = hid->report_enum + type;
hdrv = hid->driver; hdrv = hid->driver;
data = dispatch_hid_bpf_device_event(hid, type, data, &size, interrupt);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto unlock;
}
if (!size) { if (!size) {
dbg_hid("empty report\n"); dbg_hid("empty report\n");
ret = -1; ret = -1;
...@@ -2160,6 +2163,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) ...@@ -2160,6 +2163,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
int len; int len;
int ret; int ret;
ret = hid_bpf_connect_device(hdev);
if (ret)
return ret;
if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE) if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV); connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE) if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE)
...@@ -2261,6 +2268,8 @@ void hid_disconnect(struct hid_device *hdev) ...@@ -2261,6 +2268,8 @@ void hid_disconnect(struct hid_device *hdev)
if (hdev->claimed & HID_CLAIMED_HIDRAW) if (hdev->claimed & HID_CLAIMED_HIDRAW)
hidraw_disconnect(hdev); hidraw_disconnect(hdev);
hdev->claimed = 0; hdev->claimed = 0;
hid_bpf_disconnect_device(hdev);
} }
EXPORT_SYMBOL_GPL(hid_disconnect); EXPORT_SYMBOL_GPL(hid_disconnect);
...@@ -2796,6 +2805,8 @@ struct hid_device *hid_allocate_device(void) ...@@ -2796,6 +2805,8 @@ struct hid_device *hid_allocate_device(void)
sema_init(&hdev->driver_input_lock, 1); sema_init(&hdev->driver_input_lock, 1);
mutex_init(&hdev->ll_open_lock); mutex_init(&hdev->ll_open_lock);
hid_bpf_device_init(hdev);
return hdev; return hdev;
} }
EXPORT_SYMBOL_GPL(hid_allocate_device); EXPORT_SYMBOL_GPL(hid_allocate_device);
...@@ -2822,6 +2833,7 @@ static void hid_remove_device(struct hid_device *hdev) ...@@ -2822,6 +2833,7 @@ static void hid_remove_device(struct hid_device *hdev)
*/ */
void hid_destroy_device(struct hid_device *hdev) void hid_destroy_device(struct hid_device *hdev)
{ {
hid_bpf_destroy_device(hdev);
hid_remove_device(hdev); hid_remove_device(hdev);
put_device(&hdev->dev); put_device(&hdev->dev);
} }
...@@ -2908,20 +2920,29 @@ int hid_check_keys_pressed(struct hid_device *hid) ...@@ -2908,20 +2920,29 @@ int hid_check_keys_pressed(struct hid_device *hid)
} }
EXPORT_SYMBOL_GPL(hid_check_keys_pressed); EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
#ifdef CONFIG_HID_BPF
static struct hid_bpf_ops hid_ops = {
.hid_get_report = hid_get_report,
.hid_hw_raw_request = hid_hw_raw_request,
.owner = THIS_MODULE,
.bus_type = &hid_bus_type,
};
#endif
static int __init hid_init(void) static int __init hid_init(void)
{ {
int ret; int ret;
if (hid_debug)
pr_warn("hid_debug is now used solely for parser and driver debugging.\n"
"debugfs is now used for inspecting the device (report descriptor, reports)\n");
ret = bus_register(&hid_bus_type); ret = bus_register(&hid_bus_type);
if (ret) { if (ret) {
pr_err("can't register hid bus\n"); pr_err("can't register hid bus\n");
goto err; goto err;
} }
#ifdef CONFIG_HID_BPF
hid_bpf_ops = &hid_ops;
#endif
ret = hidraw_init(); ret = hidraw_init();
if (ret) if (ret)
goto err_bus; goto err_bus;
...@@ -2937,6 +2958,9 @@ static int __init hid_init(void) ...@@ -2937,6 +2958,9 @@ static int __init hid_init(void)
static void __exit hid_exit(void) static void __exit hid_exit(void)
{ {
#ifdef CONFIG_HID_BPF
hid_bpf_ops = NULL;
#endif
hid_debug_exit(); hid_debug_exit();
hidraw_exit(); hidraw_exit();
bus_unregister(&hid_bus_type); bus_unregister(&hid_bus_type);
......
...@@ -975,6 +975,7 @@ static const char *keys[KEY_MAX + 1] = { ...@@ -975,6 +975,7 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_CAMERA_ACCESS_DISABLE] = "CameraAccessDisable", [KEY_CAMERA_ACCESS_DISABLE] = "CameraAccessDisable",
[KEY_CAMERA_ACCESS_TOGGLE] = "CameraAccessToggle", [KEY_CAMERA_ACCESS_TOGGLE] = "CameraAccessToggle",
[KEY_DICTATE] = "Dictate", [KEY_DICTATE] = "Dictate",
[KEY_MICMUTE] = "MicrophoneMute",
[KEY_BRIGHTNESS_MIN] = "BrightnessMin", [KEY_BRIGHTNESS_MIN] = "BrightnessMin",
[KEY_BRIGHTNESS_MAX] = "BrightnessMax", [KEY_BRIGHTNESS_MAX] = "BrightnessMax",
[KEY_BRIGHTNESS_AUTO] = "BrightnessAuto", [KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",
......
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HID driver for EVision devices
* For now, only ignore bogus consumer reports
* sent after the keyboard has been configured
*
* Copyright (c) 2022 Philippe Valembois
*/
#include <linux/device.h>
#include <linux/input.h>
#include <linux/hid.h>
#include <linux/module.h>
#include "hid-ids.h"
static int evision_input_mapping(struct hid_device *hdev, struct hid_input *hi,
struct hid_field *field, struct hid_usage *usage,
unsigned long **bit, int *max)
{
if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER)
return 0;
/* Ignore key down event */
if ((usage->hid & HID_USAGE) >> 8 == 0x05)
return -1;
/* Ignore key up event */
if ((usage->hid & HID_USAGE) >> 8 == 0x06)
return -1;
switch (usage->hid & HID_USAGE) {
/* Ignore configuration saved event */
case 0x0401: return -1;
/* Ignore reset event */
case 0x0402: return -1;
}
return 0;
}
static const struct hid_device_id evision_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_EVISION, USB_DEVICE_ID_EVISION_ICL01) },
{ }
};
MODULE_DEVICE_TABLE(hid, evision_devices);
static struct hid_driver evision_driver = {
.name = "evision",
.id_table = evision_devices,
.input_mapping = evision_input_mapping,
};
module_hid_driver(evision_driver);
MODULE_LICENSE("GPL");
...@@ -424,7 +424,7 @@ static int mousevsc_hid_raw_request(struct hid_device *hid, ...@@ -424,7 +424,7 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
return 0; return 0;
} }
static struct hid_ll_driver mousevsc_ll_driver = { static const struct hid_ll_driver mousevsc_ll_driver = {
.parse = mousevsc_hid_parse, .parse = mousevsc_hid_parse,
.open = mousevsc_hid_open, .open = mousevsc_hid_open,
.close = mousevsc_hid_close, .close = mousevsc_hid_close,
......
...@@ -448,6 +448,9 @@ ...@@ -448,6 +448,9 @@
#define USB_VENDOR_ID_EMS 0x2006 #define USB_VENDOR_ID_EMS 0x2006
#define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118 #define USB_DEVICE_ID_EMS_TRIO_LINKER_PLUS_II 0x0118
#define USB_VENDOR_ID_EVISION 0x320f
#define USB_DEVICE_ID_EVISION_ICL01 0x5041
#define USB_VENDOR_ID_FLATFROG 0x25b5 #define USB_VENDOR_ID_FLATFROG 0x25b5
#define USB_DEVICE_ID_MULTITOUCH_3200 0x0002 #define USB_DEVICE_ID_MULTITOUCH_3200 0x0002
...@@ -822,6 +825,7 @@ ...@@ -822,6 +825,7 @@
#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e #define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e
#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f #define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
#define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262 #define USB_DEVICE_ID_LOGITECH_G920_WHEEL 0xc262
#define USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL 0xc26e
#define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283 #define USB_DEVICE_ID_LOGITECH_WINGMAN_F3D 0xc283
#define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286 #define USB_DEVICE_ID_LOGITECH_FORCE3D_PRO 0xc286
#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287 #define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287
...@@ -1185,6 +1189,7 @@ ...@@ -1185,6 +1189,7 @@
#define USB_VENDOR_ID_VALVE 0x28de #define USB_VENDOR_ID_VALVE 0x28de
#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102 #define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142 #define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
#define USB_DEVICE_ID_STEAM_DECK 0x1205
#define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
...@@ -1299,7 +1304,9 @@ ...@@ -1299,7 +1304,9 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2 0x0905 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2 0x0905
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L 0x0935
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW 0x0934
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S 0x0909
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW 0x0933
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078 #define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074 #define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071 #define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
......
// SPDX-License-Identifier: GPL-2.0+
/*
* HID to Linux Input mapping
*
* Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com>
*/
#include <kunit/test.h>
static void hid_test_input_set_battery_charge_status(struct kunit *test)
{
struct hid_device *dev;
bool handled;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
handled = hidinput_set_battery_charge_status(dev, HID_DG_HEIGHT, 0);
KUNIT_EXPECT_FALSE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_UNKNOWN);
handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 0);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_DISCHARGING);
handled = hidinput_set_battery_charge_status(dev, HID_BAT_CHARGING, 1);
KUNIT_EXPECT_TRUE(test, handled);
KUNIT_EXPECT_EQ(test, dev->battery_charge_status, POWER_SUPPLY_STATUS_CHARGING);
}
static void hid_test_input_get_battery_property(struct kunit *test)
{
struct power_supply *psy;
struct hid_device *dev;
union power_supply_propval val;
int ret;
dev = kunit_kzalloc(test, sizeof(*dev), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
dev->battery_avoid_query = true;
psy = kunit_kzalloc(test, sizeof(*psy), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, psy);
psy->drv_data = dev;
dev->battery_status = HID_BATTERY_UNKNOWN;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_UNKNOWN);
dev->battery_status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_CHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_CHARGING);
dev->battery_status = HID_BATTERY_REPORTED;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
ret = hidinput_get_battery_property(psy, POWER_SUPPLY_PROP_STATUS, &val);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, val.intval, POWER_SUPPLY_STATUS_DISCHARGING);
}
static struct kunit_case hid_input_tests[] = {
KUNIT_CASE(hid_test_input_set_battery_charge_status),
KUNIT_CASE(hid_test_input_get_battery_property),
{ }
};
static struct kunit_suite hid_input_test_suite = {
.name = "hid_input",
.test_cases = hid_input_tests,
};
kunit_test_suite(hid_input_test_suite);
MODULE_DESCRIPTION("HID input KUnit tests");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");
...@@ -378,6 +378,10 @@ static const struct hid_device_id hid_battery_quirks[] = { ...@@ -378,6 +378,10 @@ static const struct hid_device_id hid_battery_quirks[] = {
HID_BATTERY_QUIRK_IGNORE }, HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L), { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L),
HID_BATTERY_QUIRK_AVOID_QUERY }, HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
HID_BATTERY_QUIRK_AVOID_QUERY },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15), { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15),
HID_BATTERY_QUIRK_IGNORE }, HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100), { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100),
...@@ -486,7 +490,7 @@ static int hidinput_get_battery_property(struct power_supply *psy, ...@@ -486,7 +490,7 @@ static int hidinput_get_battery_property(struct power_supply *psy,
if (dev->battery_status == HID_BATTERY_UNKNOWN) if (dev->battery_status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN; val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else else
val->intval = POWER_SUPPLY_STATUS_DISCHARGING; val->intval = dev->battery_charge_status;
break; break;
case POWER_SUPPLY_PROP_SCOPE: case POWER_SUPPLY_PROP_SCOPE:
...@@ -554,6 +558,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, ...@@ -554,6 +558,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
dev->battery_max = max; dev->battery_max = max;
dev->battery_report_type = report_type; dev->battery_report_type = report_type;
dev->battery_report_id = field->report->id; dev->battery_report_id = field->report->id;
dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
/* /*
* Stylus is normally not connected to the device and thus we * Stylus is normally not connected to the device and thus we
...@@ -620,6 +625,20 @@ static void hidinput_update_battery(struct hid_device *dev, int value) ...@@ -620,6 +625,20 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
power_supply_changed(dev->battery); power_supply_changed(dev->battery);
} }
} }
static bool hidinput_set_battery_charge_status(struct hid_device *dev,
unsigned int usage, int value)
{
switch (usage) {
case HID_BAT_CHARGING:
dev->battery_charge_status = value ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING;
return true;
}
return false;
}
#else /* !CONFIG_HID_BATTERY_STRENGTH */ #else /* !CONFIG_HID_BATTERY_STRENGTH */
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type, static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage) struct hid_field *field, bool is_percentage)
...@@ -634,6 +653,12 @@ static void hidinput_cleanup_battery(struct hid_device *dev) ...@@ -634,6 +653,12 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
static void hidinput_update_battery(struct hid_device *dev, int value) static void hidinput_update_battery(struct hid_device *dev, int value)
{ {
} }
static bool hidinput_set_battery_charge_status(struct hid_device *dev,
unsigned int usage, int value)
{
return false;
}
#endif /* CONFIG_HID_BATTERY_STRENGTH */ #endif /* CONFIG_HID_BATTERY_STRENGTH */
static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field, static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field,
...@@ -793,6 +818,14 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -793,6 +818,14 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
break; break;
} }
if ((usage->hid & 0xf0) == 0xa0) { /* SystemControl */
switch (usage->hid & 0xf) {
case 0x9: map_key_clear(KEY_MICMUTE); break;
default: goto ignore;
}
break;
}
if ((usage->hid & 0xf0) == 0xb0) { /* SC - Display */ if ((usage->hid & 0xf0) == 0xb0) { /* SC - Display */
switch (usage->hid & 0xf) { switch (usage->hid & 0xf) {
case 0x05: map_key_clear(KEY_SWITCHVIDEOMODE); break; case 0x05: map_key_clear(KEY_SWITCHVIDEOMODE); break;
...@@ -1223,6 +1256,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel ...@@ -1223,6 +1256,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
hidinput_setup_battery(device, HID_INPUT_REPORT, field, true); hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
usage->type = EV_PWR; usage->type = EV_PWR;
return; return;
case HID_BAT_CHARGING:
usage->type = EV_PWR;
return;
} }
goto unknown; goto unknown;
...@@ -1465,7 +1501,11 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct ...@@ -1465,7 +1501,11 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return; return;
if (usage->type == EV_PWR) { if (usage->type == EV_PWR) {
bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value);
if (!handled)
hidinput_update_battery(hid, value); hidinput_update_battery(hid, value);
return; return;
} }
...@@ -2321,3 +2361,7 @@ void hidinput_disconnect(struct hid_device *hid) ...@@ -2321,3 +2361,7 @@ void hidinput_disconnect(struct hid_device *hid)
cancel_work_sync(&hid->led_work); cancel_work_sync(&hid->led_work);
} }
EXPORT_SYMBOL_GPL(hidinput_disconnect); EXPORT_SYMBOL_GPL(hidinput_disconnect);
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-input-test.c"
#endif
...@@ -238,7 +238,7 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id * ...@@ -238,7 +238,7 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id *
char buf[256]; char buf[256];
int i, ret; int i, ret;
if (!hid_is_using_ll_driver(hdev, &usb_hid_driver)) if (!hid_is_usb(hdev))
return -ENODEV; return -ENODEV;
intf = to_usb_interface(hdev->dev.parent); intf = to_usb_interface(hdev->dev.parent);
......
...@@ -554,7 +554,7 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = { ...@@ -554,7 +554,7 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
#define LOGITECH_DJ_INTERFACE_NUMBER 0x02 #define LOGITECH_DJ_INTERFACE_NUMBER 0x02
static struct hid_ll_driver logi_dj_ll_driver; static const struct hid_ll_driver logi_dj_ll_driver;
static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev); static int logi_dj_recv_query_paired_devices(struct dj_receiver_dev *djrcv_dev);
static void delayedwork_callback(struct work_struct *work); static void delayedwork_callback(struct work_struct *work);
...@@ -1506,7 +1506,7 @@ static bool logi_dj_ll_may_wakeup(struct hid_device *hid) ...@@ -1506,7 +1506,7 @@ static bool logi_dj_ll_may_wakeup(struct hid_device *hid)
return hid_hw_may_wakeup(djrcv_dev->hidpp); return hid_hw_may_wakeup(djrcv_dev->hidpp);
} }
static struct hid_ll_driver logi_dj_ll_driver = { static const struct hid_ll_driver logi_dj_ll_driver = {
.parse = logi_dj_ll_parse, .parse = logi_dj_ll_parse,
.start = logi_dj_ll_start, .start = logi_dj_ll_start,
.stop = logi_dj_ll_stop, .stop = logi_dj_ll_stop,
......
...@@ -30,11 +30,7 @@ ...@@ -30,11 +30,7 @@
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>");
MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>"); MODULE_AUTHOR("Nestor Lopez Casado <nlopezcasad@logitech.com>");
MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
static bool disable_raw_mode;
module_param(disable_raw_mode, bool, 0644);
MODULE_PARM_DESC(disable_raw_mode,
"Disable Raw mode reporting for touchpads and keep firmware gestures.");
static bool disable_tap_to_click; static bool disable_tap_to_click;
module_param(disable_tap_to_click, bool, 0644); module_param(disable_tap_to_click, bool, 0644);
...@@ -71,12 +67,13 @@ MODULE_PARM_DESC(disable_tap_to_click, ...@@ -71,12 +67,13 @@ MODULE_PARM_DESC(disable_tap_to_click,
/* bits 2..20 are reserved for classes */ /* bits 2..20 are reserved for classes */
/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */ /* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */
#define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22) #define HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS BIT(22)
#define HIDPP_QUIRK_NO_HIDINPUT BIT(23) #define HIDPP_QUIRK_DELAYED_INIT BIT(23)
#define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24) #define HIDPP_QUIRK_FORCE_OUTPUT_REPORTS BIT(24)
#define HIDPP_QUIRK_UNIFYING BIT(25) #define HIDPP_QUIRK_UNIFYING BIT(25)
#define HIDPP_QUIRK_HIDPP_WHEELS BIT(26) #define HIDPP_QUIRK_HIDPP_WHEELS BIT(26)
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27) #define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28) #define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(29)
/* These are just aliases for now */ /* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
...@@ -87,8 +84,6 @@ MODULE_PARM_DESC(disable_tap_to_click, ...@@ -87,8 +84,6 @@ MODULE_PARM_DESC(disable_tap_to_click,
HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \ HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL) HIDPP_CAPABILITY_HIDPP20_HI_RES_WHEEL)
#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT
#define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0) #define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0)
#define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1) #define HIDPP_CAPABILITY_HIDPP20_BATTERY BIT(1)
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2) #define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
...@@ -225,6 +220,16 @@ struct hidpp_device { ...@@ -225,6 +220,16 @@ struct hidpp_device {
#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b #define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b
#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c #define HIDPP_ERROR_WRONG_PIN_CODE 0x0c
/* HID++ 2.0 error codes */ /* HID++ 2.0 error codes */
#define HIDPP20_ERROR_NO_ERROR 0x00
#define HIDPP20_ERROR_UNKNOWN 0x01
#define HIDPP20_ERROR_INVALID_ARGS 0x02
#define HIDPP20_ERROR_OUT_OF_RANGE 0x03
#define HIDPP20_ERROR_HW_ERROR 0x04
#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05
#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06
#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07
#define HIDPP20_ERROR_BUSY 0x08
#define HIDPP20_ERROR_UNSUPPORTED 0x09
#define HIDPP20_ERROR 0xff #define HIDPP20_ERROR 0xff
static void hidpp_connect_event(struct hidpp_device *hidpp_dev); static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
...@@ -279,6 +284,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, ...@@ -279,6 +284,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
struct hidpp_report *response) struct hidpp_report *response)
{ {
int ret; int ret;
int max_retries = 3;
mutex_lock(&hidpp->send_mutex); mutex_lock(&hidpp->send_mutex);
...@@ -291,6 +297,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, ...@@ -291,6 +297,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
*/ */
*response = *message; *response = *message;
for (; max_retries != 0; max_retries--) {
ret = __hidpp_send_report(hidpp->hid_dev, message); ret = __hidpp_send_report(hidpp->hid_dev, message);
if (ret) { if (ret) {
...@@ -317,9 +324,13 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp, ...@@ -317,9 +324,13 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
response->report_id == REPORT_ID_HIDPP_VERY_LONG) && response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
response->fap.feature_index == HIDPP20_ERROR) { response->fap.feature_index == HIDPP20_ERROR) {
ret = response->fap.params[1]; ret = response->fap.params[1];
if (ret != HIDPP20_ERROR_BUSY) {
dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret); dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
goto exit; goto exit;
} }
dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
}
}
exit: exit:
mutex_unlock(&hidpp->send_mutex); mutex_unlock(&hidpp->send_mutex);
...@@ -334,8 +345,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp, ...@@ -334,8 +345,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
struct hidpp_report *message; struct hidpp_report *message;
int ret; int ret;
if (param_count > sizeof(message->fap.params)) if (param_count > sizeof(message->fap.params)) {
hid_dbg(hidpp->hid_dev,
"Invalid number of parameters passed to command (%d != %llu)\n",
param_count,
(unsigned long long) sizeof(message->fap.params));
return -EINVAL; return -EINVAL;
}
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL); message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
if (!message) if (!message)
...@@ -3436,11 +3452,17 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) ...@@ -3436,11 +3452,17 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
ret = hidpp10_enable_scrolling_acceleration(hidpp); ret = hidpp10_enable_scrolling_acceleration(hidpp);
multiplier = 8; multiplier = 8;
} }
if (ret) if (ret) {
hid_dbg(hidpp->hid_dev,
"Could not enable hi-res scrolling: %d\n", ret);
return ret; return ret;
}
if (multiplier == 0) if (multiplier == 0) {
hid_dbg(hidpp->hid_dev,
"Invalid multiplier 0 from device, setting it to 1\n");
multiplier = 1; multiplier = 1;
}
hidpp->vertical_wheel_counter.wheel_multiplier = multiplier; hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier); hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier);
...@@ -3472,14 +3494,8 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp) ...@@ -3472,14 +3494,8 @@ static int hidpp_initialize_hires_scroll(struct hidpp_device *hidpp)
hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n"); hid_dbg(hidpp->hid_dev, "Detected HID++ 2.0 hi-res scrolling\n");
} }
} else { } else {
struct hidpp_report response; /* We cannot detect fast scrolling support on HID++ 1.0 devices */
if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) {
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_GET_REGISTER,
HIDPP_ENABLE_FAST_SCROLL,
NULL, 0, &response);
if (!ret) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL; hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n"); hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n");
} }
...@@ -4002,7 +4018,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) ...@@ -4002,7 +4018,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL) if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
hi_res_scroll_enable(hidpp); hi_res_scroll_enable(hidpp);
if (!(hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) || hidpp->delayed_input) if (!(hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT) || hidpp->delayed_input)
/* if the input nodes are already created, we can stop now */ /* if the input nodes are already created, we can stop now */
return; return;
...@@ -4107,6 +4123,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4107,6 +4123,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
bool connected; bool connected;
unsigned int connect_mask = HID_CONNECT_DEFAULT; unsigned int connect_mask = HID_CONNECT_DEFAULT;
struct hidpp_ff_private_data data; struct hidpp_ff_private_data data;
bool will_restart = false;
/* report_fixup needs drvdata to be set before we call hid_parse */ /* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
...@@ -4147,11 +4164,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4147,11 +4164,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp_application_equals(hdev, HID_GD_KEYBOARD)) hidpp_application_equals(hdev, HID_GD_KEYBOARD))
hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS; hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS;
if (disable_raw_mode) {
hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP;
hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT;
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) {
ret = wtp_allocate(hdev, id); ret = wtp_allocate(hdev, id);
if (ret) if (ret)
...@@ -4162,6 +4174,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4162,6 +4174,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret; return ret;
} }
if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT ||
hidpp->quirks & HIDPP_QUIRK_UNIFYING)
will_restart = true;
INIT_WORK(&hidpp->work, delayed_work_cb); INIT_WORK(&hidpp->work, delayed_work_cb);
mutex_init(&hidpp->send_mutex); mutex_init(&hidpp->send_mutex);
init_waitqueue_head(&hidpp->wait); init_waitqueue_head(&hidpp->wait);
...@@ -4176,7 +4192,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4176,7 +4192,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
* Plain USB connections need to actually call start and open * Plain USB connections need to actually call start and open
* on the transport driver to allow incoming data. * on the transport driver to allow incoming data.
*/ */
ret = hid_hw_start(hdev, 0); ret = hid_hw_start(hdev, will_restart ? 0 : connect_mask);
if (ret) { if (ret) {
hid_err(hdev, "hw start failed\n"); hid_err(hdev, "hw start failed\n");
goto hid_hw_start_fail; goto hid_hw_start_fail;
...@@ -4213,6 +4229,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4213,6 +4229,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->wireless_feature_index = 0; hidpp->wireless_feature_index = 0;
else if (ret) else if (ret)
goto hid_hw_init_fail; goto hid_hw_init_fail;
ret = 0;
} }
if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
...@@ -4227,12 +4244,13 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4227,12 +4244,13 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp_connect_event(hidpp); hidpp_connect_event(hidpp);
if (will_restart) {
/* Reset the HID node state */ /* Reset the HID node state */
hid_device_io_stop(hdev); hid_device_io_stop(hdev);
hid_hw_close(hdev); hid_hw_close(hdev);
hid_hw_stop(hdev); hid_hw_stop(hdev);
if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
connect_mask &= ~HID_CONNECT_HIDINPUT; connect_mask &= ~HID_CONNECT_HIDINPUT;
/* Now export the actual inputs and hidraw nodes to the world */ /* Now export the actual inputs and hidraw nodes to the world */
...@@ -4241,6 +4259,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -4241,6 +4259,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
goto hid_hw_start_fail; goto hid_hw_start_fail;
} }
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
ret = hidpp_ff_init(hidpp, &data); ret = hidpp_ff_init(hidpp, &data);
...@@ -4297,9 +4316,15 @@ static const struct hid_device_id hidpp_devices[] = { ...@@ -4297,9 +4316,15 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_T651), USB_DEVICE_ID_LOGITECH_T651),
.driver_data = HIDPP_QUIRK_CLASS_WTP }, .driver_data = HIDPP_QUIRK_CLASS_WTP },
{ /* Mouse Logitech Anywhere MX */
LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Mouse logitech M560 */ { /* Mouse logitech M560 */
LDJ_DEVICE(0x402d), LDJ_DEVICE(0x402d),
.driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 }, .driver_data = HIDPP_QUIRK_DELAYED_INIT | HIDPP_QUIRK_CLASS_M560 },
{ /* Mouse Logitech M705 (firmware RQM17) */
LDJ_DEVICE(0x101b), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Mouse Logitech Performance MX */
LDJ_DEVICE(0x101a), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Keyboard logitech K400 */ { /* Keyboard logitech K400 */
LDJ_DEVICE(0x4024), LDJ_DEVICE(0x4024),
.driver_data = HIDPP_QUIRK_CLASS_K400 }, .driver_data = HIDPP_QUIRK_CLASS_K400 },
...@@ -4348,6 +4373,9 @@ static const struct hid_device_id hidpp_devices[] = { ...@@ -4348,6 +4373,9 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Logitech G920 Wheel over USB */ { /* Logitech G920 Wheel over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS},
{ /* Logitech G923 Wheel (Xbox version) over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G923_XBOX_WHEEL),
.driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS },
{ /* Logitech G Pro Gaming Mouse over USB */ { /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) }, HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
...@@ -4367,6 +4395,8 @@ static const struct hid_device_id hidpp_devices[] = { ...@@ -4367,6 +4395,8 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX Ergo trackball over Bluetooth */ { /* MX Ergo trackball over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) }, HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
{ /* Signature M650 over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
{ /* MX Master 3 mouse over Bluetooth */ { /* MX Master 3 mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
{} {}
......
...@@ -922,6 +922,9 @@ static void mcp2221_hid_unregister(void *ptr) ...@@ -922,6 +922,9 @@ static void mcp2221_hid_unregister(void *ptr)
/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */ /* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
static void mcp2221_remove(struct hid_device *hdev) static void mcp2221_remove(struct hid_device *hdev)
{ {
struct mcp2221 *mcp = hid_get_drvdata(hdev);
cancel_delayed_work_sync(&mcp->init_work);
} }
#if IS_REACHABLE(CONFIG_IIO) #if IS_REACHABLE(CONFIG_IIO)
......
...@@ -71,6 +71,7 @@ MODULE_LICENSE("GPL"); ...@@ -71,6 +71,7 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19)
#define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20)
#define MT_QUIRK_DISABLE_WAKEUP BIT(21) #define MT_QUIRK_DISABLE_WAKEUP BIT(21)
#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
#define MT_INPUTMODE_TOUCHSCREEN 0x02 #define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03 #define MT_INPUTMODE_TOUCHPAD 0x03
...@@ -1009,6 +1010,7 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, ...@@ -1009,6 +1010,7 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
struct mt_usages *slot) struct mt_usages *slot)
{ {
struct input_mt *mt = input->mt; struct input_mt *mt = input->mt;
struct hid_device *hdev = td->hdev;
__s32 quirks = app->quirks; __s32 quirks = app->quirks;
bool valid = true; bool valid = true;
bool confidence_state = true; bool confidence_state = true;
...@@ -1086,6 +1088,10 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, ...@@ -1086,6 +1088,10 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
int orientation = wide; int orientation = wide;
int max_azimuth; int max_azimuth;
int azimuth; int azimuth;
int x;
int y;
int cx;
int cy;
if (slot->a != DEFAULT_ZERO) { if (slot->a != DEFAULT_ZERO) {
/* /*
...@@ -1104,6 +1110,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, ...@@ -1104,6 +1110,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
if (azimuth > max_azimuth * 2) if (azimuth > max_azimuth * 2)
azimuth -= max_azimuth * 4; azimuth -= max_azimuth * 4;
orientation = -azimuth; orientation = -azimuth;
if (quirks & MT_QUIRK_ORIENTATION_INVERT)
orientation = -orientation;
} }
if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) { if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) {
...@@ -1115,10 +1124,23 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input, ...@@ -1115,10 +1124,23 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1; minor = minor >> 1;
} }
input_event(input, EV_ABS, ABS_MT_POSITION_X, *slot->x); x = hdev->quirks & HID_QUIRK_X_INVERT ?
input_event(input, EV_ABS, ABS_MT_POSITION_Y, *slot->y); input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
input_event(input, EV_ABS, ABS_MT_TOOL_X, *slot->cx); *slot->x;
input_event(input, EV_ABS, ABS_MT_TOOL_Y, *slot->cy); y = hdev->quirks & HID_QUIRK_Y_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->y :
*slot->y;
cx = hdev->quirks & HID_QUIRK_X_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->cx :
*slot->cx;
cy = hdev->quirks & HID_QUIRK_Y_INVERT ?
input_abs_get_max(input, ABS_MT_POSITION_Y) - *slot->cy :
*slot->cy;
input_event(input, EV_ABS, ABS_MT_POSITION_X, x);
input_event(input, EV_ABS, ABS_MT_POSITION_Y, y);
input_event(input, EV_ABS, ABS_MT_TOOL_X, cx);
input_event(input, EV_ABS, ABS_MT_TOOL_Y, cy);
input_event(input, EV_ABS, ABS_MT_DISTANCE, !*slot->tip_state); input_event(input, EV_ABS, ABS_MT_DISTANCE, !*slot->tip_state);
input_event(input, EV_ABS, ABS_MT_ORIENTATION, orientation); input_event(input, EV_ABS, ABS_MT_ORIENTATION, orientation);
input_event(input, EV_ABS, ABS_MT_PRESSURE, *slot->p); input_event(input, EV_ABS, ABS_MT_PRESSURE, *slot->p);
...@@ -1735,6 +1757,15 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) ...@@ -1735,6 +1757,15 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID) if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
td->serial_maybe = true; td->serial_maybe = true;
/* Orientation is inverted if the X or Y axes are
* flipped, but normalized if both are inverted.
*/
if (hdev->quirks & (HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT) &&
!((hdev->quirks & HID_QUIRK_X_INVERT)
&& (hdev->quirks & HID_QUIRK_Y_INVERT)))
td->mtclass.quirks = MT_QUIRK_ORIENTATION_INVERT;
/* This allows the driver to correctly support devices /* This allows the driver to correctly support devices
* that emit events over several HID messages. * that emit events over several HID messages.
*/ */
......
...@@ -993,19 +993,22 @@ static int dualsense_get_calibration_data(struct dualsense *ds) ...@@ -993,19 +993,22 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/ */
speed_2x = (gyro_speed_plus + gyro_speed_minus); speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds->gyro_calib_data[0].abs_code = ABS_RX; ds->gyro_calib_data[0].abs_code = ABS_RX;
ds->gyro_calib_data[0].bias = gyro_pitch_bias; ds->gyro_calib_data[0].bias = 0;
ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[0].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus; ds->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds->gyro_calib_data[1].abs_code = ABS_RY; ds->gyro_calib_data[1].abs_code = ABS_RY;
ds->gyro_calib_data[1].bias = gyro_yaw_bias; ds->gyro_calib_data[1].bias = 0;
ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[1].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus; ds->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds->gyro_calib_data[2].abs_code = ABS_RZ; ds->gyro_calib_data[2].abs_code = ABS_RZ;
ds->gyro_calib_data[2].bias = gyro_roll_bias; ds->gyro_calib_data[2].bias = 0;
ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S; ds->gyro_calib_data[2].sens_numer = speed_2x*DS_GYRO_RES_PER_DEG_S;
ds->gyro_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus; ds->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
/* /*
* Sanity check gyro calibration data. This is needed to prevent crashes * Sanity check gyro calibration data. This is needed to prevent crashes
...@@ -1388,8 +1391,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r ...@@ -1388,8 +1391,7 @@ static int dualsense_parse_report(struct ps_device *ps_dev, struct hid_report *r
for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) { for (i = 0; i < ARRAY_SIZE(ds_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]); int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);
int calib_data = mult_frac(ds->gyro_calib_data[i].sens_numer, int calib_data = mult_frac(ds->gyro_calib_data[i].sens_numer,
raw_data - ds->gyro_calib_data[i].bias, raw_data, ds->gyro_calib_data[i].sens_denom);
ds->gyro_calib_data[i].sens_denom);
input_report_abs(ds->sensors, ds->gyro_calib_data[i].abs_code, calib_data); input_report_abs(ds->sensors, ds->gyro_calib_data[i].abs_code, calib_data);
} }
...@@ -1792,11 +1794,10 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) ...@@ -1792,11 +1794,10 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
if (retries < 2) { if (retries < 2) {
hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n"); hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
continue; continue;
} else {
ret = -EILSEQ;
goto err_free;
} }
hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret); hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
ret = -EILSEQ;
goto err_free; goto err_free;
} else { } else {
break; break;
...@@ -1849,19 +1850,22 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4) ...@@ -1849,19 +1850,22 @@ static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
*/ */
speed_2x = (gyro_speed_plus + gyro_speed_minus); speed_2x = (gyro_speed_plus + gyro_speed_minus);
ds4->gyro_calib_data[0].abs_code = ABS_RX; ds4->gyro_calib_data[0].abs_code = ABS_RX;
ds4->gyro_calib_data[0].bias = gyro_pitch_bias; ds4->gyro_calib_data[0].bias = 0;
ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus; ds4->gyro_calib_data[0].sens_denom = abs(gyro_pitch_plus - gyro_pitch_bias) +
abs(gyro_pitch_minus - gyro_pitch_bias);
ds4->gyro_calib_data[1].abs_code = ABS_RY; ds4->gyro_calib_data[1].abs_code = ABS_RY;
ds4->gyro_calib_data[1].bias = gyro_yaw_bias; ds4->gyro_calib_data[1].bias = 0;
ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus; ds4->gyro_calib_data[1].sens_denom = abs(gyro_yaw_plus - gyro_yaw_bias) +
abs(gyro_yaw_minus - gyro_yaw_bias);
ds4->gyro_calib_data[2].abs_code = ABS_RZ; ds4->gyro_calib_data[2].abs_code = ABS_RZ;
ds4->gyro_calib_data[2].bias = gyro_roll_bias; ds4->gyro_calib_data[2].bias = 0;
ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S; ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
ds4->gyro_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus; ds4->gyro_calib_data[2].sens_denom = abs(gyro_roll_plus - gyro_roll_bias) +
abs(gyro_roll_minus - gyro_roll_bias);
/* /*
* Sanity check gyro calibration data. This is needed to prevent crashes * Sanity check gyro calibration data. This is needed to prevent crashes
...@@ -2242,8 +2246,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report * ...@@ -2242,8 +2246,7 @@ static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *
for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) { for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]); int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
int calib_data = mult_frac(ds4->gyro_calib_data[i].sens_numer, int calib_data = mult_frac(ds4->gyro_calib_data[i].sens_numer,
raw_data - ds4->gyro_calib_data[i].bias, raw_data, ds4->gyro_calib_data[i].sens_denom);
ds4->gyro_calib_data[i].sens_denom);
input_report_abs(ds4->sensors, ds4->gyro_calib_data[i].abs_code, calib_data); input_report_abs(ds4->sensors, ds4->gyro_calib_data[i].abs_code, calib_data);
} }
......
...@@ -1237,7 +1237,7 @@ EXPORT_SYMBOL_GPL(hid_quirks_exit); ...@@ -1237,7 +1237,7 @@ EXPORT_SYMBOL_GPL(hid_quirks_exit);
static unsigned long hid_gets_squirk(const struct hid_device *hdev) static unsigned long hid_gets_squirk(const struct hid_device *hdev)
{ {
const struct hid_device_id *bl_entry; const struct hid_device_id *bl_entry;
unsigned long quirks = 0; unsigned long quirks = hdev->initial_quirks;
if (hid_match_id(hdev, hid_ignore_list)) if (hid_match_id(hdev, hid_ignore_list))
quirks |= HID_QUIRK_IGNORE; quirks |= HID_QUIRK_IGNORE;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/dmi.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h> #include <linux/init.h>
...@@ -750,114 +751,209 @@ static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom ...@@ -750,114 +751,209 @@ static void hid_sensor_custom_dev_if_remove(struct hid_sensor_custom
} }
/* luid defined in FW (e.g. ISH). Maybe used to identify sensor. */ /*
static const char *const known_sensor_luid[] = { "020B000000000000" }; * Match a known custom sensor.
* tag and luid is mandatory.
*/
struct hid_sensor_custom_match {
const char *tag;
const char *luid;
const char *model;
const char *manufacturer;
bool check_dmi;
struct dmi_system_id dmi;
};
static int get_luid_table_index(unsigned char *usage_str) /*
{ * Custom sensor properties used for matching.
int i; */
struct hid_sensor_custom_properties {
u16 serial_num[HID_CUSTOM_MAX_FEATURE_BYTES];
u16 model[HID_CUSTOM_MAX_FEATURE_BYTES];
u16 manufacturer[HID_CUSTOM_MAX_FEATURE_BYTES];
};
static const struct hid_sensor_custom_match hid_sensor_custom_known_table[] = {
/*
* Intel Integrated Sensor Hub (ISH)
*/
{ /* Intel ISH hinge */
.tag = "INT",
.luid = "020B000000000000",
.manufacturer = "INTEL",
},
/*
* Lenovo Intelligent Sensing Solution (LISS)
*/
{ /* ambient light */
.tag = "LISS",
.luid = "0041010200000082",
.model = "STK3X3X Sensor",
.manufacturer = "Vendor 258",
.check_dmi = true,
.dmi.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
}
},
{ /* human presence */
.tag = "LISS",
.luid = "0226000171AC0081",
.model = "VL53L1_HOD Sensor",
.manufacturer = "ST_MICRO",
.check_dmi = true,
.dmi.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
}
},
{}
};
for (i = 0; i < ARRAY_SIZE(known_sensor_luid); i++) { static bool hid_sensor_custom_prop_match_str(const u16 *prop, const char *match,
if (!strncmp(usage_str, known_sensor_luid[i], size_t count)
strlen(known_sensor_luid[i]))) {
return i; while (count-- && *prop && *match) {
if (*prop != (u16) *match)
return false;
prop++;
match++;
} }
return -ENODEV; return (count == -1) || *prop == (u16)*match;
} }
static int get_known_custom_sensor_index(struct hid_sensor_hub_device *hsdev) static int hid_sensor_custom_get_prop(struct hid_sensor_hub_device *hsdev,
u32 prop_usage_id, size_t prop_size,
u16 *prop)
{ {
struct hid_sensor_hub_attribute_info sensor_manufacturer = { 0 }; struct hid_sensor_hub_attribute_info prop_attr = { 0 };
struct hid_sensor_hub_attribute_info sensor_luid_info = { 0 };
int report_size;
int ret; int ret;
static u16 w_buf[HID_CUSTOM_MAX_FEATURE_BYTES];
static char buf[HID_CUSTOM_MAX_FEATURE_BYTES];
int i;
memset(w_buf, 0, sizeof(w_buf)); memset(prop, 0, prop_size);
memset(buf, 0, sizeof(buf));
/* get manufacturer info */ ret = sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT,
ret = sensor_hub_input_get_attribute_info(hsdev, hsdev->usage, prop_usage_id,
HID_FEATURE_REPORT, hsdev->usage, &prop_attr);
HID_USAGE_SENSOR_PROP_MANUFACTURER, &sensor_manufacturer);
if (ret < 0) if (ret < 0)
return ret; return ret;
report_size = ret = sensor_hub_get_feature(hsdev, prop_attr.report_id,
sensor_hub_get_feature(hsdev, sensor_manufacturer.report_id, prop_attr.index, prop_size, prop);
sensor_manufacturer.index, sizeof(w_buf), if (ret < 0) {
w_buf); hid_err(hsdev->hdev, "Failed to get sensor property %08x %d\n",
if (report_size <= 0) { prop_usage_id, ret);
hid_err(hsdev->hdev, return ret;
"Failed to get sensor manufacturer info %d\n",
report_size);
return -ENODEV;
} }
/* convert from wide char to char */ return 0;
for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++) }
buf[i] = (char)w_buf[i];
/* ensure it's ISH sensor */ static bool
if (strncmp(buf, "INTEL", strlen("INTEL"))) hid_sensor_custom_do_match(struct hid_sensor_hub_device *hsdev,
return -ENODEV; const struct hid_sensor_custom_match *match,
const struct hid_sensor_custom_properties *prop)
{
struct dmi_system_id dmi[] = { match->dmi, { 0 } };
memset(w_buf, 0, sizeof(w_buf)); if (!hid_sensor_custom_prop_match_str(prop->serial_num, "LUID:", 5) ||
memset(buf, 0, sizeof(buf)); !hid_sensor_custom_prop_match_str(prop->serial_num + 5, match->luid,
HID_CUSTOM_MAX_FEATURE_BYTES - 5))
return false;
/* get real usage id */ if (match->model &&
ret = sensor_hub_input_get_attribute_info(hsdev, !hid_sensor_custom_prop_match_str(prop->model, match->model,
HID_FEATURE_REPORT, hsdev->usage, HID_CUSTOM_MAX_FEATURE_BYTES))
HID_USAGE_SENSOR_PROP_SERIAL_NUM, &sensor_luid_info); return false;
if (match->manufacturer &&
!hid_sensor_custom_prop_match_str(prop->manufacturer, match->manufacturer,
HID_CUSTOM_MAX_FEATURE_BYTES))
return false;
if (match->check_dmi && !dmi_check_system(dmi))
return false;
return true;
}
static int
hid_sensor_custom_properties_get(struct hid_sensor_hub_device *hsdev,
struct hid_sensor_custom_properties *prop)
{
int ret;
ret = hid_sensor_custom_get_prop(hsdev,
HID_USAGE_SENSOR_PROP_SERIAL_NUM,
HID_CUSTOM_MAX_FEATURE_BYTES,
prop->serial_num);
if (ret < 0) if (ret < 0)
return ret; return ret;
report_size = sensor_hub_get_feature(hsdev, sensor_luid_info.report_id, /*
sensor_luid_info.index, sizeof(w_buf), * Ignore errors on the following model and manufacturer properties.
w_buf); * Because these are optional, it is not an error if they are missing.
if (report_size <= 0) { */
hid_err(hsdev->hdev, "Failed to get real usage info %d\n",
report_size);
return -ENODEV;
}
/* convert from wide char to char */ hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MODEL,
for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++) HID_CUSTOM_MAX_FEATURE_BYTES,
buf[i] = (char)w_buf[i]; prop->model);
if (strlen(buf) != strlen(known_sensor_luid[0]) + 5) { hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MANUFACTURER,
hid_err(hsdev->hdev, HID_CUSTOM_MAX_FEATURE_BYTES,
"%s luid length not match %zu != (%zu + 5)\n", __func__, prop->manufacturer);
strlen(buf), strlen(known_sensor_luid[0]));
return -ENODEV;
}
/* get table index with luid (not matching 'LUID: ' in luid) */ return 0;
return get_luid_table_index(&buf[5]); }
static int
hid_sensor_custom_get_known(struct hid_sensor_hub_device *hsdev,
const struct hid_sensor_custom_match **known)
{
int ret;
const struct hid_sensor_custom_match *match =
hid_sensor_custom_known_table;
struct hid_sensor_custom_properties *prop;
prop = kmalloc(sizeof(struct hid_sensor_custom_properties), GFP_KERNEL);
if (!prop)
return -ENOMEM;
ret = hid_sensor_custom_properties_get(hsdev, prop);
if (ret < 0)
goto out;
while (match->tag) {
if (hid_sensor_custom_do_match(hsdev, match, prop)) {
*known = match;
ret = 0;
goto out;
}
match++;
}
ret = -ENODATA;
out:
kfree(prop);
return ret;
} }
static struct platform_device * static struct platform_device *
hid_sensor_register_platform_device(struct platform_device *pdev, hid_sensor_register_platform_device(struct platform_device *pdev,
struct hid_sensor_hub_device *hsdev, struct hid_sensor_hub_device *hsdev,
int index) const struct hid_sensor_custom_match *match)
{ {
char real_usage[HID_SENSOR_USAGE_LENGTH] = { 0 }; char real_usage[HID_SENSOR_USAGE_LENGTH];
struct platform_device *custom_pdev; struct platform_device *custom_pdev;
const char *dev_name; const char *dev_name;
char *c; char *c;
/* copy real usage id */ memcpy(real_usage, match->luid, 4);
memcpy(real_usage, known_sensor_luid[index], 4);
/* usage id are all lowcase */ /* usage id are all lowcase */
for (c = real_usage; *c != '\0'; c++) for (c = real_usage; *c != '\0'; c++)
*c = tolower(*c); *c = tolower(*c);
/* HID-SENSOR-INT-REAL_USAGE_ID */ /* HID-SENSOR-TAG-REAL_USAGE_ID */
dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-INT-%s", real_usage); dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-%s-%s",
match->tag, real_usage);
if (!dev_name) if (!dev_name)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
...@@ -873,7 +969,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev) ...@@ -873,7 +969,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
struct hid_sensor_custom *sensor_inst; struct hid_sensor_custom *sensor_inst;
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data; struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
int ret; int ret;
int index; const struct hid_sensor_custom_match *match;
sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst), sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
GFP_KERNEL); GFP_KERNEL);
...@@ -888,10 +984,10 @@ static int hid_sensor_custom_probe(struct platform_device *pdev) ...@@ -888,10 +984,10 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
mutex_init(&sensor_inst->mutex); mutex_init(&sensor_inst->mutex);
platform_set_drvdata(pdev, sensor_inst); platform_set_drvdata(pdev, sensor_inst);
index = get_known_custom_sensor_index(hsdev); ret = hid_sensor_custom_get_known(hsdev, &match);
if (index >= 0 && index < ARRAY_SIZE(known_sensor_luid)) { if (!ret) {
sensor_inst->custom_pdev = sensor_inst->custom_pdev =
hid_sensor_register_platform_device(pdev, hsdev, index); hid_sensor_register_platform_device(pdev, hsdev, match);
ret = PTR_ERR_OR_ZERO(sensor_inst->custom_pdev); ret = PTR_ERR_OR_ZERO(sensor_inst->custom_pdev);
if (ret) { if (ret) {
......
...@@ -397,7 +397,8 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev, ...@@ -397,7 +397,8 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev,
for (i = 0; i < report->maxfield; ++i) { for (i = 0; i < report->maxfield; ++i) {
field = report->field[i]; field = report->field[i];
if (field->maxusage) { if (field->maxusage) {
if (field->physical == usage_id && if ((field->physical == usage_id ||
field->application == usage_id) &&
(field->logical == attr_usage_id || (field->logical == attr_usage_id ||
field->usage[0].hid == field->usage[0].hid ==
attr_usage_id) && attr_usage_id) &&
...@@ -506,7 +507,8 @@ static int sensor_hub_raw_event(struct hid_device *hdev, ...@@ -506,7 +507,8 @@ static int sensor_hub_raw_event(struct hid_device *hdev,
collection->usage); collection->usage);
callback = sensor_hub_get_callback(hdev, callback = sensor_hub_get_callback(hdev,
report->field[i]->physical, report->field[i]->physical ? report->field[i]->physical :
report->field[i]->application,
report->field[i]->usage[0].collection_index, report->field[i]->usage[0].collection_index,
&hsdev, &priv); &hsdev, &priv);
if (!callback) { if (!callback) {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -174,9 +174,25 @@ static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test) ...@@ -174,9 +174,25 @@ static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test)
KUNIT_EXPECT_EQ(test, params->frame_type, frame_type); KUNIT_EXPECT_EQ(test, params->frame_type, frame_type);
} }
static void hid_test_uclogic_params_cleanup_event_hooks(struct kunit *test)
{
int res, n;
struct uclogic_params p = {0, };
res = uclogic_params_ugee_v2_init_event_hooks(NULL, &p);
KUNIT_ASSERT_EQ(test, res, 0);
/* Check that the function can be called repeatedly */
for (n = 0; n < 4; n++) {
uclogic_params_cleanup_event_hooks(&p);
KUNIT_EXPECT_PTR_EQ(test, p.event_hooks, NULL);
}
}
static struct kunit_case hid_uclogic_params_test_cases[] = { static struct kunit_case hid_uclogic_params_test_cases[] = {
KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc, KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc,
uclogic_parse_ugee_v2_desc_gen_params), uclogic_parse_ugee_v2_desc_gen_params),
KUNIT_CASE(hid_test_uclogic_params_cleanup_event_hooks),
{} {}
}; };
......
This diff is collapsed.
This diff is collapsed.
...@@ -197,8 +197,7 @@ static void hid_test_uclogic_template(struct kunit *test) ...@@ -197,8 +197,7 @@ static void hid_test_uclogic_template(struct kunit *test)
params->param_list, params->param_list,
params->param_num); params->param_num);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res);
KUNIT_EXPECT_EQ(test, 0, KUNIT_EXPECT_MEMEQ(test, res, params->expected, params->template_size);
memcmp(res, params->expected, params->template_size));
kfree(res); kfree(res);
} }
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -132,6 +132,7 @@ ...@@ -132,6 +132,7 @@
#define HID_USAGE_SENSOR_PROP_FRIENDLY_NAME 0x200301 #define HID_USAGE_SENSOR_PROP_FRIENDLY_NAME 0x200301
#define HID_USAGE_SENSOR_PROP_SERIAL_NUM 0x200307 #define HID_USAGE_SENSOR_PROP_SERIAL_NUM 0x200307
#define HID_USAGE_SENSOR_PROP_MANUFACTURER 0x200305 #define HID_USAGE_SENSOR_PROP_MANUFACTURER 0x200305
#define HID_USAGE_SENSOR_PROP_MODEL 0x200306
#define HID_USAGE_SENSOR_PROP_REPORT_INTERVAL 0x20030E #define HID_USAGE_SENSOR_PROP_REPORT_INTERVAL 0x20030E
#define HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS 0x20030F #define HID_USAGE_SENSOR_PROP_SENSITIVITY_ABS 0x20030F
#define HID_USAGE_SENSOR_PROP_SENSITIVITY_RANGE_PCT 0x200310 #define HID_USAGE_SENSOR_PROP_SENSITIVITY_RANGE_PCT 0x200310
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment