aboutsummaryrefslogtreecommitdiff
path: root/drivers/hid
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/.kunitconfig1
-rw-r--r--drivers/hid/Kconfig44
-rw-r--r--drivers/hid/Makefile3
-rw-r--r--drivers/hid/amd-sfh-hid/Kconfig2
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_client.c19
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_hid.c2
-rw-r--r--drivers/hid/amd-sfh-hid/amd_sfh_hid.h1
-rw-r--r--drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c2
-rw-r--r--drivers/hid/bpf/Kconfig16
-rw-r--r--drivers/hid/bpf/Makefile11
-rw-r--r--drivers/hid/bpf/entrypoints/Makefile93
-rw-r--r--drivers/hid/bpf/entrypoints/README4
-rw-r--r--drivers/hid/bpf/entrypoints/entrypoints.bpf.c25
-rw-r--r--drivers/hid/bpf/entrypoints/entrypoints.lskel.h248
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.c551
-rw-r--r--drivers/hid/bpf/hid_bpf_dispatch.h25
-rw-r--r--drivers/hid/bpf/hid_bpf_jmp_table.c565
-rw-r--r--drivers/hid/hid-alps.c6
-rw-r--r--drivers/hid/hid-apple.c141
-rw-r--r--drivers/hid/hid-asus.c37
-rw-r--r--drivers/hid/hid-betopff.c17
-rw-r--r--drivers/hid/hid-bigbenff.c78
-rw-r--r--drivers/hid/hid-core.c56
-rw-r--r--drivers/hid/hid-debug.c4
-rw-r--r--drivers/hid/hid-elan.c6
-rw-r--r--drivers/hid/hid-elecom.c16
-rw-r--r--drivers/hid/hid-evision.c53
-rw-r--r--drivers/hid/hid-ft260.c325
-rw-r--r--drivers/hid/hid-hyperv.c37
-rw-r--r--drivers/hid/hid-ids.h20
-rw-r--r--drivers/hid/hid-input-test.c80
-rw-r--r--drivers/hid/hid-input.c63
-rw-r--r--drivers/hid/hid-ite.c5
-rw-r--r--drivers/hid/hid-letsketch.c2
-rw-r--r--drivers/hid/hid-lg4ff.c6
-rw-r--r--drivers/hid/hid-logitech-dj.c4
-rw-r--r--drivers/hid/hid-logitech-hidpp.c196
-rw-r--r--drivers/hid/hid-mcp2221.c328
-rw-r--r--drivers/hid/hid-multitouch.c43
-rw-r--r--drivers/hid/hid-plantronics.c9
-rw-r--r--drivers/hid/hid-playstation.c1218
-rw-r--r--drivers/hid/hid-quirks.c7
-rw-r--r--drivers/hid/hid-rmi.c2
-rw-r--r--drivers/hid/hid-sensor-custom.c246
-rw-r--r--drivers/hid/hid-sensor-hub.c6
-rw-r--r--drivers/hid/hid-sony.c1023
-rw-r--r--drivers/hid/hid-steam.c385
-rw-r--r--drivers/hid/hid-u2fzero.c1
-rw-r--r--drivers/hid/hid-uclogic-core-test.c105
-rw-r--r--drivers/hid/hid-uclogic-core.c64
-rw-r--r--drivers/hid/hid-uclogic-params-test.c20
-rw-r--r--drivers/hid/hid-uclogic-params.c199
-rw-r--r--drivers/hid/hid-uclogic-params.h40
-rw-r--r--drivers/hid/hid-uclogic-rdesc-test.c7
-rw-r--r--drivers/hid/hid-uclogic-rdesc.c42
-rw-r--r--drivers/hid/hid-uclogic-rdesc.h12
-rw-r--r--drivers/hid/hid-wiimote-core.c9
-rw-r--r--drivers/hid/hid-wiimote-modules.c225
-rw-r--r--drivers/hid/hid-wiimote.h1
-rw-r--r--drivers/hid/i2c-hid/Kconfig31
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-acpi.c31
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-core.c51
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c42
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of-elan.c5
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of-goodix.c103
-rw-r--r--drivers/hid/i2c-hid/i2c-hid-of.c5
-rw-r--r--drivers/hid/i2c-hid/i2c-hid.h3
-rw-r--r--drivers/hid/intel-ish-hid/Kconfig2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp-hid.c2
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/client.c3
-rw-r--r--drivers/hid/intel-ish-hid/ishtp/dma-if.c10
-rw-r--r--drivers/hid/surface-hid/surface_hid.c8
-rw-r--r--drivers/hid/surface-hid/surface_hid_core.c2
-rw-r--r--drivers/hid/surface-hid/surface_kbd.c8
-rw-r--r--drivers/hid/uhid.c3
-rw-r--r--drivers/hid/usbhid/hid-core.c9
-rw-r--r--drivers/hid/usbhid/hiddev.c2
-rw-r--r--drivers/hid/wacom_sys.c8
-rw-r--r--drivers/hid/wacom_wac.c4
-rw-r--r--drivers/hid/wacom_wac.h1
80 files changed, 5354 insertions, 1735 deletions
diff --git a/drivers/hid/.kunitconfig b/drivers/hid/.kunitconfig
index 04daeff5c970..675a8209c7ae 100644
--- a/drivers/hid/.kunitconfig
+++ b/drivers/hid/.kunitconfig
@@ -1,5 +1,6 @@
CONFIG_KUNIT=y
CONFIG_USB=y
CONFIG_USB_HID=y
+CONFIG_HID_BATTERY_STRENGTH=y
CONFIG_HID_UCLOGIC=y
CONFIG_HID_KUNIT_TEST=y
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 185a077d59cd..82f64fb31fda 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -2,13 +2,20 @@
#
# HID driver configuration
#
-menu "HID support"
- depends on INPUT
+menuconfig HID_SUPPORT
+ bool "HID bus support"
+ default y
+ 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
- tristate "HID bus support"
- depends on INPUT
+ tristate "HID bus core support"
default y
+ depends on INPUT
help
A human interface device (HID) is a type of computer device that
interacts directly with and takes input from humans. The term "HID"
@@ -329,6 +336,13 @@ config HID_ELO
Support for the ELO USB 4000/4500 touchscreens. Note that this is for
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
tristate "Ezkey BTC 8193 keyboard"
default !EXPERT
@@ -897,7 +911,7 @@ config HID_PLAYSTATION
select CRC32
select POWER_SUPPLY
help
- Provides support for Sony PS5 controllers including support for
+ Provides support for Sony PS4/PS5 controllers including support for
its special functionalities e.g. touchpad, lights and motion
sensors.
@@ -1018,13 +1032,21 @@ config HID_SPEEDLINK
Support for Speedlink Vicious and Divine Cezanne mouse.
config HID_STEAM
- tristate "Steam Controller support"
+ tristate "Steam Controller/Deck support"
select POWER_SUPPLY
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
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
tristate "Steelseries SRW-S1 steering wheel support"
help
@@ -1252,7 +1274,8 @@ config HID_ALPS
config HID_MCP2221
tristate "Microchip MCP2221 HID USB-to-I2C/SMbus host support"
depends on USB_HID && I2C
- depends on GPIOLIB
+ imply GPIOLIB
+ imply IIO
help
Provides I2C and SMBUS host adapter functionality over USB-HID
through MCP2221 device.
@@ -1263,6 +1286,7 @@ config HID_MCP2221
config HID_KUNIT_TEST
tristate "KUnit tests for HID" if !KUNIT_ALL_TESTS
depends on KUNIT=y
+ depends on HID_BATTERY_STRENGTH
depends on HID_UCLOGIC
default KUNIT_ALL_TESTS
help
@@ -1278,6 +1302,8 @@ config HID_KUNIT_TEST
endmenu
+source "drivers/hid/bpf/Kconfig"
+
endif # HID
source "drivers/hid/usbhid/Kconfig"
@@ -1290,4 +1316,4 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
source "drivers/hid/surface-hid/Kconfig"
-endmenu
+endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e8014c1a2f8b..5d37cacbde33 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -5,6 +5,8 @@
hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o
+obj-$(CONFIG_HID_BPF) += bpf/
+
obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o
@@ -45,6 +47,7 @@ obj-$(CONFIG_HID_EMS_FF) += hid-emsff.o
obj-$(CONFIG_HID_ELAN) += hid-elan.o
obj-$(CONFIG_HID_ELECOM) += hid-elecom.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_FT260) += hid-ft260.o
obj-$(CONFIG_HID_GEMBIRD) += hid-gembird.o
diff --git a/drivers/hid/amd-sfh-hid/Kconfig b/drivers/hid/amd-sfh-hid/Kconfig
index db069a83e9a2..af752dd3a340 100644
--- a/drivers/hid/amd-sfh-hid/Kconfig
+++ b/drivers/hid/amd-sfh-hid/Kconfig
@@ -2,10 +2,10 @@
menu "AMD SFH HID Support"
depends on X86_64 || COMPILE_TEST
depends on PCI
- depends on HID
config AMD_SFH_HID
tristate "AMD Sensor Fusion Hub"
+ depends on HID
help
If you say yes to this option, support will be included for the
AMD Sensor Fusion Hub.
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
index 8275bba63611..c751d12f5df8 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c
@@ -227,6 +227,7 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->num_hid_devices = amd_mp2_get_sensor_num(privdata, &cl_data->sensor_idx[0]);
if (cl_data->num_hid_devices == 0)
return -ENODEV;
+ cl_data->is_any_sensor_enabled = false;
INIT_DELAYED_WORK(&cl_data->work, amd_sfh_work);
INIT_DELAYED_WORK(&cl_data->work_buffer, amd_sfh_work_buffer);
@@ -237,6 +238,10 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
in_data->sensor_virt_addr[i] = dma_alloc_coherent(dev, sizeof(int) * 8,
&cl_data->sensor_dma_addr[i],
GFP_KERNEL);
+ if (!in_data->sensor_virt_addr[i]) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
cl_data->sensor_sts[i] = SENSOR_DISABLED;
cl_data->sensor_requested_cnt[i] = 0;
cl_data->cur_hid_dev = i;
@@ -278,11 +283,12 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
}
rc = mp2_ops->get_rep_desc(cl_idx, cl_data->report_descr[i]);
if (rc)
- return rc;
+ goto cleanup;
mp2_ops->start(privdata, info);
status = amd_sfh_wait_for_response
(privdata, cl_data->sensor_idx[i], SENSOR_ENABLED);
if (status == SENSOR_ENABLED) {
+ cl_data->is_any_sensor_enabled = true;
cl_data->sensor_sts[i] = SENSOR_ENABLED;
rc = amdtp_hid_probe(cl_data->cur_hid_dev, cl_data);
if (rc) {
@@ -297,19 +303,26 @@ int amd_sfh_hid_client_init(struct amd_mp2_dev *privdata)
cl_data->sensor_sts[i]);
goto cleanup;
}
+ } else {
+ cl_data->sensor_sts[i] = SENSOR_DISABLED;
+ dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
+ cl_data->sensor_idx[i],
+ get_sensor_name(cl_data->sensor_idx[i]),
+ cl_data->sensor_sts[i]);
}
dev_dbg(dev, "sid 0x%x (%s) status 0x%x\n",
cl_data->sensor_idx[i], get_sensor_name(cl_data->sensor_idx[i]),
cl_data->sensor_sts[i]);
}
- if (mp2_ops->discovery_status && mp2_ops->discovery_status(privdata) == 0) {
+ if (!cl_data->is_any_sensor_enabled ||
+ (mp2_ops->discovery_status && mp2_ops->discovery_status(privdata) == 0)) {
amd_sfh_hid_client_deinit(privdata);
for (i = 0; i < cl_data->num_hid_devices; i++) {
devm_kfree(dev, cl_data->feature_report[i]);
devm_kfree(dev, in_data->input_report[i]);
devm_kfree(dev, cl_data->report_descr[i]);
}
- dev_warn(dev, "Failed to discover, sensors not enabled\n");
+ dev_warn(dev, "Failed to discover, sensors not enabled is %d\n", cl_data->is_any_sensor_enabled);
return -EOPNOTSUPP;
}
schedule_delayed_work(&cl_data->work_buffer, msecs_to_jiffies(AMD_SFH_IDLE_LOOP));
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
index 1b18291fc5af..705b52337068 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.c
@@ -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,
.start = amdtp_hid_start,
.stop = amdtp_hid_stop,
diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
index 3754fb423e3a..528036892c9d 100644
--- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h
@@ -32,6 +32,7 @@ struct amd_input_data {
struct amdtp_cl_data {
u8 init_done;
u32 cur_hid_dev;
+ bool is_any_sensor_enabled;
u32 hid_dev_count;
u32 num_hid_devices;
struct device_info *hid_devices;
diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
index 4da2f9f62aba..a1d6e08fab7d 100644
--- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
+++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_init.c
@@ -160,7 +160,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
}
rc = mp2_ops->get_rep_desc(cl_idx, cl_data->report_descr[i]);
if (rc)
- return rc;
+ goto cleanup;
writel(0, privdata->mmio + AMD_P2C_MSG(0));
mp2_ops->start(privdata, info);
diff --git a/drivers/hid/bpf/Kconfig b/drivers/hid/bpf/Kconfig
new file mode 100644
index 000000000000..83214bae6768
--- /dev/null
+++ b/drivers/hid/bpf/Kconfig
@@ -0,0 +1,16 @@
+# 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
diff --git a/drivers/hid/bpf/Makefile b/drivers/hid/bpf/Makefile
new file mode 100644
index 000000000000..cf55120cf7d6
--- /dev/null
+++ b/drivers/hid/bpf/Makefile
@@ -0,0 +1,11 @@
+# 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
diff --git a/drivers/hid/bpf/entrypoints/Makefile b/drivers/hid/bpf/entrypoints/Makefile
new file mode 100644
index 000000000000..a12edcfa4fe3
--- /dev/null
+++ b/drivers/hid/bpf/entrypoints/Makefile
@@ -0,0 +1,93 @@
+# 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
diff --git a/drivers/hid/bpf/entrypoints/README b/drivers/hid/bpf/entrypoints/README
new file mode 100644
index 000000000000..147e0d41509f
--- /dev/null
+++ b/drivers/hid/bpf/entrypoints/README
@@ -0,0 +1,4 @@
+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
diff --git a/drivers/hid/bpf/entrypoints/entrypoints.bpf.c b/drivers/hid/bpf/entrypoints/entrypoints.bpf.c
new file mode 100644
index 000000000000..c22921125a1a
--- /dev/null
+++ b/drivers/hid/bpf/entrypoints/entrypoints.bpf.c
@@ -0,0 +1,25 @@
+// 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";
diff --git a/drivers/hid/bpf/entrypoints/entrypoints.lskel.h b/drivers/hid/bpf/entrypoints/entrypoints.lskel.h
new file mode 100644
index 000000000000..35618051598c
--- /dev/null
+++ b/drivers/hid/bpf/entrypoints/entrypoints.lskel.h
@@ -0,0 +1,248 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+/* THIS FILE IS AUTOGENERATED BY BPFTOOL! */
+#ifndef __ENTRYPOINTS_BPF_SKEL_H__
+#define __ENTRYPOINTS_BPF_SKEL_H__
+
+#include <bpf/skel_internal.h>
+
+struct entrypoints_bpf {
+ struct bpf_loader_ctx ctx;
+ struct {
+ struct bpf_map_desc hid_jmp_table;
+ } maps;
+ struct {
+ struct bpf_prog_desc hid_tail_call;
+ } progs;
+ struct {
+ int hid_tail_call_fd;
+ } links;
+};
+
+static inline int
+entrypoints_bpf__hid_tail_call__attach(struct entrypoints_bpf *skel)
+{
+ int prog_fd = skel->progs.hid_tail_call.prog_fd;
+ int fd = skel_raw_tracepoint_open(NULL, prog_fd);
+
+ if (fd > 0)
+ skel->links.hid_tail_call_fd = fd;
+ return fd;
+}
+
+static inline int
+entrypoints_bpf__attach(struct entrypoints_bpf *skel)
+{
+ int ret = 0;
+
+ ret = ret < 0 ? ret : entrypoints_bpf__hid_tail_call__attach(skel);
+ return ret < 0 ? ret : 0;
+}
+
+static inline void
+entrypoints_bpf__detach(struct entrypoints_bpf *skel)
+{
+ skel_closenz(skel->links.hid_tail_call_fd);
+}
+static void
+entrypoints_bpf__destroy(struct entrypoints_bpf *skel)
+{
+ if (!skel)
+ return;
+ entrypoints_bpf__detach(skel);
+ skel_closenz(skel->progs.hid_tail_call.prog_fd);
+ skel_closenz(skel->maps.hid_jmp_table.map_fd);
+ skel_free(skel);
+}
+static inline struct entrypoints_bpf *
+entrypoints_bpf__open(void)
+{
+ struct entrypoints_bpf *skel;
+
+ skel = skel_alloc(sizeof(*skel));
+ if (!skel)
+ goto cleanup;
+ skel->ctx.sz = (void *)&skel->links - (void *)skel;
+ return skel;
+cleanup:
+ entrypoints_bpf__destroy(skel);
+ return NULL;
+}
+
+static inline int
+entrypoints_bpf__load(struct entrypoints_bpf *skel)
+{
+ struct bpf_load_and_run_opts opts = {};
+ int err;
+
+ opts.ctx = (struct bpf_loader_ctx *)skel;
+ opts.data_sz = 2856;
+ opts.data = (void *)"\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x9f\xeb\x01\0\
+\x18\0\0\0\0\0\0\0\x60\x02\0\0\x60\x02\0\0\x12\x02\0\0\0\0\0\0\0\0\0\x02\x03\0\
+\0\0\x01\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\
+\0\0\x04\0\0\0\x03\0\0\0\x05\0\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\
+\x02\x06\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\
+\0\0\0\x02\x08\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\0\x02\0\0\0\x04\0\0\0\x04\0\0\0\0\
+\0\0\0\x04\0\0\x04\x20\0\0\0\x19\0\0\0\x01\0\0\0\0\0\0\0\x1e\0\0\0\x05\0\0\0\
+\x40\0\0\0\x2a\0\0\0\x07\0\0\0\x80\0\0\0\x33\0\0\0\x07\0\0\0\xc0\0\0\0\x3e\0\0\
+\0\0\0\0\x0e\x09\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\x02\x0c\0\0\0\x4c\0\0\0\0\0\0\
+\x01\x08\0\0\0\x40\0\0\0\0\0\0\0\x01\0\0\x0d\x02\0\0\0\x5f\0\0\0\x0b\0\0\0\x63\
+\0\0\0\x01\0\0\x0c\x0d\0\0\0\x09\x01\0\0\x05\0\0\x04\x20\0\0\0\x15\x01\0\0\x10\
+\0\0\0\0\0\0\0\x1b\x01\0\0\x12\0\0\0\x40\0\0\0\x1f\x01\0\0\x10\0\0\0\x80\0\0\0\
+\x2e\x01\0\0\x14\0\0\0\xa0\0\0\0\0\0\0\0\x15\0\0\0\xc0\0\0\0\x3a\x01\0\0\0\0\0\
+\x08\x11\0\0\0\x40\x01\0\0\0\0\0\x01\x04\0\0\0\x20\0\0\0\0\0\0\0\0\0\0\x02\x13\
+\0\0\0\0\0\0\0\0\0\0\x0a\x1c\0\0\0\x4d\x01\0\0\x04\0\0\x06\x04\0\0\0\x5d\x01\0\
+\0\0\0\0\0\x6e\x01\0\0\x01\0\0\0\x80\x01\0\0\x02\0\0\0\x93\x01\0\0\x03\0\0\0\0\
+\0\0\0\x02\0\0\x05\x04\0\0\0\xa4\x01\0\0\x16\0\0\0\0\0\0\0\xab\x01\0\0\x16\0\0\
+\0\0\0\0\0\xb0\x01\0\0\0\0\0\x08\x02\0\0\0\xec\x01\0\0\0\0\0\x01\x01\0\0\0\x08\
+\0\0\x01\0\0\0\0\0\0\0\x03\0\0\0\0\x17\0\0\0\x04\0\0\0\x04\0\0\0\xf1\x01\0\0\0\
+\0\0\x0e\x18\0\0\0\x01\0\0\0\xf9\x01\0\0\x01\0\0\x0f\x20\0\0\0\x0a\0\0\0\0\0\0\
+\0\x20\0\0\0\xff\x01\0\0\x01\0\0\x0f\x04\0\0\0\x19\0\0\0\0\0\0\0\x04\0\0\0\x07\
+\x02\0\0\0\0\0\x07\0\0\0\0\0\x69\x6e\x74\0\x5f\x5f\x41\x52\x52\x41\x59\x5f\x53\
+\x49\x5a\x45\x5f\x54\x59\x50\x45\x5f\x5f\0\x74\x79\x70\x65\0\x6d\x61\x78\x5f\
+\x65\x6e\x74\x72\x69\x65\x73\0\x6b\x65\x79\x5f\x73\x69\x7a\x65\0\x76\x61\x6c\
+\x75\x65\x5f\x73\x69\x7a\x65\0\x68\x69\x64\x5f\x6a\x6d\x70\x5f\x74\x61\x62\x6c\
+\x65\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x6c\x6f\x6e\x67\x20\x6c\x6f\x6e\x67\
+\0\x63\x74\x78\0\x68\x69\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\x66\x6d\
+\x6f\x64\x5f\x72\x65\x74\x2f\x5f\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\
+\x69\x6c\x5f\x63\x61\x6c\x6c\0\x2f\x68\x6f\x6d\x65\x2f\x62\x74\x69\x73\x73\x6f\
+\x69\x72\x2f\x53\x72\x63\x2f\x68\x69\x64\x2f\x64\x72\x69\x76\x65\x72\x73\x2f\
+\x68\x69\x64\x2f\x62\x70\x66\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\
+\x2f\x65\x6e\x74\x72\x79\x70\x6f\x69\x6e\x74\x73\x2e\x62\x70\x66\x2e\x63\0\x69\
+\x6e\x74\x20\x42\x50\x46\x5f\x50\x52\x4f\x47\x28\x68\x69\x64\x5f\x74\x61\x69\
+\x6c\x5f\x63\x61\x6c\x6c\x2c\x20\x73\x74\x72\x75\x63\x74\x20\x68\x69\x64\x5f\
+\x62\x70\x66\x5f\x63\x74\x78\x20\x2a\x68\x63\x74\x78\x29\0\x68\x69\x64\x5f\x62\
+\x70\x66\x5f\x63\x74\x78\0\x69\x6e\x64\x65\x78\0\x68\x69\x64\0\x61\x6c\x6c\x6f\
+\x63\x61\x74\x65\x64\x5f\x73\x69\x7a\x65\0\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\
+\x70\x65\0\x5f\x5f\x75\x33\x32\0\x75\x6e\x73\x69\x67\x6e\x65\x64\x20\x69\x6e\
+\x74\0\x68\x69\x64\x5f\x72\x65\x70\x6f\x72\x74\x5f\x74\x79\x70\x65\0\x48\x49\
+\x44\x5f\x49\x4e\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x4f\
+\x55\x54\x50\x55\x54\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x46\x45\x41\
+\x54\x55\x52\x45\x5f\x52\x45\x50\x4f\x52\x54\0\x48\x49\x44\x5f\x52\x45\x50\x4f\
+\x52\x54\x5f\x54\x59\x50\x45\x53\0\x72\x65\x74\x76\x61\x6c\0\x73\x69\x7a\x65\0\
+\x5f\x5f\x73\x33\x32\0\x30\x3a\x30\0\x09\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\
+\x63\x61\x6c\x6c\x28\x63\x74\x78\x2c\x20\x26\x68\x69\x64\x5f\x6a\x6d\x70\x5f\
+\x74\x61\x62\x6c\x65\x2c\x20\x68\x63\x74\x78\x2d\x3e\x69\x6e\x64\x65\x78\x29\
+\x3b\0\x63\x68\x61\x72\0\x4c\x49\x43\x45\x4e\x53\x45\0\x2e\x6d\x61\x70\x73\0\
+\x6c\x69\x63\x65\x6e\x73\x65\0\x68\x69\x64\x5f\x64\x65\x76\x69\x63\x65\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x8a\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x03\
+\0\0\0\x04\0\0\0\x04\0\0\0\0\x04\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\x64\x5f\
+\x6a\x6d\x70\x5f\x74\x61\x62\x6c\x65\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\x47\x50\x4c\0\0\0\0\0\x79\x12\0\0\0\0\0\0\x61\x23\0\0\0\0\
+\0\0\x18\x52\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x85\0\0\0\x0c\0\0\0\xb7\0\0\0\0\0\0\0\
+\x95\0\0\0\0\0\0\0\0\0\0\0\x0e\0\0\0\0\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\x48\0\0\
+\x01\0\0\0\x8e\0\0\0\xba\x01\0\0\x02\x50\0\0\x05\0\0\0\x8e\0\0\0\xd3\0\0\0\x05\
+\x48\0\0\x08\0\0\0\x0f\0\0\0\xb6\x01\0\0\0\0\0\0\x1a\0\0\0\x07\0\0\0\0\0\0\0\0\
+\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x68\x69\
+\x64\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\0\0\0\0\x1a\0\0\0\0\0\0\0\
+\x08\0\0\0\0\0\0\0\0\0\0\0\x01\0\0\0\x10\0\0\0\0\0\0\0\0\0\0\0\x03\0\0\0\x01\0\
+\0\0\0\0\0\0\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x10\0\0\0\0\0\0\0\x5f\
+\x5f\x68\x69\x64\x5f\x62\x70\x66\x5f\x74\x61\x69\x6c\x5f\x63\x61\x6c\x6c\0\0\0\
+\0\0";
+ opts.insns_sz = 1192;
+ opts.insns = (void *)"\
+\xbf\x16\0\0\0\0\0\0\xbf\xa1\0\0\0\0\0\0\x07\x01\0\0\x78\xff\xff\xff\xb7\x02\0\
+\0\x88\0\0\0\xb7\x03\0\0\0\0\0\0\x85\0\0\0\x71\0\0\0\x05\0\x11\0\0\0\0\0\x61\
+\xa1\x78\xff\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x7c\xff\
+\0\0\0\0\xd5\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa1\x80\xff\0\0\0\0\xd5\
+\x01\x01\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\
+\x01\0\0\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\
+\xbf\x70\0\0\0\0\0\0\x95\0\0\0\0\0\0\0\x61\x60\x08\0\0\0\0\0\x18\x61\0\0\0\0\0\
+\0\0\0\0\0\xa8\x09\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\0\0\0\0\x18\x61\0\0\0\
+\0\0\0\0\0\0\0\xa4\x09\0\0\x63\x01\0\0\0\0\0\0\x79\x60\x10\0\0\0\0\0\x18\x61\0\
+\0\0\0\0\0\0\0\0\0\x98\x09\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
+\0\x05\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\x7b\x01\0\0\0\0\0\0\xb7\x01\
+\0\0\x12\0\0\0\x18\x62\0\0\0\0\0\0\0\0\0\0\x90\x09\0\0\xb7\x03\0\0\x1c\0\0\0\
+\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\xd7\xff\0\0\0\0\x63\x7a\x78\
+\xff\0\0\0\0\x61\x60\x1c\0\0\0\0\0\x15\0\x03\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\
+\0\0\xbc\x09\0\0\x63\x01\0\0\0\0\0\0\xb7\x01\0\0\0\0\0\0\x18\x62\0\0\0\0\0\0\0\
+\0\0\0\xb0\x09\0\0\xb7\x03\0\0\x48\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\0\0\0\0\0\
+\0\xc5\x07\xca\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x63\x71\0\0\0\0\
+\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf8\x09\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x90\
+\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\x0a\0\0\x18\x61\0\0\
+\0\0\0\0\0\0\0\0\x88\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\
+\x38\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xd0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x18\
+\x60\0\0\0\0\0\0\0\0\0\0\x40\x0a\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xe0\x0a\0\0\
+\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x70\x0a\0\0\x18\x61\0\0\0\0\0\
+\0\0\0\0\0\0\x0b\0\0\x7b\x01\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
+\x18\x61\0\0\0\0\0\0\0\0\0\0\xf8\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\x60\x08\0\0\0\
+\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x98\x0a\0\0\x63\x01\0\0\0\0\0\0\x61\x60\x0c\0\
+\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x9c\x0a\0\0\x63\x01\0\0\0\0\0\0\x79\x60\
+\x10\0\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xa0\x0a\0\0\x7b\x01\0\0\0\0\0\0\x61\
+\xa0\x78\xff\0\0\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\xc8\x0a\0\0\x63\x01\0\0\0\0\0\
+\0\x18\x61\0\0\0\0\0\0\0\0\0\0\x10\x0b\0\0\xb7\x02\0\0\x14\0\0\0\xb7\x03\0\0\
+\x0c\0\0\0\xb7\x04\0\0\0\0\0\0\x85\0\0\0\xa7\0\0\0\xbf\x07\0\0\0\0\0\0\xc5\x07\
+\x91\xff\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\x80\x0a\0\0\x63\x70\x6c\0\0\0\0\0\
+\x77\x07\0\0\x20\0\0\0\x63\x70\x70\0\0\0\0\0\xb7\x01\0\0\x05\0\0\0\x18\x62\0\0\
+\0\0\0\0\0\0\0\0\x80\x0a\0\0\xb7\x03\0\0\x8c\0\0\0\x85\0\0\0\xa6\0\0\0\xbf\x07\
+\0\0\0\0\0\0\x18\x60\0\0\0\0\0\0\0\0\0\0\xf0\x0a\0\0\x61\x01\0\0\0\0\0\0\xd5\
+\x01\x02\0\0\0\0\0\xbf\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\xc5\x07\x7f\xff\0\0\
+\0\0\x63\x7a\x80\xff\0\0\0\0\x61\xa1\x78\xff\0\0\0\0\xd5\x01\x02\0\0\0\0\0\xbf\
+\x19\0\0\0\0\0\0\x85\0\0\0\xa8\0\0\0\x61\xa0\x80\xff\0\0\0\0\x63\x06\x28\0\0\0\
+\0\0\x18\x61\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x61\x10\0\0\0\0\0\0\x63\x06\x18\0\0\0\
+\0\0\xb7\0\0\0\0\0\0\0\x95\0\0\0\0\0\0\0";
+ err = bpf_load_and_run(&opts);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+static inline struct entrypoints_bpf *
+entrypoints_bpf__open_and_load(void)
+{
+ struct entrypoints_bpf *skel;
+
+ skel = entrypoints_bpf__open();
+ if (!skel)
+ return NULL;
+ if (entrypoints_bpf__load(skel)) {
+ entrypoints_bpf__destroy(skel);
+ return NULL;
+ }
+ return skel;
+}
+
+__attribute__((unused)) static void
+entrypoints_bpf__assert(struct entrypoints_bpf *s __attribute__((unused)))
+{
+#ifdef __cplusplus
+#define _Static_assert static_assert
+#endif
+#ifdef __cplusplus
+#undef _Static_assert
+#endif
+}
+
+#endif /* __ENTRYPOINTS_BPF_SKEL_H__ */
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
new file mode 100644
index 000000000000..8a034a555d4c
--- /dev/null
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * HID-BPF support for Linux
+ *
+ * Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <linux/bitops.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/filter.h>
+#include <linux/hid.h>
+#include <linux/hid_bpf.h>
+#include <linux/init.h>
+#include <linux/kfifo.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include "hid_bpf_dispatch.h"
+#include "entrypoints/entrypoints.lskel.h"
+
+struct hid_bpf_ops *hid_bpf_ops;
+EXPORT_SYMBOL(hid_bpf_ops);
+
+/**
+ * hid_bpf_device_event - Called whenever an event is coming in from the device
+ *
+ * @ctx: The HID-BPF context
+ *
+ * @return %0 on success and keep processing; a positive value to change the
+ * incoming size buffer; a negative error code to interrupt the processing
+ * of this event
+ *
+ * Declare an %fmod_ret tracing bpf program to this function and attach this
+ * program through hid_bpf_attach_prog() to have this helper called for
+ * any incoming event from the device itself.
+ *
+ * The function is called while on IRQ context, so we can not sleep.
+ */
+/* never used by the kernel but declared so we can load and attach a tracepoint */
+__weak noinline int hid_bpf_device_event(struct hid_bpf_ctx *ctx)
+{
+ return 0;
+}
+
+u8 *
+dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
+ u32 *size, int interrupt)
+{
+ struct hid_bpf_ctx_kern ctx_kern = {
+ .ctx = {
+ .hid = hdev,
+ .report_type = type,
+ .allocated_size = hdev->bpf.allocated_data,
+ .size = *size,
+ },
+ .data = hdev->bpf.device_data,
+ };
+ int ret;
+
+ if (type >= HID_REPORT_TYPES)
+ return ERR_PTR(-EINVAL);
+
+ /* no program has been attached yet */
+ if (!hdev->bpf.device_data)
+ return data;
+
+ memset(ctx_kern.data, 0, hdev->bpf.allocated_data);
+ memcpy(ctx_kern.data, data, *size);
+
+ ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ if (ret) {
+ if (ret > ctx_kern.ctx.allocated_size)
+ return ERR_PTR(-EINVAL);
+
+ *size = ret;
+ }
+
+ return ctx_kern.data;
+}
+EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
+
+/**
+ * hid_bpf_rdesc_fixup - Called when the probe function parses the report
+ * descriptor of the HID device
+ *
+ * @ctx: The HID-BPF context
+ *
+ * @return 0 on success and keep processing; a positive value to change the
+ * incoming size buffer; a negative error code to interrupt the processing
+ * of this event
+ *
+ * Declare an %fmod_ret tracing bpf program to this function and attach this
+ * program through hid_bpf_attach_prog() to have this helper called before any
+ * parsing of the report descriptor by HID.
+ */
+/* never used by the kernel but declared so we can load and attach a tracepoint */
+__weak noinline int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
+{
+ return 0;
+}
+
+u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
+{
+ int ret;
+ struct hid_bpf_ctx_kern ctx_kern = {
+ .ctx = {
+ .hid = hdev,
+ .size = *size,
+ .allocated_size = HID_MAX_DESCRIPTOR_SIZE,
+ },
+ };
+
+ ctx_kern.data = kzalloc(ctx_kern.ctx.allocated_size, GFP_KERNEL);
+ if (!ctx_kern.data)
+ goto ignore_bpf;
+
+ memcpy(ctx_kern.data, rdesc, min_t(unsigned int, *size, HID_MAX_DESCRIPTOR_SIZE));
+
+ ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_RDESC_FIXUP, &ctx_kern);
+ if (ret < 0)
+ goto ignore_bpf;
+
+ if (ret) {
+ if (ret > ctx_kern.ctx.allocated_size)
+ goto ignore_bpf;
+
+ *size = ret;
+ }
+
+ rdesc = krealloc(ctx_kern.data, *size, GFP_KERNEL);
+
+ return rdesc;
+
+ ignore_bpf:
+ kfree(ctx_kern.data);
+ return kmemdup(rdesc, *size, GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
+
+/**
+ * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
+ *
+ * @ctx: The HID-BPF context
+ * @offset: The offset within the memory
+ * @rdwr_buf_size: the const size of the buffer
+ *
+ * @returns %NULL on error, an %__u8 memory pointer on success
+ */
+noinline __u8 *
+hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+
+ if (!ctx)
+ return NULL;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+
+ if (rdwr_buf_size + offset > ctx->allocated_size)
+ return NULL;
+
+ return ctx_kern->data + offset;
+}
+
+/*
+ * The following set contains all functions we agree BPF programs
+ * can use.
+ */
+BTF_SET8_START(hid_bpf_kfunc_ids)
+BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
+BTF_SET8_END(hid_bpf_kfunc_ids)
+
+static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &hid_bpf_kfunc_ids,
+};
+
+static int device_match_id(struct device *dev, const void *id)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+
+ return hdev->id == *(int *)id;
+}
+
+static int __hid_bpf_allocate_data(struct hid_device *hdev, u8 **data, u32 *size)
+{
+ u8 *alloc_data;
+ unsigned int i, j, max_report_len = 0;
+ size_t alloc_size = 0;
+
+ /* compute the maximum report length for this device */
+ for (i = 0; i < HID_REPORT_TYPES; i++) {
+ struct hid_report_enum *report_enum = hdev->report_enum + i;
+
+ for (j = 0; j < HID_MAX_IDS; j++) {
+ struct hid_report *report = report_enum->report_id_hash[j];
+
+ if (report)
+ max_report_len = max(max_report_len, hid_report_len(report));
+ }
+ }
+
+ /*
+ * Give us a little bit of extra space and some predictability in the
+ * buffer length we create. This way, we can tell users that they can
+ * work on chunks of 64 bytes of memory without having the bpf verifier
+ * scream at them.
+ */
+ alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64;
+
+ alloc_data = kzalloc(alloc_size, GFP_KERNEL);
+ if (!alloc_data)
+ return -ENOMEM;
+
+ *data = alloc_data;
+ *size = alloc_size;
+
+ return 0;
+}
+
+static int hid_bpf_allocate_event_data(struct hid_device *hdev)
+{
+ /* hdev->bpf.device_data is already allocated, abort */
+ if (hdev->bpf.device_data)
+ return 0;
+
+ return __hid_bpf_allocate_data(hdev, &hdev->bpf.device_data, &hdev->bpf.allocated_data);
+}
+
+int hid_bpf_reconnect(struct hid_device *hdev)
+{
+ if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
+ return device_reprobe(&hdev->dev);
+
+ return 0;
+}
+
+/**
+ * hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
+ *
+ * @hid_id: the system unique identifier of the HID device
+ * @prog_fd: an fd in the user process representing the program to attach
+ * @flags: any logical OR combination of &enum hid_bpf_attach_flags
+ *
+ * @returns an fd of a bpf_link object on success (> %0), an error code otherwise.
+ * Closing this fd will detach the program from the HID device (unless the bpf_link
+ * is pinned to the BPF file system).
+ */
+/* called from syscall */
+noinline int
+hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
+{
+ struct hid_device *hdev;
+ struct device *dev;
+ int fd, err, prog_type = hid_bpf_get_prog_attach_type(prog_fd);
+
+ if (!hid_bpf_ops)
+ return -EINVAL;
+
+ if (prog_type < 0)
+ return prog_type;
+
+ if (prog_type >= HID_BPF_PROG_TYPE_MAX)
+ return -EINVAL;
+
+ if ((flags & ~HID_BPF_FLAG_MASK))
+ return -EINVAL;
+
+ dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
+ if (!dev)
+ return -EINVAL;
+
+ hdev = to_hid_device(dev);
+
+ if (prog_type == HID_BPF_PROG_TYPE_DEVICE_EVENT) {
+ err = hid_bpf_allocate_event_data(hdev);
+ if (err)
+ return err;
+ }
+
+ fd = __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
+ if (fd < 0)
+ return fd;
+
+ if (prog_type == HID_BPF_PROG_TYPE_RDESC_FIXUP) {
+ err = hid_bpf_reconnect(hdev);
+ if (err) {
+ close_fd(fd);
+ return err;
+ }
+ }
+
+ return fd;
+}
+
+/**
+ * hid_bpf_allocate_context - Allocate a context to the given HID device
+ *
+ * @hid_id: the system unique identifier of the HID device
+ *
+ * @returns A pointer to &struct hid_bpf_ctx on success, %NULL on error.
+ */
+noinline struct hid_bpf_ctx *
+hid_bpf_allocate_context(unsigned int hid_id)
+{
+ struct hid_device *hdev;
+ struct hid_bpf_ctx_kern *ctx_kern = NULL;
+ struct device *dev;
+
+ if (!hid_bpf_ops)
+ return NULL;
+
+ dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
+ if (!dev)
+ return NULL;
+
+ hdev = to_hid_device(dev);
+
+ ctx_kern = kzalloc(sizeof(*ctx_kern), GFP_KERNEL);
+ if (!ctx_kern)
+ return NULL;
+
+ ctx_kern->ctx.hid = hdev;
+
+ return &ctx_kern->ctx;
+}
+
+/**
+ * hid_bpf_release_context - Release the previously allocated context @ctx
+ *
+ * @ctx: the HID-BPF context to release
+ *
+ */
+noinline void
+hid_bpf_release_context(struct hid_bpf_ctx *ctx)
+{
+ struct hid_bpf_ctx_kern *ctx_kern;
+
+ if (!ctx)
+ return;
+
+ ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
+
+ kfree(ctx_kern);
+}
+
+/**
+ * hid_bpf_hw_request - Communicate with a HID device
+ *
+ * @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
+ * @buf: a %PTR_TO_MEM buffer
+ * @buf__sz: the size of the data to transfer
+ * @rtype: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
+ * @reqtype: the type of the request (%HID_REQ_GET_REPORT, %HID_REQ_SET_REPORT, ...)
+ *
+ * @returns %0 on success, a negative error code otherwise.
+ */
+noinline int
+hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
+ enum hid_report_type rtype, enum hid_class_request reqtype)
+{
+ struct hid_device *hdev;
+ struct hid_report *report;
+ struct hid_report_enum *report_enum;
+ u8 *dma_data;
+ u32 report_len;
+ int ret;
+
+ /* check arguments */
+ if (!ctx || !hid_bpf_ops || !buf)
+ return -EINVAL;
+
+ switch (rtype) {
+ case HID_INPUT_REPORT:
+ case HID_OUTPUT_REPORT:
+ case HID_FEATURE_REPORT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ case HID_REQ_GET_IDLE:
+ case HID_REQ_GET_PROTOCOL:
+ case HID_REQ_SET_REPORT:
+ case HID_REQ_SET_IDLE:
+ case HID_REQ_SET_PROTOCOL:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (buf__sz < 1)
+ return -EINVAL;
+
+ hdev = (struct hid_device *)ctx->hid; /* discard const */
+
+ report_enum = hdev->report_enum + rtype;
+ report = hid_bpf_ops->hid_get_report(report_enum, buf);
+ if (!report)
+ return -EINVAL;
+
+ report_len = hid_report_len(report);
+
+ if (buf__sz > report_len)
+ buf__sz = report_len;
+
+ dma_data = kmemdup(buf, buf__sz, GFP_KERNEL);
+ if (!dma_data)
+ return -ENOMEM;
+
+ ret = hid_bpf_ops->hid_hw_raw_request(hdev,
+ dma_data[0],
+ dma_data,
+ buf__sz,
+ rtype,
+ reqtype);
+
+ if (ret > 0)
+ memcpy(buf, dma_data, ret);
+
+ kfree(dma_data);
+ return ret;
+}
+
+/* our HID-BPF entrypoints */
+BTF_SET8_START(hid_bpf_fmodret_ids)
+BTF_ID_FLAGS(func, hid_bpf_device_event)
+BTF_ID_FLAGS(func, hid_bpf_rdesc_fixup)
+BTF_ID_FLAGS(func, __hid_bpf_tail_call)
+BTF_SET8_END(hid_bpf_fmodret_ids)
+
+static const struct btf_kfunc_id_set hid_bpf_fmodret_set = {
+ .owner = THIS_MODULE,
+ .set = &hid_bpf_fmodret_ids,
+};
+
+/* for syscall HID-BPF */
+BTF_SET8_START(hid_bpf_syscall_kfunc_ids)
+BTF_ID_FLAGS(func, hid_bpf_attach_prog)
+BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL)
+BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE)
+BTF_ID_FLAGS(func, hid_bpf_hw_request)
+BTF_SET8_END(hid_bpf_syscall_kfunc_ids)
+
+static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &hid_bpf_syscall_kfunc_ids,
+};
+
+int hid_bpf_connect_device(struct hid_device *hdev)
+{
+ struct hid_bpf_prog_list *prog_list;
+
+ rcu_read_lock();
+ prog_list = rcu_dereference(hdev->bpf.progs[HID_BPF_PROG_TYPE_DEVICE_EVENT]);
+ rcu_read_unlock();
+
+ /* only allocate BPF data if there are programs attached */
+ if (!prog_list)
+ return 0;
+
+ return hid_bpf_allocate_event_data(hdev);
+}
+EXPORT_SYMBOL_GPL(hid_bpf_connect_device);
+
+void hid_bpf_disconnect_device(struct hid_device *hdev)
+{
+ kfree(hdev->bpf.device_data);
+ hdev->bpf.device_data = NULL;
+ hdev->bpf.allocated_data = 0;
+}
+EXPORT_SYMBOL_GPL(hid_bpf_disconnect_device);
+
+void hid_bpf_destroy_device(struct hid_device *hdev)
+{
+ if (!hdev)
+ return;
+
+ /* mark the device as destroyed in bpf so we don't reattach it */
+ hdev->bpf.destroyed = true;
+
+ __hid_bpf_destroy_device(hdev);
+}
+EXPORT_SYMBOL_GPL(hid_bpf_destroy_device);
+
+void hid_bpf_device_init(struct hid_device *hdev)
+{
+ spin_lock_init(&hdev->bpf.progs_lock);
+}
+EXPORT_SYMBOL_GPL(hid_bpf_device_init);
+
+static int __init hid_bpf_init(void)
+{
+ int err;
+
+ /* Note: if we exit with an error any time here, we would entirely break HID, which
+ * is probably not something we want. So we log an error and return success.
+ *
+ * This is not a big deal: the syscall allowing to attach a BPF program to a HID device
+ * will not be available, so nobody will be able to use the functionality.
+ */
+
+ err = register_btf_fmodret_id_set(&hid_bpf_fmodret_set);
+ if (err) {
+ pr_warn("error while registering fmodret entrypoints: %d", err);
+ return 0;
+ }
+
+ err = hid_bpf_preload_skel();
+ if (err) {
+ pr_warn("error while preloading HID BPF dispatcher: %d", err);
+ return 0;
+ }
+
+ /* register tracing kfuncs after we are sure we can load our preloaded bpf program */
+ err = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &hid_bpf_kfunc_set);
+ if (err) {
+ pr_warn("error while setting HID BPF tracing kfuncs: %d", err);
+ return 0;
+ }
+
+ /* register syscalls after we are sure we can load our preloaded bpf program */
+ err = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &hid_bpf_syscall_kfunc_set);
+ if (err) {
+ pr_warn("error while setting HID BPF syscall kfuncs: %d", err);
+ return 0;
+ }
+
+ return 0;
+}
+
+static void __exit hid_bpf_exit(void)
+{
+ /* HID depends on us, so if we hit that code, we are guaranteed that hid
+ * has been removed and thus we do not need to clear the HID devices
+ */
+ hid_bpf_free_links_and_skel();
+}
+
+late_initcall(hid_bpf_init);
+module_exit(hid_bpf_exit);
+MODULE_AUTHOR("Benjamin Tissoires");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/bpf/hid_bpf_dispatch.h b/drivers/hid/bpf/hid_bpf_dispatch.h
new file mode 100644
index 000000000000..63dfc8605cd2
--- /dev/null
+++ b/drivers/hid/bpf/hid_bpf_dispatch.h
@@ -0,0 +1,25 @@
+/* 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
diff --git a/drivers/hid/bpf/hid_bpf_jmp_table.c b/drivers/hid/bpf/hid_bpf_jmp_table.c
new file mode 100644
index 000000000000..eca34b7372f9
--- /dev/null
+++ b/drivers/hid/bpf/hid_bpf_jmp_table.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * HID-BPF support for Linux
+ *
+ * Copyright (c) 2022 Benjamin Tissoires
+ */
+
+#include <linux/bitops.h>
+#include <linux/btf.h>
+#include <linux/btf_ids.h>
+#include <linux/circ_buf.h>
+#include <linux/filter.h>
+#include <linux/hid.h>
+#include <linux/hid_bpf.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include "hid_bpf_dispatch.h"
+#include "entrypoints/entrypoints.lskel.h"
+
+#define HID_BPF_MAX_PROGS 1024 /* keep this in sync with preloaded bpf,
+ * needs to be a power of 2 as we use it as
+ * a circular buffer
+ */
+
+#define NEXT(idx) (((idx) + 1) & (HID_BPF_MAX_PROGS - 1))
+#define PREV(idx) (((idx) - 1) & (HID_BPF_MAX_PROGS - 1))
+
+/*
+ * represents one attached program stored in the hid jump table
+ */
+struct hid_bpf_prog_entry {
+ struct bpf_prog *prog;
+ struct hid_device *hdev;
+ enum hid_bpf_prog_type type;
+ u16 idx;
+};
+
+struct hid_bpf_jmp_table {
+ struct bpf_map *map;
+ struct hid_bpf_prog_entry entries[HID_BPF_MAX_PROGS]; /* compacted list, circular buffer */
+ int tail, head;
+ struct bpf_prog *progs[HID_BPF_MAX_PROGS]; /* idx -> progs mapping */
+ unsigned long enabled[BITS_TO_LONGS(HID_BPF_MAX_PROGS)];
+};
+
+#define FOR_ENTRIES(__i, __start, __end) \
+ for (__i = __start; CIRC_CNT(__end, __i, HID_BPF_MAX_PROGS); __i = NEXT(__i))
+
+static struct hid_bpf_jmp_table jmp_table;
+
+static DEFINE_MUTEX(hid_bpf_attach_lock); /* held when attaching/detaching programs */
+
+static void hid_bpf_release_progs(struct work_struct *work);
+
+static DECLARE_WORK(release_work, hid_bpf_release_progs);
+
+BTF_ID_LIST(hid_bpf_btf_ids)
+BTF_ID(func, hid_bpf_device_event) /* HID_BPF_PROG_TYPE_DEVICE_EVENT */
+BTF_ID(func, hid_bpf_rdesc_fixup) /* HID_BPF_PROG_TYPE_RDESC_FIXUP */
+
+static int hid_bpf_max_programs(enum hid_bpf_prog_type type)
+{
+ switch (type) {
+ case HID_BPF_PROG_TYPE_DEVICE_EVENT:
+ return HID_BPF_MAX_PROGS_PER_DEV;
+ case HID_BPF_PROG_TYPE_RDESC_FIXUP:
+ return 1;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hid_bpf_program_count(struct hid_device *hdev,
+ struct bpf_prog *prog,
+ enum hid_bpf_prog_type type)
+{
+ int i, n = 0;
+
+ if (type >= HID_BPF_PROG_TYPE_MAX)
+ return -EINVAL;
+
+ FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
+ struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
+
+ if (type != HID_BPF_PROG_TYPE_UNDEF && entry->type != type)
+ continue;
+
+ if (hdev && entry->hdev != hdev)
+ continue;
+
+ if (prog && entry->prog != prog)
+ continue;
+
+ n++;
+ }
+
+ return n;
+}
+
+__weak noinline int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx)
+{
+ return 0;
+}
+
+int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
+ struct hid_bpf_ctx_kern *ctx_kern)
+{
+ struct hid_bpf_prog_list *prog_list;
+ int i, idx, err = 0;
+
+ rcu_read_lock();
+ prog_list = rcu_dereference(hdev->bpf.progs[type]);
+
+ if (!prog_list)
+ goto out_unlock;
+
+ for (i = 0; i < prog_list->prog_cnt; i++) {
+ idx = prog_list->prog_idx[i];
+
+ if (!test_bit(idx, jmp_table.enabled))
+ continue;
+
+ ctx_kern->ctx.index = idx;
+ err = __hid_bpf_tail_call(&ctx_kern->ctx);
+ if (err < 0)
+ break;
+ if (err)
+ ctx_kern->ctx.retval = err;
+ }
+
+ out_unlock:
+ rcu_read_unlock();
+
+ return err;
+}
+
+/*
+ * assign the list of programs attached to a given hid device.
+ */
+static void __hid_bpf_set_hdev_progs(struct hid_device *hdev, struct hid_bpf_prog_list *new_list,
+ enum hid_bpf_prog_type type)
+{
+ struct hid_bpf_prog_list *old_list;
+
+ spin_lock(&hdev->bpf.progs_lock);
+ old_list = rcu_dereference_protected(hdev->bpf.progs[type],
+ lockdep_is_held(&hdev->bpf.progs_lock));
+ rcu_assign_pointer(hdev->bpf.progs[type], new_list);
+ spin_unlock(&hdev->bpf.progs_lock);
+ synchronize_rcu();
+
+ kfree(old_list);
+}
+
+/*
+ * allocate and populate the list of programs attached to a given hid device.
+ *
+ * Must be called under lock.
+ */
+static int hid_bpf_populate_hdev(struct hid_device *hdev, enum hid_bpf_prog_type type)
+{
+ struct hid_bpf_prog_list *new_list;
+ int i;
+
+ if (type >= HID_BPF_PROG_TYPE_MAX || !hdev)
+ return -EINVAL;
+
+ if (hdev->bpf.destroyed)
+ return 0;
+
+ new_list = kzalloc(sizeof(*new_list), GFP_KERNEL);
+ if (!new_list)
+ return -ENOMEM;
+
+ FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
+ struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
+
+ if (entry->type == type && entry->hdev == hdev &&
+ test_bit(entry->idx, jmp_table.enabled))
+ new_list->prog_idx[new_list->prog_cnt++] = entry->idx;
+ }
+
+ __hid_bpf_set_hdev_progs(hdev, new_list, type);
+
+ return 0;
+}
+
+static void __hid_bpf_do_release_prog(int map_fd, unsigned int idx)
+{
+ skel_map_delete_elem(map_fd, &idx);
+ jmp_table.progs[idx] = NULL;
+}
+
+static void hid_bpf_release_progs(struct work_struct *work)
+{
+ int i, j, n, map_fd = -1;
+
+ if (!jmp_table.map)
+ return;
+
+ /* retrieve a fd of our prog_array map in BPF */
+ map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
+ if (map_fd < 0)
+ return;
+
+ mutex_lock(&hid_bpf_attach_lock); /* protects against attaching new programs */
+
+ /* detach unused progs from HID devices */
+ FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
+ struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
+ enum hid_bpf_prog_type type;
+ struct hid_device *hdev;
+
+ if (test_bit(entry->idx, jmp_table.enabled))
+ continue;
+
+ /* we have an attached prog */
+ if (entry->hdev) {
+ hdev = entry->hdev;
+ type = entry->type;
+
+ hid_bpf_populate_hdev(hdev, type);
+
+ /* mark all other disabled progs from hdev of the given type as detached */
+ FOR_ENTRIES(j, i, jmp_table.head) {
+ struct hid_bpf_prog_entry *next;
+
+ next = &jmp_table.entries[j];
+
+ if (test_bit(next->idx, jmp_table.enabled))
+ continue;
+
+ if (next->hdev == hdev && next->type == type)
+ next->hdev = NULL;
+ }
+
+ /* if type was rdesc fixup, reconnect device */
+ if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP)
+ hid_bpf_reconnect(hdev);
+ }
+ }
+
+ /* remove all unused progs from the jump table */
+ FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
+ struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
+
+ if (test_bit(entry->idx, jmp_table.enabled))
+ continue;
+
+ if (entry->prog)
+ __hid_bpf_do_release_prog(map_fd, entry->idx);
+ }
+
+ /* compact the entry list */
+ n = jmp_table.tail;
+ FOR_ENTRIES(i, jmp_table.tail, jmp_table.head) {
+ struct hid_bpf_prog_entry *entry = &jmp_table.entries[i];
+
+ if (!test_bit(entry->idx, jmp_table.enabled))
+ continue;
+
+ jmp_table.entries[n] = jmp_table.entries[i];
+ n = NEXT(n);
+ }
+
+ jmp_table.head = n;
+
+ mutex_unlock(&hid_bpf_attach_lock);
+
+ if (map_fd >= 0)
+ close_fd(map_fd);
+}
+
+static void hid_bpf_release_prog_at(int idx)
+{
+ int map_fd = -1;
+
+ /* retrieve a fd of our prog_array map in BPF */
+ map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
+ if (map_fd < 0)
+ return;
+
+ __hid_bpf_do_release_prog(map_fd, idx);
+
+ close(map_fd);
+}
+
+/*
+ * Insert the given BPF program represented by its fd in the jmp table.
+ * Returns the index in the jump table or a negative error.
+ */
+static int hid_bpf_insert_prog(int prog_fd, struct bpf_prog *prog)
+{
+ int i, index = -1, map_fd = -1, err = -EINVAL;
+
+ /* retrieve a fd of our prog_array map in BPF */
+ map_fd = skel_map_get_fd_by_id(jmp_table.map->id);
+
+ if (map_fd < 0) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ /* find the first available index in the jmp_table */
+ for (i = 0; i < HID_BPF_MAX_PROGS; i++) {
+ if (!jmp_table.progs[i] && index < 0) {
+ /* mark the index as used */
+ jmp_table.progs[i] = prog;
+ index = i;
+ __set_bit(i, jmp_table.enabled);
+ }
+ }
+ if (index < 0) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ /* insert the program in the jump table */
+ err = skel_map_update_elem(map_fd, &index, &prog_fd, 0);
+ if (err)
+ goto out;
+
+ /* return the index */
+ err = index;
+
+ out:
+ if (err < 0)
+ __hid_bpf_do_release_prog(map_fd, index);
+ if (map_fd >= 0)
+ close_fd(map_fd);
+ return err;
+}
+
+int hid_bpf_get_prog_attach_type(int prog_fd)
+{
+ struct bpf_prog *prog = NULL;
+ int i;
+ int prog_type = HID_BPF_PROG_TYPE_UNDEF;
+
+ prog = bpf_prog_get(prog_fd);
+ if (IS_ERR(prog))
+ return PTR_ERR(prog);
+
+ for (i = 0; i < HID_BPF_PROG_TYPE_MAX; i++) {
+ if (hid_bpf_btf_ids[i] == prog->aux->attach_btf_id) {
+ prog_type = i;
+ break;
+ }
+ }
+
+ bpf_prog_put(prog);
+
+ return prog_type;
+}
+
+static void hid_bpf_link_release(struct bpf_link *link)
+{
+ struct hid_bpf_link *hid_link =
+ container_of(link, struct hid_bpf_link, link);
+
+ __clear_bit(hid_link->hid_table_index, jmp_table.enabled);
+ schedule_work(&release_work);
+}
+
+static void hid_bpf_link_dealloc(struct bpf_link *link)
+{
+ struct hid_bpf_link *hid_link =
+ container_of(link, struct hid_bpf_link, link);
+
+ kfree(hid_link);
+}
+
+static void hid_bpf_link_show_fdinfo(const struct bpf_link *link,
+ struct seq_file *seq)
+{
+ seq_printf(seq,
+ "attach_type:\tHID-BPF\n");
+}
+
+static const struct bpf_link_ops hid_bpf_link_lops = {
+ .release = hid_bpf_link_release,
+ .dealloc = hid_bpf_link_dealloc,
+ .show_fdinfo = hid_bpf_link_show_fdinfo,
+};
+
+/* called from syscall */
+noinline int
+__hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type,
+ int prog_fd, __u32 flags)
+{
+ struct bpf_link_primer link_primer;
+ struct hid_bpf_link *link;
+ struct bpf_prog *prog = NULL;
+ struct hid_bpf_prog_entry *prog_entry;
+ int cnt, err = -EINVAL, prog_table_idx = -1;
+
+ /* take a ref on the prog itself */
+ prog = bpf_prog_get(prog_fd);
+ if (IS_ERR(prog))
+ return PTR_ERR(prog);
+
+ mutex_lock(&hid_bpf_attach_lock);
+
+ link = kzalloc(sizeof(*link), GFP_USER);
+ if (!link) {
+ err = -ENOMEM;
+ goto err_unlock;
+ }
+
+ bpf_link_init(&link->link, BPF_LINK_TYPE_UNSPEC,
+ &hid_bpf_link_lops, prog);
+
+ /* do not attach too many programs to a given HID device */
+ cnt = hid_bpf_program_count(hdev, NULL, prog_type);
+ if (cnt < 0) {
+ err = cnt;
+ goto err_unlock;
+ }
+
+ if (cnt >= hid_bpf_max_programs(prog_type)) {
+ err = -E2BIG;
+ goto err_unlock;
+ }
+
+ prog_table_idx = hid_bpf_insert_prog(prog_fd, prog);
+ /* if the jmp table is full, abort */
+ if (prog_table_idx < 0) {
+ err = prog_table_idx;
+ goto err_unlock;
+ }
+
+ if (flags & HID_BPF_FLAG_INSERT_HEAD) {
+ /* take the previous prog_entry slot */
+ jmp_table.tail = PREV(jmp_table.tail);
+ prog_entry = &jmp_table.entries[jmp_table.tail];
+ } else {
+ /* take the next prog_entry slot */
+ prog_entry = &jmp_table.entries[jmp_table.head];
+ jmp_table.head = NEXT(jmp_table.head);
+ }
+
+ /* we steal the ref here */
+ prog_entry->prog = prog;
+ prog_entry->idx = prog_table_idx;
+ prog_entry->hdev = hdev;
+ prog_entry->type = prog_type;
+
+ /* finally store the index in the device list */
+ err = hid_bpf_populate_hdev(hdev, prog_type);
+ if (err) {
+ hid_bpf_release_prog_at(prog_table_idx);
+ goto err_unlock;
+ }
+
+ link->hid_table_index = prog_table_idx;
+
+ err = bpf_link_prime(&link->link, &link_primer);
+ if (err)
+ goto err_unlock;
+
+ mutex_unlock(&hid_bpf_attach_lock);
+
+ return bpf_link_settle(&link_primer);
+
+ err_unlock:
+ mutex_unlock(&hid_bpf_attach_lock);
+
+ bpf_prog_put(prog);
+ kfree(link);
+
+ return err;
+}
+
+void __hid_bpf_destroy_device(struct hid_device *hdev)
+{
+ int type, i;
+ struct hid_bpf_prog_list *prog_list;
+
+ rcu_read_lock();
+
+ for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++) {
+ prog_list = rcu_dereference(hdev->bpf.progs[type]);
+
+ if (!prog_list)
+ continue;
+
+ for (i = 0; i < prog_list->prog_cnt; i++)
+ __clear_bit(prog_list->prog_idx[i], jmp_table.enabled);
+ }
+
+ rcu_read_unlock();
+
+ for (type = 0; type < HID_BPF_PROG_TYPE_MAX; type++)
+ __hid_bpf_set_hdev_progs(hdev, NULL, type);
+
+ /* schedule release of all detached progs */
+ schedule_work(&release_work);
+}
+
+#define HID_BPF_PROGS_COUNT 1
+
+static struct bpf_link *links[HID_BPF_PROGS_COUNT];
+static struct entrypoints_bpf *skel;
+
+void hid_bpf_free_links_and_skel(void)
+{
+ int i;
+
+ /* the following is enough to release all programs attached to hid */
+ if (jmp_table.map)
+ bpf_map_put_with_uref(jmp_table.map);
+
+ for (i = 0; i < ARRAY_SIZE(links); i++) {
+ if (!IS_ERR_OR_NULL(links[i]))
+ bpf_link_put(links[i]);
+ }
+ entrypoints_bpf__destroy(skel);
+}
+
+#define ATTACH_AND_STORE_LINK(__name) do { \
+ err = entrypoints_bpf__##__name##__attach(skel); \
+ if (err) \
+ goto out; \
+ \
+ links[idx] = bpf_link_get_from_fd(skel->links.__name##_fd); \
+ if (IS_ERR(links[idx])) { \
+ err = PTR_ERR(links[idx]); \
+ goto out; \
+ } \
+ \
+ /* Avoid taking over stdin/stdout/stderr of init process. Zeroing out \
+ * makes skel_closenz() a no-op later in iterators_bpf__destroy(). \
+ */ \
+ close_fd(skel->links.__name##_fd); \
+ skel->links.__name##_fd = 0; \
+ idx++; \
+} while (0)
+
+int hid_bpf_preload_skel(void)
+{
+ int err, idx = 0;
+
+ skel = entrypoints_bpf__open();
+ if (!skel)
+ return -ENOMEM;
+
+ err = entrypoints_bpf__load(skel);
+ if (err)
+ goto out;
+
+ jmp_table.map = bpf_map_get_with_uref(skel->maps.hid_jmp_table.map_fd);
+ if (IS_ERR(jmp_table.map)) {
+ err = PTR_ERR(jmp_table.map);
+ goto out;
+ }
+
+ ATTACH_AND_STORE_LINK(hid_tail_call);
+
+ return 0;
+out:
+ hid_bpf_free_links_and_skel();
+ return err;
+}
diff --git a/drivers/hid/hid-alps.c b/drivers/hid/hid-alps.c
index db146d0f7937..669d769ea1dc 100644
--- a/drivers/hid/hid-alps.c
+++ b/drivers/hid/hid-alps.c
@@ -820,11 +820,6 @@ static int alps_probe(struct hid_device *hdev, const struct hid_device_id *id)
return 0;
}
-static void alps_remove(struct hid_device *hdev)
-{
- hid_hw_stop(hdev);
-}
-
static const struct hid_device_id alps_id[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
USB_VENDOR_ID_ALPS_JP, HID_DEVICE_ID_ALPS_U1_DUAL) },
@@ -842,7 +837,6 @@ static struct hid_driver alps_driver = {
.name = "hid-alps",
.id_table = alps_id,
.probe = alps_probe,
- .remove = alps_remove,
.raw_event = alps_raw_event,
.input_mapping = alps_input_mapping,
.input_configured = alps_input_configured,
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 6970797cdc56..1ccab8aa326c 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -59,6 +59,12 @@ MODULE_PARM_DESC(swap_opt_cmd, "Swap the Option (\"Alt\") and Command (\"Flag\")
"(For people who want to keep Windows PC keyboard muscle memory. "
"[0] = as-is, Mac layout. 1 = swapped, Windows layout.)");
+static unsigned int swap_ctrl_cmd;
+module_param(swap_ctrl_cmd, uint, 0644);
+MODULE_PARM_DESC(swap_ctrl_cmd, "Swap the Control (\"Ctrl\") and Command (\"Flag\") keys. "
+ "(For people who are used to Mac shortcuts involving Command instead of Control. "
+ "[0] = No change. 1 = Swapped.)");
+
static unsigned int swap_fn_leftctrl;
module_param(swap_fn_leftctrl, uint, 0644);
MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. "
@@ -308,12 +314,21 @@ static const struct apple_key_translation swapped_option_cmd_keys[] = {
{ KEY_LEFTALT, KEY_LEFTMETA },
{ KEY_LEFTMETA, KEY_LEFTALT },
{ KEY_RIGHTALT, KEY_RIGHTMETA },
- { KEY_RIGHTMETA,KEY_RIGHTALT },
+ { KEY_RIGHTMETA, KEY_RIGHTALT },
+ { }
+};
+
+static const struct apple_key_translation swapped_ctrl_cmd_keys[] = {
+ { KEY_LEFTCTRL, KEY_LEFTMETA },
+ { KEY_LEFTMETA, KEY_LEFTCTRL },
+ { KEY_RIGHTCTRL, KEY_RIGHTMETA },
+ { KEY_RIGHTMETA, KEY_RIGHTCTRL },
{ }
};
static const struct apple_key_translation swapped_fn_leftctrl_keys[] = {
{ KEY_FN, KEY_LEFTCTRL },
+ { KEY_LEFTCTRL, KEY_FN },
{ }
};
@@ -375,24 +390,47 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
struct apple_sc *asc = hid_get_drvdata(hid);
const struct apple_key_translation *trans, *table;
bool do_translate;
- u16 code = 0;
+ u16 code = usage->code;
unsigned int real_fnmode;
- u16 fn_keycode = (swap_fn_leftctrl) ? (KEY_LEFTCTRL) : (KEY_FN);
-
- if (usage->code == fn_keycode) {
- asc->fn_on = !!value;
- input_event_with_scancode(input, usage->type, KEY_FN,
- usage->hid, value);
- return 1;
- }
-
if (fnmode == 3) {
real_fnmode = (asc->quirks & APPLE_IS_NON_APPLE) ? 2 : 1;
} else {
real_fnmode = fnmode;
}
+ if (swap_fn_leftctrl) {
+ trans = apple_find_translation(swapped_fn_leftctrl_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (iso_layout > 0 || (iso_layout < 0 && (asc->quirks & APPLE_ISO_TILDE_QUIRK) &&
+ hid->country == HID_COUNTRY_INTERNATIONAL_ISO)) {
+ trans = apple_find_translation(apple_iso_keyboard, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (swap_opt_cmd) {
+ trans = apple_find_translation(swapped_option_cmd_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (swap_ctrl_cmd) {
+ trans = apple_find_translation(swapped_ctrl_cmd_keys, code);
+
+ if (trans)
+ code = trans->to;
+ }
+
+ if (code == KEY_FN)
+ asc->fn_on = !!value;
+
if (real_fnmode) {
if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI ||
hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO ||
@@ -430,15 +468,18 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
else
table = apple_fn_keys;
- trans = apple_find_translation (table, usage->code);
+ trans = apple_find_translation(table, code);
if (trans) {
- if (test_bit(trans->from, input->key))
+ bool from_is_set = test_bit(trans->from, input->key);
+ bool to_is_set = test_bit(trans->to, input->key);
+
+ if (from_is_set)
code = trans->from;
- else if (test_bit(trans->to, input->key))
+ else if (to_is_set)
code = trans->to;
- if (!code) {
+ if (!(from_is_set || to_is_set)) {
if (trans->flags & APPLE_FLAG_FKEY) {
switch (real_fnmode) {
case 1:
@@ -455,62 +496,31 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
do_translate = asc->fn_on;
}
- code = do_translate ? trans->to : trans->from;
+ if (do_translate)
+ code = trans->to;
}
-
- input_event_with_scancode(input, usage->type, code,
- usage->hid, value);
- return 1;
}
if (asc->quirks & APPLE_NUMLOCK_EMULATION &&
- (test_bit(usage->code, asc->pressed_numlock) ||
+ (test_bit(code, asc->pressed_numlock) ||
test_bit(LED_NUML, input->led))) {
- trans = apple_find_translation(powerbook_numlock_keys,
- usage->code);
+ trans = apple_find_translation(powerbook_numlock_keys, code);
if (trans) {
if (value)
- set_bit(usage->code,
- asc->pressed_numlock);
+ set_bit(code, asc->pressed_numlock);
else
- clear_bit(usage->code,
- asc->pressed_numlock);
+ clear_bit(code, asc->pressed_numlock);
- input_event_with_scancode(input, usage->type,
- trans->to, usage->hid, value);
+ code = trans->to;
}
-
- return 1;
- }
- }
-
- if (iso_layout > 0 || (iso_layout < 0 && (asc->quirks & APPLE_ISO_TILDE_QUIRK) &&
- hid->country == HID_COUNTRY_INTERNATIONAL_ISO)) {
- trans = apple_find_translation(apple_iso_keyboard, usage->code);
- if (trans) {
- input_event_with_scancode(input, usage->type,
- trans->to, usage->hid, value);
- return 1;
}
}
- if (swap_opt_cmd) {
- trans = apple_find_translation(swapped_option_cmd_keys, usage->code);
- if (trans) {
- input_event_with_scancode(input, usage->type,
- trans->to, usage->hid, value);
- return 1;
- }
- }
+ if (usage->code != code) {
+ input_event_with_scancode(input, usage->type, code, usage->hid, value);
- if (swap_fn_leftctrl) {
- trans = apple_find_translation(swapped_fn_leftctrl_keys, usage->code);
- if (trans) {
- input_event_with_scancode(input, usage->type,
- trans->to, usage->hid, value);
- return 1;
- }
+ return 1;
}
return 0;
@@ -640,9 +650,6 @@ static void apple_setup_input(struct input_dev *input)
apple_setup_key_translation(input, apple2021_fn_keys);
apple_setup_key_translation(input, macbookpro_no_esc_fn_keys);
apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys);
-
- if (swap_fn_leftctrl)
- apple_setup_key_translation(input, swapped_fn_leftctrl_keys);
}
static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi,
@@ -1011,21 +1018,21 @@ static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS),
.driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213),
- .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL },
+ .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K),
- .driver_data = APPLE_HAS_FN },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223),
- .driver_data = APPLE_HAS_FN },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K),
- .driver_data = APPLE_HAS_FN },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F),
- .driver_data = APPLE_HAS_FN },
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI),
.driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO),
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index f99752b998f3..d1094bb1aa42 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -98,6 +98,7 @@ struct asus_kbd_leds {
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
+ spinlock_t lock;
bool removed;
};
@@ -490,21 +491,42 @@ static int rog_nkey_led_init(struct hid_device *hdev)
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,
enum led_brightness brightness)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&led->lock, flags);
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)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
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)
@@ -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);
u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, 0x00 };
int ret;
+ unsigned long flags;
- if (led->removed)
- return;
-
+ spin_lock_irqsave(&led->lock, flags);
buf[4] = led->brightness;
+ spin_unlock_irqrestore(&led->lock, flags);
ret = asus_kbd_set_report(led->hdev, buf, sizeof(buf));
if (ret < 0)
@@ -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_get = asus_kbd_backlight_get;
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);
if (ret < 0) {
@@ -1119,9 +1142,13 @@ err_stop_hw:
static void asus_remove(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
+ unsigned long flags;
if (drvdata->kbd_backlight) {
+ spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
+ spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
+
cancel_work_sync(&drvdata->kbd_backlight->work);
}
diff --git a/drivers/hid/hid-betopff.c b/drivers/hid/hid-betopff.c
index 467d789f9bc2..25ed7b9a917e 100644
--- a/drivers/hid/hid-betopff.c
+++ b/drivers/hid/hid-betopff.c
@@ -60,7 +60,6 @@ static int betopff_init(struct hid_device *hid)
struct list_head *report_list =
&hid->report_enum[HID_OUTPUT_REPORT].report_list;
struct input_dev *dev;
- int field_count = 0;
int error;
int i, j;
@@ -86,19 +85,21 @@ static int betopff_init(struct hid_device *hid)
* -----------------------------------------
* Do init them with default value.
*/
+ if (report->maxfield < 4) {
+ hid_err(hid, "not enough fields in the report: %d\n",
+ report->maxfield);
+ return -ENODEV;
+ }
for (i = 0; i < report->maxfield; i++) {
+ if (report->field[i]->report_count < 1) {
+ hid_err(hid, "no values in the field\n");
+ return -ENODEV;
+ }
for (j = 0; j < report->field[i]->report_count; j++) {
report->field[i]->value[j] = 0x00;
- field_count++;
}
}
- if (field_count < 4) {
- hid_err(hid, "not enough fields in the report: %d\n",
- field_count);
- return -ENODEV;
- }
-
betopff = kzalloc(sizeof(*betopff), GFP_KERNEL);
if (!betopff)
return -ENOMEM;
diff --git a/drivers/hid/hid-bigbenff.c b/drivers/hid/hid-bigbenff.c
index e8c5e3ac9fff..a02cb517b4c4 100644
--- a/drivers/hid/hid-bigbenff.c
+++ b/drivers/hid/hid-bigbenff.c
@@ -174,6 +174,7 @@ static __u8 pid0902_rdesc_fixed[] = {
struct bigben_device {
struct hid_device *hid;
struct hid_report *report;
+ spinlock_t lock;
bool removed;
u8 led_state; /* LED1 = 1 .. LED4 = 8 */
u8 right_motor_on; /* right motor off/on 0/1 */
@@ -184,18 +185,39 @@ struct bigben_device {
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)
{
struct bigben_device *bigben = container_of(work,
struct bigben_device, worker);
struct hid_field *report_field = bigben->report->field[0];
-
- if (bigben->removed || !report_field)
+ bool do_work_led = false;
+ bool do_work_ff = false;
+ u8 *buf;
+ u32 len;
+ unsigned long flags;
+
+ buf = hid_alloc_report_buf(bigben->report, GFP_KERNEL);
+ if (!buf)
return;
+ len = hid_report_len(bigben->report);
+
+ /* LED work */
+ spin_lock_irqsave(&bigben->lock, flags);
+
if (bigben->work_led) {
bigben->work_led = false;
+ do_work_led = true;
report_field->value[0] = 0x01; /* 1 = led message */
report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->led_state;
@@ -204,11 +226,22 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 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) {
bigben->work_ff = false;
+ do_work_ff = true;
report_field->value[0] = 0x02; /* 2 = rumble effect message */
report_field->value[1] = 0x08; /* reserved value, always 8 */
report_field->value[2] = bigben->right_motor_on;
@@ -217,8 +250,17 @@ static void bigben_worker(struct work_struct *work)
report_field->value[5] = 0x00; /* padding */
report_field->value[6] = 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,
@@ -228,6 +270,7 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
struct bigben_device *bigben = hid_get_drvdata(hid);
u8 right_motor_on;
u8 left_motor_force;
+ unsigned long flags;
if (!bigben) {
hid_err(hid, "no device data\n");
@@ -242,10 +285,13 @@ static int hid_bigben_play_effect(struct input_dev *dev, void *data,
if (right_motor_on != bigben->right_motor_on ||
left_motor_force != bigben->left_motor_force) {
+ spin_lock_irqsave(&bigben->lock, flags);
bigben->right_motor_on = right_motor_on;
bigben->left_motor_force = left_motor_force;
bigben->work_ff = true;
- schedule_work(&bigben->worker);
+ spin_unlock_irqrestore(&bigben->lock, flags);
+
+ bigben_schedule_work(bigben);
}
return 0;
@@ -259,6 +305,7 @@ static void bigben_set_led(struct led_classdev *led,
struct bigben_device *bigben = hid_get_drvdata(hid);
int n;
bool work;
+ unsigned long flags;
if (!bigben) {
hid_err(hid, "no device data\n");
@@ -267,6 +314,7 @@ static void bigben_set_led(struct led_classdev *led,
for (n = 0; n < NUM_LEDS; n++) {
if (led == bigben->leds[n]) {
+ spin_lock_irqsave(&bigben->lock, flags);
if (value == LED_OFF) {
work = (bigben->led_state & BIT(n));
bigben->led_state &= ~BIT(n);
@@ -274,10 +322,11 @@ static void bigben_set_led(struct led_classdev *led,
work = !(bigben->led_state & BIT(n));
bigben->led_state |= BIT(n);
}
+ spin_unlock_irqrestore(&bigben->lock, flags);
if (work) {
bigben->work_led = true;
- schedule_work(&bigben->worker);
+ bigben_schedule_work(bigben);
}
return;
}
@@ -307,8 +356,12 @@ static enum led_brightness bigben_get_led(struct led_classdev *led)
static void bigben_remove(struct hid_device *hid)
{
struct bigben_device *bigben = hid_get_drvdata(hid);
+ unsigned long flags;
+ spin_lock_irqsave(&bigben->lock, flags);
bigben->removed = true;
+ spin_unlock_irqrestore(&bigben->lock, flags);
+
cancel_work_sync(&bigben->worker);
hid_hw_stop(hid);
}
@@ -318,7 +371,6 @@ static int bigben_probe(struct hid_device *hid,
{
struct bigben_device *bigben;
struct hid_input *hidinput;
- struct list_head *report_list;
struct led_classdev *led;
char *name;
size_t name_sz;
@@ -343,9 +395,12 @@ static int bigben_probe(struct hid_device *hid,
return error;
}
- report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
- bigben->report = list_entry(report_list->next,
- struct hid_report, list);
+ bigben->report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 8);
+ if (!bigben->report) {
+ hid_err(hid, "no output report found\n");
+ error = -ENODEV;
+ goto error_hw_stop;
+ }
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
@@ -357,6 +412,7 @@ static int bigben_probe(struct hid_device *hid,
set_bit(FF_RUMBLE, hidinput->input->ffbit);
INIT_WORK(&bigben->worker, bigben_worker);
+ spin_lock_init(&bigben->lock);
error = input_ff_create_memless(hidinput->input, NULL,
hid_bigben_play_effect);
@@ -397,7 +453,7 @@ static int bigben_probe(struct hid_device *hid,
bigben->left_motor_force = 0;
bigben->work_led = 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");
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 9c1d31f63f85..1ee623c26c49 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -41,11 +41,6 @@
#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;
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");
@@ -804,7 +799,8 @@ static void hid_scan_collection(struct hid_parser *parser, unsigned type)
int i;
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;
if (hid->vendor == USB_VENDOR_ID_MICROSOFT &&
@@ -993,8 +989,8 @@ struct hid_report *hid_validate_values(struct hid_device *hid,
* Validating on id 0 means we should examine the first
* report in the list.
*/
- report = list_entry(
- hid->report_enum[type].report_list.next,
+ report = list_first_entry_or_null(
+ &hid->report_enum[type].report_list,
struct hid_report, list);
} else {
report = hid->report_enum[type].report_id_hash[id];
@@ -1202,6 +1198,7 @@ int hid_open_report(struct hid_device *device)
__u8 *end;
__u8 *next;
int ret;
+ int i;
static int (*dispatch_type[])(struct hid_parser *parser,
struct hid_item *item) = {
hid_parser_main,
@@ -1218,7 +1215,8 @@ int hid_open_report(struct hid_device *device)
return -ENODEV;
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)
return -ENOMEM;
@@ -1252,6 +1250,8 @@ int hid_open_report(struct hid_device *device)
goto err;
}
device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;
+ for (i = 0; i < HID_DEFAULT_NUM_COLLECTIONS; i++)
+ device->collection[i].parent_idx = -1;
ret = -EINVAL;
while ((next = fetch_item(start, end, &item)) != NULL) {
@@ -1315,6 +1315,9 @@ static s32 snto32(__u32 value, unsigned n)
if (!value || !n)
return 0;
+ if (n > 32)
+ n = 32;
+
switch (n) {
case 8: return ((__s8)value);
case 16: return ((__s16)value);
@@ -2040,6 +2043,12 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
report_enum = hid->report_enum + type;
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) {
dbg_hid("empty report\n");
ret = -1;
@@ -2154,6 +2163,10 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
int len;
int ret;
+ ret = hid_bpf_connect_device(hdev);
+ if (ret)
+ return ret;
+
if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE)
connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV);
if (hdev->quirks & HID_QUIRK_HIDINPUT_FORCE)
@@ -2255,6 +2268,8 @@ void hid_disconnect(struct hid_device *hdev)
if (hdev->claimed & HID_CLAIMED_HIDRAW)
hidraw_disconnect(hdev);
hdev->claimed = 0;
+
+ hid_bpf_disconnect_device(hdev);
}
EXPORT_SYMBOL_GPL(hid_disconnect);
@@ -2790,6 +2805,8 @@ struct hid_device *hid_allocate_device(void)
sema_init(&hdev->driver_input_lock, 1);
mutex_init(&hdev->ll_open_lock);
+ hid_bpf_device_init(hdev);
+
return hdev;
}
EXPORT_SYMBOL_GPL(hid_allocate_device);
@@ -2816,6 +2833,7 @@ static void hid_remove_device(struct hid_device *hdev)
*/
void hid_destroy_device(struct hid_device *hdev)
{
+ hid_bpf_destroy_device(hdev);
hid_remove_device(hdev);
put_device(&hdev->dev);
}
@@ -2902,20 +2920,29 @@ int hid_check_keys_pressed(struct hid_device *hid)
}
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)
{
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);
if (ret) {
pr_err("can't register hid bus\n");
goto err;
}
+#ifdef CONFIG_HID_BPF
+ hid_bpf_ops = &hid_ops;
+#endif
+
ret = hidraw_init();
if (ret)
goto err_bus;
@@ -2931,6 +2958,9 @@ err:
static void __exit hid_exit(void)
{
+#ifdef CONFIG_HID_BPF
+ hid_bpf_ops = NULL;
+#endif
hid_debug_exit();
hidraw_exit();
bus_unregister(&hid_bus_type);
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index 2ca6ab600bc9..e7ef1ea107c9 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -971,7 +971,11 @@ static const char *keys[KEY_MAX + 1] = {
[KEY_ASSISTANT] = "Assistant",
[KEY_KBD_LAYOUT_NEXT] = "KbdLayoutNext",
[KEY_EMOJI_PICKER] = "EmojiPicker",
+ [KEY_CAMERA_ACCESS_ENABLE] = "CameraAccessEnable",
+ [KEY_CAMERA_ACCESS_DISABLE] = "CameraAccessDisable",
+ [KEY_CAMERA_ACCESS_TOGGLE] = "CameraAccessToggle",
[KEY_DICTATE] = "Dictate",
+ [KEY_MICMUTE] = "MicrophoneMute",
[KEY_BRIGHTNESS_MIN] = "BrightnessMin",
[KEY_BRIGHTNESS_MAX] = "BrightnessMax",
[KEY_BRIGHTNESS_AUTO] = "BrightnessAuto",
diff --git a/drivers/hid/hid-elan.c b/drivers/hid/hid-elan.c
index 8e4a5528e25d..76d93fc48f6a 100644
--- a/drivers/hid/hid-elan.c
+++ b/drivers/hid/hid-elan.c
@@ -507,11 +507,6 @@ err:
return ret;
}
-static void elan_remove(struct hid_device *hdev)
-{
- hid_hw_stop(hdev);
-}
-
static const struct hid_device_id elan_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_HP_X2),
.driver_data = ELAN_HAS_LED },
@@ -529,7 +524,6 @@ static struct hid_driver elan_driver = {
.input_configured = elan_input_configured,
.raw_event = elan_raw_event,
.probe = elan_probe,
- .remove = elan_remove,
};
module_hid_driver(elan_driver);
diff --git a/drivers/hid/hid-elecom.c b/drivers/hid/hid-elecom.c
index e59e9911fc37..4fa45ee77503 100644
--- a/drivers/hid/hid-elecom.c
+++ b/drivers/hid/hid-elecom.c
@@ -12,6 +12,7 @@
* Copyright (c) 2017 Alex Manoussakis <[email protected]>
* Copyright (c) 2017 Tomasz Kramkowski <[email protected]>
* Copyright (c) 2020 YOSHIOKA Takuma <[email protected]>
+ * Copyright (c) 2022 Takahiro Fujii <[email protected]>
*/
/*
@@ -89,7 +90,7 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
case USB_DEVICE_ID_ELECOM_M_DT1URBK:
case USB_DEVICE_ID_ELECOM_M_DT1DRBK:
case USB_DEVICE_ID_ELECOM_M_HT1URBK:
- case USB_DEVICE_ID_ELECOM_M_HT1DRBK:
+ case USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D:
/*
* Report descriptor format:
* 12: button bit count
@@ -99,6 +100,16 @@ static __u8 *elecom_report_fixup(struct hid_device *hdev, __u8 *rdesc,
*/
mouse_button_fixup(hdev, rdesc, *rsize, 12, 30, 14, 20, 8);
break;
+ case USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C:
+ /*
+ * Report descriptor format:
+ * 22: button bit count
+ * 30: padding bit count
+ * 24: button report size
+ * 16: button usage maximum
+ */
+ mouse_button_fixup(hdev, rdesc, *rsize, 22, 30, 24, 16, 8);
+ break;
}
return rdesc;
}
@@ -112,7 +123,8 @@ static const struct hid_device_id elecom_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
{ }
};
MODULE_DEVICE_TABLE(hid, elecom_devices);
diff --git a/drivers/hid/hid-evision.c b/drivers/hid/hid-evision.c
new file mode 100644
index 000000000000..ef6b4b435215
--- /dev/null
+++ b/drivers/hid/hid-evision.c
@@ -0,0 +1,53 @@
+// 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");
diff --git a/drivers/hid/hid-ft260.c b/drivers/hid/hid-ft260.c
index 79505c64dbfe..333341e80b0e 100644
--- a/drivers/hid/hid-ft260.c
+++ b/drivers/hid/hid-ft260.c
@@ -30,12 +30,21 @@ MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");
#define FT260_REPORT_MAX_LENGTH (64)
#define FT260_I2C_DATA_REPORT_ID(len) (FT260_I2C_REPORT_MIN + (len - 1) / 4)
+
+#define FT260_WAKEUP_NEEDED_AFTER_MS (4800) /* 5s minus 200ms margin */
+
/*
- * The input report format assigns 62 bytes for the data payload, but ft260
- * returns 60 and 2 in two separate transactions. To minimize transfer time
- * in reading chunks mode, set the maximum read payload length to 60 bytes.
- */
-#define FT260_RD_DATA_MAX (60)
+ * The ft260 input report format defines 62 bytes for the data payload, but
+ * when requested 62 bytes, the controller returns 60 and 2 in separate input
+ * reports. To achieve better performance with the multi-report read data
+ * transfers, we set the maximum read payload length to a multiple of 60.
+ * With a 100 kHz I2C clock, one 240 bytes read takes about 1/27 second,
+ * which is excessive; On the other hand, some higher layer drivers like at24
+ * or optoe limit the i2c reads to 128 bytes. To not block other drivers out
+ * of I2C for potentially troublesome amounts of time, we select the maximum
+ * read payload length to be 180 bytes.
+*/
+#define FT260_RD_DATA_MAX (180)
#define FT260_WR_DATA_MAX (60)
/*
@@ -230,6 +239,7 @@ struct ft260_device {
struct completion wait;
struct mutex lock;
u8 write_buf[FT260_REPORT_MAX_LENGTH];
+ unsigned long need_wakeup_at;
u8 *read_buf;
u16 read_idx;
u16 read_len;
@@ -293,12 +303,26 @@ static int ft260_i2c_reset(struct hid_device *hdev)
return ret;
}
-static int ft260_xfer_status(struct ft260_device *dev)
+static int ft260_xfer_status(struct ft260_device *dev, u8 bus_busy)
{
struct hid_device *hdev = dev->hdev;
struct ft260_get_i2c_status_report report;
int ret;
+ if (time_is_before_jiffies(dev->need_wakeup_at)) {
+ ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
+ (u8 *)&report, sizeof(report));
+ if (unlikely(ret < 0)) {
+ hid_err(hdev, "failed to retrieve status: %d, no wakeup\n",
+ ret);
+ } else {
+ dev->need_wakeup_at = jiffies +
+ msecs_to_jiffies(FT260_WAKEUP_NEEDED_AFTER_MS);
+ ft260_dbg("bus_status %#02x, wakeup\n",
+ report.bus_status);
+ }
+ }
+
ret = ft260_hid_feature_report_get(hdev, FT260_I2C_STATUS,
(u8 *)&report, sizeof(report));
if (unlikely(ret < 0)) {
@@ -310,30 +334,20 @@ static int ft260_xfer_status(struct ft260_device *dev)
ft260_dbg("bus_status %#02x, clock %u\n", report.bus_status,
dev->clock);
- if (report.bus_status & FT260_I2C_STATUS_CTRL_BUSY)
+ if (report.bus_status & (FT260_I2C_STATUS_CTRL_BUSY | bus_busy))
return -EAGAIN;
- if (report.bus_status & FT260_I2C_STATUS_BUS_BUSY)
- return -EBUSY;
-
- if (report.bus_status & FT260_I2C_STATUS_ERROR)
+ /*
+ * The error condition (bit 1) is a status bit reflecting any
+ * error conditions. When any of the bits 2, 3, or 4 are raised
+ * to 1, bit 1 is also set to 1.
+ */
+ if (report.bus_status & FT260_I2C_STATUS_ERROR) {
+ hid_err(hdev, "i2c bus error: %#02x\n", report.bus_status);
return -EIO;
+ }
- ret = -EIO;
-
- if (report.bus_status & FT260_I2C_STATUS_ADDR_NO_ACK)
- ft260_dbg("unacknowledged address\n");
-
- if (report.bus_status & FT260_I2C_STATUS_DATA_NO_ACK)
- ft260_dbg("unacknowledged data\n");
-
- if (report.bus_status & FT260_I2C_STATUS_ARBITR_LOST)
- ft260_dbg("arbitration loss\n");
-
- if (report.bus_status & FT260_I2C_STATUS_CTRL_IDLE)
- ret = 0;
-
- return ret;
+ return 0;
}
static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
@@ -355,8 +369,11 @@ static int ft260_hid_output_report(struct hid_device *hdev, u8 *data,
static int ft260_hid_output_report_check_status(struct ft260_device *dev,
u8 *data, int len)
{
- int ret, usec, try = 3;
+ u8 bus_busy;
+ int ret, usec, try = 100;
struct hid_device *hdev = dev->hdev;
+ struct ft260_i2c_write_request_report *rep =
+ (struct ft260_i2c_write_request_report *)data;
ret = ft260_hid_output_report(hdev, data, len);
if (ret < 0) {
@@ -366,17 +383,31 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
return ret;
}
- /* transfer time = 1 / clock(KHz) * 10 bits * bytes */
- usec = 10000 / dev->clock * len;
- usleep_range(usec, usec + 100);
- ft260_dbg("wait %d usec, len %d\n", usec, len);
+ /* transfer time = 1 / clock(KHz) * 9 bits * bytes */
+ usec = len * 9000 / dev->clock;
+ if (usec > 2000) {
+ usec -= 1500;
+ usleep_range(usec, usec + 100);
+ ft260_dbg("wait %d usec, len %d\n", usec, len);
+ }
+
+ /*
+ * Do not check the busy bit for combined transactions
+ * since the controller keeps the bus busy between writing
+ * and reading IOs to ensure an atomic operation.
+ */
+ if (rep->flag == FT260_FLAG_START)
+ bus_busy = 0;
+ else
+ bus_busy = FT260_I2C_STATUS_BUS_BUSY;
+
do {
- ret = ft260_xfer_status(dev);
+ ret = ft260_xfer_status(dev, bus_busy);
if (ret != -EAGAIN)
break;
} while (--try);
- if (ret == 0 || ret == -EBUSY)
+ if (ret == 0)
return 0;
ft260_i2c_reset(hdev);
@@ -384,41 +415,49 @@ static int ft260_hid_output_report_check_status(struct ft260_device *dev,
}
static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
- int data_len, u8 flag)
+ int len, u8 flag)
{
- int len, ret, idx = 0;
+ int ret, wr_len, idx = 0;
struct hid_device *hdev = dev->hdev;
struct ft260_i2c_write_request_report *rep =
(struct ft260_i2c_write_request_report *)dev->write_buf;
+ if (len < 1)
+ return -EINVAL;
+
+ rep->flag = FT260_FLAG_START;
+
do {
- if (data_len <= FT260_WR_DATA_MAX)
- len = data_len;
- else
- len = FT260_WR_DATA_MAX;
+ if (len <= FT260_WR_DATA_MAX) {
+ wr_len = len;
+ if (flag == FT260_FLAG_START_STOP)
+ rep->flag |= FT260_FLAG_STOP;
+ } else {
+ wr_len = FT260_WR_DATA_MAX;
+ }
- rep->report = FT260_I2C_DATA_REPORT_ID(len);
+ rep->report = FT260_I2C_DATA_REPORT_ID(wr_len);
rep->address = addr;
- rep->length = len;
- rep->flag = flag;
+ rep->length = wr_len;
- memcpy(rep->data, &data[idx], len);
+ memcpy(rep->data, &data[idx], wr_len);
- ft260_dbg("rep %#02x addr %#02x off %d len %d d[0] %#02x\n",
- rep->report, addr, idx, len, data[0]);
+ ft260_dbg("rep %#02x addr %#02x off %d len %d wlen %d flag %#x d[0] %#02x\n",
+ rep->report, addr, idx, len, wr_len,
+ rep->flag, data[0]);
ret = ft260_hid_output_report_check_status(dev, (u8 *)rep,
- len + 4);
+ wr_len + 4);
if (ret < 0) {
- hid_err(hdev, "%s: failed to start transfer, ret %d\n",
- __func__, ret);
+ hid_err(hdev, "%s: failed with %d\n", __func__, ret);
return ret;
}
- data_len -= len;
- idx += len;
+ len -= wr_len;
+ idx += wr_len;
+ rep->flag = 0;
- } while (data_len > 0);
+ } while (len > 0);
return 0;
}
@@ -457,49 +496,74 @@ static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
u16 len, u8 flag)
{
+ u16 rd_len;
+ u16 rd_data_max = 60;
+ int timeout, ret = 0;
struct ft260_i2c_read_request_report rep;
struct hid_device *hdev = dev->hdev;
- int timeout;
- int ret;
+ u8 bus_busy = 0;
- if (len > FT260_RD_DATA_MAX) {
- hid_err(hdev, "%s: unsupported rd len: %d\n", __func__, len);
- return -EINVAL;
- }
+ if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
+ flag = FT260_FLAG_START_REPEATED;
+ else
+ flag = FT260_FLAG_START;
+ do {
+ if (len <= rd_data_max) {
+ rd_len = len;
+ flag |= FT260_FLAG_STOP;
+ } else {
+ rd_len = rd_data_max;
+ }
+ rd_data_max = FT260_RD_DATA_MAX;
- dev->read_idx = 0;
- dev->read_buf = data;
- dev->read_len = len;
+ rep.report = FT260_I2C_READ_REQ;
+ rep.length = cpu_to_le16(rd_len);
+ rep.address = addr;
+ rep.flag = flag;
- rep.report = FT260_I2C_READ_REQ;
- rep.length = cpu_to_le16(len);
- rep.address = addr;
- rep.flag = flag;
+ ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
+ rep.report, rep.address, len, rd_len, flag);
- ft260_dbg("rep %#02x addr %#02x len %d\n", rep.report, rep.address,
- rep.length);
+ reinit_completion(&dev->wait);
- reinit_completion(&dev->wait);
+ dev->read_idx = 0;
+ dev->read_buf = data;
+ dev->read_len = rd_len;
- ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
- if (ret < 0) {
- hid_err(hdev, "%s: failed to start transaction, ret %d\n",
- __func__, ret);
- return ret;
- }
+ ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
+ if (ret < 0) {
+ hid_err(hdev, "%s: failed with %d\n", __func__, ret);
+ goto ft260_i2c_read_exit;
+ }
- timeout = msecs_to_jiffies(5000);
- if (!wait_for_completion_timeout(&dev->wait, timeout)) {
- ft260_i2c_reset(hdev);
- return -ETIMEDOUT;
- }
+ timeout = msecs_to_jiffies(5000);
+ if (!wait_for_completion_timeout(&dev->wait, timeout)) {
+ ret = -ETIMEDOUT;
+ ft260_i2c_reset(hdev);
+ goto ft260_i2c_read_exit;
+ }
- ret = ft260_xfer_status(dev);
- if (ret == 0)
- return 0;
+ dev->read_buf = NULL;
- ft260_i2c_reset(hdev);
- return -EIO;
+ if (flag & FT260_FLAG_STOP)
+ bus_busy = FT260_I2C_STATUS_BUS_BUSY;
+
+ ret = ft260_xfer_status(dev, bus_busy);
+ if (ret < 0) {
+ ret = -EIO;
+ ft260_i2c_reset(hdev);
+ goto ft260_i2c_read_exit;
+ }
+
+ len -= rd_len;
+ data += rd_len;
+ flag = 0;
+
+ } while (len > 0);
+
+ft260_i2c_read_exit:
+ dev->read_buf = NULL;
+ return ret;
}
/*
@@ -510,45 +574,37 @@ static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
*/
static int ft260_i2c_write_read(struct ft260_device *dev, struct i2c_msg *msgs)
{
- int len, ret;
- u16 left_len = msgs[1].len;
- u8 *read_buf = msgs[1].buf;
+ int ret;
+ int wr_len = msgs[0].len;
+ int rd_len = msgs[1].len;
+ struct hid_device *hdev = dev->hdev;
u8 addr = msgs[0].addr;
u16 read_off = 0;
- struct hid_device *hdev = dev->hdev;
- if (msgs[0].len > 2) {
- hid_err(hdev, "%s: unsupported wr len: %d\n", __func__,
- msgs[0].len);
+ if (wr_len > 2) {
+ hid_err(hdev, "%s: invalid wr_len: %d\n", __func__, wr_len);
return -EOPNOTSUPP;
}
- memcpy(&read_off, msgs[0].buf, msgs[0].len);
-
- do {
- if (left_len <= FT260_RD_DATA_MAX)
- len = left_len;
+ if (ft260_debug) {
+ if (wr_len == 2)
+ read_off = be16_to_cpu(*(__be16 *)msgs[0].buf);
else
- len = FT260_RD_DATA_MAX;
-
- ft260_dbg("read_off %#x left_len %d len %d\n", read_off,
- left_len, len);
-
- ret = ft260_i2c_write(dev, addr, (u8 *)&read_off, msgs[0].len,
- FT260_FLAG_START);
- if (ret < 0)
- return ret;
+ read_off = *msgs[0].buf;
- ret = ft260_i2c_read(dev, addr, read_buf, len,
- FT260_FLAG_START_STOP);
- if (ret < 0)
- return ret;
+ pr_info("%s: off %#x rlen %d wlen %d\n", __func__,
+ read_off, rd_len, wr_len);
+ }
- left_len -= len;
- read_buf += len;
- read_off += len;
+ ret = ft260_i2c_write(dev, addr, msgs[0].buf, wr_len,
+ FT260_FLAG_START);
+ if (ret < 0)
+ return ret;
- } while (left_len > 0);
+ ret = ft260_i2c_read(dev, addr, msgs[1].buf, rd_len,
+ FT260_FLAG_START_STOP_REPEATED);
+ if (ret < 0)
+ return ret;
return 0;
}
@@ -613,14 +669,6 @@ static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
}
switch (size) {
- case I2C_SMBUS_QUICK:
- if (read_write == I2C_SMBUS_READ)
- ret = ft260_i2c_read(dev, addr, &data->byte, 0,
- FT260_FLAG_START_STOP);
- else
- ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
- FT260_FLAG_START_STOP);
- break;
case I2C_SMBUS_BYTE:
if (read_write == I2C_SMBUS_READ)
ret = ft260_i2c_read(dev, addr, &data->byte, 1,
@@ -703,7 +751,7 @@ smbus_exit:
static u32 ft260_functionality(struct i2c_adapter *adap)
{
- return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_QUICK |
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK;
}
@@ -782,7 +830,7 @@ static int ft260_byte_show(struct hid_device *hdev, int id, u8 *cfg, int len,
}
static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
- u16 *field, u8 *buf)
+ __le16 *field, u8 *buf)
{
int ret;
@@ -811,9 +859,9 @@ static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
#define FT260_I2CST_ATTR_SHOW(name) \
FT260_ATTR_SHOW(name, ft260_get_i2c_status_report, \
- FT260_I2C_STATUS, u16, ft260_word_show)
+ FT260_I2C_STATUS, __le16, ft260_word_show)
-#define FT260_ATTR_STORE(name, reptype, id, req, type, func) \
+#define FT260_ATTR_STORE(name, reptype, id, req, type, ctype, func) \
static ssize_t name##_store(struct device *kdev, \
struct device_attribute *attr, \
const char *buf, size_t count) \
@@ -823,7 +871,7 @@ static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
type name; \
int ret; \
\
- if (!func(buf, 10, &name)) { \
+ if (!func(buf, 10, (ctype *)&name)) { \
rep.name = name; \
rep.report = id; \
rep.request = req; \
@@ -839,11 +887,11 @@ static int ft260_word_show(struct hid_device *hdev, int id, u8 *cfg, int len,
#define FT260_BYTE_ATTR_STORE(name, reptype, req) \
FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \
- u8, kstrtou8)
+ u8, u8, kstrtou8)
#define FT260_WORD_ATTR_STORE(name, reptype, req) \
FT260_ATTR_STORE(name, reptype, FT260_SYSTEM_SETTINGS, req, \
- u16, kstrtou16)
+ __le16, u16, kstrtou16)
FT260_SSTAT_ATTR_SHOW(chip_mode);
static DEVICE_ATTR_RO(chip_mode);
@@ -928,7 +976,7 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
- ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ ret = hid_hw_start(hdev, 0);
if (ret) {
hid_err(hdev, "failed to start HID HW\n");
return ret;
@@ -955,6 +1003,10 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
if (ret <= 0)
goto err_hid_close;
+ hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
+ hdev->version >> 8, hdev->version & 0xff, hdev->name,
+ hdev->phys);
+
hid_set_drvdata(hdev, dev);
dev->hdev = hdev;
dev->adap.owner = THIS_MODULE;
@@ -963,13 +1015,12 @@ static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
dev->adap.quirks = &ft260_i2c_quirks;
dev->adap.dev.parent = &hdev->dev;
snprintf(dev->adap.name, sizeof(dev->adap.name),
- "FT260 usb-i2c bridge on hidraw%d",
- ((struct hidraw *)hdev->hidraw)->minor);
+ "FT260 usb-i2c bridge");
mutex_init(&dev->lock);
init_completion(&dev->wait);
- ret = ft260_xfer_status(dev);
+ ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
if (ret)
ft260_i2c_reset(hdev);
@@ -1022,6 +1073,13 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
xfer->length);
+ if ((dev->read_buf == NULL) ||
+ (xfer->length > dev->read_len - dev->read_idx)) {
+ hid_err(hdev, "unexpected report %#02x, length %d\n",
+ xfer->report, xfer->length);
+ return -1;
+ }
+
memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
xfer->length);
dev->read_idx += xfer->length;
@@ -1030,10 +1088,9 @@ static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
complete(&dev->wait);
} else {
- hid_err(hdev, "unknown report: %#02x\n", xfer->report);
- return 0;
+ hid_err(hdev, "unhandled report %#02x\n", xfer->report);
}
- return 1;
+ return 0;
}
static struct hid_driver ft260_driver = {
diff --git a/drivers/hid/hid-hyperv.c b/drivers/hid/hid-hyperv.c
index ab57b49a44ed..49d4a26895e7 100644
--- a/drivers/hid/hid-hyperv.c
+++ b/drivers/hid/hid-hyperv.c
@@ -22,9 +22,6 @@ struct hv_input_dev_info {
unsigned short reserved[11];
};
-/* The maximum size of a synthetic input message. */
-#define SYNTHHID_MAX_INPUT_REPORT_SIZE 16
-
/*
* Current version
*
@@ -59,11 +56,6 @@ struct synthhid_msg_hdr {
u32 size;
};
-struct synthhid_msg {
- struct synthhid_msg_hdr header;
- char data[1]; /* Enclosed message */
-};
-
union synthhid_version {
struct {
u16 minor_version;
@@ -99,7 +91,7 @@ struct synthhid_device_info_ack {
struct synthhid_input_report {
struct synthhid_msg_hdr header;
- char buffer[1];
+ char buffer[];
};
#pragma pack(pop)
@@ -118,7 +110,7 @@ enum pipe_prot_msg_type {
struct pipe_prt_msg {
enum pipe_prot_msg_type type;
u32 size;
- char data[1];
+ char data[];
};
struct mousevsc_prt_msg {
@@ -232,7 +224,7 @@ static void mousevsc_on_receive_device_info(struct mousevsc_dev *input_device,
ret = vmbus_sendpacket(input_device->device->channel,
&ack,
- sizeof(struct pipe_prt_msg) - sizeof(unsigned char) +
+ sizeof(struct pipe_prt_msg) +
sizeof(struct synthhid_device_info_ack),
(unsigned long)&ack,
VM_PKT_DATA_INBAND,
@@ -251,7 +243,7 @@ static void mousevsc_on_receive(struct hv_device *device,
struct vmpacket_descriptor *packet)
{
struct pipe_prt_msg *pipe_msg;
- struct synthhid_msg *hid_msg;
+ struct synthhid_msg_hdr *hid_msg_hdr;
struct mousevsc_dev *input_dev = hv_get_drvdata(device);
struct synthhid_input_report *input_report;
size_t len;
@@ -262,25 +254,23 @@ static void mousevsc_on_receive(struct hv_device *device,
if (pipe_msg->type != PIPE_MESSAGE_DATA)
return;
- hid_msg = (struct synthhid_msg *)pipe_msg->data;
+ hid_msg_hdr = (struct synthhid_msg_hdr *)pipe_msg->data;
- switch (hid_msg->header.type) {
+ switch (hid_msg_hdr->type) {
case SYNTH_HID_PROTOCOL_RESPONSE:
/*
* While it will be impossible for us to protect against
* malicious/buggy hypervisor/host, add a check here to
* ensure we don't corrupt memory.
*/
- if ((pipe_msg->size + sizeof(struct pipe_prt_msg)
- - sizeof(unsigned char))
+ if (struct_size(pipe_msg, data, pipe_msg->size)
> sizeof(struct mousevsc_prt_msg)) {
WARN_ON(1);
break;
}
memcpy(&input_dev->protocol_resp, pipe_msg,
- pipe_msg->size + sizeof(struct pipe_prt_msg) -
- sizeof(unsigned char));
+ struct_size(pipe_msg, data, pipe_msg->size));
complete(&input_dev->wait_event);
break;
@@ -311,7 +301,7 @@ static void mousevsc_on_receive(struct hv_device *device,
break;
default:
pr_err("unsupported hid msg type - type %d len %d\n",
- hid_msg->header.type, hid_msg->header.size);
+ hid_msg_hdr->type, hid_msg_hdr->size);
break;
}
@@ -359,8 +349,7 @@ static int mousevsc_connect_to_vsp(struct hv_device *device)
request->request.version_requested.version = SYNTHHID_INPUT_VERSION;
ret = vmbus_sendpacket(device->channel, request,
- sizeof(struct pipe_prt_msg) -
- sizeof(unsigned char) +
+ sizeof(struct pipe_prt_msg) +
sizeof(struct synthhid_protocol_request),
(unsigned long)request,
VM_PKT_DATA_INBAND,
@@ -435,7 +424,7 @@ static int mousevsc_hid_raw_request(struct hid_device *hid,
return 0;
}
-static struct hid_ll_driver mousevsc_ll_driver = {
+static const struct hid_ll_driver mousevsc_ll_driver = {
.parse = mousevsc_hid_parse,
.open = mousevsc_hid_open,
.close = mousevsc_hid_close,
@@ -535,7 +524,7 @@ probe_err0:
}
-static int mousevsc_remove(struct hv_device *dev)
+static void mousevsc_remove(struct hv_device *dev)
{
struct mousevsc_dev *input_dev = hv_get_drvdata(dev);
@@ -544,8 +533,6 @@ static int mousevsc_remove(struct hv_device *dev)
hid_hw_stop(input_dev->hid_device);
hid_destroy_device(input_dev->hid_device);
mousevsc_free_device(input_dev);
-
- return 0;
}
static int mousevsc_suspend(struct hv_device *dev)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index dad953f66996..63545cd307e5 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -411,7 +411,10 @@
#define USB_DEVICE_ID_HP_X2_10_COVER 0x0755
#define I2C_DEVICE_ID_HP_ENVY_X360_15 0x2d05
#define I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100 0x29CF
+#define I2C_DEVICE_ID_HP_ENVY_X360_EU0009NV 0x2CF9
#define I2C_DEVICE_ID_HP_SPECTRE_X360_15 0x2817
+#define I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG 0x29DF
+#define I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN 0x2BC8
#define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544
#define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706
#define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A
@@ -427,7 +430,8 @@
#define USB_DEVICE_ID_ELECOM_M_DT1URBK 0x00fe
#define USB_DEVICE_ID_ELECOM_M_DT1DRBK 0x00ff
#define USB_DEVICE_ID_ELECOM_M_HT1URBK 0x010c
-#define USB_DEVICE_ID_ELECOM_M_HT1DRBK 0x010d
+#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D 0x010d
+#define USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C 0x011c
#define USB_VENDOR_ID_DREAM_CHEEKY 0x1d34
#define USB_DEVICE_ID_DREAM_CHEEKY_WN 0x0004
@@ -444,6 +448,9 @@
#define USB_VENDOR_ID_EMS 0x2006
#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_DEVICE_ID_MULTITOUCH_3200 0x0002
@@ -818,6 +825,7 @@
#define USB_DEVICE_ID_LOGITECH_G510_USB_AUDIO 0xc22e
#define USB_DEVICE_ID_LOGITECH_G29_WHEEL 0xc24f
#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_FORCE3D_PRO 0xc286
#define USB_DEVICE_ID_LOGITECH_FLIGHT_SYSTEM_G940 0xc287
@@ -917,6 +925,7 @@
#define USB_DEVICE_ID_MS_XBOX_ONE_S_CONTROLLER 0x02fd
#define USB_DEVICE_ID_MS_PIXART_MOUSE 0x00cb
#define USB_DEVICE_ID_8BITDO_SN30_PRO_PLUS 0x02e0
+#define USB_DEVICE_ID_MS_MOUSE_0783 0x0783
#define USB_VENDOR_ID_MOJO 0x8282
#define USB_DEVICE_ID_RETRO_ADAPTER 0x3201
@@ -993,7 +1002,10 @@
#define USB_DEVICE_ID_ORTEK_IHOME_IMAC_A210S 0x8003
#define USB_VENDOR_ID_PLANTRONICS 0x047f
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES 0xc055
#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES 0xc056
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES 0xc057
+#define USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES 0xc058
#define USB_VENDOR_ID_PANASONIC 0x04da
#define USB_DEVICE_ID_PANABOARD_UBT780 0x1044
@@ -1177,6 +1189,7 @@
#define USB_VENDOR_ID_VALVE 0x28de
#define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102
#define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142
+#define USB_DEVICE_ID_STEAM_DECK 0x1205
#define USB_VENDOR_ID_STEELSERIES 0x1038
#define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410
@@ -1215,6 +1228,7 @@
#define USB_DEVICE_ID_SYNAPTICS_DELL_K15A 0x6e21
#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1002 0x73f4
#define USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003 0x73f5
+#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017 0x73f6
#define USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5 0x81a7
#define USB_VENDOR_ID_TEXAS_INSTRUMENTS 0x2047
@@ -1288,8 +1302,11 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094
#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_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_SW 0x0933
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06 0x0078
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
@@ -1381,6 +1398,7 @@
#define USB_VENDOR_ID_PRIMAX 0x0461
#define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22
+#define USB_DEVICE_ID_PRIMAX_MOUSE_4E2A 0x4e2a
#define USB_DEVICE_ID_PRIMAX_KEYBOARD 0x4e05
#define USB_DEVICE_ID_PRIMAX_REZEL 0x4e72
#define USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F 0x4d0f
diff --git a/drivers/hid/hid-input-test.c b/drivers/hid/hid-input-test.c
new file mode 100644
index 000000000000..77c2d45ac62a
--- /dev/null
+++ b/drivers/hid/hid-input-test.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID to Linux Input mapping
+ *
+ * Copyright (c) 2022 José Expósito <[email protected]>
+ */
+
+#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 <[email protected]>");
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 859aeb07542e..7fc967964dd8 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -340,6 +340,7 @@ static enum power_supply_property hidinput_battery_props[] = {
#define HID_BATTERY_QUIRK_PERCENT (1 << 0) /* always reports percent */
#define HID_BATTERY_QUIRK_FEATURE (1 << 1) /* ask for feature report */
#define HID_BATTERY_QUIRK_IGNORE (1 << 2) /* completely ignore the battery */
+#define HID_BATTERY_QUIRK_AVOID_QUERY (1 << 3) /* do not query the battery */
static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE,
@@ -369,16 +370,28 @@ static const struct hid_device_id hid_battery_quirks[] = {
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_ASUS_TP420IA_TOUCHSCREEN),
+ HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELAN, USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L),
+ HID_BATTERY_QUIRK_AVOID_QUERY },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW),
+ 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_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_15T_DR100),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_ENVY_X360_EU0009NV),
+ HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_15),
HID_BATTERY_QUIRK_IGNORE },
+ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_HP_SPECTRE_X360_13_AW0020NG),
+ HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN),
HID_BATTERY_QUIRK_IGNORE },
{ HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN),
@@ -477,7 +490,7 @@ static int hidinput_get_battery_property(struct power_supply *psy,
if (dev->battery_status == HID_BATTERY_UNKNOWN)
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
else
- val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ val->intval = dev->battery_charge_status;
break;
case POWER_SUPPLY_PROP_SCOPE:
@@ -545,6 +558,7 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
dev->battery_max = max;
dev->battery_report_type = report_type;
dev->battery_report_id = field->report->id;
+ dev->battery_charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
/*
* Stylus is normally not connected to the device and thus we
@@ -554,6 +568,9 @@ static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
dev->battery_avoid_query = report_type == HID_INPUT_REPORT &&
field->physical == HID_DG_STYLUS;
+ if (quirks & HID_BATTERY_QUIRK_AVOID_QUERY)
+ dev->battery_avoid_query = true;
+
dev->battery = power_supply_register(&dev->dev, psy_desc, &psy_cfg);
if (IS_ERR(dev->battery)) {
error = PTR_ERR(dev->battery);
@@ -608,6 +625,20 @@ static void hidinput_update_battery(struct hid_device *dev, int value)
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 */
static int hidinput_setup_battery(struct hid_device *dev, unsigned report_type,
struct hid_field *field, bool is_percentage)
@@ -622,6 +653,12 @@ static void hidinput_cleanup_battery(struct hid_device *dev)
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 */
static bool hidinput_field_in_collection(struct hid_device *device, struct hid_field *field,
@@ -781,6 +818,14 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
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 */
switch (usage->hid & 0xf) {
case 0x05: map_key_clear(KEY_SWITCHVIDEOMODE); break;
@@ -1086,6 +1131,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0x0cd: map_key_clear(KEY_PLAYPAUSE); break;
case 0x0cf: map_key_clear(KEY_VOICECOMMAND); break;
+ case 0x0d5: map_key_clear(KEY_CAMERA_ACCESS_ENABLE); break;
+ case 0x0d6: map_key_clear(KEY_CAMERA_ACCESS_DISABLE); break;
+ case 0x0d7: map_key_clear(KEY_CAMERA_ACCESS_TOGGLE); break;
case 0x0d8: map_key_clear(KEY_DICTATE); break;
case 0x0d9: map_key_clear(KEY_EMOJI_PICKER); break;
@@ -1208,6 +1256,9 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
hidinput_setup_battery(device, HID_INPUT_REPORT, field, true);
usage->type = EV_PWR;
return;
+ case HID_BAT_CHARGING:
+ usage->type = EV_PWR;
+ return;
}
goto unknown;
@@ -1450,7 +1501,11 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct
return;
if (usage->type == EV_PWR) {
- hidinput_update_battery(hid, value);
+ bool handled = hidinput_set_battery_charge_status(hid, usage->hid, value);
+
+ if (!handled)
+ hidinput_update_battery(hid, value);
+
return;
}
@@ -2306,3 +2361,7 @@ void hidinput_disconnect(struct hid_device *hid)
cancel_work_sync(&hid->led_work);
}
EXPORT_SYMBOL_GPL(hidinput_disconnect);
+
+#ifdef CONFIG_HID_KUNIT_TEST
+#include "hid-input-test.c"
+#endif
diff --git a/drivers/hid/hid-ite.c b/drivers/hid/hid-ite.c
index 430fa4f52ed3..75ebfcf31889 100644
--- a/drivers/hid/hid-ite.c
+++ b/drivers/hid/hid-ite.c
@@ -121,6 +121,11 @@ static const struct hid_device_id ite_devices[] = {
USB_VENDOR_ID_SYNAPTICS,
USB_DEVICE_ID_SYNAPTICS_ACER_ONE_S1003),
.driver_data = QUIRK_TOUCHPAD_ON_OFF_REPORT },
+ /* ITE8910 USB kbd ctlr, with Synaptics touchpad connected to it. */
+ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+ USB_VENDOR_ID_SYNAPTICS,
+ USB_DEVICE_ID_SYNAPTICS_ACER_SWITCH5_017),
+ .driver_data = QUIRK_TOUCHPAD_ON_OFF_REPORT },
{ }
};
MODULE_DEVICE_TABLE(hid, ite_devices);
diff --git a/drivers/hid/hid-letsketch.c b/drivers/hid/hid-letsketch.c
index 74d17cf518ba..97f047f18136 100644
--- a/drivers/hid/hid-letsketch.c
+++ b/drivers/hid/hid-letsketch.c
@@ -238,7 +238,7 @@ static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id *
char buf[256];
int i, ret;
- if (!hid_is_using_ll_driver(hdev, &usb_hid_driver))
+ if (!hid_is_usb(hdev))
return -ENODEV;
intf = to_usb_interface(hdev->dev.parent);
diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c
index 5e6a0cef2a06..e3fcf1353fb3 100644
--- a/drivers/hid/hid-lg4ff.c
+++ b/drivers/hid/hid-lg4ff.c
@@ -872,6 +872,12 @@ static ssize_t lg4ff_alternate_modes_store(struct device *dev, struct device_att
return -ENOMEM;
i = strlen(lbuf);
+
+ if (i == 0) {
+ kfree(lbuf);
+ return -EINVAL;
+ }
+
if (lbuf[i-1] == '\n') {
if (i == 1) {
kfree(lbuf);
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index c358778e070b..62180414efcc 100644
--- a/drivers/hid/hid-logitech-dj.c
+++ b/drivers/hid/hid-logitech-dj.c
@@ -554,7 +554,7 @@ static const u8 hid_reportid_size_map[NUMBER_OF_HID_REPORTS] = {
#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 void delayedwork_callback(struct work_struct *work);
@@ -1506,7 +1506,7 @@ static bool logi_dj_ll_may_wakeup(struct hid_device *hid)
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,
.start = logi_dj_ll_start,
.stop = logi_dj_ll_stop,
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 71a9c258a20b..25dcda76d6c7 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -30,11 +30,7 @@
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Benjamin Tissoires <[email protected]>");
MODULE_AUTHOR("Nestor Lopez Casado <[email protected]>");
-
-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.");
+MODULE_AUTHOR("Bastien Nocera <[email protected]>");
static bool disable_tap_to_click;
module_param(disable_tap_to_click, bool, 0644);
@@ -71,12 +67,13 @@ MODULE_PARM_DESC(disable_tap_to_click,
/* bits 2..20 are reserved for classes */
/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */
#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_UNIFYING BIT(25)
#define HIDPP_QUIRK_HIDPP_WHEELS BIT(26)
#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(27)
#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(28)
+#define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(29)
/* These are just aliases for now */
#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -87,8 +84,6 @@ MODULE_PARM_DESC(disable_tap_to_click,
HIDPP_CAPABILITY_HIDPP20_HI_RES_SCROLL | \
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_HIDPP20_BATTERY BIT(1)
#define HIDPP_CAPABILITY_BATTERY_MILEAGE BIT(2)
@@ -225,6 +220,16 @@ struct hidpp_device {
#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b
#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c
/* 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
static void hidpp_connect_event(struct hidpp_device *hidpp_dev);
@@ -279,6 +284,7 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
struct hidpp_report *response)
{
int ret;
+ int max_retries = 3;
mutex_lock(&hidpp->send_mutex);
@@ -291,34 +297,39 @@ static int hidpp_send_message_sync(struct hidpp_device *hidpp,
*/
*response = *message;
- ret = __hidpp_send_report(hidpp->hid_dev, message);
+ for (; max_retries != 0; max_retries--) {
+ ret = __hidpp_send_report(hidpp->hid_dev, message);
- if (ret) {
- dbg_hid("__hidpp_send_report returned err: %d\n", ret);
- memset(response, 0, sizeof(struct hidpp_report));
- goto exit;
- }
+ if (ret) {
+ dbg_hid("__hidpp_send_report returned err: %d\n", ret);
+ memset(response, 0, sizeof(struct hidpp_report));
+ goto exit;
+ }
- if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
- 5*HZ)) {
- dbg_hid("%s:timeout waiting for response\n", __func__);
- memset(response, 0, sizeof(struct hidpp_report));
- ret = -ETIMEDOUT;
- }
+ if (!wait_event_timeout(hidpp->wait, hidpp->answer_available,
+ 5*HZ)) {
+ dbg_hid("%s:timeout waiting for response\n", __func__);
+ memset(response, 0, sizeof(struct hidpp_report));
+ ret = -ETIMEDOUT;
+ }
- if (response->report_id == REPORT_ID_HIDPP_SHORT &&
- response->rap.sub_id == HIDPP_ERROR) {
- ret = response->rap.params[1];
- dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
- goto exit;
- }
+ if (response->report_id == REPORT_ID_HIDPP_SHORT &&
+ response->rap.sub_id == HIDPP_ERROR) {
+ ret = response->rap.params[1];
+ dbg_hid("%s:got hidpp error %02X\n", __func__, ret);
+ goto exit;
+ }
- if ((response->report_id == REPORT_ID_HIDPP_LONG ||
- response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
- response->fap.feature_index == HIDPP20_ERROR) {
- ret = response->fap.params[1];
- dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
- goto exit;
+ if ((response->report_id == REPORT_ID_HIDPP_LONG ||
+ response->report_id == REPORT_ID_HIDPP_VERY_LONG) &&
+ response->fap.feature_index == HIDPP20_ERROR) {
+ ret = response->fap.params[1];
+ if (ret != HIDPP20_ERROR_BUSY) {
+ dbg_hid("%s:got hidpp 2.0 error %02X\n", __func__, ret);
+ goto exit;
+ }
+ dbg_hid("%s:got busy hidpp 2.0 error %02X, retrying\n", __func__, ret);
+ }
}
exit:
@@ -334,8 +345,13 @@ static int hidpp_send_fap_command_sync(struct hidpp_device *hidpp,
struct hidpp_report *message;
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;
+ }
message = kzalloc(sizeof(struct hidpp_report), GFP_KERNEL);
if (!message)
@@ -895,7 +911,7 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
ret = hidpp_send_rap_command_sync(hidpp,
REPORT_ID_HIDPP_SHORT,
HIDPP_PAGE_ROOT_IDX,
- CMD_ROOT_GET_PROTOCOL_VERSION,
+ CMD_ROOT_GET_PROTOCOL_VERSION | LINUX_KERNEL_SW_ID,
ping_data, sizeof(ping_data), &response);
if (ret == HIDPP_ERROR_INVALID_SUBID) {
@@ -2548,12 +2564,17 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
struct hid_device *hid = hidpp->hid_dev;
struct hid_input *hidinput;
struct input_dev *dev;
- const struct usb_device_descriptor *udesc = &(hid_to_usb_dev(hid)->descriptor);
- const u16 bcdDevice = le16_to_cpu(udesc->bcdDevice);
+ struct usb_device_descriptor *udesc;
+ u16 bcdDevice;
struct ff_device *ff;
int error, j, num_slots = data->num_effects;
u8 version;
+ if (!hid_is_usb(hid)) {
+ hid_err(hid, "device is not USB\n");
+ return -ENODEV;
+ }
+
if (list_empty(&hid->inputs)) {
hid_err(hid, "no inputs found\n");
return -ENODEV;
@@ -2567,6 +2588,8 @@ static int hidpp_ff_init(struct hidpp_device *hidpp,
}
/* Get firmware release */
+ udesc = &(hid_to_usb_dev(hid)->descriptor);
+ bcdDevice = le16_to_cpu(udesc->bcdDevice);
version = bcdDevice & 255;
/* Set supported force feedback capabilities */
@@ -3429,11 +3452,17 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp)
ret = hidpp10_enable_scrolling_acceleration(hidpp);
multiplier = 8;
}
- if (ret)
+ if (ret) {
+ hid_dbg(hidpp->hid_dev,
+ "Could not enable hi-res scrolling: %d\n", 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;
+ }
hidpp->vertical_wheel_counter.wheel_multiplier = multiplier;
hid_dbg(hidpp->hid_dev, "wheel multiplier = %d\n", multiplier);
@@ -3465,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");
}
} else {
- struct hidpp_report response;
-
- ret = hidpp_send_rap_command_sync(hidpp,
- REPORT_ID_HIDPP_SHORT,
- HIDPP_GET_REGISTER,
- HIDPP_ENABLE_FAST_SCROLL,
- NULL, 0, &response);
- if (!ret) {
+ /* We cannot detect fast scrolling support on HID++ 1.0 devices */
+ if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL_1P0) {
hidpp->capabilities |= HIDPP_CAPABILITY_HIDPP10_FAST_SCROLL;
hid_dbg(hidpp->hid_dev, "Detected HID++ 1.0 fast scroll\n");
}
@@ -3971,7 +3994,8 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
}
hidpp_initialize_battery(hidpp);
- hidpp_initialize_hires_scroll(hidpp);
+ if (!hid_is_usb(hidpp->hid_dev))
+ hidpp_initialize_hires_scroll(hidpp);
/* forward current battery state */
if (hidpp->capabilities & HIDPP_CAPABILITY_HIDPP10_BATTERY) {
@@ -3994,7 +4018,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp)
if (hidpp->capabilities & HIDPP_CAPABILITY_HI_RES_SCROLL)
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 */
return;
@@ -4099,6 +4123,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
bool connected;
unsigned int connect_mask = HID_CONNECT_DEFAULT;
struct hidpp_ff_private_data data;
+ bool will_restart = false;
/* report_fixup needs drvdata to be set before we call hid_parse */
hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL);
@@ -4139,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->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) {
ret = wtp_allocate(hdev, id);
if (ret)
@@ -4154,6 +4174,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
+ if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT ||
+ hidpp->quirks & HIDPP_QUIRK_UNIFYING)
+ will_restart = true;
+
INIT_WORK(&hidpp->work, delayed_work_cb);
mutex_init(&hidpp->send_mutex);
init_waitqueue_head(&hidpp->wait);
@@ -4168,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
* 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) {
hid_err(hdev, "hw start failed\n");
goto hid_hw_start_fail;
@@ -4205,6 +4229,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp->wireless_feature_index = 0;
else if (ret)
goto hid_hw_init_fail;
+ ret = 0;
}
if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) {
@@ -4219,19 +4244,21 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hidpp_connect_event(hidpp);
- /* Reset the HID node state */
- hid_device_io_stop(hdev);
- hid_hw_close(hdev);
- hid_hw_stop(hdev);
+ if (will_restart) {
+ /* Reset the HID node state */
+ hid_device_io_stop(hdev);
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
- if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT)
- connect_mask &= ~HID_CONNECT_HIDINPUT;
+ if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
+ connect_mask &= ~HID_CONNECT_HIDINPUT;
- /* Now export the actual inputs and hidraw nodes to the world */
- ret = hid_hw_start(hdev, connect_mask);
- if (ret) {
- hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
- goto hid_hw_start_fail;
+ /* Now export the actual inputs and hidraw nodes to the world */
+ ret = hid_hw_start(hdev, connect_mask);
+ if (ret) {
+ hid_err(hdev, "%s:hid_hw_start returned error\n", __func__);
+ goto hid_hw_start_fail;
+ }
}
if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
@@ -4269,21 +4296,6 @@ static void hidpp_remove(struct hid_device *hdev)
mutex_destroy(&hidpp->send_mutex);
}
-static const struct hid_device_id unhandled_hidpp_devices[] = {
- /* Logitech Harmony Adapter for PS3, handled in hid-sony */
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
- /* Handled in hid-generic */
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD) },
- {}
-};
-
-static bool hidpp_match(struct hid_device *hdev,
- bool ignore_special_driver)
-{
- /* Refuse to handle devices handled by other HID drivers */
- return !hid_match_id(hdev, unhandled_hidpp_devices);
-}
-
#define LDJ_DEVICE(product) \
HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \
USB_VENDOR_ID_LOGITECH, (product))
@@ -4304,9 +4316,15 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_T651),
.driver_data = HIDPP_QUIRK_CLASS_WTP },
+ { /* Mouse Logitech Anywhere MX */
+ LDJ_DEVICE(0x1017), .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_1P0 },
{ /* Mouse logitech M560 */
LDJ_DEVICE(0x402d),
.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 */
LDJ_DEVICE(0x4024),
.driver_data = HIDPP_QUIRK_CLASS_K400 },
@@ -4355,6 +4373,9 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* Logitech G920 Wheel over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL),
.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 */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
@@ -4367,9 +4388,17 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX5500 keyboard over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb30b),
.driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
-
- { /* And try to enable HID++ for all the Logitech Bluetooth devices */
- HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_ANY, USB_VENDOR_ID_LOGITECH, HID_ANY_ID) },
+ { /* M-RCQ142 V470 Cordless Laser Mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb008) },
+ { /* MX Master mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) },
+ { /* MX Ergo trackball over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
+ { 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 */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
{}
};
@@ -4383,7 +4412,6 @@ static const struct hid_usage_id hidpp_usages[] = {
static struct hid_driver hidpp_driver = {
.name = "logitech-hidpp-device",
.id_table = hidpp_devices,
- .match = hidpp_match,
.report_fixup = hidpp_report_fixup,
.probe = hidpp_probe,
.remove = hidpp_remove,
diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c
index de52e9f7bb8c..f74a977cf8f8 100644
--- a/drivers/hid/hid-mcp2221.c
+++ b/drivers/hid/hid-mcp2221.c
@@ -10,12 +10,14 @@
#include <linux/module.h>
#include <linux/err.h>
#include <linux/mutex.h>
+#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/i2c.h>
#include <linux/gpio/driver.h>
+#include <linux/iio/iio.h>
#include "hid-ids.h"
/* Commands codes in a raw output report */
@@ -30,6 +32,9 @@ enum {
MCP2221_I2C_CANCEL = 0x10,
MCP2221_GPIO_SET = 0x50,
MCP2221_GPIO_GET = 0x51,
+ MCP2221_SET_SRAM_SETTINGS = 0x60,
+ MCP2221_GET_SRAM_SETTINGS = 0x61,
+ MCP2221_READ_FLASH_DATA = 0xb0,
};
/* Response codes in a raw input report */
@@ -89,6 +94,7 @@ struct mcp2221 {
struct i2c_adapter adapter;
struct mutex lock;
struct completion wait_in_report;
+ struct delayed_work init_work;
u8 *rxbuf;
u8 txbuf[64];
int rxbuf_idx;
@@ -97,6 +103,18 @@ struct mcp2221 {
struct gpio_chip *gc;
u8 gp_idx;
u8 gpio_dir;
+ u8 mode[4];
+#if IS_REACHABLE(CONFIG_IIO)
+ struct iio_chan_spec iio_channels[3];
+ u16 adc_values[3];
+ u8 adc_scale;
+ u8 dac_value;
+ u16 dac_scale;
+#endif
+};
+
+struct mcp2221_iio {
+ struct mcp2221 *mcp;
};
/*
@@ -567,6 +585,7 @@ static const struct i2c_algorithm mcp_i2c_algo = {
.functionality = mcp_i2c_func,
};
+#if IS_REACHABLE(CONFIG_GPIOLIB)
static int mcp_gpio_get(struct gpio_chip *gc,
unsigned int offset)
{
@@ -670,6 +689,7 @@ static int mcp_gpio_get_direction(struct gpio_chip *gc,
return GPIO_LINE_DIRECTION_OUT;
}
+#endif
/* Gives current state of i2c engine inside mcp2221 */
static int mcp_get_i2c_eng_state(struct mcp2221 *mcp,
@@ -745,6 +765,9 @@ static int mcp2221_raw_event(struct hid_device *hdev,
break;
}
mcp->status = mcp_get_i2c_eng_state(mcp, data, 8);
+#if IS_REACHABLE(CONFIG_IIO)
+ memcpy(&mcp->adc_values, &data[50], sizeof(mcp->adc_values));
+#endif
break;
default:
mcp->status = -EIO;
@@ -816,6 +839,69 @@ static int mcp2221_raw_event(struct hid_device *hdev,
complete(&mcp->wait_in_report);
break;
+ case MCP2221_SET_SRAM_SETTINGS:
+ switch (data[1]) {
+ case MCP2221_SUCCESS:
+ mcp->status = 0;
+ break;
+ default:
+ mcp->status = -EAGAIN;
+ }
+ complete(&mcp->wait_in_report);
+ break;
+
+ case MCP2221_GET_SRAM_SETTINGS:
+ switch (data[1]) {
+ case MCP2221_SUCCESS:
+ memcpy(&mcp->mode, &data[22], 4);
+#if IS_REACHABLE(CONFIG_IIO)
+ mcp->dac_value = data[6] & GENMASK(4, 0);
+#endif
+ mcp->status = 0;
+ break;
+ default:
+ mcp->status = -EAGAIN;
+ }
+ complete(&mcp->wait_in_report);
+ break;
+
+ case MCP2221_READ_FLASH_DATA:
+ switch (data[1]) {
+ case MCP2221_SUCCESS:
+ mcp->status = 0;
+
+ /* Only handles CHIP SETTINGS subpage currently */
+ if (mcp->txbuf[1] != 0) {
+ mcp->status = -EIO;
+ break;
+ }
+
+#if IS_REACHABLE(CONFIG_IIO)
+ {
+ u8 tmp;
+ /* DAC scale value */
+ tmp = FIELD_GET(GENMASK(7, 6), data[6]);
+ if ((data[6] & BIT(5)) && tmp)
+ mcp->dac_scale = tmp + 4;
+ else
+ mcp->dac_scale = 5;
+
+ /* ADC scale value */
+ tmp = FIELD_GET(GENMASK(4, 3), data[7]);
+ if ((data[7] & BIT(2)) && tmp)
+ mcp->adc_scale = tmp - 1;
+ else
+ mcp->adc_scale = 0;
+ }
+#endif
+
+ break;
+ default:
+ mcp->status = -EAGAIN;
+ }
+ complete(&mcp->wait_in_report);
+ break;
+
default:
mcp->status = -EIO;
complete(&mcp->wait_in_report);
@@ -824,6 +910,193 @@ static int mcp2221_raw_event(struct hid_device *hdev,
return 1;
}
+/* Device resource managed function for HID unregistration */
+static void mcp2221_hid_unregister(void *ptr)
+{
+ struct hid_device *hdev = ptr;
+
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+/* This is needed to be sure hid_hw_stop() isn't called twice by the subsystem */
+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)
+static int mcp2221_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mcp2221_iio *priv = iio_priv(indio_dev);
+ struct mcp2221 *mcp = priv->mcp;
+ int ret;
+
+ if (mask == IIO_CHAN_INFO_SCALE) {
+ if (channel->output)
+ *val = 1 << mcp->dac_scale;
+ else
+ *val = 1 << mcp->adc_scale;
+
+ return IIO_VAL_INT;
+ }
+
+ mutex_lock(&mcp->lock);
+
+ if (channel->output) {
+ *val = mcp->dac_value;
+ ret = IIO_VAL_INT;
+ } else {
+ /* Read ADC values */
+ ret = mcp_chk_last_cmd_status(mcp);
+
+ if (!ret) {
+ *val = le16_to_cpu((__force __le16) mcp->adc_values[channel->address]);
+ if (*val >= BIT(10))
+ ret = -EINVAL;
+ else
+ ret = IIO_VAL_INT;
+ }
+ }
+
+ mutex_unlock(&mcp->lock);
+
+ return ret;
+}
+
+static int mcp2221_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct mcp2221_iio *priv = iio_priv(indio_dev);
+ struct mcp2221 *mcp = priv->mcp;
+ int ret;
+
+ if (val < 0 || val >= BIT(5))
+ return -EINVAL;
+
+ mutex_lock(&mcp->lock);
+
+ memset(mcp->txbuf, 0, 12);
+ mcp->txbuf[0] = MCP2221_SET_SRAM_SETTINGS;
+ mcp->txbuf[4] = BIT(7) | val;
+
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 12);
+ if (!ret)
+ mcp->dac_value = val;
+
+ mutex_unlock(&mcp->lock);
+
+ return ret;
+}
+
+static const struct iio_info mcp2221_info = {
+ .read_raw = &mcp2221_read_raw,
+ .write_raw = &mcp2221_write_raw,
+};
+
+static int mcp_iio_channels(struct mcp2221 *mcp)
+{
+ int idx, cnt = 0;
+ bool dac_created = false;
+
+ /* GP0 doesn't have ADC/DAC alternative function */
+ for (idx = 1; idx < MCP_NGPIO; idx++) {
+ struct iio_chan_spec *chan = &mcp->iio_channels[cnt];
+
+ switch (mcp->mode[idx]) {
+ case 2:
+ chan->address = idx - 1;
+ chan->channel = cnt++;
+ break;
+ case 3:
+ /* GP1 doesn't have DAC alternative function */
+ if (idx == 1 || dac_created)
+ continue;
+ /* DAC1 and DAC2 outputs are connected to the same DAC */
+ dac_created = true;
+ chan->output = 1;
+ cnt++;
+ break;
+ default:
+ continue;
+ };
+
+ chan->type = IIO_VOLTAGE;
+ chan->indexed = 1;
+ chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);
+ chan->scan_index = -1;
+ }
+
+ return cnt;
+}
+
+static void mcp_init_work(struct work_struct *work)
+{
+ struct iio_dev *indio_dev;
+ struct mcp2221 *mcp = container_of(work, struct mcp2221, init_work.work);
+ struct mcp2221_iio *data;
+ static int retries = 5;
+ int ret, num_channels;
+
+ hid_hw_power(mcp->hdev, PM_HINT_FULLON);
+ mutex_lock(&mcp->lock);
+
+ mcp->txbuf[0] = MCP2221_GET_SRAM_SETTINGS;
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 1);
+
+ if (ret == -EAGAIN)
+ goto reschedule_task;
+
+ num_channels = mcp_iio_channels(mcp);
+ if (!num_channels)
+ goto unlock;
+
+ mcp->txbuf[0] = MCP2221_READ_FLASH_DATA;
+ mcp->txbuf[1] = 0;
+ ret = mcp_send_data_req_status(mcp, mcp->txbuf, 2);
+
+ if (ret == -EAGAIN)
+ goto reschedule_task;
+
+ indio_dev = devm_iio_device_alloc(&mcp->hdev->dev, sizeof(*data));
+ if (!indio_dev)
+ goto unlock;
+
+ data = iio_priv(indio_dev);
+ data->mcp = mcp;
+
+ indio_dev->name = "mcp2221";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mcp2221_info;
+ indio_dev->channels = mcp->iio_channels;
+ indio_dev->num_channels = num_channels;
+
+ devm_iio_device_register(&mcp->hdev->dev, indio_dev);
+
+unlock:
+ mutex_unlock(&mcp->lock);
+ hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
+
+ return;
+
+reschedule_task:
+ mutex_unlock(&mcp->lock);
+ hid_hw_power(mcp->hdev, PM_HINT_NORMAL);
+
+ if (!retries--)
+ return;
+
+ /* Device is not ready to read SRAM or FLASH data, try again */
+ schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+}
+#endif
+
static int mcp2221_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
@@ -840,16 +1113,24 @@ static int mcp2221_probe(struct hid_device *hdev,
return ret;
}
- ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ /*
+ * This driver uses the .raw_event callback and therefore does not need any
+ * HID_CONNECT_xxx flags.
+ */
+ ret = hid_hw_start(hdev, 0);
if (ret) {
hid_err(hdev, "can't start hardware\n");
return ret;
}
+ hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8,
+ hdev->version & 0xff, hdev->name, hdev->phys);
+
ret = hid_hw_open(hdev);
if (ret) {
hid_err(hdev, "can't open device\n");
- goto err_hstop;
+ hid_hw_stop(hdev);
+ return ret;
}
mutex_init(&mcp->lock);
@@ -857,6 +1138,10 @@ static int mcp2221_probe(struct hid_device *hdev,
hid_set_drvdata(hdev, mcp);
mcp->hdev = hdev;
+ ret = devm_add_action_or_reset(&hdev->dev, mcp2221_hid_unregister, hdev);
+ if (ret)
+ return ret;
+
/* Set I2C bus clock diviser */
if (i2c_clk_freq > 400)
i2c_clk_freq = 400;
@@ -870,22 +1155,20 @@ static int mcp2221_probe(struct hid_device *hdev,
mcp->adapter.retries = 1;
mcp->adapter.dev.parent = &hdev->dev;
snprintf(mcp->adapter.name, sizeof(mcp->adapter.name),
- "MCP2221 usb-i2c bridge on hidraw%d",
- ((struct hidraw *)hdev->hidraw)->minor);
+ "MCP2221 usb-i2c bridge");
- ret = i2c_add_adapter(&mcp->adapter);
+ ret = devm_i2c_add_adapter(&hdev->dev, &mcp->adapter);
if (ret) {
hid_err(hdev, "can't add usb-i2c adapter: %d\n", ret);
- goto err_i2c;
+ return ret;
}
i2c_set_adapdata(&mcp->adapter, mcp);
+#if IS_REACHABLE(CONFIG_GPIOLIB)
/* Setup GPIO chip */
mcp->gc = devm_kzalloc(&hdev->dev, sizeof(*mcp->gc), GFP_KERNEL);
- if (!mcp->gc) {
- ret = -ENOMEM;
- goto err_gc;
- }
+ if (!mcp->gc)
+ return -ENOMEM;
mcp->gc->label = "mcp2221_gpio";
mcp->gc->direction_input = mcp_gpio_direction_input;
@@ -900,26 +1183,15 @@ static int mcp2221_probe(struct hid_device *hdev,
ret = devm_gpiochip_add_data(&hdev->dev, mcp->gc, mcp);
if (ret)
- goto err_gc;
-
- return 0;
-
-err_gc:
- i2c_del_adapter(&mcp->adapter);
-err_i2c:
- hid_hw_close(mcp->hdev);
-err_hstop:
- hid_hw_stop(mcp->hdev);
- return ret;
-}
+ return ret;
+#endif
-static void mcp2221_remove(struct hid_device *hdev)
-{
- struct mcp2221 *mcp = hid_get_drvdata(hdev);
+#if IS_REACHABLE(CONFIG_IIO)
+ INIT_DELAYED_WORK(&mcp->init_work, mcp_init_work);
+ schedule_delayed_work(&mcp->init_work, msecs_to_jiffies(100));
+#endif
- i2c_del_adapter(&mcp->adapter);
- hid_hw_close(mcp->hdev);
- hid_hw_stop(mcp->hdev);
+ return 0;
}
static const struct hid_device_id mcp2221_devices[] = {
diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
index 91a4d3fc30e0..e31be0cb8b85 100644
--- a/drivers/hid/hid-multitouch.c
+++ b/drivers/hid/hid-multitouch.c
@@ -71,6 +71,7 @@ MODULE_LICENSE("GPL");
#define MT_QUIRK_SEPARATE_APP_REPORT BIT(19)
#define MT_QUIRK_FORCE_MULTI_INPUT BIT(20)
#define MT_QUIRK_DISABLE_WAKEUP BIT(21)
+#define MT_QUIRK_ORIENTATION_INVERT BIT(22)
#define MT_INPUTMODE_TOUCHSCREEN 0x02
#define MT_INPUTMODE_TOUCHPAD 0x03
@@ -1009,6 +1010,7 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
struct mt_usages *slot)
{
struct input_mt *mt = input->mt;
+ struct hid_device *hdev = td->hdev;
__s32 quirks = app->quirks;
bool valid = true;
bool confidence_state = true;
@@ -1086,6 +1088,10 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
int orientation = wide;
int max_azimuth;
int azimuth;
+ int x;
+ int y;
+ int cx;
+ int cy;
if (slot->a != DEFAULT_ZERO) {
/*
@@ -1104,6 +1110,9 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
if (azimuth > max_azimuth * 2)
azimuth -= max_azimuth * 4;
orientation = -azimuth;
+ if (quirks & MT_QUIRK_ORIENTATION_INVERT)
+ orientation = -orientation;
+
}
if (quirks & MT_QUIRK_TOUCH_SIZE_SCALING) {
@@ -1115,10 +1124,23 @@ static int mt_process_slot(struct mt_device *td, struct input_dev *input,
minor = minor >> 1;
}
- input_event(input, EV_ABS, ABS_MT_POSITION_X, *slot->x);
- input_event(input, EV_ABS, ABS_MT_POSITION_Y, *slot->y);
- input_event(input, EV_ABS, ABS_MT_TOOL_X, *slot->cx);
- input_event(input, EV_ABS, ABS_MT_TOOL_Y, *slot->cy);
+ x = hdev->quirks & HID_QUIRK_X_INVERT ?
+ input_abs_get_max(input, ABS_MT_POSITION_X) - *slot->x :
+ *slot->x;
+ 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_ORIENTATION, orientation);
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)
if (id->vendor == HID_ANY_ID && id->product == HID_ANY_ID)
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
* that emit events over several HID messages.
*/
@@ -1967,6 +1998,10 @@ static const struct hid_device_id mt_devices[] = {
HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_ELAN, 0x313a) },
+ { .driver_data = MT_CLS_WIN_8_FORCE_MULTI_INPUT,
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8,
+ USB_VENDOR_ID_ELAN, 0x3148) },
+
/* Elitegroup panel */
{ .driver_data = MT_CLS_SERIAL,
MT_USB_DEVICE(USB_VENDOR_ID_ELITEGROUP,
diff --git a/drivers/hid/hid-plantronics.c b/drivers/hid/hid-plantronics.c
index e81b7cec2d12..3d414ae194ac 100644
--- a/drivers/hid/hid-plantronics.c
+++ b/drivers/hid/hid-plantronics.c
@@ -199,8 +199,17 @@ err:
static const struct hid_device_id plantronics_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
+ USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3210_SERIES),
+ .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3220_SERIES),
.driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
+ USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3215_SERIES),
+ .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS,
+ USB_DEVICE_ID_PLANTRONICS_BLACKWIRE_3225_SERIES),
+ .driver_data = PLT_QUIRK_DOUBLE_VOLUME_KEYS },
{ HID_USB_DEVICE(USB_VENDOR_ID_PLANTRONICS, HID_ANY_ID) },
{ }
};
diff --git a/drivers/hid/hid-playstation.c b/drivers/hid/hid-playstation.c
index 0b58763bfd30..8ac8f7b8e317 100644
--- a/drivers/hid/hid-playstation.c
+++ b/drivers/hid/hid-playstation.c
@@ -2,7 +2,7 @@
/*
* HID driver for Sony DualSense(TM) controller.
*
- * Copyright (c) 2020 Sony Interactive Entertainment
+ * Copyright (c) 2020-2022 Sony Interactive Entertainment
*/
#include <linux/bits.h>
@@ -60,8 +60,10 @@ struct ps_calibration_data {
struct ps_led_info {
const char *name;
const char *color;
+ int max_brightness;
enum led_brightness (*brightness_get)(struct led_classdev *cdev);
int (*brightness_set)(struct led_classdev *cdev, enum led_brightness);
+ int (*blink_set)(struct led_classdev *led, unsigned long *on, unsigned long *off);
};
/* Seed values for DualShock4 / DualSense CRC32 for different report types. */
@@ -283,6 +285,225 @@ struct dualsense_output_report {
struct dualsense_output_report_common *common;
};
+#define DS4_INPUT_REPORT_USB 0x01
+#define DS4_INPUT_REPORT_USB_SIZE 64
+#define DS4_INPUT_REPORT_BT 0x11
+#define DS4_INPUT_REPORT_BT_SIZE 78
+#define DS4_OUTPUT_REPORT_USB 0x05
+#define DS4_OUTPUT_REPORT_USB_SIZE 32
+#define DS4_OUTPUT_REPORT_BT 0x11
+#define DS4_OUTPUT_REPORT_BT_SIZE 78
+
+#define DS4_FEATURE_REPORT_CALIBRATION 0x02
+#define DS4_FEATURE_REPORT_CALIBRATION_SIZE 37
+#define DS4_FEATURE_REPORT_CALIBRATION_BT 0x05
+#define DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE 41
+#define DS4_FEATURE_REPORT_FIRMWARE_INFO 0xa3
+#define DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE 49
+#define DS4_FEATURE_REPORT_PAIRING_INFO 0x12
+#define DS4_FEATURE_REPORT_PAIRING_INFO_SIZE 16
+
+/*
+ * Status of a DualShock4 touch point contact.
+ * Contact IDs, with highest bit set are 'inactive'
+ * and any associated data is then invalid.
+ */
+#define DS4_TOUCH_POINT_INACTIVE BIT(7)
+
+/* Status field of DualShock4 input report. */
+#define DS4_STATUS0_BATTERY_CAPACITY GENMASK(3, 0)
+#define DS4_STATUS0_CABLE_STATE BIT(4)
+/* Battery status within batery_status field. */
+#define DS4_BATTERY_STATUS_FULL 11
+/* Status1 bit2 contains dongle connection state:
+ * 0 = connectd
+ * 1 = disconnected
+ */
+#define DS4_STATUS1_DONGLE_STATE BIT(2)
+
+/* The lower 6 bits of hw_control of the Bluetooth main output report
+ * control the interval at which Dualshock 4 reports data:
+ * 0x00 - 1ms
+ * 0x01 - 1ms
+ * 0x02 - 2ms
+ * 0x3E - 62ms
+ * 0x3F - disabled
+ */
+#define DS4_OUTPUT_HWCTL_BT_POLL_MASK 0x3F
+/* Default to 4ms poll interval, which is same as USB (not adjustable). */
+#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4
+#define DS4_OUTPUT_HWCTL_CRC32 0x40
+#define DS4_OUTPUT_HWCTL_HID 0x80
+
+/* Flags for DualShock4 output report. */
+#define DS4_OUTPUT_VALID_FLAG0_MOTOR 0x01
+#define DS4_OUTPUT_VALID_FLAG0_LED 0x02
+#define DS4_OUTPUT_VALID_FLAG0_LED_BLINK 0x04
+
+/* DualShock4 hardware limits */
+#define DS4_ACC_RES_PER_G 8192
+#define DS4_ACC_RANGE (4*DS_ACC_RES_PER_G)
+#define DS4_GYRO_RES_PER_DEG_S 1024
+#define DS4_GYRO_RANGE (2048*DS_GYRO_RES_PER_DEG_S)
+#define DS4_LIGHTBAR_MAX_BLINK 255 /* 255 centiseconds */
+#define DS4_TOUCHPAD_WIDTH 1920
+#define DS4_TOUCHPAD_HEIGHT 942
+
+enum dualshock4_dongle_state {
+ DONGLE_DISCONNECTED,
+ DONGLE_CALIBRATING,
+ DONGLE_CONNECTED,
+ DONGLE_DISABLED
+};
+
+struct dualshock4 {
+ struct ps_device base;
+ struct input_dev *gamepad;
+ struct input_dev *sensors;
+ struct input_dev *touchpad;
+
+ /* Calibration data for accelerometer and gyroscope. */
+ struct ps_calibration_data accel_calib_data[3];
+ struct ps_calibration_data gyro_calib_data[3];
+
+ /* Only used on dongle to track state transitions. */
+ enum dualshock4_dongle_state dongle_state;
+ /* Used during calibration. */
+ struct work_struct dongle_hotplug_worker;
+
+ /* Timestamp for sensor data */
+ bool sensor_timestamp_initialized;
+ uint32_t prev_sensor_timestamp;
+ uint32_t sensor_timestamp_us;
+
+ /* Bluetooth poll interval */
+ bool update_bt_poll_interval;
+ uint8_t bt_poll_interval;
+
+ bool update_rumble;
+ uint8_t motor_left;
+ uint8_t motor_right;
+
+ /* Lightbar leds */
+ bool update_lightbar;
+ bool update_lightbar_blink;
+ bool lightbar_enabled; /* For use by global LED control. */
+ uint8_t lightbar_red;
+ uint8_t lightbar_green;
+ uint8_t lightbar_blue;
+ uint8_t lightbar_blink_on; /* In increments of 10ms. */
+ uint8_t lightbar_blink_off; /* In increments of 10ms. */
+ struct led_classdev lightbar_leds[4];
+
+ struct work_struct output_worker;
+ bool output_worker_initialized;
+ void *output_report_dmabuf;
+};
+
+struct dualshock4_touch_point {
+ uint8_t contact;
+ uint8_t x_lo;
+ uint8_t x_hi:4, y_lo:4;
+ uint8_t y_hi;
+} __packed;
+static_assert(sizeof(struct dualshock4_touch_point) == 4);
+
+struct dualshock4_touch_report {
+ uint8_t timestamp;
+ struct dualshock4_touch_point points[2];
+} __packed;
+static_assert(sizeof(struct dualshock4_touch_report) == 9);
+
+/* Main DualShock4 input report excluding any BT/USB specific headers. */
+struct dualshock4_input_report_common {
+ uint8_t x, y;
+ uint8_t rx, ry;
+ uint8_t buttons[3];
+ uint8_t z, rz;
+
+ /* Motion sensors */
+ __le16 sensor_timestamp;
+ uint8_t sensor_temperature;
+ __le16 gyro[3]; /* x, y, z */
+ __le16 accel[3]; /* x, y, z */
+ uint8_t reserved2[5];
+
+ uint8_t status[2];
+ uint8_t reserved3;
+} __packed;
+static_assert(sizeof(struct dualshock4_input_report_common) == 32);
+
+struct dualshock4_input_report_usb {
+ uint8_t report_id; /* 0x01 */
+ struct dualshock4_input_report_common common;
+ uint8_t num_touch_reports;
+ struct dualshock4_touch_report touch_reports[3];
+ uint8_t reserved[3];
+} __packed;
+static_assert(sizeof(struct dualshock4_input_report_usb) == DS4_INPUT_REPORT_USB_SIZE);
+
+struct dualshock4_input_report_bt {
+ uint8_t report_id; /* 0x11 */
+ uint8_t reserved[2];
+ struct dualshock4_input_report_common common;
+ uint8_t num_touch_reports;
+ struct dualshock4_touch_report touch_reports[4]; /* BT has 4 compared to 3 for USB */
+ uint8_t reserved2[2];
+ __le32 crc32;
+} __packed;
+static_assert(sizeof(struct dualshock4_input_report_bt) == DS4_INPUT_REPORT_BT_SIZE);
+
+/* Common data between Bluetooth and USB DualShock4 output reports. */
+struct dualshock4_output_report_common {
+ uint8_t valid_flag0;
+ uint8_t valid_flag1;
+
+ uint8_t reserved;
+
+ uint8_t motor_right;
+ uint8_t motor_left;
+
+ uint8_t lightbar_red;
+ uint8_t lightbar_green;
+ uint8_t lightbar_blue;
+ uint8_t lightbar_blink_on;
+ uint8_t lightbar_blink_off;
+} __packed;
+
+struct dualshock4_output_report_usb {
+ uint8_t report_id; /* 0x5 */
+ struct dualshock4_output_report_common common;
+ uint8_t reserved[21];
+} __packed;
+static_assert(sizeof(struct dualshock4_output_report_usb) == DS4_OUTPUT_REPORT_USB_SIZE);
+
+struct dualshock4_output_report_bt {
+ uint8_t report_id; /* 0x11 */
+ uint8_t hw_control;
+ uint8_t audio_control;
+ struct dualshock4_output_report_common common;
+ uint8_t reserved[61];
+ __le32 crc32;
+} __packed;
+static_assert(sizeof(struct dualshock4_output_report_bt) == DS4_OUTPUT_REPORT_BT_SIZE);
+
+/*
+ * The DualShock4 has a main output report used to control most features. It is
+ * largely the same between Bluetooth and USB except for different headers and CRC.
+ * This structure hide the differences between the two to simplify sending output reports.
+ */
+struct dualshock4_output_report {
+ uint8_t *data; /* Start of data */
+ uint8_t len; /* Size of output report */
+
+ /* Points to Bluetooth data payload in case for a Bluetooth report else NULL. */
+ struct dualshock4_output_report_bt *bt;
+ /* Points to USB data payload in case for a USB report else NULL. */
+ struct dualshock4_output_report_usb *usb;
+ /* Points to common section of report, so past any headers. */
+ struct dualshock4_output_report_common *common;
+};
+
/*
* Common gamepad buttons across DualShock 3 / 4 and DualSense.
* Note: for device with a touchpad, touchpad button is not included
@@ -309,8 +530,11 @@ static const struct {int x; int y; } ps_gamepad_hat_mapping[] = {
{0, 0},
};
+static int dualshock4_get_calibration_data(struct dualshock4 *ds4);
static inline void dualsense_schedule_work(struct dualsense *ds);
+static inline void dualshock4_schedule_work(struct dualshock4 *ds4);
static void dualsense_set_lightbar(struct dualsense *ds, uint8_t red, uint8_t green, uint8_t blue);
+static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4);
/*
* Add a new ps_device to ps_devices if it doesn't exist.
@@ -514,7 +738,8 @@ static struct input_dev *ps_gamepad_create(struct hid_device *hdev,
return gamepad;
}
-static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size)
+static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *buf, size_t size,
+ bool check_crc)
{
int ret;
@@ -535,7 +760,7 @@ static int ps_get_report(struct hid_device *hdev, uint8_t report_id, uint8_t *bu
return -EINVAL;
}
- if (hdev->bus == BUS_BLUETOOTH) {
+ if (hdev->bus == BUS_BLUETOOTH && check_crc) {
/* Last 4 bytes contains crc32. */
uint8_t crc_offset = size - 4;
uint32_t report_crc = get_unaligned_le32(&buf[crc_offset]);
@@ -554,17 +779,24 @@ static int ps_led_register(struct ps_device *ps_dev, struct led_classdev *led,
{
int ret;
- led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
- "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
+ if (led_info->name) {
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
+ "%s:%s:%s", ps_dev->input_dev_name, led_info->color, led_info->name);
+ } else {
+ /* Backwards compatible mode for hid-sony, but not compliant with LED class spec. */
+ led->name = devm_kasprintf(&ps_dev->hdev->dev, GFP_KERNEL,
+ "%s:%s", ps_dev->input_dev_name, led_info->color);
+ }
if (!led->name)
return -ENOMEM;
led->brightness = 0;
- led->max_brightness = 1;
+ led->max_brightness = led_info->max_brightness;
led->flags = LED_CORE_SUSPENDRESUME;
led->brightness_get = led_info->brightness_get;
led->brightness_set_blocking = led_info->brightness_set;
+ led->blink_set = led_info->blink_set;
ret = devm_led_classdev_register(&ps_dev->hdev->dev, led);
if (ret) {
@@ -712,6 +944,7 @@ ATTRIBUTE_GROUPS(ps_device);
static int dualsense_get_calibration_data(struct dualsense *ds)
{
+ struct hid_device *hdev = ds->base.hdev;
short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus;
short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus;
short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus;
@@ -722,6 +955,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
int speed_2x;
int range_2g;
int ret = 0;
+ int i;
uint8_t *buf;
buf = kzalloc(DS_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
@@ -729,7 +963,7 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_CALIBRATION, buf,
- DS_FEATURE_REPORT_CALIBRATION_SIZE);
+ DS_FEATURE_REPORT_CALIBRATION_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense calibration info: %d\n", ret);
goto err_free;
@@ -759,19 +993,37 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
*/
speed_2x = (gyro_speed_plus + gyro_speed_minus);
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_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].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_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].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_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
+ * during report handling of virtual, clone or broken devices not implementing
+ * calibration data properly.
+ */
+ for (i = 0; i < ARRAY_SIZE(ds->gyro_calib_data); i++) {
+ if (ds->gyro_calib_data[i].sens_denom == 0) {
+ hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds->gyro_calib_data[i].abs_code);
+ ds->gyro_calib_data[i].bias = 0;
+ ds->gyro_calib_data[i].sens_numer = DS_GYRO_RANGE;
+ ds->gyro_calib_data[i].sens_denom = S16_MAX;
+ }
+ }
/*
* Set accelerometer calibration and normalization parameters.
@@ -795,6 +1047,21 @@ static int dualsense_get_calibration_data(struct dualsense *ds)
ds->accel_calib_data[2].sens_numer = 2*DS_ACC_RES_PER_G;
ds->accel_calib_data[2].sens_denom = range_2g;
+ /*
+ * Sanity check accelerometer calibration data. This is needed to prevent crashes
+ * during report handling of virtual, clone or broken devices not implementing calibration
+ * data properly.
+ */
+ for (i = 0; i < ARRAY_SIZE(ds->accel_calib_data); i++) {
+ if (ds->accel_calib_data[i].sens_denom == 0) {
+ hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds->accel_calib_data[i].abs_code);
+ ds->accel_calib_data[i].bias = 0;
+ ds->accel_calib_data[i].sens_numer = DS_ACC_RANGE;
+ ds->accel_calib_data[i].sens_denom = S16_MAX;
+ }
+ }
+
err_free:
kfree(buf);
return ret;
@@ -811,7 +1078,7 @@ static int dualsense_get_firmware_info(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_FIRMWARE_INFO, buf,
- DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE);
+ DS_FEATURE_REPORT_FIRMWARE_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense firmware info: %d\n", ret);
goto err_free;
@@ -844,7 +1111,7 @@ static int dualsense_get_mac_address(struct dualsense *ds)
return -ENOMEM;
ret = ps_get_report(ds->base.hdev, DS_FEATURE_REPORT_PAIRING_INFO, buf,
- DS_FEATURE_REPORT_PAIRING_INFO_SIZE);
+ DS_FEATURE_REPORT_PAIRING_INFO_SIZE, true);
if (ret) {
hid_err(ds->base.hdev, "Failed to retrieve DualSense pairing info: %d\n", ret);
goto err_free;
@@ -1124,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++) {
int raw_data = (short)le16_to_cpu(ds_report->gyro[i]);
int calib_data = mult_frac(ds->gyro_calib_data[i].sens_numer,
- raw_data - ds->gyro_calib_data[i].bias,
- ds->gyro_calib_data[i].sens_denom);
+ raw_data, ds->gyro_calib_data[i].sens_denom);
input_report_abs(ds->sensors, ds->gyro_calib_data[i].abs_code, calib_data);
}
@@ -1317,15 +1583,15 @@ static struct ps_device *dualsense_create(struct hid_device *hdev)
int i, ret;
static const struct ps_led_info player_leds_info[] = {
- { LED_FUNCTION_PLAYER1, "white", dualsense_player_led_get_brightness,
+ { LED_FUNCTION_PLAYER1, "white", 1, dualsense_player_led_get_brightness,
dualsense_player_led_set_brightness },
- { LED_FUNCTION_PLAYER2, "white", dualsense_player_led_get_brightness,
+ { LED_FUNCTION_PLAYER2, "white", 1, dualsense_player_led_get_brightness,
dualsense_player_led_set_brightness },
- { LED_FUNCTION_PLAYER3, "white", dualsense_player_led_get_brightness,
+ { LED_FUNCTION_PLAYER3, "white", 1, dualsense_player_led_get_brightness,
dualsense_player_led_set_brightness },
- { LED_FUNCTION_PLAYER4, "white", dualsense_player_led_get_brightness,
+ { LED_FUNCTION_PLAYER4, "white", 1, dualsense_player_led_get_brightness,
dualsense_player_led_set_brightness },
- { LED_FUNCTION_PLAYER5, "white", dualsense_player_led_get_brightness,
+ { LED_FUNCTION_PLAYER5, "white", 1, dualsense_player_led_get_brightness,
dualsense_player_led_set_brightness }
};
@@ -1465,6 +1731,896 @@ err:
return ERR_PTR(ret);
}
+static void dualshock4_dongle_calibration_work(struct work_struct *work)
+{
+ struct dualshock4 *ds4 = container_of(work, struct dualshock4, dongle_hotplug_worker);
+ unsigned long flags;
+ enum dualshock4_dongle_state dongle_state;
+ int ret;
+
+ ret = dualshock4_get_calibration_data(ds4);
+ if (ret < 0) {
+ /* This call is very unlikely to fail for the dongle. When it
+ * fails we are probably in a very bad state, so mark the
+ * dongle as disabled. We will re-enable the dongle if a new
+ * DS4 hotplug is detect from sony_raw_event as any issues
+ * are likely resolved then (the dongle is quite stupid).
+ */
+ hid_err(ds4->base.hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
+ dongle_state = DONGLE_DISABLED;
+ } else {
+ hid_info(ds4->base.hdev, "DualShock 4 USB dongle: calibration completed\n");
+ dongle_state = DONGLE_CONNECTED;
+ }
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+ ds4->dongle_state = dongle_state;
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+}
+
+static int dualshock4_get_calibration_data(struct dualshock4 *ds4)
+{
+ struct hid_device *hdev = ds4->base.hdev;
+ short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus;
+ short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus;
+ short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus;
+ short gyro_speed_plus, gyro_speed_minus;
+ short acc_x_plus, acc_x_minus;
+ short acc_y_plus, acc_y_minus;
+ short acc_z_plus, acc_z_minus;
+ int speed_2x;
+ int range_2g;
+ int ret = 0;
+ int i;
+ uint8_t *buf;
+
+ if (ds4->base.hdev->bus == BUS_USB) {
+ int retries;
+
+ buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* We should normally receive the feature report data we asked
+ * for, but hidraw applications such as Steam can issue feature
+ * reports as well. In particular for Dongle reconnects, Steam
+ * and this function are competing resulting in often receiving
+ * data for a different HID report, so retry a few times.
+ */
+ for (retries = 0; retries < 3; retries++) {
+ ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION, buf,
+ DS4_FEATURE_REPORT_CALIBRATION_SIZE, true);
+ if (ret) {
+ if (retries < 2) {
+ hid_warn(hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
+ continue;
+ }
+
+ hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+ ret = -EILSEQ;
+ goto err_free;
+ } else {
+ break;
+ }
+ }
+ } else { /* Bluetooth */
+ buf = kzalloc(DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ps_get_report(hdev, DS4_FEATURE_REPORT_CALIBRATION_BT, buf,
+ DS4_FEATURE_REPORT_CALIBRATION_BT_SIZE, true);
+ if (ret) {
+ hid_err(hdev, "Failed to retrieve DualShock4 calibration info: %d\n", ret);
+ goto err_free;
+ }
+ }
+
+ gyro_pitch_bias = get_unaligned_le16(&buf[1]);
+ gyro_yaw_bias = get_unaligned_le16(&buf[3]);
+ gyro_roll_bias = get_unaligned_le16(&buf[5]);
+ if (ds4->base.hdev->bus == BUS_USB) {
+ gyro_pitch_plus = get_unaligned_le16(&buf[7]);
+ gyro_pitch_minus = get_unaligned_le16(&buf[9]);
+ gyro_yaw_plus = get_unaligned_le16(&buf[11]);
+ gyro_yaw_minus = get_unaligned_le16(&buf[13]);
+ gyro_roll_plus = get_unaligned_le16(&buf[15]);
+ gyro_roll_minus = get_unaligned_le16(&buf[17]);
+ } else {
+ /* BT + Dongle */
+ gyro_pitch_plus = get_unaligned_le16(&buf[7]);
+ gyro_yaw_plus = get_unaligned_le16(&buf[9]);
+ gyro_roll_plus = get_unaligned_le16(&buf[11]);
+ gyro_pitch_minus = get_unaligned_le16(&buf[13]);
+ gyro_yaw_minus = get_unaligned_le16(&buf[15]);
+ gyro_roll_minus = get_unaligned_le16(&buf[17]);
+ }
+ gyro_speed_plus = get_unaligned_le16(&buf[19]);
+ gyro_speed_minus = get_unaligned_le16(&buf[21]);
+ acc_x_plus = get_unaligned_le16(&buf[23]);
+ acc_x_minus = get_unaligned_le16(&buf[25]);
+ acc_y_plus = get_unaligned_le16(&buf[27]);
+ acc_y_minus = get_unaligned_le16(&buf[29]);
+ acc_z_plus = get_unaligned_le16(&buf[31]);
+ acc_z_minus = get_unaligned_le16(&buf[33]);
+
+ /*
+ * Set gyroscope calibration and normalization parameters.
+ * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
+ */
+ speed_2x = (gyro_speed_plus + gyro_speed_minus);
+ ds4->gyro_calib_data[0].abs_code = ABS_RX;
+ 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_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].bias = 0;
+ ds4->gyro_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ 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].bias = 0;
+ ds4->gyro_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
+ 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
+ * during report handling of virtual, clone or broken devices not implementing
+ * calibration data properly.
+ */
+ for (i = 0; i < ARRAY_SIZE(ds4->gyro_calib_data); i++) {
+ if (ds4->gyro_calib_data[i].sens_denom == 0) {
+ hid_warn(hdev, "Invalid gyro calibration data for axis (%d), disabling calibration.",
+ ds4->gyro_calib_data[i].abs_code);
+ ds4->gyro_calib_data[i].bias = 0;
+ ds4->gyro_calib_data[i].sens_numer = DS4_GYRO_RANGE;
+ ds4->gyro_calib_data[i].sens_denom = S16_MAX;
+ }
+ }
+
+ /*
+ * Set accelerometer calibration and normalization parameters.
+ * Data values will be normalized to 1/DS4_ACC_RES_PER_G g.
+ */
+ range_2g = acc_x_plus - acc_x_minus;
+ ds4->accel_calib_data[0].abs_code = ABS_X;
+ ds4->accel_calib_data[0].bias = acc_x_plus - range_2g / 2;
+ ds4->accel_calib_data[0].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[0].sens_denom = range_2g;
+
+ range_2g = acc_y_plus - acc_y_minus;
+ ds4->accel_calib_data[1].abs_code = ABS_Y;
+ ds4->accel_calib_data[1].bias = acc_y_plus - range_2g / 2;
+ ds4->accel_calib_data[1].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[1].sens_denom = range_2g;
+
+ range_2g = acc_z_plus - acc_z_minus;
+ ds4->accel_calib_data[2].abs_code = ABS_Z;
+ ds4->accel_calib_data[2].bias = acc_z_plus - range_2g / 2;
+ ds4->accel_calib_data[2].sens_numer = 2*DS4_ACC_RES_PER_G;
+ ds4->accel_calib_data[2].sens_denom = range_2g;
+
+ /*
+ * Sanity check accelerometer calibration data. This is needed to prevent crashes
+ * during report handling of virtual, clone or broken devices not implementing calibration
+ * data properly.
+ */
+ for (i = 0; i < ARRAY_SIZE(ds4->accel_calib_data); i++) {
+ if (ds4->accel_calib_data[i].sens_denom == 0) {
+ hid_warn(hdev, "Invalid accelerometer calibration data for axis (%d), disabling calibration.",
+ ds4->accel_calib_data[i].abs_code);
+ ds4->accel_calib_data[i].bias = 0;
+ ds4->accel_calib_data[i].sens_numer = DS4_ACC_RANGE;
+ ds4->accel_calib_data[i].sens_denom = S16_MAX;
+ }
+ }
+
+err_free:
+ kfree(buf);
+ return ret;
+}
+
+static int dualshock4_get_firmware_info(struct dualshock4 *ds4)
+{
+ uint8_t *buf;
+ int ret;
+
+ buf = kzalloc(DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ /* Note USB and BT support the same feature report, but this report
+ * lacks CRC support, so must be disabled in ps_get_report.
+ */
+ ret = ps_get_report(ds4->base.hdev, DS4_FEATURE_REPORT_FIRMWARE_INFO, buf,
+ DS4_FEATURE_REPORT_FIRMWARE_INFO_SIZE, false);
+ if (ret) {
+ hid_err(ds4->base.hdev, "Failed to retrieve DualShock4 firmware info: %d\n", ret);
+ goto err_free;
+ }
+
+ ds4->base.hw_version = get_unaligned_le16(&buf[35]);
+ ds4->base.fw_version = get_unaligned_le16(&buf[41]);
+
+err_free:
+ kfree(buf);
+ return ret;
+}
+
+static int dualshock4_get_mac_address(struct dualshock4 *ds4)
+{
+ struct hid_device *hdev = ds4->base.hdev;
+ uint8_t *buf;
+ int ret = 0;
+
+ if (hdev->bus == BUS_USB) {
+ buf = kzalloc(DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = ps_get_report(hdev, DS4_FEATURE_REPORT_PAIRING_INFO, buf,
+ DS4_FEATURE_REPORT_PAIRING_INFO_SIZE, false);
+ if (ret) {
+ hid_err(hdev, "Failed to retrieve DualShock4 pairing info: %d\n", ret);
+ goto err_free;
+ }
+
+ memcpy(ds4->base.mac_address, &buf[1], sizeof(ds4->base.mac_address));
+ } else {
+ /* Rely on HIDP for Bluetooth */
+ if (strlen(hdev->uniq) != 17)
+ return -EINVAL;
+
+ ret = sscanf(hdev->uniq, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &ds4->base.mac_address[5], &ds4->base.mac_address[4],
+ &ds4->base.mac_address[3], &ds4->base.mac_address[2],
+ &ds4->base.mac_address[1], &ds4->base.mac_address[0]);
+
+ if (ret != sizeof(ds4->base.mac_address))
+ return -EINVAL;
+
+ return 0;
+ }
+
+err_free:
+ kfree(buf);
+ return ret;
+}
+
+static enum led_brightness dualshock4_led_get_brightness(struct led_classdev *led)
+{
+ struct hid_device *hdev = to_hid_device(led->dev->parent);
+ struct dualshock4 *ds4 = hid_get_drvdata(hdev);
+ unsigned int led_index;
+
+ led_index = led - ds4->lightbar_leds;
+ switch (led_index) {
+ case 0:
+ return ds4->lightbar_red;
+ case 1:
+ return ds4->lightbar_green;
+ case 2:
+ return ds4->lightbar_blue;
+ case 3:
+ return ds4->lightbar_enabled;
+ }
+
+ return -1;
+}
+
+static int dualshock4_led_set_blink(struct led_classdev *led, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct hid_device *hdev = to_hid_device(led->dev->parent);
+ struct dualshock4 *ds4 = hid_get_drvdata(hdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+
+ if (!*delay_on && !*delay_off) {
+ /* Default to 1 Hz (50 centiseconds on, 50 centiseconds off). */
+ ds4->lightbar_blink_on = 50;
+ ds4->lightbar_blink_off = 50;
+ } else {
+ /* Blink delays in centiseconds. */
+ ds4->lightbar_blink_on = min_t(unsigned long, *delay_on/10, DS4_LIGHTBAR_MAX_BLINK);
+ ds4->lightbar_blink_off = min_t(unsigned long, *delay_off/10, DS4_LIGHTBAR_MAX_BLINK);
+ }
+
+ ds4->update_lightbar_blink = true;
+
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+
+ dualshock4_schedule_work(ds4);
+
+ *delay_on = ds4->lightbar_blink_on;
+ *delay_off = ds4->lightbar_blink_off;
+
+ return 0;
+}
+
+static int dualshock4_led_set_brightness(struct led_classdev *led, enum led_brightness value)
+{
+ struct hid_device *hdev = to_hid_device(led->dev->parent);
+ struct dualshock4 *ds4 = hid_get_drvdata(hdev);
+ unsigned long flags;
+ unsigned int led_index;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+
+ led_index = led - ds4->lightbar_leds;
+ switch (led_index) {
+ case 0:
+ ds4->lightbar_red = value;
+ break;
+ case 1:
+ ds4->lightbar_green = value;
+ break;
+ case 2:
+ ds4->lightbar_blue = value;
+ break;
+ case 3:
+ ds4->lightbar_enabled = !!value;
+ }
+
+ ds4->update_lightbar = true;
+
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+
+ dualshock4_schedule_work(ds4);
+
+ return 0;
+}
+
+static void dualshock4_init_output_report(struct dualshock4 *ds4,
+ struct dualshock4_output_report *rp, void *buf)
+{
+ struct hid_device *hdev = ds4->base.hdev;
+
+ if (hdev->bus == BUS_BLUETOOTH) {
+ struct dualshock4_output_report_bt *bt = buf;
+
+ memset(bt, 0, sizeof(*bt));
+ bt->report_id = DS4_OUTPUT_REPORT_BT;
+
+ rp->data = buf;
+ rp->len = sizeof(*bt);
+ rp->bt = bt;
+ rp->usb = NULL;
+ rp->common = &bt->common;
+ } else { /* USB */
+ struct dualshock4_output_report_usb *usb = buf;
+
+ memset(usb, 0, sizeof(*usb));
+ usb->report_id = DS4_OUTPUT_REPORT_USB;
+
+ rp->data = buf;
+ rp->len = sizeof(*usb);
+ rp->bt = NULL;
+ rp->usb = usb;
+ rp->common = &usb->common;
+ }
+}
+
+static void dualshock4_output_worker(struct work_struct *work)
+{
+ struct dualshock4 *ds4 = container_of(work, struct dualshock4, output_worker);
+ struct dualshock4_output_report report;
+ struct dualshock4_output_report_common *common;
+ unsigned long flags;
+
+ dualshock4_init_output_report(ds4, &report, ds4->output_report_dmabuf);
+ common = report.common;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+
+ if (ds4->update_rumble) {
+ /* Select classic rumble style haptics and enable it. */
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_MOTOR;
+ common->motor_left = ds4->motor_left;
+ common->motor_right = ds4->motor_right;
+ ds4->update_rumble = false;
+ }
+
+ if (ds4->update_lightbar) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED;
+ /* Comptabile behavior with hid-sony, which used a dummy global LED to
+ * allow enabling/disabling the lightbar. The global LED maps to
+ * lightbar_enabled.
+ */
+ common->lightbar_red = ds4->lightbar_enabled ? ds4->lightbar_red : 0;
+ common->lightbar_green = ds4->lightbar_enabled ? ds4->lightbar_green : 0;
+ common->lightbar_blue = ds4->lightbar_enabled ? ds4->lightbar_blue : 0;
+ ds4->update_lightbar = false;
+ }
+
+ if (ds4->update_lightbar_blink) {
+ common->valid_flag0 |= DS4_OUTPUT_VALID_FLAG0_LED_BLINK;
+ common->lightbar_blink_on = ds4->lightbar_blink_on;
+ common->lightbar_blink_off = ds4->lightbar_blink_off;
+ ds4->update_lightbar_blink = false;
+ }
+
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+
+ /* Bluetooth packets need additional flags as well as a CRC in the last 4 bytes. */
+ if (report.bt) {
+ uint32_t crc;
+ uint8_t seed = PS_OUTPUT_CRC32_SEED;
+
+ /* Hardware control flags need to set to let the device know
+ * there is HID data as well as CRC.
+ */
+ report.bt->hw_control = DS4_OUTPUT_HWCTL_HID | DS4_OUTPUT_HWCTL_CRC32;
+
+ if (ds4->update_bt_poll_interval) {
+ report.bt->hw_control |= ds4->bt_poll_interval;
+ ds4->update_bt_poll_interval = false;
+ }
+
+ crc = crc32_le(0xFFFFFFFF, &seed, 1);
+ crc = ~crc32_le(crc, report.data, report.len - 4);
+
+ report.bt->crc32 = cpu_to_le32(crc);
+ }
+
+ hid_hw_output_report(ds4->base.hdev, report.data, report.len);
+}
+
+static int dualshock4_parse_report(struct ps_device *ps_dev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct hid_device *hdev = ps_dev->hdev;
+ struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
+ struct dualshock4_input_report_common *ds4_report;
+ struct dualshock4_touch_report *touch_reports;
+ uint8_t battery_capacity, num_touch_reports, value;
+ int battery_status, i, j;
+ uint16_t sensor_timestamp;
+ unsigned long flags;
+
+ /*
+ * DualShock4 in USB uses the full HID report for reportID 1, but
+ * Bluetooth uses a minimal HID report for reportID 1 and reports
+ * the full report using reportID 17.
+ */
+ if (hdev->bus == BUS_USB && report->id == DS4_INPUT_REPORT_USB &&
+ size == DS4_INPUT_REPORT_USB_SIZE) {
+ struct dualshock4_input_report_usb *usb = (struct dualshock4_input_report_usb *)data;
+
+ ds4_report = &usb->common;
+ num_touch_reports = usb->num_touch_reports;
+ touch_reports = usb->touch_reports;
+ } else if (hdev->bus == BUS_BLUETOOTH && report->id == DS4_INPUT_REPORT_BT &&
+ size == DS4_INPUT_REPORT_BT_SIZE) {
+ struct dualshock4_input_report_bt *bt = (struct dualshock4_input_report_bt *)data;
+ uint32_t report_crc = get_unaligned_le32(&bt->crc32);
+
+ /* Last 4 bytes of input report contains CRC. */
+ if (!ps_check_crc32(PS_INPUT_CRC32_SEED, data, size - 4, report_crc)) {
+ hid_err(hdev, "DualShock4 input CRC's check failed\n");
+ return -EILSEQ;
+ }
+
+ ds4_report = &bt->common;
+ num_touch_reports = bt->num_touch_reports;
+ touch_reports = bt->touch_reports;
+ } else {
+ hid_err(hdev, "Unhandled reportID=%d\n", report->id);
+ return -1;
+ }
+
+ input_report_abs(ds4->gamepad, ABS_X, ds4_report->x);
+ input_report_abs(ds4->gamepad, ABS_Y, ds4_report->y);
+ input_report_abs(ds4->gamepad, ABS_RX, ds4_report->rx);
+ input_report_abs(ds4->gamepad, ABS_RY, ds4_report->ry);
+ input_report_abs(ds4->gamepad, ABS_Z, ds4_report->z);
+ input_report_abs(ds4->gamepad, ABS_RZ, ds4_report->rz);
+
+ value = ds4_report->buttons[0] & DS_BUTTONS0_HAT_SWITCH;
+ if (value >= ARRAY_SIZE(ps_gamepad_hat_mapping))
+ value = 8; /* center */
+ input_report_abs(ds4->gamepad, ABS_HAT0X, ps_gamepad_hat_mapping[value].x);
+ input_report_abs(ds4->gamepad, ABS_HAT0Y, ps_gamepad_hat_mapping[value].y);
+
+ input_report_key(ds4->gamepad, BTN_WEST, ds4_report->buttons[0] & DS_BUTTONS0_SQUARE);
+ input_report_key(ds4->gamepad, BTN_SOUTH, ds4_report->buttons[0] & DS_BUTTONS0_CROSS);
+ input_report_key(ds4->gamepad, BTN_EAST, ds4_report->buttons[0] & DS_BUTTONS0_CIRCLE);
+ input_report_key(ds4->gamepad, BTN_NORTH, ds4_report->buttons[0] & DS_BUTTONS0_TRIANGLE);
+ input_report_key(ds4->gamepad, BTN_TL, ds4_report->buttons[1] & DS_BUTTONS1_L1);
+ input_report_key(ds4->gamepad, BTN_TR, ds4_report->buttons[1] & DS_BUTTONS1_R1);
+ input_report_key(ds4->gamepad, BTN_TL2, ds4_report->buttons[1] & DS_BUTTONS1_L2);
+ input_report_key(ds4->gamepad, BTN_TR2, ds4_report->buttons[1] & DS_BUTTONS1_R2);
+ input_report_key(ds4->gamepad, BTN_SELECT, ds4_report->buttons[1] & DS_BUTTONS1_CREATE);
+ input_report_key(ds4->gamepad, BTN_START, ds4_report->buttons[1] & DS_BUTTONS1_OPTIONS);
+ input_report_key(ds4->gamepad, BTN_THUMBL, ds4_report->buttons[1] & DS_BUTTONS1_L3);
+ input_report_key(ds4->gamepad, BTN_THUMBR, ds4_report->buttons[1] & DS_BUTTONS1_R3);
+ input_report_key(ds4->gamepad, BTN_MODE, ds4_report->buttons[2] & DS_BUTTONS2_PS_HOME);
+ input_sync(ds4->gamepad);
+
+ /* Parse and calibrate gyroscope data. */
+ for (i = 0; i < ARRAY_SIZE(ds4_report->gyro); i++) {
+ int raw_data = (short)le16_to_cpu(ds4_report->gyro[i]);
+ int calib_data = mult_frac(ds4->gyro_calib_data[i].sens_numer,
+ raw_data, ds4->gyro_calib_data[i].sens_denom);
+
+ input_report_abs(ds4->sensors, ds4->gyro_calib_data[i].abs_code, calib_data);
+ }
+
+ /* Parse and calibrate accelerometer data. */
+ for (i = 0; i < ARRAY_SIZE(ds4_report->accel); i++) {
+ int raw_data = (short)le16_to_cpu(ds4_report->accel[i]);
+ int calib_data = mult_frac(ds4->accel_calib_data[i].sens_numer,
+ raw_data - ds4->accel_calib_data[i].bias,
+ ds4->accel_calib_data[i].sens_denom);
+
+ input_report_abs(ds4->sensors, ds4->accel_calib_data[i].abs_code, calib_data);
+ }
+
+ /* Convert timestamp (in 5.33us unit) to timestamp_us */
+ sensor_timestamp = le16_to_cpu(ds4_report->sensor_timestamp);
+ if (!ds4->sensor_timestamp_initialized) {
+ ds4->sensor_timestamp_us = DIV_ROUND_CLOSEST(sensor_timestamp*16, 3);
+ ds4->sensor_timestamp_initialized = true;
+ } else {
+ uint16_t delta;
+
+ if (ds4->prev_sensor_timestamp > sensor_timestamp)
+ delta = (U16_MAX - ds4->prev_sensor_timestamp + sensor_timestamp + 1);
+ else
+ delta = sensor_timestamp - ds4->prev_sensor_timestamp;
+ ds4->sensor_timestamp_us += DIV_ROUND_CLOSEST(delta*16, 3);
+ }
+ ds4->prev_sensor_timestamp = sensor_timestamp;
+ input_event(ds4->sensors, EV_MSC, MSC_TIMESTAMP, ds4->sensor_timestamp_us);
+ input_sync(ds4->sensors);
+
+ for (i = 0; i < num_touch_reports; i++) {
+ struct dualshock4_touch_report *touch_report = &touch_reports[i];
+
+ for (j = 0; j < ARRAY_SIZE(touch_report->points); j++) {
+ struct dualshock4_touch_point *point = &touch_report->points[j];
+ bool active = (point->contact & DS4_TOUCH_POINT_INACTIVE) ? false : true;
+
+ input_mt_slot(ds4->touchpad, j);
+ input_mt_report_slot_state(ds4->touchpad, MT_TOOL_FINGER, active);
+
+ if (active) {
+ int x = (point->x_hi << 8) | point->x_lo;
+ int y = (point->y_hi << 4) | point->y_lo;
+
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_X, x);
+ input_report_abs(ds4->touchpad, ABS_MT_POSITION_Y, y);
+ }
+ }
+ input_mt_sync_frame(ds4->touchpad);
+ input_sync(ds4->touchpad);
+ }
+ input_report_key(ds4->touchpad, BTN_LEFT, ds4_report->buttons[2] & DS_BUTTONS2_TOUCHPAD);
+
+ /*
+ * Interpretation of the battery_capacity data depends on the cable state.
+ * When no cable is connected (bit4 is 0):
+ * - 0:10: percentage in units of 10%.
+ * When a cable is plugged in:
+ * - 0-10: percentage in units of 10%.
+ * - 11: battery is full
+ * - 14: not charging due to Voltage or temperature error
+ * - 15: charge error
+ */
+ if (ds4_report->status[0] & DS4_STATUS0_CABLE_STATE) {
+ uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+
+ if (battery_data < 10) {
+ /* Take the mid-point for each battery capacity value,
+ * because on the hardware side 0 = 0-9%, 1=10-19%, etc.
+ * This matches official platform behavior, which does
+ * the same.
+ */
+ battery_capacity = battery_data * 10 + 5;
+ battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (battery_data == 10) {
+ battery_capacity = 100;
+ battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (battery_data == DS4_BATTERY_STATUS_FULL) {
+ battery_capacity = 100;
+ battery_status = POWER_SUPPLY_STATUS_FULL;
+ } else { /* 14, 15 and undefined values */
+ battery_capacity = 0;
+ battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+ } else {
+ uint8_t battery_data = ds4_report->status[0] & DS4_STATUS0_BATTERY_CAPACITY;
+
+ if (battery_data < 10)
+ battery_capacity = battery_data * 10 + 5;
+ else /* 10 */
+ battery_capacity = 100;
+
+ battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ spin_lock_irqsave(&ps_dev->lock, flags);
+ ps_dev->battery_capacity = battery_capacity;
+ ps_dev->battery_status = battery_status;
+ spin_unlock_irqrestore(&ps_dev->lock, flags);
+
+ return 0;
+}
+
+static int dualshock4_dongle_parse_report(struct ps_device *ps_dev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
+ bool connected = false;
+
+ /* The dongle reports data using the main USB report (0x1) no matter whether a controller
+ * is connected with mostly zeros. The report does contain dongle status, which we use to
+ * determine if a controller is connected and if so we forward to the regular DualShock4
+ * parsing code.
+ */
+ if (data[0] == DS4_INPUT_REPORT_USB && size == DS4_INPUT_REPORT_USB_SIZE) {
+ struct dualshock4_input_report_common *ds4_report = (struct dualshock4_input_report_common *)&data[1];
+ unsigned long flags;
+
+ connected = ds4_report->status[1] & DS4_STATUS1_DONGLE_STATE ? false : true;
+
+ if (ds4->dongle_state == DONGLE_DISCONNECTED && connected) {
+ hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller connected\n");
+
+ dualshock4_set_default_lightbar_colors(ds4);
+
+ spin_lock_irqsave(&ps_dev->lock, flags);
+ ds4->dongle_state = DONGLE_CALIBRATING;
+ spin_unlock_irqrestore(&ps_dev->lock, flags);
+
+ schedule_work(&ds4->dongle_hotplug_worker);
+
+ /* Don't process the report since we don't have
+ * calibration data, but let hidraw have it anyway.
+ */
+ return 0;
+ } else if ((ds4->dongle_state == DONGLE_CONNECTED ||
+ ds4->dongle_state == DONGLE_DISABLED) && !connected) {
+ hid_info(ps_dev->hdev, "DualShock 4 USB dongle: controller disconnected\n");
+
+ spin_lock_irqsave(&ps_dev->lock, flags);
+ ds4->dongle_state = DONGLE_DISCONNECTED;
+ spin_unlock_irqrestore(&ps_dev->lock, flags);
+
+ /* Return 0, so hidraw can get the report. */
+ return 0;
+ } else if (ds4->dongle_state == DONGLE_CALIBRATING ||
+ ds4->dongle_state == DONGLE_DISABLED ||
+ ds4->dongle_state == DONGLE_DISCONNECTED) {
+ /* Return 0, so hidraw can get the report. */
+ return 0;
+ }
+ }
+
+ if (connected)
+ return dualshock4_parse_report(ps_dev, report, data, size);
+
+ return 0;
+}
+
+static int dualshock4_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct hid_device *hdev = input_get_drvdata(dev);
+ struct dualshock4 *ds4 = hid_get_drvdata(hdev);
+ unsigned long flags;
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+ ds4->update_rumble = true;
+ ds4->motor_left = effect->u.rumble.strong_magnitude / 256;
+ ds4->motor_right = effect->u.rumble.weak_magnitude / 256;
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+
+ dualshock4_schedule_work(ds4);
+ return 0;
+}
+
+static void dualshock4_remove(struct ps_device *ps_dev)
+{
+ struct dualshock4 *ds4 = container_of(ps_dev, struct dualshock4, base);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+ ds4->output_worker_initialized = false;
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+
+ cancel_work_sync(&ds4->output_worker);
+
+ if (ps_dev->hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE)
+ cancel_work_sync(&ds4->dongle_hotplug_worker);
+}
+
+static inline void dualshock4_schedule_work(struct dualshock4 *ds4)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ds4->base.lock, flags);
+ if (ds4->output_worker_initialized)
+ schedule_work(&ds4->output_worker);
+ spin_unlock_irqrestore(&ds4->base.lock, flags);
+}
+
+static void dualshock4_set_bt_poll_interval(struct dualshock4 *ds4, uint8_t interval)
+{
+ ds4->bt_poll_interval = interval;
+ ds4->update_bt_poll_interval = true;
+ dualshock4_schedule_work(ds4);
+}
+
+/* Set default lightbar color based on player. */
+static void dualshock4_set_default_lightbar_colors(struct dualshock4 *ds4)
+{
+ /* Use same player colors as PlayStation 4.
+ * Array of colors is in RGB.
+ */
+ static const int player_colors[4][3] = {
+ { 0x00, 0x00, 0x40 }, /* Blue */
+ { 0x40, 0x00, 0x00 }, /* Red */
+ { 0x00, 0x40, 0x00 }, /* Green */
+ { 0x20, 0x00, 0x20 } /* Pink */
+ };
+
+ uint8_t player_id = ds4->base.player_id % ARRAY_SIZE(player_colors);
+
+ ds4->lightbar_enabled = true;
+ ds4->lightbar_red = player_colors[player_id][0];
+ ds4->lightbar_green = player_colors[player_id][1];
+ ds4->lightbar_blue = player_colors[player_id][2];
+
+ ds4->update_lightbar = true;
+ dualshock4_schedule_work(ds4);
+}
+
+static struct ps_device *dualshock4_create(struct hid_device *hdev)
+{
+ struct dualshock4 *ds4;
+ struct ps_device *ps_dev;
+ uint8_t max_output_report_size;
+ int i, ret;
+
+ /* The DualShock4 has an RGB lightbar, which the original hid-sony driver
+ * exposed as a set of 4 LEDs for the 3 color channels and a global control.
+ * Ideally this should have used the multi-color LED class, which didn't exist
+ * yet. In addition the driver used a naming scheme not compliant with the LED
+ * naming spec by using "<mac_address>:<color>", which contained many colons.
+ * We use a more compliant by using "<device_name>:<color>" name now. Ideally
+ * would have been "<device_name>:<color>:indicator", but that would break
+ * existing applications (e.g. Android). Nothing matches against MAC address.
+ */
+ static const struct ps_led_info lightbar_leds_info[] = {
+ { NULL, "red", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
+ { NULL, "green", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
+ { NULL, "blue", 255, dualshock4_led_get_brightness, dualshock4_led_set_brightness },
+ { NULL, "global", 1, dualshock4_led_get_brightness, dualshock4_led_set_brightness,
+ dualshock4_led_set_blink },
+ };
+
+ ds4 = devm_kzalloc(&hdev->dev, sizeof(*ds4), GFP_KERNEL);
+ if (!ds4)
+ return ERR_PTR(-ENOMEM);
+
+ /*
+ * Patch version to allow userspace to distinguish between
+ * hid-generic vs hid-playstation axis and button mapping.
+ */
+ hdev->version |= HID_PLAYSTATION_VERSION_PATCH;
+
+ ps_dev = &ds4->base;
+ ps_dev->hdev = hdev;
+ spin_lock_init(&ps_dev->lock);
+ ps_dev->battery_capacity = 100; /* initial value until parse_report. */
+ ps_dev->battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ ps_dev->parse_report = dualshock4_parse_report;
+ ps_dev->remove = dualshock4_remove;
+ INIT_WORK(&ds4->output_worker, dualshock4_output_worker);
+ ds4->output_worker_initialized = true;
+ hid_set_drvdata(hdev, ds4);
+
+ max_output_report_size = sizeof(struct dualshock4_output_report_bt);
+ ds4->output_report_dmabuf = devm_kzalloc(&hdev->dev, max_output_report_size, GFP_KERNEL);
+ if (!ds4->output_report_dmabuf)
+ return ERR_PTR(-ENOMEM);
+
+ if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
+ ds4->dongle_state = DONGLE_DISCONNECTED;
+ INIT_WORK(&ds4->dongle_hotplug_worker, dualshock4_dongle_calibration_work);
+
+ /* Override parse report for dongle specific hotplug handling. */
+ ps_dev->parse_report = dualshock4_dongle_parse_report;
+ }
+
+ ret = dualshock4_get_mac_address(ds4);
+ if (ret) {
+ hid_err(hdev, "Failed to get MAC address from DualShock4\n");
+ return ERR_PTR(ret);
+ }
+ snprintf(hdev->uniq, sizeof(hdev->uniq), "%pMR", ds4->base.mac_address);
+
+ ret = dualshock4_get_firmware_info(ds4);
+ if (ret) {
+ hid_err(hdev, "Failed to get firmware info from DualShock4\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = ps_devices_list_add(ps_dev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = dualshock4_get_calibration_data(ds4);
+ if (ret) {
+ hid_err(hdev, "Failed to get calibration data from DualShock4\n");
+ goto err;
+ }
+
+ ds4->gamepad = ps_gamepad_create(hdev, dualshock4_play_effect);
+ if (IS_ERR(ds4->gamepad)) {
+ ret = PTR_ERR(ds4->gamepad);
+ goto err;
+ }
+
+ /* Use gamepad input device name as primary device name for e.g. LEDs */
+ ps_dev->input_dev_name = dev_name(&ds4->gamepad->dev);
+
+ ds4->sensors = ps_sensors_create(hdev, DS4_ACC_RANGE, DS4_ACC_RES_PER_G,
+ DS4_GYRO_RANGE, DS4_GYRO_RES_PER_DEG_S);
+ if (IS_ERR(ds4->sensors)) {
+ ret = PTR_ERR(ds4->sensors);
+ goto err;
+ }
+
+ ds4->touchpad = ps_touchpad_create(hdev, DS4_TOUCHPAD_WIDTH, DS4_TOUCHPAD_HEIGHT, 2);
+ if (IS_ERR(ds4->touchpad)) {
+ ret = PTR_ERR(ds4->touchpad);
+ goto err;
+ }
+
+ ret = ps_device_register_battery(ps_dev);
+ if (ret)
+ goto err;
+
+ for (i = 0; i < ARRAY_SIZE(lightbar_leds_info); i++) {
+ const struct ps_led_info *led_info = &lightbar_leds_info[i];
+
+ ret = ps_led_register(ps_dev, &ds4->lightbar_leds[i], led_info);
+ if (ret < 0)
+ goto err;
+ }
+
+ dualshock4_set_bt_poll_interval(ds4, DS4_BT_DEFAULT_POLL_INTERVAL_MS);
+
+ ret = ps_device_set_player_id(ps_dev);
+ if (ret) {
+ hid_err(hdev, "Failed to assign player id for DualShock4: %d\n", ret);
+ goto err;
+ }
+
+ dualshock4_set_default_lightbar_colors(ds4);
+
+ /*
+ * Reporting hardware and firmware is important as there are frequent updates, which
+ * can change behavior.
+ */
+ hid_info(hdev, "Registered DualShock4 controller hw_version=0x%08x fw_version=0x%08x\n",
+ ds4->base.hw_version, ds4->base.fw_version);
+ return &ds4->base;
+
+err:
+ ps_devices_list_remove(ps_dev);
+ return ERR_PTR(ret);
+}
+
static int ps_raw_event(struct hid_device *hdev, struct hid_report *report,
u8 *data, int size)
{
@@ -1499,7 +2655,16 @@ static int ps_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto err_stop;
}
- if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER ||
+ if (hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER ||
+ hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_2 ||
+ hdev->product == USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) {
+ dev = dualshock4_create(hdev);
+ if (IS_ERR(dev)) {
+ hid_err(hdev, "Failed to create dualshock4.\n");
+ ret = PTR_ERR(dev);
+ goto err_close;
+ }
+ } else if (hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER ||
hdev->product == USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) {
dev = dualsense_create(hdev);
if (IS_ERR(dev)) {
@@ -1533,6 +2698,13 @@ static void ps_remove(struct hid_device *hdev)
}
static const struct hid_device_id ps_devices[] = {
+ /* Sony DualShock 4 controllers for PS4 */
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE) },
+ /* Sony DualSense controllers for PS5 */
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS5_CONTROLLER_2) },
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 50e1c717fc0a..66e64350f138 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -122,6 +122,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C05A), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MOUSE_C06A), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_MCS, USB_DEVICE_ID_MCS_GAMEPADBLOCK), HID_QUIRK_MULTI_INPUT },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_MOUSE_0783), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_PIXART_MOUSE), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_POWER_COVER), HID_QUIRK_NO_INIT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_MS_SURFACE3_COVER), HID_QUIRK_NO_INIT_REPORTS },
@@ -146,6 +147,7 @@ static const struct hid_device_id hid_quirks[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_OPTICAL_TOUCH_SCREEN), HID_QUIRK_NO_INIT_REPORTS },
{ HID_USB_DEVICE(USB_VENDOR_ID_PIXART, USB_DEVICE_ID_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4D22), HID_QUIRK_ALWAYS_POLL },
+ { HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_MOUSE_4E2A), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D0F), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4D65), HID_QUIRK_ALWAYS_POLL },
{ HID_USB_DEVICE(USB_VENDOR_ID_PRIMAX, USB_DEVICE_ID_PRIMAX_PIXART_MOUSE_4E22), HID_QUIRK_ALWAYS_POLL },
@@ -391,7 +393,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1URBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_DT1DRBK) },
{ HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1URBK) },
- { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_010D) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_ELECOM, USB_DEVICE_ID_ELECOM_M_HT1DRBK_011C) },
#endif
#if IS_ENABLED(CONFIG_HID_ELO)
{ HID_USB_DEVICE(USB_VENDOR_ID_ELO, 0x0009) },
@@ -1234,7 +1237,7 @@ EXPORT_SYMBOL_GPL(hid_quirks_exit);
static unsigned long hid_gets_squirk(const struct hid_device *hdev)
{
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))
quirks |= HID_QUIRK_IGNORE;
diff --git a/drivers/hid/hid-rmi.c b/drivers/hid/hid-rmi.c
index bb1f423f4ace..84e7ba5314d3 100644
--- a/drivers/hid/hid-rmi.c
+++ b/drivers/hid/hid-rmi.c
@@ -326,6 +326,8 @@ static int rmi_input_event(struct hid_device *hdev, u8 *data, int size)
if (!(test_bit(RMI_STARTED, &hdata->flags)))
return 0;
+ pm_wakeup_event(hdev->dev.parent, 0);
+
local_irq_save(flags);
rmi_set_attn_data(rmi_dev, data[1], &data[2], size - 2);
diff --git a/drivers/hid/hid-sensor-custom.c b/drivers/hid/hid-sensor-custom.c
index 32c2306e240d..3e3f89e01d81 100644
--- a/drivers/hid/hid-sensor-custom.c
+++ b/drivers/hid/hid-sensor-custom.c
@@ -5,6 +5,7 @@
*/
#include <linux/ctype.h>
+#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@@ -62,7 +63,7 @@ struct hid_sensor_sample {
u32 raw_len;
} __packed;
-static struct attribute hid_custom_attrs[] = {
+static struct attribute hid_custom_attrs[HID_CUSTOM_TOTAL_ATTRS] = {
{.name = "name", .mode = S_IRUGO},
{.name = "units", .mode = S_IRUGO},
{.name = "unit-expo", .mode = S_IRUGO},
@@ -750,119 +751,214 @@ 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)
-{
- int i;
+/*
+ * Custom sensor properties used for matching.
+ */
+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++) {
- if (!strncmp(usage_str, known_sensor_luid[i],
- strlen(known_sensor_luid[i])))
- return i;
+static bool hid_sensor_custom_prop_match_str(const u16 *prop, const char *match,
+ size_t count)
+{
+ 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 sensor_luid_info = { 0 };
- int report_size;
+ struct hid_sensor_hub_attribute_info prop_attr = { 0 };
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(buf, 0, sizeof(buf));
+ memset(prop, 0, prop_size);
- /* get manufacturer info */
- ret = sensor_hub_input_get_attribute_info(hsdev,
- HID_FEATURE_REPORT, hsdev->usage,
- HID_USAGE_SENSOR_PROP_MANUFACTURER, &sensor_manufacturer);
+ ret = sensor_hub_input_get_attribute_info(hsdev, HID_FEATURE_REPORT,
+ hsdev->usage, prop_usage_id,
+ &prop_attr);
if (ret < 0)
return ret;
- report_size =
- sensor_hub_get_feature(hsdev, sensor_manufacturer.report_id,
- sensor_manufacturer.index, sizeof(w_buf),
- w_buf);
- if (report_size <= 0) {
- hid_err(hsdev->hdev,
- "Failed to get sensor manufacturer info %d\n",
- report_size);
- return -ENODEV;
+ ret = sensor_hub_get_feature(hsdev, prop_attr.report_id,
+ prop_attr.index, prop_size, prop);
+ if (ret < 0) {
+ hid_err(hsdev->hdev, "Failed to get sensor property %08x %d\n",
+ prop_usage_id, ret);
+ return ret;
}
- /* convert from wide char to char */
- for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++)
- buf[i] = (char)w_buf[i];
+ return 0;
+}
- /* ensure it's ISH sensor */
- if (strncmp(buf, "INTEL", strlen("INTEL")))
- return -ENODEV;
+static bool
+hid_sensor_custom_do_match(struct hid_sensor_hub_device *hsdev,
+ 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));
- memset(buf, 0, sizeof(buf));
+ if (!hid_sensor_custom_prop_match_str(prop->serial_num, "LUID:", 5) ||
+ !hid_sensor_custom_prop_match_str(prop->serial_num + 5, match->luid,
+ HID_CUSTOM_MAX_FEATURE_BYTES - 5))
+ return false;
- /* get real usage id */
- ret = sensor_hub_input_get_attribute_info(hsdev,
- HID_FEATURE_REPORT, hsdev->usage,
- HID_USAGE_SENSOR_PROP_SERIAL_NUM, &sensor_luid_info);
+ if (match->model &&
+ !hid_sensor_custom_prop_match_str(prop->model, match->model,
+ HID_CUSTOM_MAX_FEATURE_BYTES))
+ 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)
return ret;
- report_size = sensor_hub_get_feature(hsdev, sensor_luid_info.report_id,
- sensor_luid_info.index, sizeof(w_buf),
- w_buf);
- if (report_size <= 0) {
- hid_err(hsdev->hdev, "Failed to get real usage info %d\n",
- report_size);
- return -ENODEV;
- }
+ /*
+ * Ignore errors on the following model and manufacturer properties.
+ * Because these are optional, it is not an error if they are missing.
+ */
- /* convert from wide char to char */
- for (i = 0; i < ARRAY_SIZE(buf) - 1 && w_buf[i]; i++)
- buf[i] = (char)w_buf[i];
+ hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MODEL,
+ HID_CUSTOM_MAX_FEATURE_BYTES,
+ prop->model);
- if (strlen(buf) != strlen(known_sensor_luid[0]) + 5) {
- hid_err(hsdev->hdev,
- "%s luid length not match %zu != (%zu + 5)\n", __func__,
- strlen(buf), strlen(known_sensor_luid[0]));
- return -ENODEV;
- }
+ hid_sensor_custom_get_prop(hsdev, HID_USAGE_SENSOR_PROP_MANUFACTURER,
+ HID_CUSTOM_MAX_FEATURE_BYTES,
+ prop->manufacturer);
- /* get table index with luid (not matching 'LUID: ' in luid) */
- return get_luid_table_index(&buf[5]);
+ return 0;
+}
+
+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 *
hid_sensor_register_platform_device(struct platform_device *pdev,
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;
const char *dev_name;
char *c;
- /* copy real usage id */
- memcpy(real_usage, known_sensor_luid[index], 4);
+ memcpy(real_usage, match->luid, 4);
/* usage id are all lowcase */
for (c = real_usage; *c != '\0'; c++)
*c = tolower(*c);
- /* HID-SENSOR-INT-REAL_USAGE_ID */
- dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-INT-%s", real_usage);
+ /* HID-SENSOR-TAG-REAL_USAGE_ID */
+ dev_name = kasprintf(GFP_KERNEL, "HID-SENSOR-%s-%s",
+ match->tag, real_usage);
if (!dev_name)
return ERR_PTR(-ENOMEM);
custom_pdev = platform_device_register_data(pdev->dev.parent, dev_name,
- PLATFORM_DEVID_NONE, hsdev,
+ PLATFORM_DEVID_AUTO, hsdev,
sizeof(*hsdev));
kfree(dev_name);
return custom_pdev;
@@ -873,7 +969,7 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
struct hid_sensor_custom *sensor_inst;
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
int ret;
- int index;
+ const struct hid_sensor_custom_match *match;
sensor_inst = devm_kzalloc(&pdev->dev, sizeof(*sensor_inst),
GFP_KERNEL);
@@ -888,10 +984,10 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
mutex_init(&sensor_inst->mutex);
platform_set_drvdata(pdev, sensor_inst);
- index = get_known_custom_sensor_index(hsdev);
- if (index >= 0 && index < ARRAY_SIZE(known_sensor_luid)) {
+ ret = hid_sensor_custom_get_known(hsdev, &match);
+ if (!ret) {
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);
if (ret) {
diff --git a/drivers/hid/hid-sensor-hub.c b/drivers/hid/hid-sensor-hub.c
index 6abd3e2a9094..83237b86c8ff 100644
--- a/drivers/hid/hid-sensor-hub.c
+++ b/drivers/hid/hid-sensor-hub.c
@@ -397,7 +397,8 @@ int sensor_hub_input_get_attribute_info(struct hid_sensor_hub_device *hsdev,
for (i = 0; i < report->maxfield; ++i) {
field = report->field[i];
if (field->maxusage) {
- if (field->physical == usage_id &&
+ if ((field->physical == usage_id ||
+ field->application == usage_id) &&
(field->logical == attr_usage_id ||
field->usage[0].hid ==
attr_usage_id) &&
@@ -506,7 +507,8 @@ static int sensor_hub_raw_event(struct hid_device *hdev,
collection->usage);
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,
&hsdev, &priv);
if (!callback) {
diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c
index 03691cdcfb8e..dd942061fd77 100644
--- a/drivers/hid/hid-sony.c
+++ b/drivers/hid/hid-sony.c
@@ -49,38 +49,28 @@
#define SIXAXIS_CONTROLLER_BT BIT(2)
#define BUZZ_CONTROLLER BIT(3)
#define PS3REMOTE BIT(4)
-#define DUALSHOCK4_CONTROLLER_USB BIT(5)
-#define DUALSHOCK4_CONTROLLER_BT BIT(6)
-#define DUALSHOCK4_DONGLE BIT(7)
-#define MOTION_CONTROLLER_USB BIT(8)
-#define MOTION_CONTROLLER_BT BIT(9)
-#define NAVIGATION_CONTROLLER_USB BIT(10)
-#define NAVIGATION_CONTROLLER_BT BIT(11)
-#define SINO_LITE_CONTROLLER BIT(12)
-#define FUTUREMAX_DANCE_MAT BIT(13)
-#define NSG_MR5U_REMOTE_BT BIT(14)
-#define NSG_MR7U_REMOTE_BT BIT(15)
-#define SHANWAN_GAMEPAD BIT(16)
-#define GH_GUITAR_CONTROLLER BIT(17)
-#define GHL_GUITAR_PS3WIIU BIT(18)
-#define GHL_GUITAR_PS4 BIT(19)
+#define MOTION_CONTROLLER_USB BIT(5)
+#define MOTION_CONTROLLER_BT BIT(6)
+#define NAVIGATION_CONTROLLER_USB BIT(7)
+#define NAVIGATION_CONTROLLER_BT BIT(8)
+#define SINO_LITE_CONTROLLER BIT(9)
+#define FUTUREMAX_DANCE_MAT BIT(10)
+#define NSG_MR5U_REMOTE_BT BIT(11)
+#define NSG_MR7U_REMOTE_BT BIT(12)
+#define SHANWAN_GAMEPAD BIT(13)
+#define GH_GUITAR_CONTROLLER BIT(14)
+#define GHL_GUITAR_PS3WIIU BIT(15)
+#define GHL_GUITAR_PS4 BIT(16)
#define SIXAXIS_CONTROLLER (SIXAXIS_CONTROLLER_USB | SIXAXIS_CONTROLLER_BT)
#define MOTION_CONTROLLER (MOTION_CONTROLLER_USB | MOTION_CONTROLLER_BT)
#define NAVIGATION_CONTROLLER (NAVIGATION_CONTROLLER_USB |\
NAVIGATION_CONTROLLER_BT)
-#define DUALSHOCK4_CONTROLLER (DUALSHOCK4_CONTROLLER_USB |\
- DUALSHOCK4_CONTROLLER_BT | \
- DUALSHOCK4_DONGLE)
#define SONY_LED_SUPPORT (SIXAXIS_CONTROLLER | BUZZ_CONTROLLER |\
- DUALSHOCK4_CONTROLLER | MOTION_CONTROLLER |\
- NAVIGATION_CONTROLLER)
-#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER |\
- MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER)
-#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER |\
- MOTION_CONTROLLER)
-#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | DUALSHOCK4_CONTROLLER_BT |\
- MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
+ MOTION_CONTROLLER | NAVIGATION_CONTROLLER)
+#define SONY_BATTERY_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER)
+#define SONY_FF_SUPPORT (SIXAXIS_CONTROLLER | MOTION_CONTROLLER)
+#define SONY_BT_DEVICE (SIXAXIS_CONTROLLER_BT | MOTION_CONTROLLER_BT | NAVIGATION_CONTROLLER_BT)
#define NSG_MRXU_REMOTE (NSG_MR5U_REMOTE_BT | NSG_MR7U_REMOTE_BT)
#define MAX_LEDS 4
@@ -428,36 +418,6 @@ static const unsigned int sixaxis_keymap[] = {
[0x11] = BTN_MODE, /* PS */
};
-static const unsigned int ds4_absmap[] = {
- [0x30] = ABS_X,
- [0x31] = ABS_Y,
- [0x32] = ABS_RX, /* right stick X */
- [0x33] = ABS_Z, /* L2 */
- [0x34] = ABS_RZ, /* R2 */
- [0x35] = ABS_RY, /* right stick Y */
-};
-
-static const unsigned int ds4_keymap[] = {
- [0x1] = BTN_WEST, /* Square */
- [0x2] = BTN_SOUTH, /* Cross */
- [0x3] = BTN_EAST, /* Circle */
- [0x4] = BTN_NORTH, /* Triangle */
- [0x5] = BTN_TL, /* L1 */
- [0x6] = BTN_TR, /* R1 */
- [0x7] = BTN_TL2, /* L2 */
- [0x8] = BTN_TR2, /* R2 */
- [0x9] = BTN_SELECT, /* Share */
- [0xa] = BTN_START, /* Options */
- [0xb] = BTN_THUMBL, /* L3 */
- [0xc] = BTN_THUMBR, /* R3 */
- [0xd] = BTN_MODE, /* PS */
-};
-
-static const struct {int x; int y; } ds4_hat_mapping[] = {
- {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1},
- {0, 0}
-};
-
static enum power_supply_property sony_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY,
@@ -502,35 +462,12 @@ struct motion_output_report_02 {
u8 rumble;
};
-#define DS4_FEATURE_REPORT_0x02_SIZE 37
-#define DS4_FEATURE_REPORT_0x05_SIZE 41
-#define DS4_FEATURE_REPORT_0x81_SIZE 7
-#define DS4_FEATURE_REPORT_0xA3_SIZE 49
-#define DS4_INPUT_REPORT_0x11_SIZE 78
-#define DS4_OUTPUT_REPORT_0x05_SIZE 32
-#define DS4_OUTPUT_REPORT_0x11_SIZE 78
#define SIXAXIS_REPORT_0xF2_SIZE 17
#define SIXAXIS_REPORT_0xF5_SIZE 8
#define MOTION_REPORT_0x02_SIZE 49
-/* Offsets relative to USB input report (0x1). Bluetooth (0x11) requires an
- * additional +2.
- */
-#define DS4_INPUT_REPORT_AXIS_OFFSET 1
-#define DS4_INPUT_REPORT_BUTTON_OFFSET 5
-#define DS4_INPUT_REPORT_TIMESTAMP_OFFSET 10
-#define DS4_INPUT_REPORT_GYRO_X_OFFSET 13
-#define DS4_INPUT_REPORT_BATTERY_OFFSET 30
-#define DS4_INPUT_REPORT_TOUCHPAD_OFFSET 33
-
#define SENSOR_SUFFIX " Motion Sensors"
-#define DS4_TOUCHPAD_SUFFIX " Touchpad"
-
-/* Default to 4ms poll interval, which is same as USB (not adjustable). */
-#define DS4_BT_DEFAULT_POLL_INTERVAL_MS 4
-#define DS4_BT_MAX_POLL_INTERVAL_MS 62
-#define DS4_GYRO_RES_PER_DEG_S 1024
-#define DS4_ACC_RES_PER_G 8192
+#define TOUCHPAD_SUFFIX " Touchpad"
#define SIXAXIS_INPUT_REPORT_ACC_X_OFFSET 41
#define SIXAXIS_ACC_RES_PER_G 113
@@ -539,28 +476,8 @@ static DEFINE_SPINLOCK(sony_dev_list_lock);
static LIST_HEAD(sony_device_list);
static DEFINE_IDA(sony_device_id_allocator);
-/* Used for calibration of DS4 accelerometer and gyro. */
-struct ds4_calibration_data {
- int abs_code;
- short bias;
- /* Calibration requires scaling against a sensitivity value, which is a
- * float. Store sensitivity as a fraction to limit floating point
- * calculations until final calibration.
- */
- int sens_numer;
- int sens_denom;
-};
-
-enum ds4_dongle_state {
- DONGLE_DISCONNECTED,
- DONGLE_CALIBRATING,
- DONGLE_CONNECTED,
- DONGLE_DISABLED
-};
-
enum sony_worker {
- SONY_WORKER_STATE,
- SONY_WORKER_HOTPLUG
+ SONY_WORKER_STATE
};
struct sony_sc {
@@ -571,16 +488,11 @@ struct sony_sc {
struct input_dev *sensor_dev;
struct led_classdev *leds[MAX_LEDS];
unsigned long quirks;
- struct work_struct hotplug_worker;
struct work_struct state_worker;
void (*send_output_report)(struct sony_sc *);
struct power_supply *battery;
struct power_supply_desc battery_desc;
int device_id;
- unsigned fw_version;
- bool fw_version_created;
- unsigned hw_version;
- bool hw_version_created;
u8 *output_report_dmabuf;
#ifdef CONFIG_SONY_FF
@@ -589,7 +501,6 @@ struct sony_sc {
#endif
u8 mac_address[6];
- u8 hotplug_worker_initialized;
u8 state_worker_initialized;
u8 defer_initialization;
u8 battery_capacity;
@@ -599,14 +510,6 @@ struct sony_sc {
u8 led_delay_off[MAX_LEDS];
u8 led_count;
- bool timestamp_initialized;
- u16 prev_timestamp;
- unsigned int timestamp_us;
-
- u8 ds4_bt_poll_interval;
- enum ds4_dongle_state ds4_dongle_state;
- /* DS4 calibration data */
- struct ds4_calibration_data ds4_calib_data[6];
/* GH Live */
struct urb *ghl_urb;
struct timer_list ghl_poke_timer;
@@ -626,10 +529,6 @@ static inline void sony_schedule_work(struct sony_sc *sc,
schedule_work(&sc->state_worker);
spin_unlock_irqrestore(&sc->lock, flags);
break;
- case SONY_WORKER_HOTPLUG:
- if (sc->hotplug_worker_initialized)
- schedule_work(&sc->hotplug_worker);
- break;
}
}
@@ -700,67 +599,6 @@ static int guitar_mapping(struct hid_device *hdev, struct hid_input *hi,
return 0;
}
-static ssize_t ds4_show_poll_interval(struct device *dev,
- struct device_attribute
- *attr, char *buf)
-{
- struct hid_device *hdev = to_hid_device(dev);
- struct sony_sc *sc = hid_get_drvdata(hdev);
-
- return snprintf(buf, PAGE_SIZE, "%i\n", sc->ds4_bt_poll_interval);
-}
-
-static ssize_t ds4_store_poll_interval(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t count)
-{
- struct hid_device *hdev = to_hid_device(dev);
- struct sony_sc *sc = hid_get_drvdata(hdev);
- unsigned long flags;
- u8 interval;
-
- if (kstrtou8(buf, 0, &interval))
- return -EINVAL;
-
- if (interval > DS4_BT_MAX_POLL_INTERVAL_MS)
- return -EINVAL;
-
- spin_lock_irqsave(&sc->lock, flags);
- sc->ds4_bt_poll_interval = interval;
- spin_unlock_irqrestore(&sc->lock, flags);
-
- sony_schedule_work(sc, SONY_WORKER_STATE);
-
- return count;
-}
-
-static DEVICE_ATTR(bt_poll_interval, 0644, ds4_show_poll_interval,
- ds4_store_poll_interval);
-
-static ssize_t sony_show_firmware_version(struct device *dev,
- struct device_attribute
- *attr, char *buf)
-{
- struct hid_device *hdev = to_hid_device(dev);
- struct sony_sc *sc = hid_get_drvdata(hdev);
-
- return snprintf(buf, PAGE_SIZE, "0x%04x\n", sc->fw_version);
-}
-
-static DEVICE_ATTR(firmware_version, 0444, sony_show_firmware_version, NULL);
-
-static ssize_t sony_show_hardware_version(struct device *dev,
- struct device_attribute
- *attr, char *buf)
-{
- struct hid_device *hdev = to_hid_device(dev);
- struct sony_sc *sc = hid_get_drvdata(hdev);
-
- return snprintf(buf, PAGE_SIZE, "0x%04x\n", sc->hw_version);
-}
-
-static DEVICE_ATTR(hardware_version, 0444, sony_show_hardware_version, NULL);
-
static u8 *motion_fixup(struct hid_device *hdev, u8 *rdesc,
unsigned int *rsize)
{
@@ -905,37 +743,6 @@ static int sixaxis_mapping(struct hid_device *hdev, struct hid_input *hi,
return -1;
}
-static int ds4_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_BUTTON) {
- unsigned int key = usage->hid & HID_USAGE;
-
- if (key >= ARRAY_SIZE(ds4_keymap))
- return -1;
-
- key = ds4_keymap[key];
- hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
- return 1;
- } else if ((usage->hid & HID_USAGE_PAGE) == HID_UP_GENDESK) {
- unsigned int abs = usage->hid & HID_USAGE;
-
- /* Let the HID parser deal with the HAT. */
- if (usage->hid == HID_GD_HATSWITCH)
- return 0;
-
- if (abs >= ARRAY_SIZE(ds4_absmap))
- return -1;
-
- abs = ds4_absmap[abs];
- hid_map_usage_clear(hi, usage, bit, max, EV_ABS, abs);
- return 1;
- }
-
- return 0;
-}
-
static u8 *sony_report_fixup(struct hid_device *hdev, u8 *rdesc,
unsigned int *rsize)
{
@@ -1034,216 +841,6 @@ static void sixaxis_parse_report(struct sony_sc *sc, u8 *rd, int size)
}
}
-static void dualshock4_parse_report(struct sony_sc *sc, u8 *rd, int size)
-{
- struct hid_input *hidinput = list_entry(sc->hdev->inputs.next,
- struct hid_input, list);
- struct input_dev *input_dev = hidinput->input;
- unsigned long flags;
- int n, m, offset, num_touch_data, max_touch_data;
- u8 cable_state, battery_capacity;
- int battery_status;
- u16 timestamp;
-
- /* When using Bluetooth the header is 2 bytes longer, so skip these. */
- int data_offset = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 2 : 0;
-
- /* Second bit of third button byte is for the touchpad button. */
- offset = data_offset + DS4_INPUT_REPORT_BUTTON_OFFSET;
- input_report_key(sc->touchpad, BTN_LEFT, rd[offset+2] & 0x2);
-
- /*
- * The default behavior of the Dualshock 4 is to send reports using
- * report type 1 when running over Bluetooth. However, when feature
- * report 2 is requested during the controller initialization it starts
- * sending input reports in report 17. Since report 17 is undefined
- * in the default HID descriptor, the HID layer won't generate events.
- * While it is possible (and this was done before) to fixup the HID
- * descriptor to add this mapping, it was better to do this manually.
- * The reason is there were various pieces software both open and closed
- * source, relying on the descriptors to be the same across various
- * operating systems. If the descriptors wouldn't match some
- * applications e.g. games on Wine would not be able to function due
- * to different descriptors, which such applications are not parsing.
- */
- if (rd[0] == 17) {
- int value;
-
- offset = data_offset + DS4_INPUT_REPORT_AXIS_OFFSET;
- input_report_abs(input_dev, ABS_X, rd[offset]);
- input_report_abs(input_dev, ABS_Y, rd[offset+1]);
- input_report_abs(input_dev, ABS_RX, rd[offset+2]);
- input_report_abs(input_dev, ABS_RY, rd[offset+3]);
-
- value = rd[offset+4] & 0xf;
- if (value > 7)
- value = 8; /* Center 0, 0 */
- input_report_abs(input_dev, ABS_HAT0X, ds4_hat_mapping[value].x);
- input_report_abs(input_dev, ABS_HAT0Y, ds4_hat_mapping[value].y);
-
- input_report_key(input_dev, BTN_WEST, rd[offset+4] & 0x10);
- input_report_key(input_dev, BTN_SOUTH, rd[offset+4] & 0x20);
- input_report_key(input_dev, BTN_EAST, rd[offset+4] & 0x40);
- input_report_key(input_dev, BTN_NORTH, rd[offset+4] & 0x80);
-
- input_report_key(input_dev, BTN_TL, rd[offset+5] & 0x1);
- input_report_key(input_dev, BTN_TR, rd[offset+5] & 0x2);
- input_report_key(input_dev, BTN_TL2, rd[offset+5] & 0x4);
- input_report_key(input_dev, BTN_TR2, rd[offset+5] & 0x8);
- input_report_key(input_dev, BTN_SELECT, rd[offset+5] & 0x10);
- input_report_key(input_dev, BTN_START, rd[offset+5] & 0x20);
- input_report_key(input_dev, BTN_THUMBL, rd[offset+5] & 0x40);
- input_report_key(input_dev, BTN_THUMBR, rd[offset+5] & 0x80);
-
- input_report_key(input_dev, BTN_MODE, rd[offset+6] & 0x1);
-
- input_report_abs(input_dev, ABS_Z, rd[offset+7]);
- input_report_abs(input_dev, ABS_RZ, rd[offset+8]);
-
- input_sync(input_dev);
- }
-
- /* Convert timestamp (in 5.33us unit) to timestamp_us */
- offset = data_offset + DS4_INPUT_REPORT_TIMESTAMP_OFFSET;
- timestamp = get_unaligned_le16(&rd[offset]);
- if (!sc->timestamp_initialized) {
- sc->timestamp_us = ((unsigned int)timestamp * 16) / 3;
- sc->timestamp_initialized = true;
- } else {
- u16 delta;
-
- if (sc->prev_timestamp > timestamp)
- delta = (U16_MAX - sc->prev_timestamp + timestamp + 1);
- else
- delta = timestamp - sc->prev_timestamp;
- sc->timestamp_us += (delta * 16) / 3;
- }
- sc->prev_timestamp = timestamp;
- input_event(sc->sensor_dev, EV_MSC, MSC_TIMESTAMP, sc->timestamp_us);
-
- offset = data_offset + DS4_INPUT_REPORT_GYRO_X_OFFSET;
- for (n = 0; n < 6; n++) {
- /* Store data in int for more precision during mult_frac. */
- int raw_data = (short)((rd[offset+1] << 8) | rd[offset]);
- struct ds4_calibration_data *calib = &sc->ds4_calib_data[n];
-
- /* High precision is needed during calibration, but the
- * calibrated values are within 32-bit.
- * Note: we swap numerator 'x' and 'numer' in mult_frac for
- * precision reasons so we don't need 64-bit.
- */
- int calib_data = mult_frac(calib->sens_numer,
- raw_data - calib->bias,
- calib->sens_denom);
-
- input_report_abs(sc->sensor_dev, calib->abs_code, calib_data);
- offset += 2;
- }
- input_sync(sc->sensor_dev);
-
- /*
- * The lower 4 bits of byte 30 (or 32 for BT) contain the battery level
- * and the 5th bit contains the USB cable state.
- */
- offset = data_offset + DS4_INPUT_REPORT_BATTERY_OFFSET;
- cable_state = (rd[offset] >> 4) & 0x01;
-
- /*
- * Interpretation of the battery_capacity data depends on the cable state.
- * When no cable is connected (bit4 is 0):
- * - 0:10: percentage in units of 10%.
- * When a cable is plugged in:
- * - 0-10: percentage in units of 10%.
- * - 11: battery is full
- * - 14: not charging due to Voltage or temperature error
- * - 15: charge error
- */
- if (cable_state) {
- u8 battery_data = rd[offset] & 0xf;
-
- if (battery_data < 10) {
- /* Take the mid-point for each battery capacity value,
- * because on the hardware side 0 = 0-9%, 1=10-19%, etc.
- * This matches official platform behavior, which does
- * the same.
- */
- battery_capacity = battery_data * 10 + 5;
- battery_status = POWER_SUPPLY_STATUS_CHARGING;
- } else if (battery_data == 10) {
- battery_capacity = 100;
- battery_status = POWER_SUPPLY_STATUS_CHARGING;
- } else if (battery_data == 11) {
- battery_capacity = 100;
- battery_status = POWER_SUPPLY_STATUS_FULL;
- } else { /* 14, 15 and undefined values */
- battery_capacity = 0;
- battery_status = POWER_SUPPLY_STATUS_UNKNOWN;
- }
- } else {
- u8 battery_data = rd[offset] & 0xf;
-
- if (battery_data < 10)
- battery_capacity = battery_data * 10 + 5;
- else /* 10 */
- battery_capacity = 100;
-
- battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
- }
-
- spin_lock_irqsave(&sc->lock, flags);
- sc->battery_capacity = battery_capacity;
- sc->battery_status = battery_status;
- spin_unlock_irqrestore(&sc->lock, flags);
-
- /*
- * The Dualshock 4 multi-touch trackpad data starts at offset 33 on USB
- * and 35 on Bluetooth.
- * The first byte indicates the number of touch data in the report.
- * Trackpad data starts 2 bytes later (e.g. 35 for USB).
- */
- offset = data_offset + DS4_INPUT_REPORT_TOUCHPAD_OFFSET;
- max_touch_data = (sc->quirks & DUALSHOCK4_CONTROLLER_BT) ? 4 : 3;
- if (rd[offset] > 0 && rd[offset] <= max_touch_data)
- num_touch_data = rd[offset];
- else
- num_touch_data = 1;
- offset += 1;
-
- for (m = 0; m < num_touch_data; m++) {
- /* Skip past timestamp */
- offset += 1;
-
- /*
- * The first 7 bits of the first byte is a counter and bit 8 is
- * a touch indicator that is 0 when pressed and 1 when not
- * pressed.
- * The next 3 bytes are two 12 bit touch coordinates, X and Y.
- * The data for the second touch is in the same format and
- * immediately follows the data for the first.
- */
- for (n = 0; n < 2; n++) {
- u16 x, y;
- bool active;
-
- x = rd[offset+1] | ((rd[offset+2] & 0xF) << 8);
- y = ((rd[offset+2] & 0xF0) >> 4) | (rd[offset+3] << 4);
-
- active = !(rd[offset] >> 7);
- input_mt_slot(sc->touchpad, n);
- input_mt_report_slot_state(sc->touchpad, MT_TOOL_FINGER, active);
-
- if (active) {
- input_report_abs(sc->touchpad, ABS_MT_POSITION_X, x);
- input_report_abs(sc->touchpad, ABS_MT_POSITION_Y, y);
- }
-
- offset += 4;
- }
- input_mt_sync_frame(sc->touchpad);
- input_sync(sc->touchpad);
- }
-}
-
static void nsg_mrxu_parse_report(struct sony_sc *sc, u8 *rd, int size)
{
int n, offset, relx, rely;
@@ -1350,83 +947,6 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
} else if ((sc->quirks & NAVIGATION_CONTROLLER) && rd[0] == 0x01 &&
size == 49) {
sixaxis_parse_report(sc, rd, size);
- } else if ((sc->quirks & DUALSHOCK4_CONTROLLER_USB) && rd[0] == 0x01 &&
- size == 64) {
- dualshock4_parse_report(sc, rd, size);
- } else if (((sc->quirks & DUALSHOCK4_CONTROLLER_BT) && rd[0] == 0x11 &&
- size == 78)) {
- /* CRC check */
- u8 bthdr = 0xA1;
- u32 crc;
- u32 report_crc;
-
- crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
- crc = ~crc32_le(crc, rd, DS4_INPUT_REPORT_0x11_SIZE-4);
- report_crc = get_unaligned_le32(&rd[DS4_INPUT_REPORT_0x11_SIZE-4]);
- if (crc != report_crc) {
- hid_dbg(sc->hdev, "DualShock 4 input report's CRC check failed, received crc 0x%0x != 0x%0x\n",
- report_crc, crc);
- return -EILSEQ;
- }
-
- dualshock4_parse_report(sc, rd, size);
- } else if ((sc->quirks & DUALSHOCK4_DONGLE) && rd[0] == 0x01 &&
- size == 64) {
- unsigned long flags;
- enum ds4_dongle_state dongle_state;
-
- /*
- * In the case of a DS4 USB dongle, bit[2] of byte 31 indicates
- * if a DS4 is actually connected (indicated by '0').
- * For non-dongle, this bit is always 0 (connected).
- */
- bool connected = (rd[31] & 0x04) ? false : true;
-
- spin_lock_irqsave(&sc->lock, flags);
- dongle_state = sc->ds4_dongle_state;
- spin_unlock_irqrestore(&sc->lock, flags);
-
- /*
- * The dongle always sends input reports even when no
- * DS4 is attached. When a DS4 is connected, we need to
- * obtain calibration data before we can use it.
- * The code below tracks dongle state and kicks of
- * calibration when needed and only allows us to process
- * input if a DS4 is actually connected.
- */
- if (dongle_state == DONGLE_DISCONNECTED && connected) {
- hid_info(sc->hdev, "DualShock 4 USB dongle: controller connected\n");
- sony_set_leds(sc);
-
- spin_lock_irqsave(&sc->lock, flags);
- sc->ds4_dongle_state = DONGLE_CALIBRATING;
- spin_unlock_irqrestore(&sc->lock, flags);
-
- sony_schedule_work(sc, SONY_WORKER_HOTPLUG);
-
- /* Don't process the report since we don't have
- * calibration data, but let hidraw have it anyway.
- */
- return 0;
- } else if ((dongle_state == DONGLE_CONNECTED ||
- dongle_state == DONGLE_DISABLED) && !connected) {
- hid_info(sc->hdev, "DualShock 4 USB dongle: controller disconnected\n");
-
- spin_lock_irqsave(&sc->lock, flags);
- sc->ds4_dongle_state = DONGLE_DISCONNECTED;
- spin_unlock_irqrestore(&sc->lock, flags);
-
- /* Return 0, so hidraw can get the report. */
- return 0;
- } else if (dongle_state == DONGLE_CALIBRATING ||
- dongle_state == DONGLE_DISABLED ||
- dongle_state == DONGLE_DISCONNECTED) {
- /* Return 0, so hidraw can get the report. */
- return 0;
- }
-
- dualshock4_parse_report(sc, rd, size);
-
} else if ((sc->quirks & NSG_MRXU_REMOTE) && rd[0] == 0x02) {
nsg_mrxu_parse_report(sc, rd, size);
return 1;
@@ -1478,9 +998,6 @@ static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
if (sc->quirks & SIXAXIS_CONTROLLER)
return sixaxis_mapping(hdev, hi, field, usage, bit, max);
- if (sc->quirks & DUALSHOCK4_CONTROLLER)
- return ds4_mapping(hdev, hi, field, usage, bit, max);
-
if (sc->quirks & GH_GUITAR_CONTROLLER)
return guitar_mapping(hdev, hi, field, usage, bit, max);
@@ -1508,14 +1025,17 @@ static int sony_register_touchpad(struct sony_sc *sc, int touch_count,
sc->touchpad->id.product = sc->hdev->product;
sc->touchpad->id.version = sc->hdev->version;
- /* Append a suffix to the controller name as there are various
- * DS4 compatible non-Sony devices with different names.
+ /* This suffix was originally apended when hid-sony also
+ * supported DS4 devices. The DS4 was implemented using multiple
+ * evdev nodes and hence had the need to separete them out using
+ * a suffix. Other devices which were added later like Sony TV remotes
+ * inhirited this suffix.
*/
- name_sz = strlen(sc->hdev->name) + sizeof(DS4_TOUCHPAD_SUFFIX);
+ name_sz = strlen(sc->hdev->name) + sizeof(TOUCHPAD_SUFFIX);
name = devm_kzalloc(&sc->hdev->dev, name_sz, GFP_KERNEL);
if (!name)
return -ENOMEM;
- snprintf(name, name_sz, "%s" DS4_TOUCHPAD_SUFFIX, sc->hdev->name);
+ snprintf(name, name_sz, "%s" TOUCHPAD_SUFFIX, sc->hdev->name);
sc->touchpad->name = name;
/* We map the button underneath the touchpad to BTN_LEFT. */
@@ -1557,7 +1077,6 @@ static int sony_register_sensors(struct sony_sc *sc)
size_t name_sz;
char *name;
int ret;
- int range;
sc->sensor_dev = devm_input_allocate_device(&sc->hdev->dev);
if (!sc->sensor_dev)
@@ -1595,25 +1114,6 @@ static int sony_register_sensors(struct sony_sc *sc)
input_abs_set_res(sc->sensor_dev, ABS_X, SIXAXIS_ACC_RES_PER_G);
input_abs_set_res(sc->sensor_dev, ABS_Y, SIXAXIS_ACC_RES_PER_G);
input_abs_set_res(sc->sensor_dev, ABS_Z, SIXAXIS_ACC_RES_PER_G);
- } else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
- range = DS4_ACC_RES_PER_G*4;
- input_set_abs_params(sc->sensor_dev, ABS_X, -range, range, 16, 0);
- input_set_abs_params(sc->sensor_dev, ABS_Y, -range, range, 16, 0);
- input_set_abs_params(sc->sensor_dev, ABS_Z, -range, range, 16, 0);
- input_abs_set_res(sc->sensor_dev, ABS_X, DS4_ACC_RES_PER_G);
- input_abs_set_res(sc->sensor_dev, ABS_Y, DS4_ACC_RES_PER_G);
- input_abs_set_res(sc->sensor_dev, ABS_Z, DS4_ACC_RES_PER_G);
-
- range = DS4_GYRO_RES_PER_DEG_S*2048;
- input_set_abs_params(sc->sensor_dev, ABS_RX, -range, range, 16, 0);
- input_set_abs_params(sc->sensor_dev, ABS_RY, -range, range, 16, 0);
- input_set_abs_params(sc->sensor_dev, ABS_RZ, -range, range, 16, 0);
- input_abs_set_res(sc->sensor_dev, ABS_RX, DS4_GYRO_RES_PER_DEG_S);
- input_abs_set_res(sc->sensor_dev, ABS_RY, DS4_GYRO_RES_PER_DEG_S);
- input_abs_set_res(sc->sensor_dev, ABS_RZ, DS4_GYRO_RES_PER_DEG_S);
-
- __set_bit(EV_MSC, sc->sensor_dev->evbit);
- __set_bit(MSC_TIMESTAMP, sc->sensor_dev->mscbit);
}
__set_bit(INPUT_PROP_ACCELEROMETER, sc->sensor_dev->propbit);
@@ -1697,224 +1197,6 @@ static int sixaxis_set_operational_bt(struct hid_device *hdev)
return ret;
}
-/*
- * Request DS4 calibration data for the motion sensors.
- * For Bluetooth this also affects the operating mode (see below).
- */
-static int dualshock4_get_calibration_data(struct sony_sc *sc)
-{
- u8 *buf;
- int ret;
- short gyro_pitch_bias, gyro_pitch_plus, gyro_pitch_minus;
- short gyro_yaw_bias, gyro_yaw_plus, gyro_yaw_minus;
- short gyro_roll_bias, gyro_roll_plus, gyro_roll_minus;
- short gyro_speed_plus, gyro_speed_minus;
- short acc_x_plus, acc_x_minus;
- short acc_y_plus, acc_y_minus;
- short acc_z_plus, acc_z_minus;
- int speed_2x;
- int range_2g;
-
- /* For Bluetooth we use a different request, which supports CRC.
- * Note: in Bluetooth mode feature report 0x02 also changes the state
- * of the controller, so that it sends input reports of type 0x11.
- */
- if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
- int retries;
-
- buf = kmalloc(DS4_FEATURE_REPORT_0x02_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- /* We should normally receive the feature report data we asked
- * for, but hidraw applications such as Steam can issue feature
- * reports as well. In particular for Dongle reconnects, Steam
- * and this function are competing resulting in often receiving
- * data for a different HID report, so retry a few times.
- */
- for (retries = 0; retries < 3; retries++) {
- ret = hid_hw_raw_request(sc->hdev, 0x02, buf,
- DS4_FEATURE_REPORT_0x02_SIZE,
- HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
- if (ret < 0)
- goto err_stop;
-
- if (buf[0] != 0x02) {
- if (retries < 2) {
- hid_warn(sc->hdev, "Retrying DualShock 4 get calibration report (0x02) request\n");
- continue;
- } else {
- ret = -EILSEQ;
- goto err_stop;
- }
- } else {
- break;
- }
- }
- } else {
- u8 bthdr = 0xA3;
- u32 crc;
- u32 report_crc;
- int retries;
-
- buf = kmalloc(DS4_FEATURE_REPORT_0x05_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- for (retries = 0; retries < 3; retries++) {
- ret = hid_hw_raw_request(sc->hdev, 0x05, buf,
- DS4_FEATURE_REPORT_0x05_SIZE,
- HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
- if (ret < 0)
- goto err_stop;
-
- /* CRC check */
- crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
- crc = ~crc32_le(crc, buf, DS4_FEATURE_REPORT_0x05_SIZE-4);
- report_crc = get_unaligned_le32(&buf[DS4_FEATURE_REPORT_0x05_SIZE-4]);
- if (crc != report_crc) {
- hid_warn(sc->hdev, "DualShock 4 calibration report's CRC check failed, received crc 0x%0x != 0x%0x\n",
- report_crc, crc);
- if (retries < 2) {
- hid_warn(sc->hdev, "Retrying DualShock 4 get calibration report request\n");
- continue;
- } else {
- ret = -EILSEQ;
- goto err_stop;
- }
- } else {
- break;
- }
- }
- }
-
- gyro_pitch_bias = get_unaligned_le16(&buf[1]);
- gyro_yaw_bias = get_unaligned_le16(&buf[3]);
- gyro_roll_bias = get_unaligned_le16(&buf[5]);
- if (sc->quirks & DUALSHOCK4_CONTROLLER_USB) {
- gyro_pitch_plus = get_unaligned_le16(&buf[7]);
- gyro_pitch_minus = get_unaligned_le16(&buf[9]);
- gyro_yaw_plus = get_unaligned_le16(&buf[11]);
- gyro_yaw_minus = get_unaligned_le16(&buf[13]);
- gyro_roll_plus = get_unaligned_le16(&buf[15]);
- gyro_roll_minus = get_unaligned_le16(&buf[17]);
- } else {
- /* BT + Dongle */
- gyro_pitch_plus = get_unaligned_le16(&buf[7]);
- gyro_yaw_plus = get_unaligned_le16(&buf[9]);
- gyro_roll_plus = get_unaligned_le16(&buf[11]);
- gyro_pitch_minus = get_unaligned_le16(&buf[13]);
- gyro_yaw_minus = get_unaligned_le16(&buf[15]);
- gyro_roll_minus = get_unaligned_le16(&buf[17]);
- }
- gyro_speed_plus = get_unaligned_le16(&buf[19]);
- gyro_speed_minus = get_unaligned_le16(&buf[21]);
- acc_x_plus = get_unaligned_le16(&buf[23]);
- acc_x_minus = get_unaligned_le16(&buf[25]);
- acc_y_plus = get_unaligned_le16(&buf[27]);
- acc_y_minus = get_unaligned_le16(&buf[29]);
- acc_z_plus = get_unaligned_le16(&buf[31]);
- acc_z_minus = get_unaligned_le16(&buf[33]);
-
- /* Set gyroscope calibration and normalization parameters.
- * Data values will be normalized to 1/DS4_GYRO_RES_PER_DEG_S degree/s.
- */
- speed_2x = (gyro_speed_plus + gyro_speed_minus);
- sc->ds4_calib_data[0].abs_code = ABS_RX;
- sc->ds4_calib_data[0].bias = gyro_pitch_bias;
- sc->ds4_calib_data[0].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
- sc->ds4_calib_data[0].sens_denom = gyro_pitch_plus - gyro_pitch_minus;
-
- sc->ds4_calib_data[1].abs_code = ABS_RY;
- sc->ds4_calib_data[1].bias = gyro_yaw_bias;
- sc->ds4_calib_data[1].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
- sc->ds4_calib_data[1].sens_denom = gyro_yaw_plus - gyro_yaw_minus;
-
- sc->ds4_calib_data[2].abs_code = ABS_RZ;
- sc->ds4_calib_data[2].bias = gyro_roll_bias;
- sc->ds4_calib_data[2].sens_numer = speed_2x*DS4_GYRO_RES_PER_DEG_S;
- sc->ds4_calib_data[2].sens_denom = gyro_roll_plus - gyro_roll_minus;
-
- /* Set accelerometer calibration and normalization parameters.
- * Data values will be normalized to 1/DS4_ACC_RES_PER_G G.
- */
- range_2g = acc_x_plus - acc_x_minus;
- sc->ds4_calib_data[3].abs_code = ABS_X;
- sc->ds4_calib_data[3].bias = acc_x_plus - range_2g / 2;
- sc->ds4_calib_data[3].sens_numer = 2*DS4_ACC_RES_PER_G;
- sc->ds4_calib_data[3].sens_denom = range_2g;
-
- range_2g = acc_y_plus - acc_y_minus;
- sc->ds4_calib_data[4].abs_code = ABS_Y;
- sc->ds4_calib_data[4].bias = acc_y_plus - range_2g / 2;
- sc->ds4_calib_data[4].sens_numer = 2*DS4_ACC_RES_PER_G;
- sc->ds4_calib_data[4].sens_denom = range_2g;
-
- range_2g = acc_z_plus - acc_z_minus;
- sc->ds4_calib_data[5].abs_code = ABS_Z;
- sc->ds4_calib_data[5].bias = acc_z_plus - range_2g / 2;
- sc->ds4_calib_data[5].sens_numer = 2*DS4_ACC_RES_PER_G;
- sc->ds4_calib_data[5].sens_denom = range_2g;
-
-err_stop:
- kfree(buf);
- return ret;
-}
-
-static void dualshock4_calibration_work(struct work_struct *work)
-{
- struct sony_sc *sc = container_of(work, struct sony_sc, hotplug_worker);
- unsigned long flags;
- enum ds4_dongle_state dongle_state;
- int ret;
-
- ret = dualshock4_get_calibration_data(sc);
- if (ret < 0) {
- /* This call is very unlikely to fail for the dongle. When it
- * fails we are probably in a very bad state, so mark the
- * dongle as disabled. We will re-enable the dongle if a new
- * DS4 hotplug is detect from sony_raw_event as any issues
- * are likely resolved then (the dongle is quite stupid).
- */
- hid_err(sc->hdev, "DualShock 4 USB dongle: calibration failed, disabling device\n");
- dongle_state = DONGLE_DISABLED;
- } else {
- hid_info(sc->hdev, "DualShock 4 USB dongle: calibration completed\n");
- dongle_state = DONGLE_CONNECTED;
- }
-
- spin_lock_irqsave(&sc->lock, flags);
- sc->ds4_dongle_state = dongle_state;
- spin_unlock_irqrestore(&sc->lock, flags);
-}
-
-static int dualshock4_get_version_info(struct sony_sc *sc)
-{
- u8 *buf;
- int ret;
-
- buf = kmalloc(DS4_FEATURE_REPORT_0xA3_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- ret = hid_hw_raw_request(sc->hdev, 0xA3, buf,
- DS4_FEATURE_REPORT_0xA3_SIZE,
- HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
- if (ret < 0) {
- kfree(buf);
- return ret;
- }
-
- sc->hw_version = get_unaligned_le16(&buf[35]);
- sc->fw_version = get_unaligned_le16(&buf[41]);
-
- kfree(buf);
- return 0;
-}
-
static void sixaxis_set_leds_from_id(struct sony_sc *sc)
{
static const u8 sixaxis_leds[10][4] = {
@@ -1941,30 +1223,6 @@ static void sixaxis_set_leds_from_id(struct sony_sc *sc)
memcpy(sc->led_state, sixaxis_leds[id], sizeof(sixaxis_leds[id]));
}
-static void dualshock4_set_leds_from_id(struct sony_sc *sc)
-{
- /* The first 4 color/index entries match what the PS4 assigns */
- static const u8 color_code[7][3] = {
- /* Blue */ { 0x00, 0x00, 0x40 },
- /* Red */ { 0x40, 0x00, 0x00 },
- /* Green */ { 0x00, 0x40, 0x00 },
- /* Pink */ { 0x20, 0x00, 0x20 },
- /* Orange */ { 0x02, 0x01, 0x00 },
- /* Teal */ { 0x00, 0x01, 0x01 },
- /* White */ { 0x01, 0x01, 0x01 }
- };
-
- int id = sc->device_id;
-
- BUILD_BUG_ON(MAX_LEDS < ARRAY_SIZE(color_code[0]));
-
- if (id < 0)
- return;
-
- id %= 7;
- memcpy(sc->led_state, color_code[id], sizeof(color_code[id]));
-}
-
static void buzz_set_leds(struct sony_sc *sc)
{
struct hid_device *hdev = sc->hdev;
@@ -2110,13 +1368,13 @@ static int sony_leds_init(struct sony_sc *sc)
{
struct hid_device *hdev = sc->hdev;
int n, ret = 0;
- int use_ds4_names;
+ int use_color_names;
struct led_classdev *led;
size_t name_sz;
char *name;
size_t name_len;
const char *name_fmt;
- static const char * const ds4_name_str[] = { "red", "green", "blue",
+ static const char * const color_name_str[] = { "red", "green", "blue",
"global" };
u8 max_brightness[MAX_LEDS] = { [0 ... (MAX_LEDS - 1)] = 1 };
u8 use_hw_blink[MAX_LEDS] = { 0 };
@@ -2125,25 +1383,16 @@ static int sony_leds_init(struct sony_sc *sc)
if (sc->quirks & BUZZ_CONTROLLER) {
sc->led_count = 4;
- use_ds4_names = 0;
+ use_color_names = 0;
name_len = strlen("::buzz#");
name_fmt = "%s::buzz%d";
/* Validate expected report characteristics. */
if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7))
return -ENODEV;
- } else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
- dualshock4_set_leds_from_id(sc);
- sc->led_state[3] = 1;
- sc->led_count = 4;
- memset(max_brightness, 255, 3);
- use_hw_blink[3] = 1;
- use_ds4_names = 1;
- name_len = 0;
- name_fmt = "%s:%s";
} else if (sc->quirks & MOTION_CONTROLLER) {
sc->led_count = 3;
memset(max_brightness, 255, 3);
- use_ds4_names = 1;
+ use_color_names = 1;
name_len = 0;
name_fmt = "%s:%s";
} else if (sc->quirks & NAVIGATION_CONTROLLER) {
@@ -2152,14 +1401,14 @@ static int sony_leds_init(struct sony_sc *sc)
memcpy(sc->led_state, navigation_leds, sizeof(navigation_leds));
sc->led_count = 1;
memset(use_hw_blink, 1, 4);
- use_ds4_names = 0;
+ use_color_names = 0;
name_len = strlen("::sony#");
name_fmt = "%s::sony%d";
} else {
sixaxis_set_leds_from_id(sc);
sc->led_count = 4;
memset(use_hw_blink, 1, 4);
- use_ds4_names = 0;
+ use_color_names = 0;
name_len = strlen("::sony#");
name_fmt = "%s::sony%d";
}
@@ -2175,8 +1424,8 @@ static int sony_leds_init(struct sony_sc *sc)
for (n = 0; n < sc->led_count; n++) {
- if (use_ds4_names)
- name_sz = strlen(dev_name(&hdev->dev)) + strlen(ds4_name_str[n]) + 2;
+ if (use_color_names)
+ name_sz = strlen(dev_name(&hdev->dev)) + strlen(color_name_str[n]) + 2;
led = devm_kzalloc(&hdev->dev, sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
if (!led) {
@@ -2185,9 +1434,9 @@ static int sony_leds_init(struct sony_sc *sc)
}
name = (void *)(&led[1]);
- if (use_ds4_names)
+ if (use_color_names)
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev),
- ds4_name_str[n]);
+ color_name_str[n]);
else
snprintf(name, name_sz, name_fmt, dev_name(&hdev->dev), n + 1);
led->name = name;
@@ -2273,68 +1522,6 @@ static void sixaxis_send_output_report(struct sony_sc *sc)
HID_OUTPUT_REPORT, HID_REQ_SET_REPORT);
}
-static void dualshock4_send_output_report(struct sony_sc *sc)
-{
- struct hid_device *hdev = sc->hdev;
- u8 *buf = sc->output_report_dmabuf;
- int offset;
-
- /*
- * NOTE: The lower 6 bits of buf[1] field of the Bluetooth report
- * control the interval at which Dualshock 4 reports data:
- * 0x00 - 1ms
- * 0x01 - 1ms
- * 0x02 - 2ms
- * 0x3E - 62ms
- * 0x3F - disabled
- */
- if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
- memset(buf, 0, DS4_OUTPUT_REPORT_0x05_SIZE);
- buf[0] = 0x05;
- buf[1] = 0x07; /* blink + LEDs + motor */
- offset = 4;
- } else {
- memset(buf, 0, DS4_OUTPUT_REPORT_0x11_SIZE);
- buf[0] = 0x11;
- buf[1] = 0xC0 /* HID + CRC */ | sc->ds4_bt_poll_interval;
- buf[3] = 0x07; /* blink + LEDs + motor */
- offset = 6;
- }
-
-#ifdef CONFIG_SONY_FF
- buf[offset++] = sc->right;
- buf[offset++] = sc->left;
-#else
- offset += 2;
-#endif
-
- /* LED 3 is the global control */
- if (sc->led_state[3]) {
- buf[offset++] = sc->led_state[0];
- buf[offset++] = sc->led_state[1];
- buf[offset++] = sc->led_state[2];
- } else {
- offset += 3;
- }
-
- /* If both delay values are zero the DualShock 4 disables blinking. */
- buf[offset++] = sc->led_delay_on[3];
- buf[offset++] = sc->led_delay_off[3];
-
- if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE))
- hid_hw_output_report(hdev, buf, DS4_OUTPUT_REPORT_0x05_SIZE);
- else {
- /* CRC generation */
- u8 bthdr = 0xA2;
- u32 crc;
-
- crc = crc32_le(0xFFFFFFFF, &bthdr, 1);
- crc = ~crc32_le(crc, buf, DS4_OUTPUT_REPORT_0x11_SIZE-4);
- put_unaligned_le32(crc, &buf[74]);
- hid_hw_output_report(hdev, buf, DS4_OUTPUT_REPORT_0x11_SIZE);
- }
-}
-
static void motion_send_output_report(struct sony_sc *sc)
{
struct hid_device *hdev = sc->hdev;
@@ -2355,11 +1542,13 @@ static void motion_send_output_report(struct sony_sc *sc)
hid_hw_output_report(hdev, (u8 *)report, MOTION_REPORT_0x02_SIZE);
}
+#ifdef CONFIG_SONY_FF
static inline void sony_send_output_report(struct sony_sc *sc)
{
if (sc->send_output_report)
sc->send_output_report(sc);
}
+#endif
static void sony_state_worker(struct work_struct *work)
{
@@ -2376,14 +1565,6 @@ static int sony_allocate_output_report(struct sony_sc *sc)
devm_kmalloc(&sc->hdev->dev,
sizeof(union sixaxis_output_report_01),
GFP_KERNEL);
- else if (sc->quirks & DUALSHOCK4_CONTROLLER_BT)
- sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
- DS4_OUTPUT_REPORT_0x11_SIZE,
- GFP_KERNEL);
- else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE))
- sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
- DS4_OUTPUT_REPORT_0x05_SIZE,
- GFP_KERNEL);
else if (sc->quirks & MOTION_CONTROLLER)
sc->output_report_dmabuf = devm_kmalloc(&sc->hdev->dev,
MOTION_REPORT_0x02_SIZE,
@@ -2598,8 +1779,7 @@ static int sony_check_add(struct sony_sc *sc)
u8 *buf = NULL;
int n, ret;
- if ((sc->quirks & DUALSHOCK4_CONTROLLER_BT) ||
- (sc->quirks & MOTION_CONTROLLER_BT) ||
+ if ((sc->quirks & MOTION_CONTROLLER_BT) ||
(sc->quirks & NAVIGATION_CONTROLLER_BT) ||
(sc->quirks & SIXAXIS_CONTROLLER_BT)) {
/*
@@ -2612,30 +1792,6 @@ static int sony_check_add(struct sony_sc *sc)
hid_warn(sc->hdev, "UNIQ does not contain a MAC address; duplicate check skipped\n");
return 0;
}
- } else if (sc->quirks & (DUALSHOCK4_CONTROLLER_USB | DUALSHOCK4_DONGLE)) {
- buf = kmalloc(DS4_FEATURE_REPORT_0x81_SIZE, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- /*
- * The MAC address of a DS4 controller connected via USB can be
- * retrieved with feature report 0x81. The address begins at
- * offset 1.
- */
- ret = hid_hw_raw_request(sc->hdev, 0x81, buf,
- DS4_FEATURE_REPORT_0x81_SIZE, HID_FEATURE_REPORT,
- HID_REQ_GET_REPORT);
-
- if (ret != DS4_FEATURE_REPORT_0x81_SIZE) {
- hid_err(sc->hdev, "failed to retrieve feature report 0x81 with the DualShock 4 MAC address\n");
- ret = ret < 0 ? ret : -EINVAL;
- goto out_free;
- }
-
- memcpy(sc->mac_address, &buf[1], sizeof(sc->mac_address));
-
- snprintf(sc->hdev->uniq, sizeof(sc->hdev->uniq),
- "%pMR", sc->mac_address);
} else if ((sc->quirks & SIXAXIS_CONTROLLER_USB) ||
(sc->quirks & NAVIGATION_CONTROLLER_USB)) {
buf = kmalloc(SIXAXIS_REPORT_0xF2_SIZE, GFP_KERNEL);
@@ -2684,11 +1840,10 @@ static int sony_set_device_id(struct sony_sc *sc)
int ret;
/*
- * Only DualShock 4 or Sixaxis controllers get an id.
+ * Only Sixaxis controllers get an id.
* All others are set to -1.
*/
- if ((sc->quirks & SIXAXIS_CONTROLLER) ||
- (sc->quirks & DUALSHOCK4_CONTROLLER)) {
+ if (sc->quirks & SIXAXIS_CONTROLLER) {
ret = ida_simple_get(&sony_device_id_allocator, 0, 0,
GFP_KERNEL);
if (ret < 0) {
@@ -2726,8 +1881,6 @@ static inline void sony_cancel_work_sync(struct sony_sc *sc)
{
unsigned long flags;
- if (sc->hotplug_worker_initialized)
- cancel_work_sync(&sc->hotplug_worker);
if (sc->state_worker_initialized) {
spin_lock_irqsave(&sc->lock, flags);
sc->state_worker_initialized = 0;
@@ -2847,68 +2000,6 @@ static int sony_input_configured(struct hid_device *hdev,
}
sony_init_output_report(sc, sixaxis_send_output_report);
- } else if (sc->quirks & DUALSHOCK4_CONTROLLER) {
- ret = dualshock4_get_calibration_data(sc);
- if (ret < 0) {
- hid_err(hdev, "Failed to get calibration data from Dualshock 4\n");
- goto err_stop;
- }
-
- ret = dualshock4_get_version_info(sc);
- if (ret < 0) {
- hid_err(sc->hdev, "Failed to get version data from Dualshock 4\n");
- goto err_stop;
- }
-
- ret = device_create_file(&sc->hdev->dev, &dev_attr_firmware_version);
- if (ret) {
- hid_err(sc->hdev, "can't create sysfs firmware_version attribute err: %d\n", ret);
- goto err_stop;
- }
- sc->fw_version_created = true;
-
- ret = device_create_file(&sc->hdev->dev, &dev_attr_hardware_version);
- if (ret) {
- hid_err(sc->hdev, "can't create sysfs hardware_version attribute err: %d\n", ret);
- goto err_stop;
- }
- sc->hw_version_created = true;
-
- /*
- * The Dualshock 4 touchpad supports 2 touches and has a
- * resolution of 1920x942 (44.86 dots/mm).
- */
- ret = sony_register_touchpad(sc, 2, 1920, 942, 0, 0, 0);
- if (ret) {
- hid_err(sc->hdev,
- "Unable to initialize multi-touch slots: %d\n",
- ret);
- goto err_stop;
- }
-
- ret = sony_register_sensors(sc);
- if (ret) {
- hid_err(sc->hdev,
- "Unable to initialize motion sensors: %d\n", ret);
- goto err_stop;
- }
-
- if (sc->quirks & DUALSHOCK4_CONTROLLER_BT) {
- sc->ds4_bt_poll_interval = DS4_BT_DEFAULT_POLL_INTERVAL_MS;
- ret = device_create_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
- if (ret)
- hid_warn(sc->hdev,
- "can't create sysfs bt_poll_interval attribute err: %d\n",
- ret);
- }
-
- if (sc->quirks & DUALSHOCK4_DONGLE) {
- INIT_WORK(&sc->hotplug_worker, dualshock4_calibration_work);
- sc->hotplug_worker_initialized = 1;
- sc->ds4_dongle_state = DONGLE_DISCONNECTED;
- }
-
- sony_init_output_report(sc, dualshock4_send_output_report);
} else if (sc->quirks & NSG_MRXU_REMOTE) {
/*
* The NSG-MRxU touchpad supports 2 touches and has a
@@ -2958,16 +2049,6 @@ static int sony_input_configured(struct hid_device *hdev,
err_close:
hid_hw_close(hdev);
err_stop:
- /* Piggy back on the default ds4_bt_ poll_interval to determine
- * if we need to remove the file as we don't know for sure if we
- * executed that logic.
- */
- if (sc->ds4_bt_poll_interval)
- device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
- if (sc->fw_version_created)
- device_remove_file(&sc->hdev->dev, &dev_attr_firmware_version);
- if (sc->hw_version_created)
- device_remove_file(&sc->hdev->dev, &dev_attr_hardware_version);
sony_cancel_work_sync(sc);
sony_remove_dev_list(sc);
sony_release_device_id(sc);
@@ -3012,13 +2093,13 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
else if (sc->quirks & SIXAXIS_CONTROLLER)
connect_mask |= HID_CONNECT_HIDDEV_FORCE;
- /* Patch the hw version on DS3/4 compatible devices, so applications can
+ /* Patch the hw version on DS3 compatible devices, so applications can
* distinguish between the default HID mappings and the mappings defined
* by the Linux game controller spec. This is important for the SDL2
* library, which has a game controller database, which uses device ids
* in combination with version as a key.
*/
- if (sc->quirks & (SIXAXIS_CONTROLLER | DUALSHOCK4_CONTROLLER))
+ if (sc->quirks & SIXAXIS_CONTROLLER)
hdev->version |= 0x8000;
ret = hid_hw_start(hdev, connect_mask);
@@ -3089,15 +2170,6 @@ static void sony_remove(struct hid_device *hdev)
hid_hw_close(hdev);
- if (sc->quirks & DUALSHOCK4_CONTROLLER_BT)
- device_remove_file(&sc->hdev->dev, &dev_attr_bt_poll_interval);
-
- if (sc->fw_version_created)
- device_remove_file(&sc->hdev->dev, &dev_attr_firmware_version);
-
- if (sc->hw_version_created)
- device_remove_file(&sc->hdev->dev, &dev_attr_hardware_version);
-
sony_cancel_work_sync(sc);
sony_remove_dev_list(sc);
@@ -3178,17 +2250,6 @@ static const struct hid_device_id sony_devices[] = {
/* SMK-Link PS3 BD Remote Control */
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK, USB_DEVICE_ID_SMK_PS3_BDREMOTE),
.driver_data = PS3REMOTE },
- /* Sony Dualshock 4 controllers for PS4 */
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
- .driver_data = DUALSHOCK4_CONTROLLER_USB },
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER),
- .driver_data = DUALSHOCK4_CONTROLLER_BT },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
- .driver_data = DUALSHOCK4_CONTROLLER_USB },
- { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_2),
- .driver_data = DUALSHOCK4_CONTROLLER_BT },
- { HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS4_CONTROLLER_DONGLE),
- .driver_data = DUALSHOCK4_DONGLE },
/* Nyko Core Controller for PS3 */
{ HID_USB_DEVICE(USB_VENDOR_ID_SINO_LITE, USB_DEVICE_ID_SINO_LITE_CONTROLLER),
.driver_data = SIXAXIS_CONTROLLER_USB | SINO_LITE_CONTROLLER },
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
index 8ee43cb225fc..b110818fc945 100644
--- a/drivers/hid/hid-steam.c
+++ b/drivers/hid/hid-steam.c
@@ -3,6 +3,7 @@
* HID driver for Valve Steam Controller
*
* Copyright (c) 2018 Rodrigo Rivas Costa <[email protected]>
+ * Copyright (c) 2022 Valve Software
*
* Supports both the wired and wireless interfaces.
*
@@ -53,6 +54,7 @@ static DEFINE_MUTEX(steam_devices_lock);
static LIST_HEAD(steam_devices);
#define STEAM_QUIRK_WIRELESS BIT(0)
+#define STEAM_QUIRK_DECK BIT(1)
/* Touch pads are 40 mm in diameter and 65535 units */
#define STEAM_PAD_RESOLUTION 1638
@@ -60,6 +62,10 @@ static LIST_HEAD(steam_devices);
#define STEAM_TRIGGER_RESOLUTION 51
/* Joystick runs are about 5 mm and 256 units */
#define STEAM_JOYSTICK_RESOLUTION 51
+/* Trigger runs are about 6 mm and 32768 units */
+#define STEAM_DECK_TRIGGER_RESOLUTION 5461
+/* Joystick runs are about 5 mm and 32768 units */
+#define STEAM_DECK_JOYSTICK_RESOLUTION 6553
#define STEAM_PAD_FUZZ 256
@@ -85,6 +91,7 @@ static LIST_HEAD(steam_devices);
#define STEAM_CMD_FORCEFEEDBAK 0x8f
#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
#define STEAM_CMD_GET_SERIAL 0xae
+#define STEAM_CMD_HAPTIC_RUMBLE 0xeb
/* Some useful register ids */
#define STEAM_REG_LPAD_MODE 0x07
@@ -92,11 +99,14 @@ static LIST_HEAD(steam_devices);
#define STEAM_REG_RPAD_MARGIN 0x18
#define STEAM_REG_LED 0x2d
#define STEAM_REG_GYRO_MODE 0x30
+#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
+#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
/* Raw event identifiers */
#define STEAM_EV_INPUT_DATA 0x01
#define STEAM_EV_CONNECT 0x03
#define STEAM_EV_BATTERY 0x04
+#define STEAM_EV_DECK_INPUT_DATA 0x09
/* Values for GYRO_MODE (bitmask) */
#define STEAM_GYRO_MODE_OFF 0x0000
@@ -124,6 +134,10 @@ struct steam_device {
struct power_supply __rcu *battery;
u8 battery_charge;
u16 voltage;
+ struct delayed_work heartbeat;
+ struct work_struct rumble_work;
+ u16 rumble_left;
+ u16 rumble_right;
};
static int steam_recv_report(struct steam_device *steam,
@@ -193,7 +207,7 @@ static int steam_send_report(struct steam_device *steam,
*/
do {
ret = hid_hw_raw_request(steam->hdev, 0,
- buf, size + 1,
+ buf, max(size, 64) + 1,
HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
if (ret != -EPIPE)
break;
@@ -219,6 +233,7 @@ static int steam_write_registers(struct steam_device *steam,
u8 reg;
u16 val;
u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
+ int ret;
va_list args;
va_start(args, steam);
@@ -234,7 +249,16 @@ static int steam_write_registers(struct steam_device *steam,
}
va_end(args);
- return steam_send_report(steam, cmd, 2 + cmd[1]);
+ ret = steam_send_report(steam, cmd, 2 + cmd[1]);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Sometimes a lingering report for this command can
+ * get read back instead of the last set report if
+ * this isn't explicitly queried
+ */
+ return steam_recv_report(steam, cmd, 2 + cmd[1]);
}
static int steam_get_serial(struct steam_device *steam)
@@ -270,6 +294,45 @@ static inline int steam_request_conn_status(struct steam_device *steam)
return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
}
+static inline int steam_haptic_rumble(struct steam_device *steam,
+ u16 intensity, u16 left_speed, u16 right_speed,
+ u8 left_gain, u8 right_gain)
+{
+ u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
+
+ report[3] = intensity & 0xFF;
+ report[4] = intensity >> 8;
+ report[5] = left_speed & 0xFF;
+ report[6] = left_speed >> 8;
+ report[7] = right_speed & 0xFF;
+ report[8] = right_speed >> 8;
+ report[9] = left_gain;
+ report[10] = right_gain;
+
+ return steam_send_report(steam, report, sizeof(report));
+}
+
+static void steam_haptic_rumble_cb(struct work_struct *work)
+{
+ struct steam_device *steam = container_of(work, struct steam_device,
+ rumble_work);
+ steam_haptic_rumble(steam, 0, steam->rumble_left,
+ steam->rumble_right, 2, 0);
+}
+
+#ifdef CONFIG_STEAM_FF
+static int steam_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct steam_device *steam = input_get_drvdata(dev);
+
+ steam->rumble_left = effect->u.rumble.strong_magnitude;
+ steam->rumble_right = effect->u.rumble.weak_magnitude;
+
+ return schedule_work(&steam->rumble_work);
+}
+#endif
+
static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
{
if (enable) {
@@ -280,13 +343,33 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
steam_write_registers(steam,
STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
0);
+
+ cancel_delayed_work_sync(&steam->heartbeat);
} else {
/* disable esc, enter, cursor */
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
- steam_write_registers(steam,
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
- 0);
+
+ if (steam->quirks & STEAM_QUIRK_DECK) {
+ steam_write_registers(steam,
+ STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
+ STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
+ STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+ STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
+ 0);
+ /*
+ * The Steam Deck has a watchdog that automatically enables
+ * lizard mode if it doesn't see any traffic for too long
+ */
+ if (!work_busy(&steam->heartbeat.work))
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ } else {
+ steam_write_registers(steam,
+ STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
+ STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
+ STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+ 0);
+ }
}
}
@@ -413,8 +496,8 @@ static int steam_input_register(struct steam_device *steam)
input->open = steam_input_open;
input->close = steam_input_close;
- input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ?
- "Wireless Steam Controller" :
+ input->name = (steam->quirks & STEAM_QUIRK_WIRELESS) ? "Wireless Steam Controller" :
+ (steam->quirks & STEAM_QUIRK_DECK) ? "Steam Deck" :
"Steam Controller";
input->phys = hdev->phys;
input->uniq = steam->serial_no;
@@ -438,33 +521,76 @@ static int steam_input_register(struct steam_device *steam)
input_set_capability(input, EV_KEY, BTN_SELECT);
input_set_capability(input, EV_KEY, BTN_MODE);
input_set_capability(input, EV_KEY, BTN_START);
- input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
- input_set_capability(input, EV_KEY, BTN_GEAR_UP);
input_set_capability(input, EV_KEY, BTN_THUMBR);
input_set_capability(input, EV_KEY, BTN_THUMBL);
input_set_capability(input, EV_KEY, BTN_THUMB);
input_set_capability(input, EV_KEY, BTN_THUMB2);
+ if (steam->quirks & STEAM_QUIRK_DECK) {
+ input_set_capability(input, EV_KEY, BTN_BASE);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY1);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY2);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY3);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY4);
+ } else {
+ input_set_capability(input, EV_KEY, BTN_GEAR_DOWN);
+ input_set_capability(input, EV_KEY, BTN_GEAR_UP);
+ }
- input_set_abs_params(input, ABS_HAT2Y, 0, 255, 0, 0);
- input_set_abs_params(input, ABS_HAT2X, 0, 255, 0, 0);
input_set_abs_params(input, ABS_X, -32767, 32767, 0, 0);
input_set_abs_params(input, ABS_Y, -32767, 32767, 0, 0);
- input_set_abs_params(input, ABS_RX, -32767, 32767,
- STEAM_PAD_FUZZ, 0);
- input_set_abs_params(input, ABS_RY, -32767, 32767,
- STEAM_PAD_FUZZ, 0);
+
input_set_abs_params(input, ABS_HAT0X, -32767, 32767,
STEAM_PAD_FUZZ, 0);
input_set_abs_params(input, ABS_HAT0Y, -32767, 32767,
STEAM_PAD_FUZZ, 0);
- input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
- input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
- input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
- input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
+
+ if (steam->quirks & STEAM_QUIRK_DECK) {
+ input_set_abs_params(input, ABS_HAT2Y, 0, 32767, 0, 0);
+ input_set_abs_params(input, ABS_HAT2X, 0, 32767, 0, 0);
+
+ input_set_abs_params(input, ABS_RX, -32767, 32767, 0, 0);
+ input_set_abs_params(input, ABS_RY, -32767, 32767, 0, 0);
+
+ input_set_abs_params(input, ABS_HAT1X, -32767, 32767,
+ STEAM_PAD_FUZZ, 0);
+ input_set_abs_params(input, ABS_HAT1Y, -32767, 32767,
+ STEAM_PAD_FUZZ, 0);
+
+ input_abs_set_res(input, ABS_X, STEAM_DECK_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_Y, STEAM_DECK_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_RX, STEAM_DECK_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_RY, STEAM_DECK_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT1X, STEAM_PAD_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT1Y, STEAM_PAD_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT2Y, STEAM_DECK_TRIGGER_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT2X, STEAM_DECK_TRIGGER_RESOLUTION);
+ } else {
+ input_set_abs_params(input, ABS_HAT2Y, 0, 255, 0, 0);
+ input_set_abs_params(input, ABS_HAT2X, 0, 255, 0, 0);
+
+ input_set_abs_params(input, ABS_RX, -32767, 32767,
+ STEAM_PAD_FUZZ, 0);
+ input_set_abs_params(input, ABS_RY, -32767, 32767,
+ STEAM_PAD_FUZZ, 0);
+
+ input_abs_set_res(input, ABS_X, STEAM_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_Y, STEAM_JOYSTICK_RESOLUTION);
+ input_abs_set_res(input, ABS_RX, STEAM_PAD_RESOLUTION);
+ input_abs_set_res(input, ABS_RY, STEAM_PAD_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT2Y, STEAM_TRIGGER_RESOLUTION);
+ input_abs_set_res(input, ABS_HAT2X, STEAM_TRIGGER_RESOLUTION);
+ }
input_abs_set_res(input, ABS_HAT0X, STEAM_PAD_RESOLUTION);
input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION);
- input_abs_set_res(input, ABS_HAT2Y, STEAM_TRIGGER_RESOLUTION);
- input_abs_set_res(input, ABS_HAT2X, STEAM_TRIGGER_RESOLUTION);
+
+#ifdef CONFIG_STEAM_FF
+ if (steam->quirks & STEAM_QUIRK_DECK) {
+ input_set_capability(input, EV_FF, FF_RUMBLE);
+ ret = input_ff_create_memless(input, NULL, steam_play_effect);
+ if (ret)
+ goto input_register_fail;
+ }
+#endif
ret = input_register_device(input);
if (ret)
@@ -612,6 +738,22 @@ static bool steam_is_valve_interface(struct hid_device *hdev)
return !list_empty(&rep_enum->report_list);
}
+static void steam_lizard_mode_heartbeat(struct work_struct *work)
+{
+ struct steam_device *steam = container_of(work, struct steam_device,
+ heartbeat.work);
+
+ mutex_lock(&steam->mutex);
+ if (!steam->client_opened && steam->client_hdev) {
+ steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
+ steam_write_registers(steam,
+ STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
+ 0);
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
+ }
+ mutex_unlock(&steam->mutex);
+}
+
static int steam_client_ll_parse(struct hid_device *hdev)
{
struct steam_device *steam = hdev->driver_data;
@@ -674,7 +816,7 @@ static int steam_client_ll_raw_request(struct hid_device *hdev,
report_type, reqtype);
}
-static struct hid_ll_driver steam_client_ll_driver = {
+static const struct hid_ll_driver steam_client_ll_driver = {
.parse = steam_client_ll_parse,
.start = steam_client_ll_start,
.stop = steam_client_ll_stop,
@@ -750,6 +892,8 @@ static int steam_probe(struct hid_device *hdev,
steam->quirks = id->driver_data;
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
INIT_LIST_HEAD(&steam->list);
+ INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
+ INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
steam->client_hdev = steam_create_client_hid(hdev);
if (IS_ERR(steam->client_hdev)) {
@@ -805,6 +949,8 @@ hid_hw_start_fail:
hid_destroy_device(steam->client_hdev);
client_hdev_fail:
cancel_work_sync(&steam->work_connect);
+ cancel_delayed_work_sync(&steam->heartbeat);
+ cancel_work_sync(&steam->rumble_work);
steam_alloc_fail:
hid_err(hdev, "%s: failed with error %d\n",
__func__, ret);
@@ -821,7 +967,11 @@ static void steam_remove(struct hid_device *hdev)
}
hid_destroy_device(steam->client_hdev);
+ mutex_lock(&steam->mutex);
+ steam->client_hdev = NULL;
steam->client_opened = false;
+ cancel_delayed_work_sync(&steam->heartbeat);
+ mutex_unlock(&steam->mutex);
cancel_work_sync(&steam->work_connect);
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
hid_info(hdev, "Steam wireless receiver disconnected");
@@ -906,10 +1056,10 @@ static inline s16 steam_le16(u8 *data)
* 8.5 | BTN_B | button B
* 8.6 | BTN_X | button X
* 8.7 | BTN_A | button A
- * 9.0 | BTN_DPAD_UP | lef-pad up
- * 9.1 | BTN_DPAD_RIGHT | lef-pad right
- * 9.2 | BTN_DPAD_LEFT | lef-pad left
- * 9.3 | BTN_DPAD_DOWN | lef-pad down
+ * 9.0 | BTN_DPAD_UP | left-pad up
+ * 9.1 | BTN_DPAD_RIGHT | left-pad right
+ * 9.2 | BTN_DPAD_LEFT | left-pad left
+ * 9.3 | BTN_DPAD_DOWN | left-pad down
* 9.4 | BTN_SELECT | menu left
* 9.5 | BTN_MODE | steam logo
* 9.6 | BTN_START | menu right
@@ -994,6 +1144,172 @@ static void steam_do_input_event(struct steam_device *steam,
}
/*
+ * The size for this message payload is 56.
+ * The known values are:
+ * Offset| Type | Mapped to |Meaning
+ * -------+-------+-----------+--------------------------
+ * 4-7 | u32 | -- | sequence number
+ * 8-15 | u64 | see below | buttons
+ * 16-17 | s16 | ABS_HAT0X | left-pad X value
+ * 18-19 | s16 | ABS_HAT0Y | left-pad Y value
+ * 20-21 | s16 | ABS_HAT1X | right-pad X value
+ * 22-23 | s16 | ABS_HAT1Y | right-pad Y value
+ * 24-25 | s16 | -- | accelerometer X value
+ * 26-27 | s16 | -- | accelerometer Y value
+ * 28-29 | s16 | -- | accelerometer Z value
+ * 30-31 | s16 | -- | gyro X value
+ * 32-33 | s16 | -- | gyro Y value
+ * 34-35 | s16 | -- | gyro Z value
+ * 36-37 | s16 | -- | quaternion W value
+ * 38-39 | s16 | -- | quaternion X value
+ * 40-41 | s16 | -- | quaternion Y value
+ * 42-43 | s16 | -- | quaternion Z value
+ * 44-45 | u16 | ABS_HAT2Y | left trigger (uncalibrated)
+ * 46-47 | u16 | ABS_HAT2X | right trigger (uncalibrated)
+ * 48-49 | s16 | ABS_X | left joystick X
+ * 50-51 | s16 | ABS_Y | left joystick Y
+ * 52-53 | s16 | ABS_RX | right joystick X
+ * 54-55 | s16 | ABS_RY | right joystick Y
+ * 56-57 | u16 | -- | left pad pressure
+ * 58-59 | u16 | -- | right pad pressure
+ *
+ * The buttons are:
+ * Bit | Mapped to | Description
+ * ------+------------+--------------------------------
+ * 8.0 | BTN_TR2 | right trigger fully pressed
+ * 8.1 | BTN_TL2 | left trigger fully pressed
+ * 8.2 | BTN_TR | right shoulder
+ * 8.3 | BTN_TL | left shoulder
+ * 8.4 | BTN_Y | button Y
+ * 8.5 | BTN_B | button B
+ * 8.6 | BTN_X | button X
+ * 8.7 | BTN_A | button A
+ * 9.0 | BTN_DPAD_UP | left-pad up
+ * 9.1 | BTN_DPAD_RIGHT | left-pad right
+ * 9.2 | BTN_DPAD_LEFT | left-pad left
+ * 9.3 | BTN_DPAD_DOWN | left-pad down
+ * 9.4 | BTN_SELECT | menu left
+ * 9.5 | BTN_MODE | steam logo
+ * 9.6 | BTN_START | menu right
+ * 9.7 | BTN_TRIGGER_HAPPY3 | left bottom grip button
+ * 10.0 | BTN_TRIGGER_HAPPY4 | right bottom grip button
+ * 10.1 | BTN_THUMB | left pad pressed
+ * 10.2 | BTN_THUMB2 | right pad pressed
+ * 10.3 | -- | left pad touched
+ * 10.4 | -- | right pad touched
+ * 10.5 | -- | unknown
+ * 10.6 | BTN_THUMBL | left joystick clicked
+ * 10.7 | -- | unknown
+ * 11.0 | -- | unknown
+ * 11.1 | -- | unknown
+ * 11.2 | BTN_THUMBR | right joystick clicked
+ * 11.3 | -- | unknown
+ * 11.4 | -- | unknown
+ * 11.5 | -- | unknown
+ * 11.6 | -- | unknown
+ * 11.7 | -- | unknown
+ * 12.0 | -- | unknown
+ * 12.1 | -- | unknown
+ * 12.2 | -- | unknown
+ * 12.3 | -- | unknown
+ * 12.4 | -- | unknown
+ * 12.5 | -- | unknown
+ * 12.6 | -- | unknown
+ * 12.7 | -- | unknown
+ * 13.0 | -- | unknown
+ * 13.1 | BTN_TRIGGER_HAPPY1 | left top grip button
+ * 13.2 | BTN_TRIGGER_HAPPY2 | right top grip button
+ * 13.3 | -- | unknown
+ * 13.4 | -- | unknown
+ * 13.5 | -- | unknown
+ * 13.6 | -- | left joystick touched
+ * 13.7 | -- | right joystick touched
+ * 14.0 | -- | unknown
+ * 14.1 | -- | unknown
+ * 14.2 | BTN_BASE | quick access button
+ * 14.3 | -- | unknown
+ * 14.4 | -- | unknown
+ * 14.5 | -- | unknown
+ * 14.6 | -- | unknown
+ * 14.7 | -- | unknown
+ * 15.0 | -- | unknown
+ * 15.1 | -- | unknown
+ * 15.2 | -- | unknown
+ * 15.3 | -- | unknown
+ * 15.4 | -- | unknown
+ * 15.5 | -- | unknown
+ * 15.6 | -- | unknown
+ * 15.7 | -- | unknown
+ */
+static void steam_do_deck_input_event(struct steam_device *steam,
+ struct input_dev *input, u8 *data)
+{
+ u8 b8, b9, b10, b11, b13, b14;
+ bool lpad_touched, rpad_touched;
+
+ b8 = data[8];
+ b9 = data[9];
+ b10 = data[10];
+ b11 = data[11];
+ b13 = data[13];
+ b14 = data[14];
+
+ lpad_touched = b10 & BIT(3);
+ rpad_touched = b10 & BIT(4);
+
+ if (lpad_touched) {
+ input_report_abs(input, ABS_HAT0X, steam_le16(data + 16));
+ input_report_abs(input, ABS_HAT0Y, steam_le16(data + 18));
+ } else {
+ input_report_abs(input, ABS_HAT0X, 0);
+ input_report_abs(input, ABS_HAT0Y, 0);
+ }
+
+ if (rpad_touched) {
+ input_report_abs(input, ABS_HAT1X, steam_le16(data + 20));
+ input_report_abs(input, ABS_HAT1Y, steam_le16(data + 22));
+ } else {
+ input_report_abs(input, ABS_HAT1X, 0);
+ input_report_abs(input, ABS_HAT1Y, 0);
+ }
+
+ input_report_abs(input, ABS_X, steam_le16(data + 48));
+ input_report_abs(input, ABS_Y, -steam_le16(data + 50));
+ input_report_abs(input, ABS_RX, steam_le16(data + 52));
+ input_report_abs(input, ABS_RY, -steam_le16(data + 54));
+
+ input_report_abs(input, ABS_HAT2Y, steam_le16(data + 44));
+ input_report_abs(input, ABS_HAT2X, steam_le16(data + 46));
+
+ input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0)));
+ input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1)));
+ input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2)));
+ input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3)));
+ input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4)));
+ input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5)));
+ input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6)));
+ input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7)));
+ input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4)));
+ input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5)));
+ input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6)));
+ input_event(input, EV_KEY, BTN_TRIGGER_HAPPY3, !!(b9 & BIT(7)));
+ input_event(input, EV_KEY, BTN_TRIGGER_HAPPY4, !!(b10 & BIT(0)));
+ input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6)));
+ input_event(input, EV_KEY, BTN_THUMBR, !!(b11 & BIT(2)));
+ input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0)));
+ input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1)));
+ input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2)));
+ input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3)));
+ input_event(input, EV_KEY, BTN_THUMB, !!(b10 & BIT(1)));
+ input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(2)));
+ input_event(input, EV_KEY, BTN_TRIGGER_HAPPY1, !!(b13 & BIT(1)));
+ input_event(input, EV_KEY, BTN_TRIGGER_HAPPY2, !!(b13 & BIT(2)));
+ input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2)));
+
+ input_sync(input);
+}
+
+/*
* The size for this message payload is 11.
* The known values are:
* Offset| Type | Meaning
@@ -1052,6 +1368,7 @@ static int steam_raw_event(struct hid_device *hdev,
* 0x01: input data (60 bytes)
* 0x03: wireless connect/disconnect (1 byte)
* 0x04: battery status (11 bytes)
+ * 0x09: Steam Deck input data (56 bytes)
*/
if (size != 64 || data[0] != 1 || data[1] != 0)
@@ -1067,6 +1384,15 @@ static int steam_raw_event(struct hid_device *hdev,
steam_do_input_event(steam, input, data);
rcu_read_unlock();
break;
+ case STEAM_EV_DECK_INPUT_DATA:
+ if (steam->client_opened)
+ return 0;
+ rcu_read_lock();
+ input = rcu_dereference(steam->input);
+ if (likely(input))
+ steam_do_deck_input_event(steam, input, data);
+ rcu_read_unlock();
+ break;
case STEAM_EV_CONNECT:
/*
* The payload of this event is a single byte:
@@ -1141,6 +1467,11 @@ static const struct hid_device_id steam_controllers[] = {
USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS),
.driver_data = STEAM_QUIRK_WIRELESS
},
+ { /* Steam Deck */
+ HID_USB_DEVICE(USB_VENDOR_ID_VALVE,
+ USB_DEVICE_ID_STEAM_DECK),
+ .driver_data = STEAM_QUIRK_DECK
+ },
{}
};
diff --git a/drivers/hid/hid-u2fzero.c b/drivers/hid/hid-u2fzero.c
index ad489caf53ad..744a91e6e78c 100644
--- a/drivers/hid/hid-u2fzero.c
+++ b/drivers/hid/hid-u2fzero.c
@@ -261,7 +261,6 @@ static int u2fzero_init_hwrng(struct u2fzero_device *dev,
dev->hwrng.name = dev->rng_name;
dev->hwrng.read = u2fzero_rng_read;
- dev->hwrng.quality = 1;
return devm_hwrng_register(&dev->hdev->dev, &dev->hwrng);
}
diff --git a/drivers/hid/hid-uclogic-core-test.c b/drivers/hid/hid-uclogic-core-test.c
new file mode 100644
index 000000000000..2bb916226a38
--- /dev/null
+++ b/drivers/hid/hid-uclogic-core-test.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ *
+ * Copyright (c) 2022 José Expósito <[email protected]>
+ */
+
+#include <kunit/test.h>
+#include "./hid-uclogic-params.h"
+
+#define MAX_EVENT_SIZE 12
+
+struct uclogic_raw_event_hook_test {
+ u8 event[MAX_EVENT_SIZE];
+ size_t size;
+ bool expected;
+};
+
+static struct uclogic_raw_event_hook_test hook_events[] = {
+ {
+ .event = { 0xA1, 0xB2, 0xC3, 0xD4 },
+ .size = 4,
+ },
+ {
+ .event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A },
+ .size = 6,
+ },
+};
+
+static struct uclogic_raw_event_hook_test test_events[] = {
+ {
+ .event = { 0xA1, 0xB2, 0xC3, 0xD4 },
+ .size = 4,
+ .expected = true,
+ },
+ {
+ .event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A },
+ .size = 6,
+ .expected = true,
+ },
+ {
+ .event = { 0xA1, 0xB2, 0xC3 },
+ .size = 3,
+ .expected = false,
+ },
+ {
+ .event = { 0xA1, 0xB2, 0xC3, 0xD4, 0x00 },
+ .size = 5,
+ .expected = false,
+ },
+ {
+ .event = { 0x2E, 0x3D, 0x4C, 0x5B, 0x6A, 0x1F },
+ .size = 6,
+ .expected = false,
+ },
+};
+
+static void hid_test_uclogic_exec_event_hook_test(struct kunit *test)
+{
+ struct uclogic_params p = {0, };
+ struct uclogic_raw_event_hook *filter;
+ bool res;
+ int n;
+
+ /* Initialize the list of events to hook */
+ p.event_hooks = kunit_kzalloc(test, sizeof(*p.event_hooks), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p.event_hooks);
+ INIT_LIST_HEAD(&p.event_hooks->list);
+
+ for (n = 0; n < ARRAY_SIZE(hook_events); n++) {
+ filter = kunit_kzalloc(test, sizeof(*filter), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter);
+
+ filter->size = hook_events[n].size;
+ filter->event = kunit_kzalloc(test, filter->size, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter->event);
+ memcpy(filter->event, &hook_events[n].event[0], filter->size);
+
+ list_add_tail(&filter->list, &p.event_hooks->list);
+ }
+
+ /* Test uclogic_exec_event_hook() */
+ for (n = 0; n < ARRAY_SIZE(test_events); n++) {
+ res = uclogic_exec_event_hook(&p, &test_events[n].event[0],
+ test_events[n].size);
+ KUNIT_ASSERT_EQ(test, res, test_events[n].expected);
+ }
+}
+
+static struct kunit_case hid_uclogic_core_test_cases[] = {
+ KUNIT_CASE(hid_test_uclogic_exec_event_hook_test),
+ {}
+};
+
+static struct kunit_suite hid_uclogic_core_test_suite = {
+ .name = "hid_uclogic_core_test",
+ .test_cases = hid_uclogic_core_test_cases,
+};
+
+kunit_test_suite(hid_uclogic_core_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the UC-Logic driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("José Expósito <[email protected]>");
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index 0fbc408c2607..f67835f9ed4c 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -22,25 +22,6 @@
#include "hid-ids.h"
-/* Driver data */
-struct uclogic_drvdata {
- /* Interface parameters */
- struct uclogic_params params;
- /* Pointer to the replacement report descriptor. NULL if none. */
- __u8 *desc_ptr;
- /*
- * Size of the replacement report descriptor.
- * Only valid if desc_ptr is not NULL
- */
- unsigned int desc_size;
- /* Pen input device */
- struct input_dev *pen_input;
- /* In-range timer */
- struct timer_list inrange_timer;
- /* Last rotary encoder state, or U8_MAX for none */
- u8 re_state;
-};
-
/**
* uclogic_inrange_timeout - handle pen in-range state timeout.
* Emulate input events normally generated when pen goes out of range for
@@ -192,6 +173,7 @@ static int uclogic_probe(struct hid_device *hdev,
* than the pen, so use QUIRK_MULTI_INPUT for all tablets.
*/
hdev->quirks |= HID_QUIRK_MULTI_INPUT;
+ hdev->quirks |= HID_QUIRK_HIDINPUT_FORCE;
/* Allocate and assign driver data */
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
@@ -201,6 +183,7 @@ static int uclogic_probe(struct hid_device *hdev,
}
timer_setup(&drvdata->inrange_timer, uclogic_inrange_timeout, 0);
drvdata->re_state = U8_MAX;
+ drvdata->quirks = id->driver_data;
hid_set_drvdata(hdev, drvdata);
/* Initialize the device and retrieve interface parameters */
@@ -267,6 +250,34 @@ static int uclogic_resume(struct hid_device *hdev)
#endif
/**
+ * uclogic_exec_event_hook - if the received event is hooked schedules the
+ * associated work.
+ *
+ * @p: Tablet interface report parameters.
+ * @event: Raw event.
+ * @size: The size of event.
+ *
+ * Returns:
+ * Whether the event was hooked or not.
+ */
+static bool uclogic_exec_event_hook(struct uclogic_params *p, u8 *event, int size)
+{
+ struct uclogic_raw_event_hook *curr;
+
+ if (!p->event_hooks)
+ return false;
+
+ list_for_each_entry(curr, &p->event_hooks->list, list) {
+ if (curr->size == size && memcmp(curr->event, event, size) == 0) {
+ schedule_work(&curr->work);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
* uclogic_raw_event_pen - handle raw pen events (pen HID reports).
*
* @drvdata: Driver data.
@@ -424,6 +435,9 @@ static int uclogic_raw_event(struct hid_device *hdev,
if (report->type != HID_INPUT_REPORT)
return 0;
+ if (uclogic_exec_event_hook(params, data, size))
+ return 0;
+
while (true) {
/* Tweak pen reports, if necessary */
if ((report_id == params->pen.id) && (size >= 2)) {
@@ -525,10 +539,18 @@ static const struct hid_device_id uclogic_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2) },
+ { 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_PRO_MW),
+ .driver_data = UCLOGIC_MOUSE_FRAME_QUIRK | UCLOGIC_BATTERY_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW),
+ .driver_data = UCLOGIC_MOUSE_FRAME_QUIRK | UCLOGIC_BATTERY_QUIRK },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06) },
{ }
};
@@ -553,3 +575,7 @@ module_hid_driver(uclogic_driver);
MODULE_AUTHOR("Martin Rusko");
MODULE_AUTHOR("Nikolai Kondrashov");
MODULE_LICENSE("GPL");
+
+#ifdef CONFIG_HID_KUNIT_TEST
+#include "hid-uclogic-core-test.c"
+#endif
diff --git a/drivers/hid/hid-uclogic-params-test.c b/drivers/hid/hid-uclogic-params-test.c
index 57ef5d3e4b74..678f50cbb160 100644
--- a/drivers/hid/hid-uclogic-params-test.c
+++ b/drivers/hid/hid-uclogic-params-test.c
@@ -136,7 +136,7 @@ static void uclogic_parse_ugee_v2_desc_case_desc(struct uclogic_parse_ugee_v2_de
KUNIT_ARRAY_PARAM(uclogic_parse_ugee_v2_desc, uclogic_parse_ugee_v2_desc_cases,
uclogic_parse_ugee_v2_desc_case_desc);
-static void uclogic_parse_ugee_v2_desc_test(struct kunit *test)
+static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test)
{
int res;
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
@@ -174,9 +174,25 @@ static void uclogic_parse_ugee_v2_desc_test(struct kunit *test)
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[] = {
- KUNIT_CASE_PARAM(uclogic_parse_ugee_v2_desc_test,
+ KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc,
uclogic_parse_ugee_v2_desc_gen_params),
+ KUNIT_CASE(hid_test_uclogic_params_cleanup_event_hooks),
{}
};
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 34fa991e6267..9859dad36495 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -18,6 +18,7 @@
#include "usbhid/usbhid.h"
#include "hid-ids.h"
#include <linux/ctype.h>
+#include <linux/string.h>
#include <asm/unaligned.h>
/**
@@ -615,6 +616,31 @@ cleanup:
}
/**
+ * uclogic_params_cleanup_event_hooks - free resources used by the list of raw
+ * event hooks.
+ * Can be called repeatedly.
+ *
+ * @params: Input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_cleanup_event_hooks(struct uclogic_params *params)
+{
+ struct uclogic_raw_event_hook *curr, *n;
+
+ if (!params || !params->event_hooks)
+ return;
+
+ list_for_each_entry_safe(curr, n, &params->event_hooks->list, list) {
+ cancel_work_sync(&curr->work);
+ list_del(&curr->list);
+ kfree(curr->event);
+ kfree(curr);
+ }
+
+ kfree(params->event_hooks);
+ params->event_hooks = NULL;
+}
+
+/**
* uclogic_params_cleanup - free resources used by struct uclogic_params
* (tablet interface's parameters).
* Can be called repeatedly.
@@ -630,6 +656,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
uclogic_params_frame_cleanup(&params->frame_list[i]);
+ uclogic_params_cleanup_event_hooks(params);
memset(params, 0, sizeof(*params));
}
}
@@ -1020,8 +1047,8 @@ cleanup:
* Returns:
* Zero, if successful. A negative errno code on error.
*/
-static int uclogic_probe_interface(struct hid_device *hdev, u8 *magic_arr,
- int magic_size, int endpoint)
+static int uclogic_probe_interface(struct hid_device *hdev, const u8 *magic_arr,
+ size_t magic_size, int endpoint)
{
struct usb_device *udev;
unsigned int pipe = 0;
@@ -1212,6 +1239,140 @@ static int uclogic_params_ugee_v2_init_frame_mouse(struct uclogic_params *p)
}
/**
+ * uclogic_params_ugee_v2_has_battery() - check whether a UGEE v2 device has
+ * battery or not.
+ * @hdev: The HID device of the tablet interface.
+ *
+ * Returns:
+ * True if the device has battery, false otherwise.
+ */
+static bool uclogic_params_ugee_v2_has_battery(struct hid_device *hdev)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (drvdata->quirks & UCLOGIC_BATTERY_QUIRK)
+ return true;
+
+ /* The XP-PEN Deco LW vendor, product and version are identical to the
+ * Deco L. The only difference reported by their firmware is the product
+ * name. Add a quirk to support battery reporting on the wireless
+ * version.
+ */
+ if (hdev->vendor == USB_VENDOR_ID_UGEE &&
+ hdev->product == USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L) {
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+
+ if (strstarts(udev->product, "Deco LW"))
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * uclogic_params_ugee_v2_init_battery() - initialize UGEE v2 battery reporting.
+ * @hdev: The HID device of the tablet interface, cannot be NULL.
+ * @p: Parameters to fill in, cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
+ struct uclogic_params *p)
+{
+ int rc = 0;
+
+ if (!hdev || !p)
+ return -EINVAL;
+
+ /* Some tablets contain invalid characters in hdev->uniq, throwing a
+ * "hwmon: '<name>' is not a valid name attribute, please fix" error.
+ * Use the device vendor and product IDs instead.
+ */
+ snprintf(hdev->uniq, sizeof(hdev->uniq), "%x-%x", hdev->vendor,
+ hdev->product);
+
+ rc = uclogic_params_frame_init_with_desc(&p->frame_list[1],
+ uclogic_rdesc_ugee_v2_battery_template_arr,
+ uclogic_rdesc_ugee_v2_battery_template_size,
+ UCLOGIC_RDESC_UGEE_V2_BATTERY_ID);
+ if (rc)
+ return rc;
+
+ p->frame_list[1].suffix = "Battery";
+ p->pen.subreport_list[1].value = 0xf2;
+ p->pen.subreport_list[1].id = UCLOGIC_RDESC_UGEE_V2_BATTERY_ID;
+
+ return rc;
+}
+
+/**
+ * uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
+ * connection to the USB dongle and reconnects, either because of its physical
+ * distance or because it was switches off and on using the frame's switch,
+ * uclogic_probe_interface() needs to be called again to enable the tablet.
+ *
+ * @work: The work that triggered this function.
+ */
+static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
+{
+ struct uclogic_raw_event_hook *event_hook;
+
+ event_hook = container_of(work, struct uclogic_raw_event_hook, work);
+ uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
+}
+
+/**
+ * uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
+ * to be hooked for UGEE v2 devices.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from.
+ * @p: Parameters to fill in, cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
+ struct uclogic_params *p)
+{
+ struct uclogic_raw_event_hook *event_hook;
+ __u8 reconnect_event[] = {
+ /* Event received on wireless tablet reconnection */
+ 0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ if (!p)
+ return -EINVAL;
+
+ /* The reconnection event is only received if the tablet has battery */
+ if (!uclogic_params_ugee_v2_has_battery(hdev))
+ return 0;
+
+ p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
+ if (!p->event_hooks)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&p->event_hooks->list);
+
+ event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
+ if (!event_hook)
+ return -ENOMEM;
+
+ INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
+ event_hook->hdev = hdev;
+ event_hook->size = ARRAY_SIZE(reconnect_event);
+ event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
+ if (!event_hook->event)
+ return -ENOMEM;
+
+ list_add_tail(&event_hook->list, &p->event_hooks->list);
+
+ return 0;
+}
+
+/**
* uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
* discovering their parameters.
*
@@ -1234,6 +1395,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
struct hid_device *hdev)
{
int rc = 0;
+ struct uclogic_drvdata *drvdata;
struct usb_interface *iface;
__u8 bInterfaceNumber;
const int str_desc_len = 12;
@@ -1241,9 +1403,6 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
__u8 *rdesc_pen = NULL;
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
enum uclogic_params_frame_type frame_type;
- __u8 magic_arr[] = {
- 0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
/* The resulting parameters (noop) */
struct uclogic_params p = {0, };
@@ -1252,6 +1411,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
goto cleanup;
}
+ drvdata = hid_get_drvdata(hdev);
iface = to_usb_interface(hdev->dev.parent);
bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
@@ -1273,7 +1433,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
* The specific data was discovered by sniffing the Windows driver
* traffic.
*/
- rc = uclogic_probe_interface(hdev, magic_arr, sizeof(magic_arr), 0x03);
+ rc = uclogic_probe_interface(hdev, uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
if (rc) {
uclogic_params_init_invalid(&p);
goto output;
@@ -1318,6 +1480,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
/* Initialize the frame interface */
+ if (drvdata->quirks & UCLOGIC_MOUSE_FRAME_QUIRK)
+ frame_type = UCLOGIC_PARAMS_FRAME_MOUSE;
+
switch (frame_type) {
case UCLOGIC_PARAMS_FRAME_DIAL:
case UCLOGIC_PARAMS_FRAME_MOUSE:
@@ -1334,6 +1499,22 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
if (rc)
goto cleanup;
+ /* Initialize the battery interface*/
+ if (uclogic_params_ugee_v2_has_battery(hdev)) {
+ rc = uclogic_params_ugee_v2_init_battery(hdev, &p);
+ if (rc) {
+ hid_err(hdev, "error initializing battery: %d\n", rc);
+ goto cleanup;
+ }
+ }
+
+ /* Create a list of raw events to be ignored */
+ rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
+ if (rc) {
+ hid_err(hdev, "error initializing event hook list: %d\n", rc);
+ goto cleanup;
+ }
+
output:
/* Output parameters */
memcpy(params, &p, sizeof(*params));
@@ -1583,9 +1764,15 @@ int uclogic_params_init(struct uclogic_params *params,
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_PARBLO_A610_PRO):
case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2):
+ case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW):
+ case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S):
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW):
rc = uclogic_params_ugee_v2_init(&p, hdev);
if (rc != 0)
goto cleanup;
diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h
index a97477c02ff8..d6ffadb2f601 100644
--- a/drivers/hid/hid-uclogic-params.h
+++ b/drivers/hid/hid-uclogic-params.h
@@ -18,6 +18,10 @@
#include <linux/usb.h>
#include <linux/hid.h>
+#include <linux/list.h>
+
+#define UCLOGIC_MOUSE_FRAME_QUIRK BIT(0)
+#define UCLOGIC_BATTERY_QUIRK BIT(1)
/* Types of pen in-range reporting */
enum uclogic_params_pen_inrange {
@@ -174,6 +178,17 @@ struct uclogic_params_frame {
};
/*
+ * List of works to be performed when a certain raw event is received.
+ */
+struct uclogic_raw_event_hook {
+ struct hid_device *hdev;
+ __u8 *event;
+ size_t size;
+ struct work_struct work;
+ struct list_head list;
+};
+
+/*
* Tablet interface report parameters.
*
* Must use declarative (descriptive) language, not imperative, to simplify
@@ -213,6 +228,31 @@ struct uclogic_params {
* parts. Only valid, if "invalid" is false.
*/
struct uclogic_params_frame frame_list[3];
+ /*
+ * List of event hooks.
+ */
+ struct uclogic_raw_event_hook *event_hooks;
+};
+
+/* Driver data */
+struct uclogic_drvdata {
+ /* Interface parameters */
+ struct uclogic_params params;
+ /* Pointer to the replacement report descriptor. NULL if none. */
+ __u8 *desc_ptr;
+ /*
+ * Size of the replacement report descriptor.
+ * Only valid if desc_ptr is not NULL
+ */
+ unsigned int desc_size;
+ /* Pen input device */
+ struct input_dev *pen_input;
+ /* In-range timer */
+ struct timer_list inrange_timer;
+ /* Last rotary encoder state, or U8_MAX for none */
+ u8 re_state;
+ /* Device quirks */
+ unsigned long quirks;
};
/* Initialize a tablet interface and discover its parameters */
diff --git a/drivers/hid/hid-uclogic-rdesc-test.c b/drivers/hid/hid-uclogic-rdesc-test.c
index 3971a0854c3e..90bf4e586e01 100644
--- a/drivers/hid/hid-uclogic-rdesc-test.c
+++ b/drivers/hid/hid-uclogic-rdesc-test.c
@@ -187,7 +187,7 @@ static void uclogic_template_case_desc(struct uclogic_template_case *t,
KUNIT_ARRAY_PARAM(uclogic_template, uclogic_template_cases,
uclogic_template_case_desc);
-static void uclogic_template_test(struct kunit *test)
+static void hid_test_uclogic_template(struct kunit *test)
{
__u8 *res;
const struct uclogic_template_case *params = test->param_value;
@@ -197,13 +197,12 @@ static void uclogic_template_test(struct kunit *test)
params->param_list,
params->param_num);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, res);
- KUNIT_EXPECT_EQ(test, 0,
- memcmp(res, params->expected, params->template_size));
+ KUNIT_EXPECT_MEMEQ(test, res, params->expected, params->template_size);
kfree(res);
}
static struct kunit_case hid_uclogic_rdesc_test_cases[] = {
- KUNIT_CASE_PARAM(uclogic_template_test, uclogic_template_gen_params),
+ KUNIT_CASE_PARAM(hid_test_uclogic_template, uclogic_template_gen_params),
{}
};
diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c
index 4bd54c4fb5b0..b6dfdf6356a6 100644
--- a/drivers/hid/hid-uclogic-rdesc.c
+++ b/drivers/hid/hid-uclogic-rdesc.c
@@ -859,6 +859,12 @@ const __u8 uclogic_rdesc_v2_frame_dial_arr[] = {
const size_t uclogic_rdesc_v2_frame_dial_size =
sizeof(uclogic_rdesc_v2_frame_dial_arr);
+const __u8 uclogic_ugee_v2_probe_arr[] = {
+ 0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+const size_t uclogic_ugee_v2_probe_size = sizeof(uclogic_ugee_v2_probe_arr);
+const int uclogic_ugee_v2_probe_endpoint = 0x03;
+
/* Fixed report descriptor template for UGEE v2 pen reports */
const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[] = {
0x05, 0x0d, /* Usage Page (Digitizers), */
@@ -1035,6 +1041,40 @@ const __u8 uclogic_rdesc_ugee_v2_frame_mouse_template_arr[] = {
const size_t uclogic_rdesc_ugee_v2_frame_mouse_template_size =
sizeof(uclogic_rdesc_ugee_v2_frame_mouse_template_arr);
+/* Fixed report descriptor template for UGEE v2 battery reports */
+const __u8 uclogic_rdesc_ugee_v2_battery_template_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, UCLOGIC_RDESC_UGEE_V2_BATTERY_ID,
+ /* Report ID, */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x05, 0x84, /* Usage Page (Power Device), */
+ 0x05, 0x85, /* Usage Page (Battery System), */
+ 0x09, 0x65, /* Usage Page (AbsoluteStateOfCharge), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x26, 0xff, 0x00, /* Logical Maximum (255), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x15, 0x00, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x09, 0x44, /* Usage Page (Charging), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x07, /* Report Count (7), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x07, /* Report Count (7), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0xC0 /* End Collection */
+};
+const size_t uclogic_rdesc_ugee_v2_battery_template_size =
+ sizeof(uclogic_rdesc_ugee_v2_battery_template_arr);
+
/* Fixed report descriptor for Ugee EX07 frame */
const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = {
0x05, 0x01, /* Usage Page (Desktop), */
@@ -1193,7 +1233,7 @@ __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr,
p[sizeof(btn_head)] < param_num) {
v = param_list[p[sizeof(btn_head)]];
put_unaligned((__u8)0x2A, p); /* Usage Maximum */
- put_unaligned_le16((__force u16)cpu_to_le16(v), p + 1);
+ put_unaligned((__force u16)cpu_to_le16(v), (s16 *)(p + 1));
p += sizeof(btn_head) + 1;
} else {
p++;
diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
index 0502a0656496..906d068f50a9 100644
--- a/drivers/hid/hid-uclogic-rdesc.h
+++ b/drivers/hid/hid-uclogic-rdesc.h
@@ -161,6 +161,14 @@ extern const size_t uclogic_rdesc_v2_frame_dial_size;
/* Device ID byte offset in v2 frame dial reports */
#define UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE 0x4
+/* Report ID for tweaked UGEE v2 battery reports */
+#define UCLOGIC_RDESC_UGEE_V2_BATTERY_ID 0xba
+
+/* Magic data expected by UGEEv2 devices on probe */
+extern const __u8 uclogic_ugee_v2_probe_arr[];
+extern const size_t uclogic_ugee_v2_probe_size;
+extern const int uclogic_ugee_v2_probe_endpoint;
+
/* Fixed report descriptor template for UGEE v2 pen reports */
extern const __u8 uclogic_rdesc_ugee_v2_pen_template_arr[];
extern const size_t uclogic_rdesc_ugee_v2_pen_template_size;
@@ -177,6 +185,10 @@ extern const size_t uclogic_rdesc_ugee_v2_frame_dial_template_size;
extern const __u8 uclogic_rdesc_ugee_v2_frame_mouse_template_arr[];
extern const size_t uclogic_rdesc_ugee_v2_frame_mouse_template_size;
+/* Fixed report descriptor template for UGEE v2 battery reports */
+extern const __u8 uclogic_rdesc_ugee_v2_battery_template_arr[];
+extern const size_t uclogic_rdesc_ugee_v2_battery_template_size;
+
/* Fixed report descriptor for Ugee EX07 frame */
extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[];
extern const size_t uclogic_rdesc_ugee_ex07_frame_size;
diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c
index 4399d6c6afef..26167cfb696f 100644
--- a/drivers/hid/hid-wiimote-core.c
+++ b/drivers/hid/hid-wiimote-core.c
@@ -458,6 +458,9 @@ static __u8 wiimote_cmd_read_ext(struct wiimote_data *wdata, __u8 *rmem)
if (rmem[0] == 0x00 && rmem[1] == 0x00 &&
rmem[4] == 0x01 && rmem[5] == 0x03)
return WIIMOTE_EXT_GUITAR;
+ if (rmem[0] == 0x03 && rmem[1] == 0x00 &&
+ rmem[4] == 0x01 && rmem[5] == 0x03)
+ return WIIMOTE_EXT_TURNTABLE;
return WIIMOTE_EXT_UNKNOWN;
}
@@ -495,6 +498,7 @@ static bool wiimote_cmd_map_mp(struct wiimote_data *wdata, __u8 exttype)
case WIIMOTE_EXT_GUITAR:
wmem = 0x07;
break;
+ case WIIMOTE_EXT_TURNTABLE:
case WIIMOTE_EXT_NUNCHUK:
wmem = 0x05;
break;
@@ -1082,6 +1086,7 @@ static const char *wiimote_exttype_names[WIIMOTE_EXT_NUM] = {
[WIIMOTE_EXT_PRO_CONTROLLER] = "Nintendo Wii U Pro Controller",
[WIIMOTE_EXT_DRUMS] = "Nintendo Wii Drums",
[WIIMOTE_EXT_GUITAR] = "Nintendo Wii Guitar",
+ [WIIMOTE_EXT_TURNTABLE] = "Nintendo Wii Turntable"
};
/*
@@ -1669,6 +1674,8 @@ static ssize_t wiimote_ext_show(struct device *dev,
return sprintf(buf, "drums\n");
case WIIMOTE_EXT_GUITAR:
return sprintf(buf, "guitar\n");
+ case WIIMOTE_EXT_TURNTABLE:
+ return sprintf(buf, "turntable\n");
case WIIMOTE_EXT_UNKNOWN:
default:
return sprintf(buf, "unknown\n");
@@ -1764,7 +1771,7 @@ static void wiimote_destroy(struct wiimote_data *wdata)
spin_unlock_irqrestore(&wdata->state.lock, flags);
cancel_work_sync(&wdata->init_worker);
- del_timer_sync(&wdata->timer);
+ timer_shutdown_sync(&wdata->timer);
device_remove_file(&wdata->hdev->dev, &dev_attr_devtype);
device_remove_file(&wdata->hdev->dev, &dev_attr_extension);
diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c
index 213c58bf2495..dbccdfa63916 100644
--- a/drivers/hid/hid-wiimote-modules.c
+++ b/drivers/hid/hid-wiimote-modules.c
@@ -2403,6 +2403,230 @@ static const struct wiimod_ops wiimod_guitar = {
.in_ext = wiimod_guitar_in_ext,
};
+/*
+ * Turntable
+ * DJ Hero came with a Turntable Controller that was plugged in
+ * as an extension.
+ * We create a separate device for turntables and report all information via this
+ * input device.
+*/
+
+enum wiimod_turntable_keys {
+ WIIMOD_TURNTABLE_KEY_G_RIGHT,
+ WIIMOD_TURNTABLE_KEY_R_RIGHT,
+ WIIMOD_TURNTABLE_KEY_B_RIGHT,
+ WIIMOD_TURNTABLE_KEY_G_LEFT,
+ WIIMOD_TURNTABLE_KEY_R_LEFT,
+ WIIMOD_TURNTABLE_KEY_B_LEFT,
+ WIIMOD_TURNTABLE_KEY_EUPHORIA,
+ WIIMOD_TURNTABLE_KEY_PLUS,
+ WIIMOD_TURNTABLE_KEY_MINUS,
+ WIIMOD_TURNTABLE_KEY_NUM
+};
+
+static const __u16 wiimod_turntable_map[] = {
+ BTN_1, /* WIIMOD_TURNTABLE_KEY_G_RIGHT */
+ BTN_2, /* WIIMOD_TURNTABLE_KEY_R_RIGHT */
+ BTN_3, /* WIIMOD_TURNTABLE_KEY_B_RIGHT */
+ BTN_4, /* WIIMOD_TURNTABLE_KEY_G_LEFT */
+ BTN_5, /* WIIMOD_TURNTABLE_KEY_R_LEFT */
+ BTN_6, /* WIIMOD_TURNTABLE_KEY_B_LEFT */
+ BTN_7, /* WIIMOD_TURNTABLE_KEY_EUPHORIA */
+ BTN_START, /* WIIMOD_TURNTABLE_KEY_PLUS */
+ BTN_SELECT, /* WIIMOD_TURNTABLE_KEY_MINUS */
+};
+
+static void wiimod_turntable_in_ext(struct wiimote_data *wdata, const __u8 *ext)
+{
+ __u8 be, cs, sx, sy, ed, rtt, rbg, rbr, rbb, ltt, lbg, lbr, lbb, bp, bm;
+ /*
+ * Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 0 | RTT<4:3> | SX <5:0> |
+ * 1 | RTT<2:1> | SY <5:0> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 2 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 3 | ED<2:0> | LTT<4:0> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 4 | 0 | 0 | LBR | B- | 0 | B+ | RBR | LTT<5> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 5 | LBB | 0 | RBG | BE | LBG | RBB | 0 | 0 |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * All pressed buttons are 0
+ *
+ * With Motion+ enabled, it will look like this:
+ * Byte | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 1 | RTT<4:3> | SX <5:1> | 0 |
+ * 2 | RTT<2:1> | SY <5:1> | 0 |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 3 |RTT<0>| ED<4:3> | CS<3:0> | RTT<5> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 4 | ED<2:0> | LTT<4:0> |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 5 | 0 | 0 | LBR | B- | 0 | B+ | RBR | XXXX |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ * 6 | LBB | 0 | RBG | BE | LBG | RBB | XXXX | XXXX |
+ *------+------+-----+-----+-----+-----+------+------+--------+
+ */
+
+ be = !(ext[5] & 0x10);
+ cs = ((ext[2] & 0x1e));
+ sx = ext[0] & 0x3f;
+ sy = ext[1] & 0x3f;
+ ed = (ext[3] & 0xe0) >> 5;
+ rtt = ((ext[2] & 0x01) << 5 | (ext[0] & 0xc0) >> 3 | (ext[1] & 0xc0) >> 5 | ( ext[2] & 0x80 ) >> 7);
+ ltt = ((ext[4] & 0x01) << 5 | (ext[3] & 0x1f));
+ rbg = !(ext[5] & 0x20);
+ rbr = !(ext[4] & 0x02);
+ rbb = !(ext[5] & 0x04);
+ lbg = !(ext[5] & 0x08);
+ lbb = !(ext[5] & 0x80);
+ lbr = !(ext[4] & 0x20);
+ bm = !(ext[4] & 0x10);
+ bp = !(ext[4] & 0x04);
+
+ if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) {
+ ltt = (ext[4] & 0x01) << 5;
+ sx &= 0x3e;
+ sy &= 0x3e;
+ }
+
+ input_report_abs(wdata->extension.input, ABS_X, sx);
+ input_report_abs(wdata->extension.input, ABS_Y, sy);
+ input_report_abs(wdata->extension.input, ABS_HAT0X, rtt);
+ input_report_abs(wdata->extension.input, ABS_HAT1X, ltt);
+ input_report_abs(wdata->extension.input, ABS_HAT2X, cs);
+ input_report_abs(wdata->extension.input, ABS_HAT3X, ed);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_RIGHT],
+ rbg);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_R_RIGHT],
+ rbr);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_RIGHT],
+ rbb);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_G_LEFT],
+ lbg);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_R_LEFT],
+ lbr);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_B_LEFT],
+ lbb);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_EUPHORIA],
+ be);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_PLUS],
+ bp);
+ input_report_key(wdata->extension.input,
+ wiimod_turntable_map[WIIMOD_TURNTABLE_KEY_MINUS],
+ bm);
+
+ input_sync(wdata->extension.input);
+}
+
+static int wiimod_turntable_open(struct input_dev *dev)
+{
+ struct wiimote_data *wdata = input_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdata->state.lock, flags);
+ wdata->state.flags |= WIIPROTO_FLAG_EXT_USED;
+ wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+ spin_unlock_irqrestore(&wdata->state.lock, flags);
+
+ return 0;
+}
+
+static void wiimod_turntable_close(struct input_dev *dev)
+{
+ struct wiimote_data *wdata = input_get_drvdata(dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&wdata->state.lock, flags);
+ wdata->state.flags &= ~WIIPROTO_FLAG_EXT_USED;
+ wiiproto_req_drm(wdata, WIIPROTO_REQ_NULL);
+ spin_unlock_irqrestore(&wdata->state.lock, flags);
+}
+
+static int wiimod_turntable_probe(const struct wiimod_ops *ops,
+ struct wiimote_data *wdata)
+{
+ int ret, i;
+
+ wdata->extension.input = input_allocate_device();
+ if (!wdata->extension.input)
+ return -ENOMEM;
+
+ input_set_drvdata(wdata->extension.input, wdata);
+ wdata->extension.input->open = wiimod_turntable_open;
+ wdata->extension.input->close = wiimod_turntable_close;
+ wdata->extension.input->dev.parent = &wdata->hdev->dev;
+ wdata->extension.input->id.bustype = wdata->hdev->bus;
+ wdata->extension.input->id.vendor = wdata->hdev->vendor;
+ wdata->extension.input->id.product = wdata->hdev->product;
+ wdata->extension.input->id.version = wdata->hdev->version;
+ wdata->extension.input->name = WIIMOTE_NAME " Turntable";
+
+ set_bit(EV_KEY, wdata->extension.input->evbit);
+ for (i = 0; i < WIIMOD_TURNTABLE_KEY_NUM; ++i)
+ set_bit(wiimod_turntable_map[i],
+ wdata->extension.input->keybit);
+
+ set_bit(EV_ABS, wdata->extension.input->evbit);
+ set_bit(ABS_X, wdata->extension.input->absbit);
+ set_bit(ABS_Y, wdata->extension.input->absbit);
+ set_bit(ABS_HAT0X, wdata->extension.input->absbit);
+ set_bit(ABS_HAT1X, wdata->extension.input->absbit);
+ set_bit(ABS_HAT2X, wdata->extension.input->absbit);
+ set_bit(ABS_HAT3X, wdata->extension.input->absbit);
+ input_set_abs_params(wdata->extension.input,
+ ABS_X, 0, 63, 1, 0);
+ input_set_abs_params(wdata->extension.input,
+ ABS_Y, 63, 0, 1, 0);
+ input_set_abs_params(wdata->extension.input,
+ ABS_HAT0X, -8, 8, 0, 0);
+ input_set_abs_params(wdata->extension.input,
+ ABS_HAT1X, -8, 8, 0, 0);
+ input_set_abs_params(wdata->extension.input,
+ ABS_HAT2X, 0, 31, 1, 1);
+ input_set_abs_params(wdata->extension.input,
+ ABS_HAT3X, 0, 7, 0, 0);
+ ret = input_register_device(wdata->extension.input);
+ if (ret)
+ goto err_free;
+
+ return 0;
+
+err_free:
+ input_free_device(wdata->extension.input);
+ wdata->extension.input = NULL;
+ return ret;
+}
+
+static void wiimod_turntable_remove(const struct wiimod_ops *ops,
+ struct wiimote_data *wdata)
+{
+ if (!wdata->extension.input)
+ return;
+
+ input_unregister_device(wdata->extension.input);
+ wdata->extension.input = NULL;
+}
+
+static const struct wiimod_ops wiimod_turntable = {
+ .flags = 0,
+ .arg = 0,
+ .probe = wiimod_turntable_probe,
+ .remove = wiimod_turntable_remove,
+ .in_ext = wiimod_turntable_in_ext,
+};
+
/*
* Builtin Motion Plus
* This module simply sets the WIIPROTO_FLAG_BUILTIN_MP protocol flag which
@@ -2657,4 +2881,5 @@ const struct wiimod_ops *wiimod_ext_table[WIIMOTE_EXT_NUM] = {
[WIIMOTE_EXT_PRO_CONTROLLER] = &wiimod_pro,
[WIIMOTE_EXT_DRUMS] = &wiimod_drums,
[WIIMOTE_EXT_GUITAR] = &wiimod_guitar,
+ [WIIMOTE_EXT_TURNTABLE] = &wiimod_turntable,
};
diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h
index ad4ff837f43e..9c12f63f6dd2 100644
--- a/drivers/hid/hid-wiimote.h
+++ b/drivers/hid/hid-wiimote.h
@@ -88,6 +88,7 @@ enum wiimote_exttype {
WIIMOTE_EXT_PRO_CONTROLLER,
WIIMOTE_EXT_DRUMS,
WIIMOTE_EXT_GUITAR,
+ WIIMOTE_EXT_TURNTABLE,
WIIMOTE_EXT_NUM,
};
diff --git a/drivers/hid/i2c-hid/Kconfig b/drivers/hid/i2c-hid/Kconfig
index 5273ee2bb134..4439be7fa74d 100644
--- a/drivers/hid/i2c-hid/Kconfig
+++ b/drivers/hid/i2c-hid/Kconfig
@@ -1,11 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
-menu "I2C HID support"
- depends on I2C
+menuconfig I2C_HID
+ tristate "I2C HID support"
+ default y
+ depends on I2C && INPUT && HID
+
+if I2C_HID
config I2C_HID_ACPI
tristate "HID over I2C transport layer ACPI driver"
- default n
- depends on I2C && INPUT && ACPI
+ depends on ACPI
+ select I2C_HID_CORE
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
other HID based devices which is connected to your computer via I2C.
@@ -19,8 +23,8 @@ config I2C_HID_ACPI
config I2C_HID_OF
tristate "HID over I2C transport layer Open Firmware driver"
- default n
- depends on I2C && INPUT && OF
+ depends on OF
+ select I2C_HID_CORE
help
Say Y here if you use a keyboard, a touchpad, a touchscreen, or any
other HID based devices which is connected to your computer via I2C.
@@ -34,8 +38,8 @@ config I2C_HID_OF
config I2C_HID_OF_ELAN
tristate "Driver for Elan hid-i2c based devices on OF systems"
- default n
- depends on I2C && INPUT && OF
+ depends on OF
+ select I2C_HID_CORE
help
Say Y here if you want support for Elan i2c devices that use
the i2c-hid protocol on Open Firmware (Device Tree)-based
@@ -49,8 +53,8 @@ config I2C_HID_OF_ELAN
config I2C_HID_OF_GOODIX
tristate "Driver for Goodix hid-i2c based devices on OF systems"
- default n
- depends on I2C && INPUT && OF
+ depends on OF
+ select I2C_HID_CORE
help
Say Y here if you want support for Goodix i2c devices that use
the i2c-hid protocol on Open Firmware (Device Tree)-based
@@ -62,10 +66,7 @@ config I2C_HID_OF_GOODIX
will be called i2c-hid-of-goodix. It will also build/depend on
the module i2c-hid.
-endmenu
-
config I2C_HID_CORE
tristate
- default y if I2C_HID_ACPI=y || I2C_HID_OF=y || I2C_HID_OF_GOODIX=y
- default m if I2C_HID_ACPI=m || I2C_HID_OF=m || I2C_HID_OF_GOODIX=m
- select HID
+endif
+
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
index b96ae15e0ad9..3a7e2bcb5412 100644
--- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -39,8 +39,8 @@ static const struct acpi_device_id i2c_hid_acpi_blacklist[] = {
* The CHPN0001 ACPI device, which is used to describe the Chipone
* ICN8505 controller, has a _CID of PNP0C50 but is not HID compatible.
*/
- {"CHPN0001", 0 },
- { },
+ { "CHPN0001" },
+ { }
};
/* HID I²C Device: 3cdff6f7-4267-4555-ad05-b30a3d8938de */
@@ -48,8 +48,9 @@ static guid_t i2c_hid_guid =
GUID_INIT(0x3CDFF6F7, 0x4267, 0x4555,
0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE);
-static int i2c_hid_acpi_get_descriptor(struct acpi_device *adev)
+static int i2c_hid_acpi_get_descriptor(struct i2c_hid_acpi *ihid_acpi)
{
+ struct acpi_device *adev = ihid_acpi->adev;
acpi_handle handle = acpi_device_handle(adev);
union acpi_object *obj;
u16 hid_descriptor_address;
@@ -81,43 +82,31 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct i2c_hid_acpi *ihid_acpi;
- struct acpi_device *adev;
u16 hid_descriptor_address;
int ret;
- adev = ACPI_COMPANION(dev);
- if (!adev) {
- dev_err(&client->dev, "Error could not get ACPI device\n");
- return -ENODEV;
- }
-
ihid_acpi = devm_kzalloc(&client->dev, sizeof(*ihid_acpi), GFP_KERNEL);
if (!ihid_acpi)
return -ENOMEM;
- ihid_acpi->adev = adev;
+ ihid_acpi->adev = ACPI_COMPANION(dev);
ihid_acpi->ops.shutdown_tail = i2c_hid_acpi_shutdown_tail;
- ret = i2c_hid_acpi_get_descriptor(adev);
+ ret = i2c_hid_acpi_get_descriptor(ihid_acpi);
if (ret < 0)
return ret;
hid_descriptor_address = ret;
- acpi_device_fix_up_power(adev);
-
- if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
- device_set_wakeup_capable(dev, true);
- device_set_wakeup_enable(dev, false);
- }
+ acpi_device_fix_up_power(ihid_acpi->adev);
return i2c_hid_core_probe(client, &ihid_acpi->ops,
hid_descriptor_address, 0);
}
static const struct acpi_device_id i2c_hid_acpi_match[] = {
- {"ACPI0C50", 0 },
- {"PNP0C50", 0 },
- { },
+ { "ACPI0C50" },
+ { "PNP0C50" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index 0667b6022c3b..efbba0465eef 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -26,6 +26,7 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pm.h>
+#include <linux/pm_wakeirq.h>
#include <linux/device.h>
#include <linux/wait.h>
#include <linux/err.h>
@@ -66,16 +67,7 @@
#define I2C_HID_PWR_ON 0x00
#define I2C_HID_PWR_SLEEP 0x01
-/* debug option */
-static bool debug;
-module_param(debug, bool, 0444);
-MODULE_PARM_DESC(debug, "print a lot of debug information");
-
-#define i2c_hid_dbg(ihid, fmt, arg...) \
-do { \
- if (debug) \
- dev_printk(KERN_DEBUG, &(ihid)->client->dev, fmt, ##arg); \
-} while (0)
+#define i2c_hid_dbg(ihid, ...) dev_dbg(&(ihid)->client->dev, __VA_ARGS__)
struct i2c_hid_desc {
__le16 wHIDDescLength;
@@ -112,7 +104,6 @@ struct i2c_hid {
wait_queue_head_t wait; /* For waiting the interrupt */
- bool irq_wake_enabled;
struct mutex reset_lock;
struct i2chid_ops *ops;
@@ -554,7 +545,8 @@ static void i2c_hid_get_input(struct i2c_hid *ihid)
i2c_hid_dbg(ihid, "input: %*ph\n", ret_size, ihid->inbuf);
if (test_bit(I2C_HID_STARTED, &ihid->flags)) {
- pm_wakeup_event(&ihid->client->dev, 0);
+ if (ihid->hid->group != HID_GROUP_RMI)
+ pm_wakeup_event(&ihid->client->dev, 0);
hid_input_report(ihid->hid, HID_INPUT_REPORT,
ihid->inbuf + sizeof(__le16),
@@ -841,7 +833,7 @@ static void i2c_hid_close(struct hid_device *hid)
clear_bit(I2C_HID_STARTED, &ihid->flags);
}
-struct hid_ll_driver i2c_hid_ll_driver = {
+static const struct hid_ll_driver i2c_hid_ll_driver = {
.parse = i2c_hid_parse,
.start = i2c_hid_start,
.stop = i2c_hid_stop,
@@ -850,7 +842,6 @@ struct hid_ll_driver i2c_hid_ll_driver = {
.output_report = i2c_hid_output_report,
.raw_request = i2c_hid_raw_request,
};
-EXPORT_SYMBOL_GPL(i2c_hid_ll_driver);
static int i2c_hid_init_irq(struct i2c_client *client)
{
@@ -858,7 +849,7 @@ static int i2c_hid_init_irq(struct i2c_client *client)
unsigned long irqflags = 0;
int ret;
- dev_dbg(&client->dev, "Requesting IRQ: %d\n", client->irq);
+ i2c_hid_dbg(ihid, "Requesting IRQ: %d\n", client->irq);
if (!irq_get_trigger_type(client->irq))
irqflags = IRQF_TRIGGER_LOW;
@@ -1002,7 +993,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
/* Make sure there is something at this address */
ret = i2c_smbus_read_byte(client);
if (ret < 0) {
- dev_dbg(&client->dev, "nothing at this address: %d\n", ret);
+ i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
ret = -ENXIO;
goto err_powered;
}
@@ -1034,6 +1025,10 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
hid->vendor = le16_to_cpu(ihid->hdesc.wVendorID);
hid->product = le16_to_cpu(ihid->hdesc.wProductID);
+ hid->initial_quirks = quirks;
+ hid->initial_quirks |= i2c_hid_get_dmi_quirks(hid->vendor,
+ hid->product);
+
snprintf(hid->name, sizeof(hid->name), "%s %04X:%04X",
client->name, (u16)hid->vendor, (u16)hid->product);
strscpy(hid->phys, dev_name(&client->dev), sizeof(hid->phys));
@@ -1047,8 +1042,6 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
goto err_mem_free;
}
- hid->quirks |= quirks;
-
return 0;
err_mem_free:
@@ -1099,7 +1092,6 @@ static int i2c_hid_core_suspend(struct device *dev)
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid = ihid->hid;
int ret;
- int wake_status;
ret = hid_driver_suspend(hid, PMSG_SUSPEND);
if (ret < 0)
@@ -1110,16 +1102,8 @@ static int i2c_hid_core_suspend(struct device *dev)
disable_irq(client->irq);
- if (device_may_wakeup(&client->dev)) {
- wake_status = enable_irq_wake(client->irq);
- if (!wake_status)
- ihid->irq_wake_enabled = true;
- else
- hid_warn(hid, "Failed to enable irq wake: %d\n",
- wake_status);
- } else {
+ if (!device_may_wakeup(&client->dev))
i2c_hid_core_power_down(ihid);
- }
return 0;
}
@@ -1130,18 +1114,9 @@ static int i2c_hid_core_resume(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct i2c_hid *ihid = i2c_get_clientdata(client);
struct hid_device *hid = ihid->hid;
- int wake_status;
- if (!device_may_wakeup(&client->dev)) {
+ if (!device_may_wakeup(&client->dev))
i2c_hid_core_power_up(ihid);
- } else if (ihid->irq_wake_enabled) {
- wake_status = disable_irq_wake(client->irq);
- if (!wake_status)
- ihid->irq_wake_enabled = false;
- else
- hid_warn(hid, "Failed to disable irq wake: %d\n",
- wake_status);
- }
enable_irq(client->irq);
diff --git a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
index 8e0f67455c09..210f17c3a0be 100644
--- a/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
+++ b/drivers/hid/i2c-hid/i2c-hid-dmi-quirks.c
@@ -10,8 +10,10 @@
#include <linux/types.h>
#include <linux/dmi.h>
#include <linux/mod_devicetable.h>
+#include <linux/hid.h>
#include "i2c-hid.h"
+#include "../hid-ids.h"
struct i2c_hid_desc_override {
@@ -416,6 +418,28 @@ static const struct dmi_system_id i2c_hid_dmi_desc_override_table[] = {
{ } /* Terminate list */
};
+static const struct hid_device_id i2c_hid_elan_flipped_quirks = {
+ HID_DEVICE(BUS_I2C, HID_GROUP_MULTITOUCH_WIN_8, USB_VENDOR_ID_ELAN, 0x2dcd),
+ HID_QUIRK_X_INVERT | HID_QUIRK_Y_INVERT
+};
+
+/*
+ * This list contains devices which have specific issues based on the system
+ * they're on and not just the device itself. The driver_data will have a
+ * specific hid device to match against.
+ */
+static const struct dmi_system_id i2c_hid_dmi_quirk_table[] = {
+ {
+ .ident = "DynaBook K50/FR",
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dynabook Inc."),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "dynabook K50/FR"),
+ },
+ .driver_data = (void *)&i2c_hid_elan_flipped_quirks,
+ },
+ { } /* Terminate list */
+};
+
struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name)
{
@@ -450,3 +474,21 @@ char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
*size = override->hid_report_desc_size;
return override->hid_report_desc;
}
+
+u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
+{
+ u32 quirks = 0;
+ const struct dmi_system_id *system_id =
+ dmi_first_match(i2c_hid_dmi_quirk_table);
+
+ if (system_id) {
+ const struct hid_device_id *device_id =
+ (struct hid_device_id *)(system_id->driver_data);
+
+ if (device_id && device_id->vendor == vendor &&
+ device_id->product == product)
+ quirks = device_id->driver_data;
+ }
+
+ return quirks;
+}
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-elan.c b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
index 2d991325e734..76ddc8be1cbb 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of-elan.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of-elan.c
@@ -68,8 +68,7 @@ static void elan_i2c_hid_power_down(struct i2chid_ops *ops)
regulator_disable(ihid_elan->vcc33);
}
-static int i2c_hid_of_elan_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int i2c_hid_of_elan_probe(struct i2c_client *client)
{
struct i2c_hid_of_elan *ihid_elan;
@@ -119,7 +118,7 @@ static struct i2c_driver elan_i2c_hid_ts_driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = of_match_ptr(elan_i2c_hid_of_match),
},
- .probe = i2c_hid_of_elan_probe,
+ .probe_new = i2c_hid_of_elan_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
};
diff --git a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
index ec6c73f75ffe..0060e3dcd775 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of-goodix.c
@@ -26,28 +26,33 @@ struct i2c_hid_of_goodix {
struct i2chid_ops ops;
struct regulator *vdd;
- struct notifier_block nb;
+ struct regulator *vddio;
struct gpio_desc *reset_gpio;
const struct goodix_i2c_hid_timing_data *timings;
};
-static void goodix_i2c_hid_deassert_reset(struct i2c_hid_of_goodix *ihid_goodix,
- bool regulator_just_turned_on)
+static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
{
- if (regulator_just_turned_on && ihid_goodix->timings->post_power_delay_ms)
+ struct i2c_hid_of_goodix *ihid_goodix =
+ container_of(ops, struct i2c_hid_of_goodix, ops);
+ int ret;
+
+ ret = regulator_enable(ihid_goodix->vdd);
+ if (ret)
+ return ret;
+
+ ret = regulator_enable(ihid_goodix->vddio);
+ if (ret)
+ return ret;
+
+ if (ihid_goodix->timings->post_power_delay_ms)
msleep(ihid_goodix->timings->post_power_delay_ms);
gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 0);
if (ihid_goodix->timings->post_gpio_reset_delay_ms)
msleep(ihid_goodix->timings->post_gpio_reset_delay_ms);
-}
-
-static int goodix_i2c_hid_power_up(struct i2chid_ops *ops)
-{
- struct i2c_hid_of_goodix *ihid_goodix =
- container_of(ops, struct i2c_hid_of_goodix, ops);
- return regulator_enable(ihid_goodix->vdd);
+ return 0;
}
static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
@@ -55,43 +60,15 @@ static void goodix_i2c_hid_power_down(struct i2chid_ops *ops)
struct i2c_hid_of_goodix *ihid_goodix =
container_of(ops, struct i2c_hid_of_goodix, ops);
+ gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
+ regulator_disable(ihid_goodix->vddio);
regulator_disable(ihid_goodix->vdd);
}
-static int ihid_goodix_vdd_notify(struct notifier_block *nb,
- unsigned long event,
- void *ignored)
-{
- struct i2c_hid_of_goodix *ihid_goodix =
- container_of(nb, struct i2c_hid_of_goodix, nb);
- int ret = NOTIFY_OK;
-
- switch (event) {
- case REGULATOR_EVENT_PRE_DISABLE:
- gpiod_set_value_cansleep(ihid_goodix->reset_gpio, 1);
- break;
-
- case REGULATOR_EVENT_ENABLE:
- goodix_i2c_hid_deassert_reset(ihid_goodix, true);
- break;
-
- case REGULATOR_EVENT_ABORT_DISABLE:
- goodix_i2c_hid_deassert_reset(ihid_goodix, false);
- break;
-
- default:
- ret = NOTIFY_DONE;
- break;
- }
-
- return ret;
-}
-
-static int i2c_hid_of_goodix_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int i2c_hid_of_goodix_probe(struct i2c_client *client)
{
struct i2c_hid_of_goodix *ihid_goodix;
- int ret;
+
ihid_goodix = devm_kzalloc(&client->dev, sizeof(*ihid_goodix),
GFP_KERNEL);
if (!ihid_goodix)
@@ -110,41 +87,11 @@ static int i2c_hid_of_goodix_probe(struct i2c_client *client,
if (IS_ERR(ihid_goodix->vdd))
return PTR_ERR(ihid_goodix->vdd);
- ihid_goodix->timings = device_get_match_data(&client->dev);
+ ihid_goodix->vddio = devm_regulator_get(&client->dev, "mainboard-vddio");
+ if (IS_ERR(ihid_goodix->vddio))
+ return PTR_ERR(ihid_goodix->vddio);
- /*
- * We need to control the "reset" line in lockstep with the regulator
- * actually turning on an off instead of just when we make the request.
- * This matters if the regulator is shared with another consumer.
- * - If the regulator is off then we must assert reset. The reset
- * line is active low and on some boards it could cause a current
- * leak if left high.
- * - If the regulator is on then we don't want reset asserted for very
- * long. Holding the controller in reset apparently draws extra
- * power.
- */
- ihid_goodix->nb.notifier_call = ihid_goodix_vdd_notify;
- ret = devm_regulator_register_notifier(ihid_goodix->vdd, &ihid_goodix->nb);
- if (ret)
- return dev_err_probe(&client->dev, ret,
- "regulator notifier request failed\n");
-
- /*
- * If someone else is holding the regulator on (or the regulator is
- * an always-on one) we might never be told to deassert reset. Do it
- * now... and temporarily bump the regulator reference count just to
- * make sure it is impossible for this to race with our own notifier!
- * We also assume that someone else might have _just barely_ turned
- * the regulator on so we'll do the full "post_power_delay" just in
- * case.
- */
- if (ihid_goodix->reset_gpio && regulator_is_enabled(ihid_goodix->vdd)) {
- ret = regulator_enable(ihid_goodix->vdd);
- if (ret)
- return ret;
- goodix_i2c_hid_deassert_reset(ihid_goodix, true);
- regulator_disable(ihid_goodix->vdd);
- }
+ ihid_goodix->timings = device_get_match_data(&client->dev);
return i2c_hid_core_probe(client, &ihid_goodix->ops, 0x0001, 0);
}
@@ -167,7 +114,7 @@ static struct i2c_driver goodix_i2c_hid_ts_driver = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
.of_match_table = of_match_ptr(goodix_i2c_hid_of_match),
},
- .probe = i2c_hid_of_goodix_probe,
+ .probe_new = i2c_hid_of_goodix_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
};
diff --git a/drivers/hid/i2c-hid/i2c-hid-of.c b/drivers/hid/i2c-hid/i2c-hid-of.c
index 97a27a803f58..10176568133a 100644
--- a/drivers/hid/i2c-hid/i2c-hid-of.c
+++ b/drivers/hid/i2c-hid/i2c-hid-of.c
@@ -66,8 +66,7 @@ static void i2c_hid_of_power_down(struct i2chid_ops *ops)
ihid_of->supplies);
}
-static int i2c_hid_of_probe(struct i2c_client *client,
- const struct i2c_device_id *dev_id)
+static int i2c_hid_of_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct i2c_hid_of *ihid_of;
@@ -138,7 +137,7 @@ static struct i2c_driver i2c_hid_of_driver = {
.of_match_table = of_match_ptr(i2c_hid_of_match),
},
- .probe = i2c_hid_of_probe,
+ .probe_new = i2c_hid_of_probe,
.remove = i2c_hid_core_remove,
.shutdown = i2c_hid_core_shutdown,
.id_table = i2c_hid_of_id_table,
diff --git a/drivers/hid/i2c-hid/i2c-hid.h b/drivers/hid/i2c-hid/i2c-hid.h
index 96c75510ad3f..2c7b66d5caa0 100644
--- a/drivers/hid/i2c-hid/i2c-hid.h
+++ b/drivers/hid/i2c-hid/i2c-hid.h
@@ -9,6 +9,7 @@
struct i2c_hid_desc *i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name);
char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
unsigned int *size);
+u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product);
#else
static inline struct i2c_hid_desc
*i2c_hid_get_dmi_i2c_hid_desc_override(uint8_t *i2c_name)
@@ -16,6 +17,8 @@ static inline struct i2c_hid_desc
static inline char *i2c_hid_get_dmi_hid_report_desc_override(uint8_t *i2c_name,
unsigned int *size)
{ return NULL; }
+static inline u32 i2c_hid_get_dmi_quirks(const u16 vendor, const u16 product)
+{ return 0; }
#endif
/**
diff --git a/drivers/hid/intel-ish-hid/Kconfig b/drivers/hid/intel-ish-hid/Kconfig
index 689da84a520d..253dc10d35ef 100644
--- a/drivers/hid/intel-ish-hid/Kconfig
+++ b/drivers/hid/intel-ish-hid/Kconfig
@@ -6,7 +6,7 @@ config INTEL_ISH_HID
tristate "Intel Integrated Sensor Hub"
default n
depends on X86
- select HID
+ depends on HID
help
The Integrated Sensor Hub (ISH) enables the ability to offload
sensor polling and algorithm processing to a dedicated low power
diff --git a/drivers/hid/intel-ish-hid/ishtp-hid.c b/drivers/hid/intel-ish-hid/ishtp-hid.c
index 14c271d7d8a9..00c6f0ebf356 100644
--- a/drivers/hid/intel-ish-hid/ishtp-hid.c
+++ b/drivers/hid/intel-ish-hid/ishtp-hid.c
@@ -183,7 +183,7 @@ void ishtp_hid_wakeup(struct hid_device *hid)
wake_up_interruptible(&hid_data->hid_wait);
}
-static struct hid_ll_driver ishtp_hid_ll_driver = {
+static const struct hid_ll_driver ishtp_hid_ll_driver = {
.parse = ishtp_hid_parse,
.start = ishtp_hid_start,
.stop = ishtp_hid_stop,
diff --git a/drivers/hid/intel-ish-hid/ishtp/client.c b/drivers/hid/intel-ish-hid/ishtp/client.c
index df0a825694f5..2d92fc129ce4 100644
--- a/drivers/hid/intel-ish-hid/ishtp/client.c
+++ b/drivers/hid/intel-ish-hid/ishtp/client.c
@@ -841,7 +841,6 @@ void recv_ishtp_cl_msg(struct ishtp_device *dev,
unsigned char *buffer = NULL;
struct ishtp_cl_rb *complete_rb = NULL;
unsigned long flags;
- int rb_count;
if (ishtp_hdr->reserved) {
dev_err(dev->devc, "corrupted message header.\n");
@@ -855,9 +854,7 @@ void recv_ishtp_cl_msg(struct ishtp_device *dev,
}
spin_lock_irqsave(&dev->read_list_spinlock, flags);
- rb_count = -1;
list_for_each_entry(rb, &dev->read_list.list, list) {
- ++rb_count;
cl = rb->cl;
if (!cl || !(cl->host_client_id == ishtp_hdr->host_addr &&
cl->fw_client_id == ishtp_hdr->fw_addr) ||
diff --git a/drivers/hid/intel-ish-hid/ishtp/dma-if.c b/drivers/hid/intel-ish-hid/ishtp/dma-if.c
index 40554c8daca0..00046cbfd4ed 100644
--- a/drivers/hid/intel-ish-hid/ishtp/dma-if.c
+++ b/drivers/hid/intel-ish-hid/ishtp/dma-if.c
@@ -104,6 +104,11 @@ void *ishtp_cl_get_dma_send_buf(struct ishtp_device *dev,
int required_slots = (size / DMA_SLOT_SIZE)
+ 1 * (size % DMA_SLOT_SIZE != 0);
+ if (!dev->ishtp_dma_tx_map) {
+ dev_err(dev->devc, "Fail to allocate Tx map\n");
+ return NULL;
+ }
+
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (i = 0; i <= (dev->ishtp_dma_num_slots - required_slots); i++) {
free = 1;
@@ -150,6 +155,11 @@ void ishtp_cl_release_dma_acked_mem(struct ishtp_device *dev,
return;
}
+ if (!dev->ishtp_dma_tx_map) {
+ dev_err(dev->devc, "Fail to allocate Tx map\n");
+ return;
+ }
+
i = (msg_addr - dev->ishtp_host_dma_tx_buf) / DMA_SLOT_SIZE;
spin_lock_irqsave(&dev->ishtp_dma_tx_lock, flags);
for (j = 0; j < acked_slots; j++) {
diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
index d4aa8c81903a..61e5814b0ad7 100644
--- a/drivers/hid/surface-hid/surface_hid.c
+++ b/drivers/hid/surface-hid/surface_hid.c
@@ -80,7 +80,7 @@ static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8
rsp.length = 0;
- status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
+ status = ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp,
sizeof(*slice));
if (status)
return status;
@@ -131,7 +131,7 @@ static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id,
buf[0] = rprt_id;
- return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
+ return ssam_retry(ssam_request_do_sync, shid->ctrl, &rqst, NULL);
}
static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
@@ -151,7 +151,7 @@ static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id,
rsp.length = 0;
rsp.pointer = buf;
- return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
+ return ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
}
static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
@@ -230,7 +230,7 @@ static void surface_hid_remove(struct ssam_device *sdev)
}
static const struct ssam_device_id surface_hid_match[] = {
- { SSAM_SDEV(HID, SSAM_ANY_TID, SSAM_ANY_IID, 0x00) },
+ { SSAM_SDEV(HID, ANY, SSAM_SSH_IID_ANY, 0x00) },
{ },
};
MODULE_DEVICE_TABLE(ssam, surface_hid_match);
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
index 87637f813de2..a3e9cceddfac 100644
--- a/drivers/hid/surface-hid/surface_hid_core.c
+++ b/drivers/hid/surface-hid/surface_hid_core.c
@@ -174,7 +174,7 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn
return -EIO;
}
-static struct hid_ll_driver surface_hid_ll_driver = {
+static const struct hid_ll_driver surface_hid_ll_driver = {
.start = surface_hid_start,
.stop = surface_hid_stop,
.open = surface_hid_open,
diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
index 0635341bc517..4fbce201db6a 100644
--- a/drivers/hid/surface-hid/surface_kbd.c
+++ b/drivers/hid/surface-hid/surface_kbd.c
@@ -49,7 +49,7 @@ static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8
rsp.length = 0;
rsp.pointer = buf;
- status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
+ status = ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
if (status)
return status;
@@ -75,7 +75,7 @@ static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
rqst.length = sizeof(value_u8);
rqst.payload = &value_u8;
- return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
+ return ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
}
static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
@@ -97,7 +97,7 @@ static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf,
rsp.length = 0;
rsp.pointer = buf;
- status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
+ status = ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
if (status)
return status;
@@ -250,7 +250,7 @@ static int surface_kbd_probe(struct platform_device *pdev)
shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
shid->uid.category = SSAM_SSH_TC_KBD;
- shid->uid.target = 2;
+ shid->uid.target = SSAM_SSH_TID_KIP;
shid->uid.instance = 0;
shid->uid.function = 0;
diff --git a/drivers/hid/uhid.c b/drivers/hid/uhid.c
index 2a918aeb0af1..f161c95a1ad2 100644
--- a/drivers/hid/uhid.c
+++ b/drivers/hid/uhid.c
@@ -387,7 +387,7 @@ static int uhid_hid_output_report(struct hid_device *hid, __u8 *buf,
return uhid_hid_output_raw(hid, buf, count, HID_OUTPUT_REPORT);
}
-struct hid_ll_driver uhid_hid_driver = {
+static const struct hid_ll_driver uhid_hid_driver = {
.start = uhid_hid_start,
.stop = uhid_hid_stop,
.open = uhid_hid_open,
@@ -396,7 +396,6 @@ struct hid_ll_driver uhid_hid_driver = {
.raw_request = uhid_hid_raw_request,
.output_report = uhid_hid_output_report,
};
-EXPORT_SYMBOL_GPL(uhid_hid_driver);
#ifdef CONFIG_COMPAT
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index be4c731aaa65..257dd73e37bf 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -1318,7 +1318,7 @@ static bool usbhid_may_wakeup(struct hid_device *hid)
return device_may_wakeup(&dev->dev);
}
-struct hid_ll_driver usb_hid_driver = {
+static const struct hid_ll_driver usb_hid_driver = {
.parse = usbhid_parse,
.start = usbhid_start,
.stop = usbhid_stop,
@@ -1332,7 +1332,12 @@ struct hid_ll_driver usb_hid_driver = {
.idle = usbhid_idle,
.may_wakeup = usbhid_may_wakeup,
};
-EXPORT_SYMBOL_GPL(usb_hid_driver);
+
+bool hid_is_usb(const struct hid_device *hdev)
+{
+ return hdev->ll_driver == &usb_hid_driver;
+}
+EXPORT_SYMBOL_GPL(hid_is_usb);
static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c
index 2fb2991dbe4c..59cf3ddfdf78 100644
--- a/drivers/hid/usbhid/hiddev.c
+++ b/drivers/hid/usbhid/hiddev.c
@@ -857,7 +857,7 @@ static const struct file_operations hiddev_fops = {
.llseek = noop_llseek,
};
-static char *hiddev_devnode(struct device *dev, umode_t *mode)
+static char *hiddev_devnode(const struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
}
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index 634263e4556b..fb538a6c4add 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -155,6 +155,9 @@ static int wacom_raw_event(struct hid_device *hdev, struct hid_report *report,
{
struct wacom *wacom = hid_get_drvdata(hdev);
+ if (wacom->wacom_wac.features.type == BOOTLOADER)
+ return 0;
+
if (size > WACOM_PKGLEN_MAX)
return 1;
@@ -2785,6 +2788,11 @@ static int wacom_probe(struct hid_device *hdev,
return error;
}
+ if (features->type == BOOTLOADER) {
+ hid_warn(hdev, "Using device in hidraw-only mode");
+ return hid_hw_start(hdev, HID_CONNECT_HIDRAW);
+ }
+
error = wacom_parse_and_register(wacom, false);
if (error)
return error;
diff --git a/drivers/hid/wacom_wac.c b/drivers/hid/wacom_wac.c
index 0f3d57b42684..9312d611db8e 100644
--- a/drivers/hid/wacom_wac.c
+++ b/drivers/hid/wacom_wac.c
@@ -4882,6 +4882,9 @@ static const struct wacom_features wacom_features_0x3dd =
static const struct wacom_features wacom_features_HID_ANY_ID =
{ "Wacom HID", .type = HID_GENERIC, .oVid = HID_ANY_ID, .oPid = HID_ANY_ID };
+static const struct wacom_features wacom_features_0x94 =
+ { "Wacom Bootloader", .type = BOOTLOADER };
+
#define USB_DEVICE_WACOM(prod) \
HID_DEVICE(BUS_USB, HID_GROUP_WACOM, USB_VENDOR_ID_WACOM, prod),\
.driver_data = (kernel_ulong_t)&wacom_features_##prod
@@ -4955,6 +4958,7 @@ const struct hid_device_id wacom_ids[] = {
{ USB_DEVICE_WACOM(0x84) },
{ USB_DEVICE_WACOM(0x90) },
{ USB_DEVICE_WACOM(0x93) },
+ { USB_DEVICE_WACOM(0x94) },
{ USB_DEVICE_WACOM(0x97) },
{ USB_DEVICE_WACOM(0x9A) },
{ USB_DEVICE_WACOM(0x9F) },
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index 5ca6c06d143b..16f221388563 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -243,6 +243,7 @@ enum {
MTTPC,
MTTPC_B,
HID_GENERIC,
+ BOOTLOADER,
MAX_TYPE
};