Commit 614da38e authored by Linus Torvalds's avatar Linus Torvalds

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

Pull HID updates from Jiri Kosina:

 - Firmware loading from host support in intel-ish driver, needed to
   support Lunar Lake and later (Zhang Lixu)

 - updates to HID-BPF infrastructure, with some of the specific fixes
   (e.g. rdesc fixups) abstracted into separate BPF programs for
   consumption from libevdev/udev-hid-bpf (Benjamin Tissoires)

 - support for Deck IMU in hid-steam (Max Maisel)

 - fixes for better support of 3rd party playstation DS4 controllers
   (Max Staudt)

 - support for missing mappings and codes from HUT 1.5 in hid-debug
   (Thomas Kuehne)

 - initial support for ROG Ally and ROG X13 devices (Luke D. Jones)

 - full support for WinWing Orion2 (Ivan Gorinov)

* tag 'hid-for-linus-2024051401' of git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid: (74 commits)
  selftests/hid: skip tests with HID-BPF if udev-hid-bpf is not installed
  selftests/hid: add tests for the Raptor Mach 2 joystick
  selftests/hid: move the gamepads definitions in the test file
  selftests/hid: import base_gamepad.py from hid-tools
  selftests/hid: add Huion Kamvas Pro 19 tests
  selftests/hid: tablets: also check for XP-Pen offset correction
  selftests/hid: tablets: add a couple of XP-PEN tablets
  selftests/hid: tablets: reduce the number of pen state
  selftests/hid: add support for HID-BPF pre-loading before starting a test
  selftests/hid: import base_device.py from hid-tools
  HID: bpf: add in-tree HID-BPF fix for the Raptor Mach 2
  HID: bpf: add in-tree HID-BPF fix for the Huion Kamvas Pro 19
  HID: bpf: add in-tree HID-BPF fix for the XBox Elite 2 over Bluetooth
  HID: bpf: add in-tree HID-BPF fix for the Wacom ArtPen
  HID: bpf: add in-tree HID-BPF fix for the IOGear Kaliber Gaming MMOmentum mouse
  HID: bpf: add in-tree HID-BPF fix for the HP Elite Presenter Mouse
  HID: bpf: add in-tree HID-BPF fix for the XPPen Artist 16
  HID: bpf: add first in-tree HID-BPF fix for the XPPen Artist 24
  HID: do not assume HAT Switch logical max < 8
  HID: amd_sfh: Use amd_get_c2p_val() to read C2P register
  ...
parents ce952d8f c9c92fc4
......@@ -179,7 +179,7 @@ Available API that can be used in syscall HID-BPF programs:
-----------------------------------------------------------
.. kernel-doc:: drivers/hid/bpf/hid_bpf_dispatch.c
:functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_allocate_context hid_bpf_release_context
:functions: hid_bpf_attach_prog hid_bpf_hw_request hid_bpf_hw_output_report hid_bpf_input_report hid_bpf_allocate_context hid_bpf_release_context
General overview of a HID-BPF program
=====================================
......
This diff is collapsed.
......@@ -1236,6 +1236,22 @@ config HID_WIIMOTE
To compile this driver as a module, choose M here: the
module will be called hid-wiimote.
config HID_WINWING
tristate "WinWing Orion2 throttle support"
depends on USB_HID
depends on NEW_LEDS
depends on LEDS_CLASS
help
Support for WinWing Orion2 throttle base with the following grips:
* TGRIP-16EX
* TGRIP-18
This driver enables all buttons and switches on the throttle base.
To compile this driver as a module, choose M here: the
module will be called hid-winwing.
config HID_XINMO
tristate "Xin-Mo non-fully compliant devices"
help
......
......@@ -150,6 +150,7 @@ wacom-objs := wacom_wac.o wacom_sys.o
obj-$(CONFIG_HID_WACOM) += wacom.o
obj-$(CONFIG_HID_WALTOP) += hid-waltop.o
obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o
obj-$(CONFIG_HID_WINWING) += hid-winwing.o
obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o
obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o
......
......@@ -333,14 +333,11 @@ static const struct dmi_system_id dmi_nodevs[] = {
static void sfh1_1_init_work(struct work_struct *work)
{
struct amd_mp2_dev *mp2 = container_of(work, struct amd_mp2_dev, work);
struct pci_dev *pdev = mp2->pdev;
int rc;
rc = mp2->sfh1_1_ops->init(mp2);
if (rc) {
dev_err(&pdev->dev, "sfh1_1_init failed err %d\n", rc);
if (rc)
return;
}
amd_sfh_clear_intr(mp2);
mp2->init_done = 1;
......
......@@ -202,7 +202,7 @@ static int amd_sfh1_1_hid_client_init(struct amd_mp2_dev *privdata)
}
if (!cl_data->is_any_sensor_enabled) {
dev_warn(dev, "Failed to discover, sensors not enabled is %d\n",
dev_warn(dev, "No sensor registered, sensors not enabled is %d\n",
cl_data->is_any_sensor_enabled);
rc = -EOPNOTSUPP;
goto cleanup;
......@@ -227,6 +227,11 @@ static void amd_sfh_resume(struct amd_mp2_dev *mp2)
struct amd_mp2_sensor_info info;
int i, status;
if (!cl_data->is_any_sensor_enabled) {
amd_sfh_clear_intr(mp2);
return;
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_sts[i] == SENSOR_DISABLED) {
info.sensor_idx = cl_data->sensor_idx[i];
......@@ -252,6 +257,11 @@ static void amd_sfh_suspend(struct amd_mp2_dev *mp2)
struct amdtp_cl_data *cl_data = mp2->cl_data;
int i, status;
if (!cl_data->is_any_sensor_enabled) {
amd_sfh_clear_intr(mp2);
return;
}
for (i = 0; i < cl_data->num_hid_devices; i++) {
if (cl_data->sensor_idx[i] != HPD_IDX &&
cl_data->sensor_sts[i] == SENSOR_ENABLED) {
......@@ -320,7 +330,7 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
memcpy_fromio(&binfo, mp2->vsbase, sizeof(struct sfh_base_info));
if (binfo.sbase.fw_info.fw_ver == 0 || binfo.sbase.s_list.sl.sensors == 0) {
dev_dbg(dev, "failed to get sensors\n");
dev_dbg(dev, "No sensor registered\n");
return -EOPNOTSUPP;
}
dev_dbg(dev, "firmware version 0x%x\n", binfo.sbase.fw_info.fw_ver);
......@@ -337,7 +347,8 @@ int amd_sfh1_1_init(struct amd_mp2_dev *mp2)
rc = amd_sfh1_1_hid_client_init(mp2);
if (rc) {
sfh_deinit_emp2();
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
if ((rc != -ENODEV) && (rc != -EOPNOTSUPP))
dev_err(dev, "amd_sfh1_1_hid_client_init failed\n");
return rc;
}
......
......@@ -97,7 +97,7 @@ static int amd_sfh_hpd_info(u8 *user_present)
if (!emp2 || !emp2->dev_en.is_hpd_present)
return -ENODEV;
hpdstatus.val = readl(emp2->mmio + AMD_C2P_MSG(4));
hpdstatus.val = readl(emp2->mmio + amd_get_c2p_val(emp2, 4));
*user_present = hpdstatus.shpd.presence;
return 0;
......
......@@ -143,48 +143,6 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
}
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
/* Disables missing prototype warnings */
__bpf_kfunc_start_defs();
/**
* 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
*/
__bpf_kfunc __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;
}
__bpf_kfunc_end_defs();
/*
* The following set contains all functions we agree BPF programs
* can use.
*/
BTF_KFUNCS_START(hid_bpf_kfunc_ids)
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
BTF_KFUNCS_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);
......@@ -281,6 +239,31 @@ static int do_hid_bpf_attach_prog(struct hid_device *hdev, int prog_fd, struct b
/* Disables missing prototype warnings */
__bpf_kfunc_start_defs();
/**
* 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
*/
__bpf_kfunc __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;
}
/**
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
*
......@@ -393,6 +376,46 @@ hid_bpf_release_context(struct hid_bpf_ctx *ctx)
put_device(&hid->dev);
}
static int
__hid_bpf_hw_check_params(struct hid_bpf_ctx *ctx, __u8 *buf, size_t *buf__sz,
enum hid_report_type rtype)
{
struct hid_report_enum *report_enum;
struct hid_report *report;
struct hid_device *hdev;
u32 report_len;
/* 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;
}
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;
return 0;
}
/**
* hid_bpf_hw_request - Communicate with a HID device
*
......@@ -409,24 +432,14 @@ 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;
size_t size = buf__sz;
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;
}
ret = __hid_bpf_hw_check_params(ctx, buf, &size, rtype);
if (ret)
return ret;
switch (reqtype) {
case HID_REQ_GET_REPORT:
......@@ -440,29 +453,16 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
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);
dma_data = kmemdup(buf, size, GFP_KERNEL);
if (!dma_data)
return -ENOMEM;
ret = hid_bpf_ops->hid_hw_raw_request(hdev,
dma_data[0],
dma_data,
buf__sz,
size,
rtype,
reqtype);
......@@ -472,8 +472,90 @@ hid_bpf_hw_request(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz,
kfree(dma_data);
return ret;
}
/**
* hid_bpf_hw_output_report - Send an output report to 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
*
* Returns the number of bytes transferred on success, a negative error code otherwise.
*/
__bpf_kfunc int
hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
{
struct hid_device *hdev;
size_t size = buf__sz;
u8 *dma_data;
int ret;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, HID_OUTPUT_REPORT);
if (ret)
return ret;
hdev = (struct hid_device *)ctx->hid; /* discard const */
dma_data = kmemdup(buf, size, GFP_KERNEL);
if (!dma_data)
return -ENOMEM;
ret = hid_bpf_ops->hid_hw_output_report(hdev,
dma_data,
size);
kfree(dma_data);
return ret;
}
/**
* hid_bpf_input_report - Inject a HID report in the kernel from a HID device
*
* @ctx: the HID-BPF context previously allocated in hid_bpf_allocate_context()
* @type: the type of the report (%HID_INPUT_REPORT, %HID_FEATURE_REPORT, %HID_OUTPUT_REPORT)
* @buf: a %PTR_TO_MEM buffer
* @buf__sz: the size of the data to transfer
*
* Returns %0 on success, a negative error code otherwise.
*/
__bpf_kfunc int
hid_bpf_input_report(struct hid_bpf_ctx *ctx, enum hid_report_type type, u8 *buf,
const size_t buf__sz)
{
struct hid_device *hdev;
size_t size = buf__sz;
int ret;
/* check arguments */
ret = __hid_bpf_hw_check_params(ctx, buf, &size, type);
if (ret)
return ret;
hdev = (struct hid_device *)ctx->hid; /* discard const */
return hid_bpf_ops->hid_input_report(hdev, type, buf, size, 0);
}
__bpf_kfunc_end_defs();
/*
* The following set contains all functions we agree BPF programs
* can use.
*/
BTF_KFUNCS_START(hid_bpf_kfunc_ids)
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
BTF_ID_FLAGS(func, hid_bpf_allocate_context, KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_release_context, KF_RELEASE | KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_hw_request, KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_hw_output_report, KF_SLEEPABLE)
BTF_ID_FLAGS(func, hid_bpf_input_report, KF_SLEEPABLE)
BTF_KFUNCS_END(hid_bpf_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
.owner = THIS_MODULE,
.set = &hid_bpf_kfunc_ids,
};
/* our HID-BPF entrypoints */
BTF_SET8_START(hid_bpf_fmodret_ids)
BTF_ID_FLAGS(func, hid_bpf_device_event)
......@@ -492,6 +574,8 @@ 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_ID_FLAGS(func, hid_bpf_hw_output_report)
BTF_ID_FLAGS(func, hid_bpf_input_report)
BTF_KFUNCS_END(hid_bpf_syscall_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2023 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_HP 0x03F0
#define PID_ELITE_PRESENTER 0x464A
HID_BPF_CONFIG(
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_HP, PID_ELITE_PRESENTER)
);
/*
* Already fixed as of commit 0db117359e47 ("HID: add quirk for 03f0:464a
* HP Elite Presenter Mouse") in the kernel, but this is a slightly better
* fix.
*
* The HP Elite Presenter Mouse HID Record Descriptor shows
* two mice (Report ID 0x1 and 0x2), one keypad (Report ID 0x5),
* two Consumer Controls (Report IDs 0x6 and 0x3).
* Prior to these fixes it registers one mouse, one keypad
* and one Consumer Control, and it was usable only as a
* digital laser pointer (one of the two mouses).
* We replace the second mouse collection with a pointer collection,
* allowing to use the device both as a mouse and a digital laser
* pointer.
*/
SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0; /* EPERM check */
/* replace application mouse by application pointer on the second collection */
if (data[79] == 0x02)
data[79] = 0x01;
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
ctx->retval = ctx->rdesc_size != 264;
if (ctx->retval)
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2023 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_IOGEAR 0x258A /* VID is shared with SinoWealth and Glorious and prob others */
#define PID_MOMENTUM 0x0027
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_IOGEAR, PID_MOMENTUM)
);
/*
* The IOGear Kaliber Gaming MMOmentum Pro mouse has multiple buttons (12)
* but only 5 are accessible out of the box because the report descriptor
* marks the other buttons as constants.
* We just fix the report descriptor to enable those missing 7 buttons.
*/
SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
const u8 offsets[] = {84, 112, 140};
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0; /* EPERM check */
/* if not Keyboard */
if (data[3] != 0x06)
return 0;
for (int idx = 0; idx < ARRAY_SIZE(offsets); idx++) {
u8 offset = offsets[idx];
/* if Input (Cnst,Var,Abs) , make it Input (Data,Var,Abs) */
if (data[offset] == 0x81 && data[offset + 1] == 0x03)
data[offset + 1] = 0x02;
}
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/* only bind to the keyboard interface */
ctx->retval = ctx->rdesc_size != 213;
if (ctx->retval)
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
# 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
SOURCES = $(wildcard *.bpf.c)
TARGETS = $(SOURCES:.bpf.c=.bpf.o)
all: $(TARGETS)
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(TARGETS)
%.bpf.o: %.bpf.c vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@
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
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_MICROSOFT 0x045e
#define PID_XBOX_ELITE_2 0x0b22
HID_BPF_CONFIG(
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC, VID_MICROSOFT, PID_XBOX_ELITE_2)
);
/*
* When using the XBox Wireless Controller Elite 2 over Bluetooth,
* the device exports the paddle on the back of the device as a single
* bitfield value of usage "Assign Selection".
*
* The kernel doesn't process those usages properly and report KEY_UNKNOWN
* for it.
*
* SDL doesn't know how to interprete that KEY_UNKNOWN and thus ignores the paddles.
*
* Given that over USB the kernel uses BTN_TRIGGER_HAPPY[5-8], we
* can tweak the report descriptor to make the kernel interprete it properly:
* - we need an application collection of gamepad (so we have to close the current
* Consumer Control one)
* - we need to change the usage to be buttons from 0x15 to 0x18
*/
#define OFFSET_ASSIGN_SELECTION 211
#define ORIGINAL_RDESC_SIZE 464
const __u8 rdesc_assign_selection[] = {
0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
0x15, 0x00, // Logical Minimum (0) 214
0x26, 0xff, 0x00, // Logical Maximum (255) 216
0x95, 0x01, // Report Count (1) 219
0x75, 0x04, // Report Size (4) 221
0x81, 0x02, // Input (Data,Var,Abs) 223
0x15, 0x00, // Logical Minimum (0) 225
0x25, 0x00, // Logical Maximum (0) 227
0x95, 0x01, // Report Count (1) 229
0x75, 0x04, // Report Size (4) 231
0x81, 0x03, // Input (Cnst,Var,Abs) 233
0x0a, 0x81, 0x00, // Usage (Assign Selection) 235
0x15, 0x00, // Logical Minimum (0) 238
0x26, 0xff, 0x00, // Logical Maximum (255) 240
0x95, 0x01, // Report Count (1) 243
0x75, 0x04, // Report Size (4) 245
0x81, 0x02, // Input (Data,Var,Abs) 247
};
/*
* we replace the above report descriptor extract
* with the one below.
* To make things equal in size, we take out a larger
* portion than just the "Assign Selection" range, because
* we need to insert a new application collection to force
* the kernel to use BTN_TRIGGER_HAPPY[4-7].
*/
const __u8 fixed_rdesc_assign_selection[] = {
0x0a, 0x99, 0x00, // Usage (Media Select Security) 211
0x15, 0x00, // Logical Minimum (0) 214
0x26, 0xff, 0x00, // Logical Maximum (255) 216
0x95, 0x01, // Report Count (1) 219
0x75, 0x04, // Report Size (4) 221
0x81, 0x02, // Input (Data,Var,Abs) 223
/* 0x15, 0x00, */ // Logical Minimum (0) ignored
0x25, 0x01, // Logical Maximum (1) 225
0x95, 0x04, // Report Count (4) 227
0x75, 0x01, // Report Size (1) 229
0x81, 0x03, // Input (Cnst,Var,Abs) 231
0xc0, // End Collection 233
0x05, 0x01, // Usage Page (Generic Desktop) 234
0x0a, 0x05, 0x00, // Usage (Game Pad) 236
0xa1, 0x01, // Collection (Application) 239
0x05, 0x09, // Usage Page (Button) 241
0x19, 0x15, // Usage Minimum (21) 243
0x29, 0x18, // Usage Maximum (24) 245
/* 0x15, 0x00, */ // Logical Minimum (0) ignored
/* 0x25, 0x01, */ // Logical Maximum (1) ignored
/* 0x95, 0x01, */ // Report Size (1) ignored
/* 0x75, 0x04, */ // Report Count (4) ignored
0x81, 0x02, // Input (Data,Var,Abs) 247
};
_Static_assert(sizeof(rdesc_assign_selection) == sizeof(fixed_rdesc_assign_selection),
"Rdesc and fixed rdesc of different size");
_Static_assert(sizeof(rdesc_assign_selection) + OFFSET_ASSIGN_SELECTION < ORIGINAL_RDESC_SIZE,
"Rdesc at given offset is too big");
SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0; /* EPERM check */
/* Check that the device is compatible */
if (__builtin_memcmp(data + OFFSET_ASSIGN_SELECTION,
rdesc_assign_selection,
sizeof(rdesc_assign_selection)))
return 0;
__builtin_memcpy(data + OFFSET_ASSIGN_SELECTION,
fixed_rdesc_assign_selection,
sizeof(fixed_rdesc_assign_selection));
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/* only bind to the keyboard interface */
ctx->retval = ctx->rdesc_size != ORIGINAL_RDESC_SIZE;
if (ctx->retval)
ctx->retval = -EINVAL;
if (__builtin_memcmp(ctx->rdesc + OFFSET_ASSIGN_SELECTION,
rdesc_assign_selection,
sizeof(rdesc_assign_selection)))
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
# HID-BPF programs
This directory contains various fixes for devices. They add new features or
fix some behaviors without being entirely mandatory. It is better to load them
when you have such a device, but they should not be a requirement for a device
to be working during the boot stage.
The .bpf.c files provided here are not automatically compiled in the kernel.
They should be loaded in the kernel by `udev-hid-bpf`:
https://gitlab.freedesktop.org/libevdev/udev-hid-bpf
The main reasons for these fixes to be here is to have a central place to
"upstream" them, but also this way we can test them thanks to the HID
selftests.
Once a .bpf.c file is accepted here, it is duplicated in `udev-hid-bpf`
in the `src/bpf/stable` directory, and distributions are encouraged to
only ship those bpf objects. So adding a file here should eventually
land in distributions when they update `udev-hid-bpf`
## Compilation
Just run `make`
## Installation
### Automated way
Just run `sudo udev-hid-bpf install ./my-awesome-fix.bpf.o`
### Manual way
- copy the `.bpf.o` you want in `/etc/udev-hid-bpf/`
- create a new udev rule to automatically load it
The following should do the trick (assuming udev-hid-bpf is available in
/usr/bin):
```
$> cp xppen-ArtistPro16Gen2.bpf.o /etc/udev-hid-bpf/
$> udev-hid-bpf inspect xppen-ArtistPro16Gen2.bpf.o
[
{
"name": "xppen-ArtistPro16Gen2.bpf.o",
"devices": [
{
"bus": "0x0003",
"group": "0x0001",
"vid": "0x28BD",
"pid": "0x095A"
},
{
"bus": "0x0003",
"group": "0x0001",
"vid": "0x28BD",
"pid": "0x095B"
}
],
...
$> cat <EOF > /etc/udev/rules.d/99-load-hid-bpf-xppen-ArtistPro16Gen2.rules
ACTION!="add|remove", GOTO="hid_bpf_end"
SUBSYSTEM!="hid", GOTO="hid_bpf_end"
# xppen-ArtistPro16Gen2.bpf.o
ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095A", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
# xppen-ArtistPro16Gen2.bpf.o
ACTION=="add",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf add $sys$devpath /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o"
ACTION=="remove",ENV{MODALIAS}=="hid:b0003g0001v000028BDp0000095B", RUN{program}+="/usr/local/bin/udev-hid-bpf remove $sys$devpath "
LABEL="hid_bpf_end"
EOF
$> udevadm control --reload
```
Then unplug and replug the device.
## Checks
### udev rule
You can check that the udev rule is correctly working by issuing
```
$> udevadm test /sys/bus/hid/devices/0003:28BD:095B*
...
run: '/usr/local/bin/udev-hid-bpf add /sys/devices/virtual/misc/uhid/0003:28BD:095B.0E57 /etc/udev-hid-bpf/xppen-ArtistPro16Gen2.bpf.o'
```
### program loaded
You can check that the program has been properly loaded with `bpftool`
```
$> bpftool prog
...
247: tracing name xppen_16_fix_eraser tag 18d389353ed2ef07 gpl
loaded_at 2024-03-28T16:02:28+0100 uid 0
xlated 120B jited 77B memlock 4096B
btf_id 487
```
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2024 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_WACOM 0x056a
#define ART_PEN_ID 0x0804
#define PID_INTUOS_PRO_2_M 0x0357
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_WACOM, PID_INTUOS_PRO_2_M)
);
/*
* This filter is here for the Art Pen stylus only:
* - when used on some Wacom devices (see the list of attached PIDs), this pen
* reports pressure every other events.
* - to solve that, given that we know that the next event will be the same as
* the current one, we can emulate a smoother pressure reporting by reporting
* the mean of the previous value and the current one.
*
* We are effectively delaying the pressure by one event every other event, but
* that's less of an annoyance compared to the chunkiness of the reported data.
*
* For example, let's assume the following set of events:
* <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
* <Tip switch 1> <X 1> <Y 1> <Pressure 100 > <Tooltype 0x0804>
* <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
* <Tip switch 1> <X 3> <Y 3> <Pressure 200 > <Tooltype 0x0804>
* <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
* <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
*
* The filter will report:
* <Tip switch 0> <X 0> <Y 0> <Pressure 0 > <Tooltype 0x0804>
* <Tip switch 1> <X 1> <Y 1> <Pressure * 50*> <Tooltype 0x0804>
* <Tip switch 1> <X 2> <Y 2> <Pressure 100 > <Tooltype 0x0804>
* <Tip switch 1> <X 3> <Y 3> <Pressure *150*> <Tooltype 0x0804>
* <Tip switch 1> <X 4> <Y 4> <Pressure 200 > <Tooltype 0x0804>
* <Tip switch 0> <X 5> <Y 5> <Pressure 0 > <Tooltype 0x0804>
*
*/
struct wacom_params {
__u16 pid;
__u16 rdesc_len;
__u8 report_id;
__u8 report_len;
struct {
__u8 tip_switch;
__u8 pressure;
__u8 tool_type;
} offsets;
};
/*
* Multiple device can support the same stylus, so
* we need to know which device has which offsets
*/
static const struct wacom_params devices[] = {
{
.pid = PID_INTUOS_PRO_2_M,
.rdesc_len = 949,
.report_id = 16,
.report_len = 27,
.offsets = {
.tip_switch = 1,
.pressure = 8,
.tool_type = 25,
},
},
};
static struct wacom_params params = { 0 };
/* HID-BPF reports a 64 bytes chunk anyway, so this ensures
* the verifier to know we are addressing the memory correctly
*/
#define PEN_REPORT_LEN 64
/* only odd frames are modified */
static bool odd;
static __u16 prev_pressure;
static inline void *get_bits(__u8 *data, unsigned int byte_offset)
{
return data + byte_offset;
}
static inline __u16 *get_u16(__u8 *data, unsigned int offset)
{
return (__u16 *)get_bits(data, offset);
}
static inline __u8 *get_u8(__u8 *data, unsigned int offset)
{
return (__u8 *)get_bits(data, offset);
}
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(artpen_pressure_interpolate, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, PEN_REPORT_LEN /* size */);
__u16 *pressure, *tool_type;
__u8 *tip_switch;
if (!data)
return 0; /* EPERM check */
if (data[0] != params.report_id ||
params.offsets.tip_switch >= PEN_REPORT_LEN ||
params.offsets.pressure >= PEN_REPORT_LEN - 1 ||
params.offsets.tool_type >= PEN_REPORT_LEN - 1)
return 0; /* invalid report or parameters */
tool_type = get_u16(data, params.offsets.tool_type);
if (*tool_type != ART_PEN_ID)
return 0;
tip_switch = get_u8(data, params.offsets.tip_switch);
if ((*tip_switch & 0x01) == 0) {
prev_pressure = 0;
odd = true;
return 0;
}
pressure = get_u16(data, params.offsets.pressure);
if (odd)
*pressure = (*pressure + prev_pressure) / 2;
prev_pressure = *pressure;
odd = !odd;
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
struct hid_bpf_ctx *hid_ctx;
__u16 pid;
int i;
/* get a struct hid_device to access the actual pid of the device */
hid_ctx = hid_bpf_allocate_context(ctx->hid);
if (!hid_ctx) {
ctx->retval = -ENODEV;
return -1; /* EPERM check */
}
pid = hid_ctx->hid->product;
ctx->retval = -EINVAL;
/* Match the given device with the list of known devices */
for (i = 0; i < ARRAY_SIZE(devices); i++) {
const struct wacom_params *device = &devices[i];
if (device->pid == pid && device->rdesc_len == ctx->rdesc_size) {
params = *device;
ctx->retval = 0;
}
}
hid_bpf_release_context(hid_ctx);
return 0;
}
char _license[] SEC("license") = "GPL";
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2023 Benjamin Tissoires
*/
#include "vmlinux.h"
#include "hid_bpf.h"
#include "hid_bpf_helpers.h"
#include <bpf/bpf_tracing.h>
#define VID_UGEE 0x28BD /* VID is shared with SinoWealth and Glorious and prob others */
#define PID_ARTIST_24 0x093A
#define PID_ARTIST_24_PRO 0x092D
HID_BPF_CONFIG(
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24),
HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, VID_UGEE, PID_ARTIST_24_PRO)
);
/*
* We need to amend the report descriptor for the following:
* - the device reports Eraser instead of using Secondary Barrel Switch
* - the pen doesn't have a rubber tail, so basically we are removing any
* eraser/invert bits
*/
static const __u8 fixed_rdesc[] = {
0x05, 0x0d, // Usage Page (Digitizers) 0
0x09, 0x02, // Usage (Pen) 2
0xa1, 0x01, // Collection (Application) 4
0x85, 0x07, // Report ID (7) 6
0x09, 0x20, // Usage (Stylus) 8
0xa1, 0x00, // Collection (Physical) 10
0x09, 0x42, // Usage (Tip Switch) 12
0x09, 0x44, // Usage (Barrel Switch) 14
0x09, 0x5a, // Usage (Secondary Barrel Switch) 16 /* changed from 0x45 (Eraser) to 0x5a (Secondary Barrel Switch) */
0x15, 0x00, // Logical Minimum (0) 18
0x25, 0x01, // Logical Maximum (1) 20
0x75, 0x01, // Report Size (1) 22
0x95, 0x03, // Report Count (3) 24
0x81, 0x02, // Input (Data,Var,Abs) 26
0x95, 0x02, // Report Count (2) 28
0x81, 0x03, // Input (Cnst,Var,Abs) 30
0x09, 0x32, // Usage (In Range) 32
0x95, 0x01, // Report Count (1) 34
0x81, 0x02, // Input (Data,Var,Abs) 36
0x95, 0x02, // Report Count (2) 38
0x81, 0x03, // Input (Cnst,Var,Abs) 40
0x75, 0x10, // Report Size (16) 42
0x95, 0x01, // Report Count (1) 44
0x35, 0x00, // Physical Minimum (0) 46
0xa4, // Push 48
0x05, 0x01, // Usage Page (Generic Desktop) 49
0x09, 0x30, // Usage (X) 51
0x65, 0x13, // Unit (EnglishLinear: in) 53
0x55, 0x0d, // Unit Exponent (-3) 55
0x46, 0xf0, 0x50, // Physical Maximum (20720) 57
0x26, 0xff, 0x7f, // Logical Maximum (32767) 60
0x81, 0x02, // Input (Data,Var,Abs) 63
0x09, 0x31, // Usage (Y) 65
0x46, 0x91, 0x2d, // Physical Maximum (11665) 67
0x26, 0xff, 0x7f, // Logical Maximum (32767) 70
0x81, 0x02, // Input (Data,Var,Abs) 73
0xb4, // Pop 75
0x09, 0x30, // Usage (Tip Pressure) 76
0x45, 0x00, // Physical Maximum (0) 78
0x26, 0xff, 0x1f, // Logical Maximum (8191) 80
0x81, 0x42, // Input (Data,Var,Abs,Null) 83
0x09, 0x3d, // Usage (X Tilt) 85
0x15, 0x81, // Logical Minimum (-127) 87
0x25, 0x7f, // Logical Maximum (127) 89
0x75, 0x08, // Report Size (8) 91
0x95, 0x01, // Report Count (1) 93
0x81, 0x02, // Input (Data,Var,Abs) 95
0x09, 0x3e, // Usage (Y Tilt) 97
0x15, 0x81, // Logical Minimum (-127) 99
0x25, 0x7f, // Logical Maximum (127) 101
0x81, 0x02, // Input (Data,Var,Abs) 103
0xc0, // End Collection 105
0xc0, // End Collection 106
};
#define BIT(n) (1UL << n)
#define TIP_SWITCH BIT(0)
#define BARREL_SWITCH BIT(1)
#define ERASER BIT(2)
/* padding BIT(3) */
/* padding BIT(4) */
#define IN_RANGE BIT(5)
/* padding BIT(6) */
/* padding BIT(7) */
#define U16(index) (data[index] | (data[index + 1] << 8))
SEC("fmod_ret/hid_bpf_rdesc_fixup")
int BPF_PROG(hid_fix_rdesc_xppen_artist24, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 4096 /* size */);
if (!data)
return 0; /* EPERM check */
__builtin_memcpy(data, fixed_rdesc, sizeof(fixed_rdesc));
return sizeof(fixed_rdesc);
}
static __u8 prev_state = 0;
/*
* There are a few cases where the device is sending wrong event
* sequences, all related to the second button (the pen doesn't
* have an eraser switch on the tail end):
*
* whenever the second button gets pressed or released, an
* out-of-proximity event is generated and then the firmware
* compensate for the missing state (and the firmware uses
* eraser for that button):
*
* - if the pen is in range, an extra out-of-range is sent
* when the second button is pressed/released:
* // Pen is in range
* E: InRange
*
* // Second button is pressed
* E:
* E: Eraser InRange
*
* // Second button is released
* E:
* E: InRange
*
* This case is ignored by this filter, it's "valid"
* and userspace knows how to deal with it, there are just
* a few out-of-prox events generated, but the user doesn´t
* see them.
*
* - if the pen is in contact, 2 extra events are added when
* the second button is pressed/released: an out of range
* and an in range:
*
* // Pen is in contact
* E: TipSwitch InRange
*
* // Second button is pressed
* E: <- false release, needs to be filtered out
* E: Eraser InRange <- false release, needs to be filtered out
* E: TipSwitch Eraser InRange
*
* // Second button is released
* E: <- false release, needs to be filtered out
* E: InRange <- false release, needs to be filtered out
* E: TipSwitch InRange
*
*/
SEC("fmod_ret/hid_bpf_device_event")
int BPF_PROG(xppen_24_fix_eraser, struct hid_bpf_ctx *hctx)
{
__u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 10 /* size */);
__u8 current_state, changed_state;
bool prev_tip;
__u16 tilt;
if (!data)
return 0; /* EPERM check */
current_state = data[1];
/* if the state is identical to previously, early return */
if (current_state == prev_state)
return 0;
prev_tip = !!(prev_state & TIP_SWITCH);
/*
* Illegal transition: pen is in range with the tip pressed, and
* it goes into out of proximity.
*
* Ideally we should hold the event, start a timer and deliver it
* only if the timer ends, but we are not capable of that now.
*
* And it doesn't matter because when we are in such cases, this
* means we are detecting a false release.
*/
if ((current_state & IN_RANGE) == 0) {
if (prev_tip)
return HID_IGNORE_EVENT;
return 0;
}
/*
* XOR to only set the bits that have changed between
* previous and current state
*/
changed_state = prev_state ^ current_state;
/* Store the new state for future processing */
prev_state = current_state;
/*
* We get both a tipswitch and eraser change in the same HID report:
* this is not an authorized transition and is unlikely to happen
* in real life.
* This is likely to be added by the firmware to emulate the
* eraser mode so we can skip the event.
*/
if ((changed_state & (TIP_SWITCH | ERASER)) == (TIP_SWITCH | ERASER)) /* we get both a tipswitch and eraser change at the same time */
return HID_IGNORE_EVENT;
return 0;
}
SEC("syscall")
int probe(struct hid_bpf_probe_args *ctx)
{
/*
* The device exports 3 interfaces.
*/
ctx->retval = ctx->rdesc_size != 107;
if (ctx->retval)
ctx->retval = -EINVAL;
/* ensure the kernel isn't fixed already */
if (ctx->rdesc[17] != 0x45) /* Eraser */
ctx->retval = -EINVAL;
return 0;
}
char _license[] SEC("license") = "GPL";
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Benjamin Tissoires
*/
#ifndef ____HID_BPF__H
#define ____HID_BPF__H
struct hid_bpf_probe_args {
unsigned int hid;
unsigned int rdesc_size;
unsigned char rdesc[4096];
int retval;
};
#endif /* ____HID_BPF__H */
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2022 Benjamin Tissoires
*/
#ifndef __HID_BPF_HELPERS_H
#define __HID_BPF_HELPERS_H
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <linux/errno.h>
extern __u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx,
unsigned int offset,
const size_t __sz) __ksym;
extern struct hid_bpf_ctx *hid_bpf_allocate_context(unsigned int hid_id) __ksym;
extern void hid_bpf_release_context(struct hid_bpf_ctx *ctx) __ksym;
extern int hid_bpf_hw_request(struct hid_bpf_ctx *ctx,
__u8 *data,
size_t buf__sz,
enum hid_report_type type,
enum hid_class_request reqtype) __ksym;
#define HID_MAX_DESCRIPTOR_SIZE 4096
#define HID_IGNORE_EVENT -1
/* extracted from <linux/input.h> */
#define BUS_ANY 0x00
#define BUS_PCI 0x01
#define BUS_ISAPNP 0x02
#define BUS_USB 0x03
#define BUS_HIL 0x04
#define BUS_BLUETOOTH 0x05
#define BUS_VIRTUAL 0x06
#define BUS_ISA 0x10
#define BUS_I8042 0x11
#define BUS_XTKBD 0x12
#define BUS_RS232 0x13
#define BUS_GAMEPORT 0x14
#define BUS_PARPORT 0x15
#define BUS_AMIGA 0x16
#define BUS_ADB 0x17
#define BUS_I2C 0x18
#define BUS_HOST 0x19
#define BUS_GSC 0x1A
#define BUS_ATARI 0x1B
#define BUS_SPI 0x1C
#define BUS_RMI 0x1D
#define BUS_CEC 0x1E
#define BUS_INTEL_ISHTP 0x1F
#define BUS_AMD_SFH 0x20
/* extracted from <linux/hid.h> */
#define HID_GROUP_ANY 0x0000
#define HID_GROUP_GENERIC 0x0001
#define HID_GROUP_MULTITOUCH 0x0002
#define HID_GROUP_SENSOR_HUB 0x0003
#define HID_GROUP_MULTITOUCH_WIN_8 0x0004
#define HID_GROUP_RMI 0x0100
#define HID_GROUP_WACOM 0x0101
#define HID_GROUP_LOGITECH_DJ_DEVICE 0x0102
#define HID_GROUP_STEAM 0x0103
#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104
#define HID_GROUP_VIVALDI 0x0105
/* include/linux/mod_devicetable.h defines as (~0), but that gives us negative size arrays */
#define HID_VID_ANY 0x0000
#define HID_PID_ANY 0x0000
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
/* Helper macro to convert (foo, __LINE__) into foo134 so we can use __LINE__ for
* field/variable names
*/
#define COMBINE1(X, Y) X ## Y
#define COMBINE(X, Y) COMBINE1(X, Y)
/* Macro magic:
* __uint(foo, 123) creates a int (*foo)[1234]
*
* We use that macro to declare an anonymous struct with several
* fields, each is the declaration of an pointer to an array of size
* bus/group/vid/pid. (Because it's a pointer to such an array, actual storage
* would be sizeof(pointer) rather than sizeof(array). Not that we ever
* instantiate it anyway).
*
* This is only used for BTF introspection, we can later check "what size
* is the bus array" in the introspection data and thus extract the bus ID
* again.
*
* And we use the __LINE__ to give each of our structs a unique name so the
* BPF program writer doesn't have to.
*
* $ bpftool btf dump file target/bpf/HP_Elite_Presenter.bpf.o
* shows the inspection data, start by searching for .hid_bpf_config
* and working backwards from that (each entry references the type_id of the
* content).
*/
#define HID_DEVICE(b, g, ven, prod) \
struct { \
__uint(name, 0); \
__uint(bus, (b)); \
__uint(group, (g)); \
__uint(vid, (ven)); \
__uint(pid, (prod)); \
} COMBINE(_entry, __LINE__)
/* Macro magic below is to make HID_BPF_CONFIG() look like a function call that
* we can pass multiple HID_DEVICE() invocations in.
*
* For up to 16 arguments, HID_BPF_CONFIG(one, two) resolves to
*
* union {
* HID_DEVICE(...);
* HID_DEVICE(...);
* } _device_ids SEC(".hid_bpf_config")
*
*/
/* Returns the number of macro arguments, this expands
* NARGS(a, b, c) to NTH_ARG(a, b, c, 15, 14, 13, .... 4, 3, 2, 1).
* NTH_ARG always returns the 16th argument which in our case is 3.
*
* If we want more than 16 values _COUNTDOWN and _NTH_ARG both need to be
* updated.
*/
#define _NARGS(...) _NARGS1(__VA_ARGS__, _COUNTDOWN)
#define _NARGS1(...) _NTH_ARG(__VA_ARGS__)
/* Add to this if we need more than 16 args */
#define _COUNTDOWN \
15, 14, 13, 12, 11, 10, 9, 8, \
7, 6, 5, 4, 3, 2, 1, 0
/* Return the 16 argument passed in. See _NARGS above for usage. Note this is
* 1-indexed.
*/
#define _NTH_ARG( \
_1, _2, _3, _4, _5, _6, _7, _8, \
_9, _10, _11, _12, _13, _14, _15,\
N, ...) N
/* Turns EXPAND(_ARG, a, b, c) into _ARG3(a, b, c) */
#define _EXPAND(func, ...) COMBINE(func, _NARGS(__VA_ARGS__)) (__VA_ARGS__)
/* And now define all the ARG macros for each number of args we want to accept */
#define _ARG1(_1) _1;
#define _ARG2(_1, _2) _1; _2;
#define _ARG3(_1, _2, _3) _1; _2; _3;
#define _ARG4(_1, _2, _3, _4) _1; _2; _3; _4;
#define _ARG5(_1, _2, _3, _4, _5) _1; _2; _3; _4; _5;
#define _ARG6(_1, _2, _3, _4, _5, _6) _1; _2; _3; _4; _5; _6;
#define _ARG7(_1, _2, _3, _4, _5, _6, _7) _1; _2; _3; _4; _5; _6; _7;
#define _ARG8(_1, _2, _3, _4, _5, _6, _7, _8) _1; _2; _3; _4; _5; _6; _7; _8;
#define _ARG9(_1, _2, _3, _4, _5, _6, _7, _8, _9) _1; _2; _3; _4; _5; _6; _7; _8; _9;
#define _ARG10(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a;
#define _ARG11(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b;
#define _ARG12(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c;
#define _ARG13(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d;
#define _ARG14(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e;
#define _ARG15(_1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, _d, _e, _f) _1; _2; _3; _4; _5; _6; _7; _8; _9; _a; _b; _c; _d; _e; _f;
#define HID_BPF_CONFIG(...) union { \
_EXPAND(_ARG, __VA_ARGS__) \
} _device_ids SEC(".hid_bpf_config")
#endif /* __HID_BPF_HELPERS_H */
......@@ -335,36 +335,20 @@ static int asus_raw_event(struct hid_device *hdev,
if (drvdata->quirks & QUIRK_MEDION_E1239T)
return asus_e1239t_event(drvdata, data, size);
if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) {
/*
* Skip these report ID, the device emits a continuous stream associated
* with the AURA mode it is in which looks like an 'echo'.
*/
if (report->id == FEATURE_KBD_LED_REPORT_ID1 || report->id == FEATURE_KBD_LED_REPORT_ID2)
return -1;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
/*
* Skip these report ID, the device emits a continuous stream associated
* with the AURA mode it is in which looks like an 'echo'.
* G713 and G733 send these codes on some keypresses, depending on
* the key pressed it can trigger a shutdown event if not caught.
*/
if (report->id == FEATURE_KBD_LED_REPORT_ID1 ||
report->id == FEATURE_KBD_LED_REPORT_ID2) {
if (data[0] == 0x02 && data[1] == 0x30) {
return -1;
/* Additional report filtering */
} else if (report->id == FEATURE_KBD_REPORT_ID) {
/*
* G14 and G15 send these codes on some keypresses with no
* discernable reason for doing so. We'll filter them out to avoid
* unmapped warning messages later.
*/
if (data[1] == 0xea || data[1] == 0xec || data[1] == 0x02 ||
data[1] == 0x8a || data[1] == 0x9e) {
return -1;
}
}
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
/*
* G713 and G733 send these codes on some keypresses, depending on
* the key pressed it can trigger a shutdown event if not caught.
*/
if(data[0] == 0x02 && data[1] == 0x30) {
return -1;
}
}
}
if (drvdata->quirks & QUIRK_ROG_CLAYMORE_II_KEYBOARD) {
......@@ -402,9 +386,9 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
return ret;
}
static int asus_kbd_init(struct hid_device *hdev)
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
......@@ -416,9 +400,10 @@ static int asus_kbd_init(struct hid_device *hdev)
}
static int asus_kbd_get_functions(struct hid_device *hdev,
unsigned char *kbd_func)
unsigned char *kbd_func,
u8 report_id)
{
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0x05, 0x20, 0x31, 0x00, 0x08 };
const u8 buf[] = { report_id, 0x05, 0x20, 0x31, 0x00, 0x08 };
u8 *readbuf;
int ret;
......@@ -447,51 +432,6 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
return ret;
}
static int rog_nkey_led_init(struct hid_device *hdev)
{
const u8 buf_init_start[] = { FEATURE_KBD_LED_REPORT_ID1, 0xB9 };
u8 buf_init2[] = { FEATURE_KBD_LED_REPORT_ID1, 0x41, 0x53, 0x55, 0x53, 0x20,
0x54, 0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
u8 buf_init3[] = { FEATURE_KBD_LED_REPORT_ID1,
0x05, 0x20, 0x31, 0x00, 0x08 };
int ret;
hid_info(hdev, "Asus initialise N-KEY Device");
/* The first message is an init start */
ret = asus_kbd_set_report(hdev, buf_init_start, sizeof(buf_init_start));
if (ret < 0) {
hid_warn(hdev, "Asus failed to send init start command: %d\n", ret);
return ret;
}
/* Followed by a string */
ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
if (ret < 0) {
hid_warn(hdev, "Asus failed to send init command 1.0: %d\n", ret);
return ret;
}
/* Followed by a string */
ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
if (ret < 0) {
hid_warn(hdev, "Asus failed to send init command 1.1: %d\n", ret);
return ret;
}
/* begin second report ID with same data */
buf_init2[0] = FEATURE_KBD_LED_REPORT_ID2;
buf_init3[0] = FEATURE_KBD_LED_REPORT_ID2;
ret = asus_kbd_set_report(hdev, buf_init2, sizeof(buf_init2));
if (ret < 0) {
hid_warn(hdev, "Asus failed to send init command 2.0: %d\n", ret);
return ret;
}
ret = asus_kbd_set_report(hdev, buf_init3, sizeof(buf_init3));
if (ret < 0)
hid_warn(hdev, "Asus failed to send init command 2.1: %d\n", ret);
return ret;
}
static void asus_schedule_work(struct asus_kbd_leds *led)
{
unsigned long flags;
......@@ -574,17 +514,27 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
int ret;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
ret = rog_nkey_led_init(hdev);
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* The LED endpoint is initialised in two HID */
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
if (ret < 0)
return ret;
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
if (ret < 0)
return ret;
} else {
/* Initialize keyboard */
ret = asus_kbd_init(hdev);
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
ret = asus_kbd_get_functions(hdev, &kbd_func);
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
......@@ -897,7 +847,10 @@ static int asus_input_mapping(struct hid_device *hdev,
case 0xb3: asus_map_key_clear(KEY_PROG3); break; /* Fn+Left next aura */
case 0x6a: asus_map_key_clear(KEY_F13); break; /* Screenpad toggle */
case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */
case 0xa5: asus_map_key_clear(KEY_F15); break; /* ROG Ally left back */
case 0xa6: asus_map_key_clear(KEY_F16); break; /* ROG Ally QAM button */
case 0xa7: asus_map_key_clear(KEY_F17); break; /* ROG Ally ROG long-press */
case 0xa8: asus_map_key_clear(KEY_F18); break; /* ROG Ally ROG long-press-release */
default:
/* ASUS lazily declares 256 usages, ignore the rest,
......@@ -1250,6 +1203,19 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
rdesc[205] = 0x01;
}
/* match many more n-key devices */
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
for (int i = 0; i < *rsize + 1; i++) {
/* offset to the count from 0x5a report part always 14 */
if (rdesc[i] == 0x85 && rdesc[i + 1] == 0x5a &&
rdesc[i + 14] == 0x95 && rdesc[i + 15] == 0x05) {
hid_info(hdev, "Fixing up Asus N-Key report descriptor\n");
rdesc[i + 15] = 0x01;
break;
}
}
}
return rdesc;
}
......@@ -1276,6 +1242,12 @@ static const struct hid_device_id asus_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD),
QUIRK_ROG_CLAYMORE_II_KEYBOARD },
......@@ -1319,4 +1291,4 @@ static struct hid_driver asus_driver = {
};
module_hid_driver(asus_driver);
MODULE_LICENSE("GPL");
\ No newline at end of file
MODULE_LICENSE("GPL");
......@@ -2974,6 +2974,8 @@ EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
static struct hid_bpf_ops hid_ops = {
.hid_get_report = hid_get_report,
.hid_hw_raw_request = hid_hw_raw_request,
.hid_hw_output_report = hid_hw_output_report,
.hid_input_report = hid_input_report,
.owner = THIS_MODULE,
.bus_type = &hid_bus_type,
};
......
......@@ -298,7 +298,7 @@ static ssize_t k90_show_macro_mode(struct device *dev,
goto out;
}
ret = snprintf(buf, PAGE_SIZE, "%s\n", macro_mode);
ret = sysfs_emit(buf, "%s\n", macro_mode);
out:
kfree(data);
......@@ -367,7 +367,7 @@ static ssize_t k90_show_current_profile(struct device *dev,
goto out;
}
ret = snprintf(buf, PAGE_SIZE, "%d\n", current_profile);
ret = sysfs_emit(buf, "%d\n", current_profile);
out:
kfree(data);
......
This diff is collapsed.
......@@ -255,7 +255,7 @@ static int cbas_ec_probe(struct platform_device *pdev)
return retval;
}
static int cbas_ec_remove(struct platform_device *pdev)
static void cbas_ec_remove(struct platform_device *pdev)
{
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
......@@ -266,7 +266,6 @@ static int cbas_ec_remove(struct platform_device *pdev)
cbas_ec_set_input(NULL);
mutex_unlock(&cbas_ec_reglock);
return 0;
}
static const struct acpi_device_id cbas_ec_acpi_ids[] = {
......@@ -285,7 +284,7 @@ MODULE_DEVICE_TABLE(of, cbas_ec_of_match);
static struct platform_driver cbas_ec_driver = {
.probe = cbas_ec_probe,
.remove = cbas_ec_remove,
.remove_new = cbas_ec_remove,
.driver = {
.name = "cbas_ec",
.acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids),
......
......@@ -208,6 +208,8 @@
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30
#define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6
#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe
#define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b
#define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869
......@@ -823,6 +825,7 @@
#define USB_DEVICE_ID_LOGITECH_AUDIOHUB 0x0a0e
#define USB_DEVICE_ID_LOGITECH_T651 0xb00c
#define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD 0xb309
#define USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD 0xbb00
#define USB_DEVICE_ID_LOGITECH_C007 0xc007
#define USB_DEVICE_ID_LOGITECH_C077 0xc077
#define USB_DEVICE_ID_LOGITECH_RECEIVER 0xc101
......
......@@ -209,7 +209,7 @@ static const __u8 pensketch_t609a_control_rdesc[] = {
0xC0 /* End Collection */
};
/* Fix indexes in kye_tablet_fixup if you change this */
/* Fix indexes in kye_tablet_fixup() if you change this */
static const __u8 kye_tablet_rdesc[] = {
0x06, 0x00, 0xFF, /* Usage Page (FF00h), */
0x09, 0x01, /* Usage (01h), */
......@@ -262,12 +262,16 @@ static const __u8 kye_tablet_rdesc[] = {
0x27, 0xFF, 0x07, 0x00, 0x00, /* Logical Maximum (2047), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0, /* End Collection, */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x21, /* Usage (Puck), */
0xC0 /* End Collection, */
};
/* Fix indexes in kye_tablet_fixup() if you change this */
static const __u8 kye_tablet_mouse_rdesc[] = {
0x05, 0x01, /* Usage Page (Desktop), */
0x09, 0x02, /* Usage (Mouse), */
0xA1, 0x01, /* Collection (Application), */
0x85, 0x11, /* Report ID (17), */
0x09, 0x21, /* Usage (Puck), */
0x09, 0x01, /* Usage (Pointer), */
0xA0, /* Collection (Physical), */
0x05, 0x09, /* Usage Page (Button), */
0x19, 0x01, /* Usage Minimum (01h), */
......@@ -280,7 +284,7 @@ static const __u8 kye_tablet_rdesc[] = {
0x95, 0x04, /* Report Count (4), */
0x81, 0x01, /* Input (Constant), */
0x05, 0x0D, /* Usage Page (Digitizer), */
0x09, 0x32, /* Usage (In Range), */
0x09, 0x37, /* Usage (Data Valid), */
0x95, 0x01, /* Report Count (1), */
0x81, 0x02, /* Input (Variable), */
0x05, 0x01, /* Usage Page (Desktop), */
......@@ -317,7 +321,7 @@ static const struct kye_tablet_info {
__s32 y_physical_maximum;
__s8 unit_exponent;
__s8 unit;
bool has_punk;
bool has_mouse;
unsigned int control_rsize;
const __u8 *control_rdesc;
} kye_tablets_info[] = {
......@@ -402,7 +406,7 @@ static __u8 *kye_consumer_control_fixup(struct hid_device *hdev, __u8 *rdesc,
static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize)
{
const struct kye_tablet_info *info;
unsigned int newsize;
__u8 *newdesc = rdesc;
if (*rsize < sizeof(kye_tablet_rdesc)) {
hid_warn(hdev,
......@@ -420,36 +424,45 @@ static __u8 *kye_tablet_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int
return rdesc;
}
newsize = info->has_punk ? sizeof(kye_tablet_rdesc) : 112;
memcpy(rdesc, kye_tablet_rdesc, newsize);
put_unaligned_le32(info->x_logical_maximum, rdesc + 66);
put_unaligned_le32(info->x_physical_maximum, rdesc + 72);
rdesc[77] = info->unit;
rdesc[79] = info->unit_exponent;
put_unaligned_le32(info->y_logical_maximum, rdesc + 87);
put_unaligned_le32(info->y_physical_maximum, rdesc + 92);
put_unaligned_le32(info->pressure_logical_maximum, rdesc + 104);
if (info->has_punk) {
put_unaligned_le32(info->x_logical_maximum, rdesc + 156);
put_unaligned_le32(info->x_physical_maximum, rdesc + 162);
rdesc[167] = info->unit;
rdesc[169] = info->unit_exponent;
put_unaligned_le32(info->y_logical_maximum, rdesc + 177);
put_unaligned_le32(info->y_physical_maximum, rdesc + 182);
memcpy(newdesc, kye_tablet_rdesc, sizeof(kye_tablet_rdesc));
put_unaligned_le32(info->x_logical_maximum, newdesc + 66);
put_unaligned_le32(info->x_physical_maximum, newdesc + 72);
newdesc[77] = info->unit;
newdesc[79] = info->unit_exponent;
put_unaligned_le32(info->y_logical_maximum, newdesc + 87);
put_unaligned_le32(info->y_physical_maximum, newdesc + 92);
put_unaligned_le32(info->pressure_logical_maximum, newdesc + 104);
newdesc += sizeof(kye_tablet_rdesc);
if (info->has_mouse) {
if (newdesc + sizeof(kye_tablet_mouse_rdesc) > rdesc + *rsize)
hid_err(hdev, "control desc unexpectedly large\n");
else {
memcpy(newdesc, kye_tablet_mouse_rdesc, sizeof(kye_tablet_mouse_rdesc));
put_unaligned_le32(info->x_logical_maximum, newdesc + 44);
put_unaligned_le32(info->x_physical_maximum, newdesc + 50);
newdesc[55] = info->unit;
newdesc[57] = info->unit_exponent;
put_unaligned_le32(info->y_logical_maximum, newdesc + 65);
put_unaligned_le32(info->y_physical_maximum, newdesc + 70);
newdesc += sizeof(kye_tablet_mouse_rdesc);
}
}
if (info->control_rsize) {
if (newsize + info->control_rsize > *rsize)
hid_err(hdev, "control rdesc unexpectedly large");
if (newdesc + info->control_rsize > rdesc + *rsize)
hid_err(hdev, "control desc unexpectedly large\n");
else {
memcpy(rdesc + newsize, info->control_rdesc, info->control_rsize);
newsize += info->control_rsize;
memcpy(newdesc, info->control_rdesc, info->control_rsize);
newdesc += info->control_rsize;
}
}
*rsize = newsize;
*rsize = newdesc - rdesc;
return rdesc;
}
......
......@@ -555,7 +555,7 @@ static ssize_t attr_fn_lock_show(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock);
return sysfs_emit(buf, "%u\n", data->fn_lock);
}
static ssize_t attr_fn_lock_store(struct device *dev,
......@@ -599,8 +599,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
cptkbd_data->sensitivity);
return sysfs_emit(buf, "%u\n", cptkbd_data->sensitivity);
}
static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
......@@ -628,8 +627,8 @@ static ssize_t attr_middleclick_workaround_show_cptkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
cptkbd_data->middleclick_workaround_cptkbd);
return sysfs_emit(buf, "%u\n",
cptkbd_data->middleclick_workaround_cptkbd);
}
static ssize_t attr_middleclick_workaround_store_cptkbd(struct device *dev,
......@@ -809,7 +808,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
return sysfs_emit(buf, "%u\n", data_pointer->press_to_select);
}
static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
......@@ -839,7 +838,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
return sysfs_emit(buf, "%u\n", data_pointer->dragging);
}
static ssize_t attr_dragging_store_tpkbd(struct device *dev,
......@@ -869,7 +868,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
return sysfs_emit(buf, "%u\n", data_pointer->release_to_select);
}
static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
......@@ -899,7 +898,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
return sysfs_emit(buf, "%u\n", data_pointer->select_right);
}
static ssize_t attr_select_right_store_tpkbd(struct device *dev,
......@@ -929,8 +928,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
data_pointer->sensitivity);
return sysfs_emit(buf, "%u\n", data_pointer->sensitivity);
}
static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
......@@ -958,8 +956,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
struct hid_device *hdev = to_hid_device(dev);
struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev);
return snprintf(buf, PAGE_SIZE, "%u\n",
data_pointer->press_speed);
return sysfs_emit(buf, "%u\n", data_pointer->press_speed);
}
static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
......
......@@ -4603,6 +4603,12 @@ static const struct hid_device_id hidpp_devices[] = {
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) },
{ /* Logitech G903 Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC086) },
{ /* Logitech G Pro Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC088) },
{ /* MX Vertical over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC08A) },
{ /* Logitech G703 Hero Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC090) },
{ /* Logitech G903 Hero Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC091) },
{ /* Logitech G915 TKL Keyboard over USB */
......@@ -4613,8 +4619,6 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* 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) },
{ /* Logitech G Pro X Superlight Gaming Mouse over USB */
HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC094) },
{ /* Logitech G Pro X Superlight 2 Gaming Mouse over USB */
......@@ -4641,9 +4645,13 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012) },
{ /* M720 Triathlon mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb015) },
{ /* MX Master 2S mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb019) },
{ /* MX Ergo trackball over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01d) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e) },
{ /* MX Vertical mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
{ /* Signature M650 over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
{ /* MX Master 3 mouse over Bluetooth */
......@@ -4652,6 +4660,8 @@ static const struct hid_device_id hidpp_devices[] = {
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb025) },
{ /* MX Master 3S mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
{ /* MX Anywhere 3SB mouse over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
{}
};
......
......@@ -2081,6 +2081,12 @@ static const struct hid_device_id mt_devices[] = {
USB_VENDOR_ID_LENOVO,
USB_DEVICE_ID_LENOVO_X12_TAB) },
/* Logitech devices */
{ .driver_data = MT_CLS_NSMU,
HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_MULTITOUCH_WIN_8,
USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD) },
/* MosArt panels */
{ .driver_data = MT_CLS_CONFIDENCE_MINUS_ONE,
MT_USB_DEVICE(USB_VENDOR_ID_ASUS,
......
......@@ -34,6 +34,7 @@
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/hid.h>
#include <linux/idr.h>
#include <linux/input.h>
#include <linux/jiffies.h>
#include <linux/leds.h>
......@@ -569,6 +570,7 @@ static const enum led_brightness joycon_player_led_patterns[JC_NUM_LED_PATTERNS]
struct joycon_ctlr {
struct hid_device *hdev;
struct input_dev *input;
u32 player_id;
struct led_classdev leds[JC_NUM_LEDS]; /* player leds */
struct led_classdev home_led;
enum joycon_ctlr_state ctlr_state;
......@@ -692,15 +694,6 @@ static inline bool joycon_device_is_n64con(struct joycon_ctlr *ctlr)
return ctlr->hdev->product == USB_DEVICE_ID_NINTENDO_N64CON;
}
static inline bool joycon_device_has_usb(struct joycon_ctlr *ctlr)
{
return joycon_device_is_procon(ctlr) ||
joycon_device_is_chrggrip(ctlr) ||
joycon_device_is_snescon(ctlr) ||
joycon_device_is_gencon(ctlr) ||
joycon_device_is_n64con(ctlr);
}
/*
* Controller type helpers
*
......@@ -2261,7 +2254,8 @@ static int joycon_home_led_brightness_set(struct led_classdev *led,
return ret;
}
static DEFINE_SPINLOCK(joycon_input_num_spinlock);
static DEFINE_IDA(nintendo_player_id_allocator);
static int joycon_leds_create(struct joycon_ctlr *ctlr)
{
struct hid_device *hdev = ctlr->hdev;
......@@ -2272,20 +2266,19 @@ static int joycon_leds_create(struct joycon_ctlr *ctlr)
char *name;
int ret;
int i;
unsigned long flags;
int player_led_pattern;
static int input_num;
/*
* Set the player leds based on controller number
* Because there is no standard concept of "player number", the pattern
* number will simply increase by 1 every time a controller is connected.
*/
spin_lock_irqsave(&joycon_input_num_spinlock, flags);
player_led_pattern = input_num++ % JC_NUM_LED_PATTERNS;
spin_unlock_irqrestore(&joycon_input_num_spinlock, flags);
/* configure the player LEDs */
ctlr->player_id = U32_MAX;
ret = ida_alloc(&nintendo_player_id_allocator, GFP_KERNEL);
if (ret < 0) {
hid_warn(hdev, "Failed to allocate player ID, skipping; ret=%d\n", ret);
goto home_led;
}
ctlr->player_id = ret;
player_led_pattern = ret % JC_NUM_LED_PATTERNS;
hid_info(ctlr->hdev, "assigned player %d led pattern", player_led_pattern + 1);
for (i = 0; i < JC_NUM_LEDS; i++) {
name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s:%s",
d_name,
......@@ -2501,8 +2494,11 @@ static int joycon_init(struct hid_device *hdev)
/* set baudrate for improved latency */
ret = joycon_send_usb(ctlr, JC_USB_CMD_BAUDRATE_3M, HZ);
if (ret) {
hid_err(hdev, "Failed to set baudrate; ret=%d\n", ret);
goto out_unlock;
/*
* We can function with the default baudrate.
* Provide a warning, and continue on.
*/
hid_warn(hdev, "Failed to set baudrate (ret=%d), continuing anyway\n", ret);
}
/* handshake */
ret = joycon_send_usb(ctlr, JC_USB_CMD_HANDSHAKE, HZ);
......@@ -2767,6 +2763,7 @@ static void nintendo_hid_remove(struct hid_device *hdev)
spin_unlock_irqrestore(&ctlr->lock, flags);
destroy_workqueue(ctlr->rumble_queue);
ida_free(&nintendo_player_id_allocator, ctlr->player_id);
hid_hw_close(hdev);
hid_hw_stop(hdev);
......@@ -2824,7 +2821,19 @@ static struct hid_driver nintendo_hid_driver = {
.resume = nintendo_hid_resume,
#endif
};
module_hid_driver(nintendo_hid_driver);
static int __init nintendo_init(void)
{
return hid_register_driver(&nintendo_hid_driver);
}
static void __exit nintendo_exit(void)
{
hid_unregister_driver(&nintendo_hid_driver);
ida_destroy(&nintendo_player_id_allocator);
}
module_init(nintendo_init);
module_exit(nintendo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ryan McClelland <rymcclel@gmail.com>");
......
......@@ -256,9 +256,9 @@ static ssize_t picolcd_operation_mode_show(struct device *dev,
struct picolcd_data *data = dev_get_drvdata(dev);
if (data->status & PICOLCD_BOOTLOADER)
return snprintf(buf, PAGE_SIZE, "[bootloader] lcd\n");
return sysfs_emit(buf, "[bootloader] lcd\n");
else
return snprintf(buf, PAGE_SIZE, "bootloader [lcd]\n");
return sysfs_emit(buf, "bootloader [lcd]\n");
}
static ssize_t picolcd_operation_mode_store(struct device *dev,
......@@ -301,7 +301,7 @@ static ssize_t picolcd_operation_mode_delay_show(struct device *dev,
{
struct picolcd_data *data = dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%hu\n", data->opmode_delay);
return sysfs_emit(buf, "%hu\n", data->opmode_delay);
}
static ssize_t picolcd_operation_mode_delay_store(struct device *dev,
......
......@@ -421,12 +421,10 @@ static ssize_t picolcd_fb_update_rate_show(struct device *dev,
size_t ret = 0;
for (i = 1; i <= PICOLCDFB_UPDATE_RATE_LIMIT; i++)
if (ret >= PAGE_SIZE)
break;
else if (i == fb_update_rate)
ret += scnprintf(buf+ret, PAGE_SIZE-ret, "[%u] ", i);
if (i == fb_update_rate)
ret += sysfs_emit_at(buf, ret, "[%u] ", i);
else
ret += scnprintf(buf+ret, PAGE_SIZE-ret, "%u ", i);
ret += sysfs_emit_at(buf, ret, "%u ", i);
if (ret > 0)
buf[min(ret, (size_t)PAGE_SIZE)-1] = '\n';
return ret;
......
This diff is collapsed.
......@@ -61,7 +61,7 @@ static ssize_t isku_sysfs_show_actual_profile(struct device *dev,
{
struct isku_device *isku =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", isku->actual_profile);
return sysfs_emit(buf, "%d\n", isku->actual_profile);
}
static ssize_t isku_sysfs_set_actual_profile(struct device *dev,
......
......@@ -400,7 +400,7 @@ static ssize_t kone_sysfs_show_actual_profile(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_profile);
return sysfs_emit(buf, "%d\n", kone->actual_profile);
}
static DEVICE_ATTR(actual_profile, 0440, kone_sysfs_show_actual_profile, NULL);
......@@ -409,7 +409,7 @@ static ssize_t kone_sysfs_show_actual_dpi(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->actual_dpi);
return sysfs_emit(buf, "%d\n", kone->actual_dpi);
}
static DEVICE_ATTR(actual_dpi, 0440, kone_sysfs_show_actual_dpi, NULL);
......@@ -432,7 +432,7 @@ static ssize_t kone_sysfs_show_weight(struct device *dev,
if (retval)
return retval;
return snprintf(buf, PAGE_SIZE, "%d\n", weight);
return sysfs_emit(buf, "%d\n", weight);
}
static DEVICE_ATTR(weight, 0440, kone_sysfs_show_weight, NULL);
......@@ -441,7 +441,7 @@ static ssize_t kone_sysfs_show_firmware_version(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->firmware_version);
return sysfs_emit(buf, "%d\n", kone->firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, kone_sysfs_show_firmware_version,
NULL);
......@@ -451,7 +451,7 @@ static ssize_t kone_sysfs_show_tcu(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.tcu);
return sysfs_emit(buf, "%d\n", kone->settings.tcu);
}
static int kone_tcu_command(struct usb_device *usb_dev, int number)
......@@ -553,7 +553,7 @@ static ssize_t kone_sysfs_show_startup_profile(struct device *dev,
{
struct kone_device *kone =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kone->settings.startup_profile);
return sysfs_emit(buf, "%d\n", kone->settings.startup_profile);
}
static ssize_t kone_sysfs_set_startup_profile(struct device *dev,
......
......@@ -242,7 +242,7 @@ static ssize_t koneplus_sysfs_show_actual_profile(struct device *dev,
{
struct koneplus_device *koneplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", koneplus->actual_profile);
return sysfs_emit(buf, "%d\n", koneplus->actual_profile);
}
static ssize_t koneplus_sysfs_set_actual_profile(struct device *dev,
......@@ -309,7 +309,7 @@ static ssize_t koneplus_sysfs_show_firmware_version(struct device *dev,
&info, KONEPLUS_SIZE_INFO);
mutex_unlock(&koneplus->koneplus_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
koneplus_sysfs_show_firmware_version, NULL);
......
......@@ -272,7 +272,7 @@ static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile);
return sysfs_emit(buf, "%d\n", kovaplus->actual_profile);
}
static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev,
......@@ -325,7 +325,7 @@ static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi);
return sysfs_emit(buf, "%d\n", kovaplus->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, kovaplus_sysfs_show_actual_cpi, NULL);
......@@ -334,7 +334,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity);
return sysfs_emit(buf, "%d\n", kovaplus->actual_x_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_x, 0440,
kovaplus_sysfs_show_actual_sensitivity_x, NULL);
......@@ -344,7 +344,7 @@ static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev,
{
struct kovaplus_device *kovaplus =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity);
return sysfs_emit(buf, "%d\n", kovaplus->actual_y_sensitivity);
}
static DEVICE_ATTR(actual_sensitivity_y, 0440,
kovaplus_sysfs_show_actual_sensitivity_y, NULL);
......@@ -365,7 +365,7 @@ static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev,
&info, KOVAPLUS_SIZE_INFO);
mutex_unlock(&kovaplus->kovaplus_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440,
kovaplus_sysfs_show_firmware_version, NULL);
......
......@@ -283,7 +283,7 @@ static ssize_t pyra_sysfs_show_actual_cpi(struct device *dev,
{
struct pyra_device *pyra =
hid_get_drvdata(dev_get_drvdata(dev->parent->parent));
return snprintf(buf, PAGE_SIZE, "%d\n", pyra->actual_cpi);
return sysfs_emit(buf, "%d\n", pyra->actual_cpi);
}
static DEVICE_ATTR(actual_cpi, 0440, pyra_sysfs_show_actual_cpi, NULL);
......@@ -300,7 +300,7 @@ static ssize_t pyra_sysfs_show_actual_profile(struct device *dev,
&settings, PYRA_SIZE_SETTINGS);
mutex_unlock(&pyra->pyra_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", settings.startup_profile);
return sysfs_emit(buf, "%d\n", settings.startup_profile);
}
static DEVICE_ATTR(actual_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
static DEVICE_ATTR(startup_profile, 0440, pyra_sysfs_show_actual_profile, NULL);
......@@ -321,7 +321,7 @@ static ssize_t pyra_sysfs_show_firmware_version(struct device *dev,
&info, PYRA_SIZE_INFO);
mutex_unlock(&pyra->pyra_lock);
return snprintf(buf, PAGE_SIZE, "%d\n", info.firmware_version);
return sysfs_emit(buf, "%d\n", info.firmware_version);
}
static DEVICE_ATTR(firmware_version, 0440, pyra_sysfs_show_firmware_version,
NULL);
......
......@@ -155,7 +155,7 @@ static ssize_t enable_sensor_show(struct device *dev,
{
struct hid_sensor_custom *sensor_inst = dev_get_drvdata(dev);
return sprintf(buf, "%d\n", sensor_inst->enable);
return sysfs_emit(buf, "%d\n", sensor_inst->enable);
}
static int set_power_report_state(struct hid_sensor_custom *sensor_inst,
......@@ -372,14 +372,13 @@ static ssize_t show_value(struct device *dev, struct device_attribute *attr,
sizeof(struct hid_custom_usage_desc),
usage_id_cmp);
if (usage_desc)
return snprintf(buf, PAGE_SIZE, "%s\n",
usage_desc->desc);
return sysfs_emit(buf, "%s\n", usage_desc->desc);
else
return sprintf(buf, "not-specified\n");
return sysfs_emit(buf, "not-specified\n");
} else
return -EINVAL;
return sprintf(buf, "%d\n", value);
return sysfs_emit(buf, "%d\n", value);
}
static ssize_t store_value(struct device *dev, struct device_attribute *attr,
......@@ -1032,14 +1031,14 @@ static int hid_sensor_custom_probe(struct platform_device *pdev)
return ret;
}
static int hid_sensor_custom_remove(struct platform_device *pdev)
static void hid_sensor_custom_remove(struct platform_device *pdev)
{
struct hid_sensor_custom *sensor_inst = platform_get_drvdata(pdev);
struct hid_sensor_hub_device *hsdev = pdev->dev.platform_data;
if (sensor_inst->custom_pdev) {
platform_device_unregister(sensor_inst->custom_pdev);
return 0;
return;
}
hid_sensor_custom_dev_if_remove(sensor_inst);
......@@ -1047,8 +1046,6 @@ static int hid_sensor_custom_remove(struct platform_device *pdev)
sysfs_remove_group(&sensor_inst->pdev->dev.kobj,
&enable_sensor_attr_group);
sensor_hub_remove_callback(hsdev, hsdev->usage);
return 0;
}
static const struct platform_device_id hid_sensor_custom_ids[] = {
......@@ -1068,7 +1065,7 @@ static struct platform_driver hid_sensor_custom_platform_driver = {
.name = KBUILD_MODNAME,
},
.probe = hid_sensor_custom_probe,
.remove = hid_sensor_custom_remove,
.remove_new = hid_sensor_custom_remove,
};
module_platform_driver(hid_sensor_custom_platform_driver);
......
......@@ -1844,8 +1844,7 @@ static int sony_set_device_id(struct sony_sc *sc)
* All others are set to -1.
*/
if (sc->quirks & SIXAXIS_CONTROLLER) {
ret = ida_simple_get(&sony_device_id_allocator, 0, 0,
GFP_KERNEL);
ret = ida_alloc(&sony_device_id_allocator, GFP_KERNEL);
if (ret < 0) {
sc->device_id = -1;
return ret;
......@@ -1861,7 +1860,7 @@ static int sony_set_device_id(struct sony_sc *sc)
static void sony_release_device_id(struct sony_sc *sc)
{
if (sc->device_id >= 0) {
ida_simple_remove(&sony_device_id_allocator, sc->device_id);
ida_free(&sony_device_id_allocator, sc->device_id);
sc->device_id = -1;
}
}
......@@ -2016,8 +2015,6 @@ static int sony_input_configured(struct hid_device *hdev,
} else if (sc->quirks & MOTION_CONTROLLER) {
sony_init_output_report(sc, motion_send_output_report);
} else {
ret = 0;
}
if (sc->quirks & SONY_LED_SUPPORT) {
......
......@@ -66,6 +66,14 @@ static LIST_HEAD(steam_devices);
#define STEAM_DECK_TRIGGER_RESOLUTION 5461
/* Joystick runs are about 5 mm and 32768 units */
#define STEAM_DECK_JOYSTICK_RESOLUTION 6553
/* Accelerometer has 16 bit resolution and a range of +/- 2g */
#define STEAM_DECK_ACCEL_RES_PER_G 16384
#define STEAM_DECK_ACCEL_RANGE 32768
#define STEAM_DECK_ACCEL_FUZZ 32
/* Gyroscope has 16 bit resolution and a range of +/- 2000 dps */
#define STEAM_DECK_GYRO_RES_PER_DPS 16
#define STEAM_DECK_GYRO_RANGE 32768
#define STEAM_DECK_GYRO_FUZZ 1
#define STEAM_PAD_FUZZ 256
......@@ -288,6 +296,7 @@ struct steam_device {
struct mutex report_mutex;
unsigned long client_opened;
struct input_dev __rcu *input;
struct input_dev __rcu *sensors;
unsigned long quirks;
struct work_struct work_connect;
bool connected;
......@@ -302,6 +311,7 @@ struct steam_device {
struct work_struct rumble_work;
u16 rumble_left;
u16 rumble_right;
unsigned int sensor_timestamp_us;
};
static int steam_recv_report(struct steam_device *steam,
......@@ -825,6 +835,74 @@ static int steam_input_register(struct steam_device *steam)
return ret;
}
static int steam_sensors_register(struct steam_device *steam)
{
struct hid_device *hdev = steam->hdev;
struct input_dev *sensors;
int ret;
if (!(steam->quirks & STEAM_QUIRK_DECK))
return 0;
rcu_read_lock();
sensors = rcu_dereference(steam->sensors);
rcu_read_unlock();
if (sensors) {
dbg_hid("%s: already connected\n", __func__);
return 0;
}
sensors = input_allocate_device();
if (!sensors)
return -ENOMEM;
input_set_drvdata(sensors, steam);
sensors->dev.parent = &hdev->dev;
sensors->name = "Steam Deck Motion Sensors";
sensors->phys = hdev->phys;
sensors->uniq = steam->serial_no;
sensors->id.bustype = hdev->bus;
sensors->id.vendor = hdev->vendor;
sensors->id.product = hdev->product;
sensors->id.version = hdev->version;
__set_bit(INPUT_PROP_ACCELEROMETER, sensors->propbit);
__set_bit(EV_MSC, sensors->evbit);
__set_bit(MSC_TIMESTAMP, sensors->mscbit);
input_set_abs_params(sensors, ABS_X, -STEAM_DECK_ACCEL_RANGE,
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
input_set_abs_params(sensors, ABS_Y, -STEAM_DECK_ACCEL_RANGE,
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
input_set_abs_params(sensors, ABS_Z, -STEAM_DECK_ACCEL_RANGE,
STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0);
input_abs_set_res(sensors, ABS_X, STEAM_DECK_ACCEL_RES_PER_G);
input_abs_set_res(sensors, ABS_Y, STEAM_DECK_ACCEL_RES_PER_G);
input_abs_set_res(sensors, ABS_Z, STEAM_DECK_ACCEL_RES_PER_G);
input_set_abs_params(sensors, ABS_RX, -STEAM_DECK_GYRO_RANGE,
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
input_set_abs_params(sensors, ABS_RY, -STEAM_DECK_GYRO_RANGE,
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
input_set_abs_params(sensors, ABS_RZ, -STEAM_DECK_GYRO_RANGE,
STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0);
input_abs_set_res(sensors, ABS_RX, STEAM_DECK_GYRO_RES_PER_DPS);
input_abs_set_res(sensors, ABS_RY, STEAM_DECK_GYRO_RES_PER_DPS);
input_abs_set_res(sensors, ABS_RZ, STEAM_DECK_GYRO_RES_PER_DPS);
ret = input_register_device(sensors);
if (ret)
goto sensors_register_fail;
rcu_assign_pointer(steam->sensors, sensors);
return 0;
sensors_register_fail:
input_free_device(sensors);
return ret;
}
static void steam_input_unregister(struct steam_device *steam)
{
struct input_dev *input;
......@@ -838,6 +916,24 @@ static void steam_input_unregister(struct steam_device *steam)
input_unregister_device(input);
}
static void steam_sensors_unregister(struct steam_device *steam)
{
struct input_dev *sensors;
if (!(steam->quirks & STEAM_QUIRK_DECK))
return;
rcu_read_lock();
sensors = rcu_dereference(steam->sensors);
rcu_read_unlock();
if (!sensors)
return;
RCU_INIT_POINTER(steam->sensors, NULL);
synchronize_rcu();
input_unregister_device(sensors);
}
static void steam_battery_unregister(struct steam_device *steam)
{
struct power_supply *battery;
......@@ -890,18 +986,28 @@ static int steam_register(struct steam_device *steam)
spin_lock_irqsave(&steam->lock, flags);
client_opened = steam->client_opened;
spin_unlock_irqrestore(&steam->lock, flags);
if (!client_opened) {
steam_set_lizard_mode(steam, lizard_mode);
ret = steam_input_register(steam);
} else
ret = 0;
if (ret != 0)
goto steam_register_input_fail;
ret = steam_sensors_register(steam);
if (ret != 0)
goto steam_register_sensors_fail;
}
return 0;
steam_register_sensors_fail:
steam_input_unregister(steam);
steam_register_input_fail:
return ret;
}
static void steam_unregister(struct steam_device *steam)
{
steam_battery_unregister(steam);
steam_sensors_unregister(steam);
steam_input_unregister(steam);
if (steam->serial_no[0]) {
hid_info(steam->hdev, "Steam Controller '%s' disconnected",
......@@ -1010,6 +1116,7 @@ static int steam_client_ll_open(struct hid_device *hdev)
steam->client_opened++;
spin_unlock_irqrestore(&steam->lock, flags);
steam_sensors_unregister(steam);
steam_input_unregister(steam);
return 0;
......@@ -1030,6 +1137,7 @@ static void steam_client_ll_close(struct hid_device *hdev)
if (connected) {
steam_set_lizard_mode(steam, lizard_mode);
steam_input_register(steam);
steam_sensors_register(steam);
}
}
......@@ -1121,6 +1229,7 @@ static int steam_probe(struct hid_device *hdev,
INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
INIT_LIST_HEAD(&steam->list);
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
steam->sensor_timestamp_us = 0;
/*
* With the real steam controller interface, do not connect hidraw.
......@@ -1380,12 +1489,12 @@ static void steam_do_input_event(struct steam_device *steam,
* 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
* 24-25 | s16 | IMU ABS_X | accelerometer X value
* 26-27 | s16 | IMU ABS_Z | accelerometer Y value
* 28-29 | s16 | IMU ABS_Y | accelerometer Z value
* 30-31 | s16 | IMU ABS_RX | gyro X value
* 32-33 | s16 | IMU ABS_RZ | gyro Y value
* 34-35 | s16 | IMU ABS_RY | gyro Z value
* 36-37 | s16 | -- | quaternion W value
* 38-39 | s16 | -- | quaternion X value
* 40-41 | s16 | -- | quaternion Y value
......@@ -1546,6 +1655,32 @@ static void steam_do_deck_input_event(struct steam_device *steam,
input_sync(input);
}
static void steam_do_deck_sensors_event(struct steam_device *steam,
struct input_dev *sensors, u8 *data)
{
/*
* The deck input report is received every 4 ms on average,
* with a jitter of +/- 4 ms even though the USB descriptor claims
* that it uses 1 kHz.
* Since the HID report does not include a sensor timestamp,
* use a fixed increment here.
*/
steam->sensor_timestamp_us += 4000;
if (!steam->gamepad_mode)
return;
input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us);
input_report_abs(sensors, ABS_X, steam_le16(data + 24));
input_report_abs(sensors, ABS_Z, -steam_le16(data + 26));
input_report_abs(sensors, ABS_Y, steam_le16(data + 28));
input_report_abs(sensors, ABS_RX, steam_le16(data + 30));
input_report_abs(sensors, ABS_RZ, -steam_le16(data + 32));
input_report_abs(sensors, ABS_RY, steam_le16(data + 34));
input_sync(sensors);
}
/*
* The size for this message payload is 11.
* The known values are:
......@@ -1583,6 +1718,7 @@ static int steam_raw_event(struct hid_device *hdev,
{
struct steam_device *steam = hid_get_drvdata(hdev);
struct input_dev *input;
struct input_dev *sensors;
struct power_supply *battery;
if (!steam)
......@@ -1628,6 +1764,9 @@ static int steam_raw_event(struct hid_device *hdev,
input = rcu_dereference(steam->input);
if (likely(input))
steam_do_deck_input_event(steam, input, data);
sensors = rcu_dereference(steam->sensors);
if (likely(sensors))
steam_do_deck_sensors_event(steam, sensors, data);
rcu_read_unlock();
break;
case ID_CONTROLLER_WIRELESS:
......
......@@ -884,6 +884,9 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
goto cleanup;
}
/* The firmware is used in userspace as unique identifier */
strscpy(hdev->uniq, ver_ptr, sizeof(hdev->uniq));
/* If this is a transition firmware */
if (strcmp(ver_ptr, transition_ver) == 0) {
hid_dbg(hdev,
......
// SPDX-License-Identifier: GPL-2.0
/*
* HID driver for WinWing Orion 2 throttle
*
* Copyright (c) 2023 Ivan Gorinov
*/
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/hidraw.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#define MAX_REPORT 16
struct winwing_led {
struct led_classdev cdev;
struct hid_device *hdev;
int number;
};
struct winwing_led_info {
int number;
int max_brightness;
const char *led_name;
};
static struct winwing_led_info led_info[3] = {
{ 0, 255, "backlight" },
{ 1, 1, "a-a" },
{ 2, 1, "a-g" },
};
struct winwing_drv_data {
struct hid_device *hdev;
__u8 *report_buf;
struct mutex lock;
unsigned int num_leds;
struct winwing_led leds[];
};
static int winwing_led_write(struct led_classdev *cdev,
enum led_brightness br)
{
struct winwing_led *led = (struct winwing_led *) cdev;
struct winwing_drv_data *data = hid_get_drvdata(led->hdev);
__u8 *buf = data->report_buf;
int ret;
mutex_lock(&data->lock);
buf[0] = 0x02;
buf[1] = 0x60;
buf[2] = 0xbe;
buf[3] = 0x00;
buf[4] = 0x00;
buf[5] = 0x03;
buf[6] = 0x49;
buf[7] = led->number;
buf[8] = br;
buf[9] = 0x00;
buf[10] = 0;
buf[11] = 0;
buf[12] = 0;
buf[13] = 0;
ret = hid_hw_output_report(led->hdev, buf, 14);
mutex_unlock(&data->lock);
return ret;
}
static int winwing_init_led(struct hid_device *hdev,
struct input_dev *input)
{
struct winwing_drv_data *data;
struct winwing_led *led;
int ret;
int i;
size_t data_size = struct_size(data, leds, 3);
data = devm_kzalloc(&hdev->dev, data_size, GFP_KERNEL);
if (!data)
return -ENOMEM;
data->report_buf = devm_kmalloc(&hdev->dev, MAX_REPORT, GFP_KERNEL);
if (!data->report_buf)
return -ENOMEM;
for (i = 0; i < 3; i += 1) {
struct winwing_led_info *info = &led_info[i];
led = &data->leds[i];
led->hdev = hdev;
led->number = info->number;
led->cdev.max_brightness = info->max_brightness;
led->cdev.brightness_set_blocking = winwing_led_write;
led->cdev.flags = LED_HW_PLUGGABLE;
led->cdev.name = devm_kasprintf(&hdev->dev, GFP_KERNEL,
"%s::%s",
dev_name(&input->dev),
info->led_name);
ret = devm_led_classdev_register(&hdev->dev, &led->cdev);
if (ret)
return ret;
}
hid_set_drvdata(hdev, data);
return ret;
}
static int winwing_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int ret;
ret = hid_parse(hdev);
if (ret) {
hid_err(hdev, "parse failed\n");
return ret;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "hw start failed\n");
return ret;
}
return 0;
}
static int winwing_input_configured(struct hid_device *hdev,
struct hid_input *hidinput)
{
int ret;
ret = winwing_init_led(hdev, hidinput->input);
if (ret)
hid_err(hdev, "led init failed\n");
return ret;
}
static __u8 original_rdesc_buttons[] = {
0x05, 0x09, 0x19, 0x01, 0x29, 0x6F,
0x15, 0x00, 0x25, 0x01, 0x35, 0x00,
0x45, 0x01, 0x75, 0x01, 0x95, 0x6F,
0x81, 0x02, 0x75, 0x01, 0x95, 0x01,
0x81, 0x01
};
/*
* HID report descriptor shows 111 buttons, which exceeds maximum
* number of buttons (80) supported by Linux kernel HID subsystem.
*
* This module skips numbers 32-63, unused on some throttle grips.
*/
static __u8 *winwing_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
int sig_length = sizeof(original_rdesc_buttons);
int unused_button_numbers = 32;
if (*rsize < 34)
return rdesc;
if (memcmp(rdesc + 8, original_rdesc_buttons, sig_length) == 0) {
/* Usage Maximum */
rdesc[13] -= unused_button_numbers;
/* Report Count for buttons */
rdesc[25] -= unused_button_numbers;
/* Report Count for padding [HID1_11, 6.2.2.9] */
rdesc[31] += unused_button_numbers;
hid_info(hdev, "winwing descriptor fixed\n");
}
return rdesc;
}
static int winwing_raw_event(struct hid_device *hdev,
struct hid_report *report, u8 *raw_data, int size)
{
if (size >= 15) {
/* Skip buttons 32 .. 63 */
memmove(raw_data + 5, raw_data + 9, 6);
/* Clear the padding */
memset(raw_data + 11, 0, 4);
}
return 0;
}
static const struct hid_device_id winwing_devices[] = {
{ HID_USB_DEVICE(0x4098, 0xbe62) }, /* TGRIP-18 */
{ HID_USB_DEVICE(0x4098, 0xbe68) }, /* TGRIP-16EX */
{}
};
MODULE_DEVICE_TABLE(hid, winwing_devices);
static struct hid_driver winwing_driver = {
.name = "winwing",
.id_table = winwing_devices,
.probe = winwing_probe,
.input_configured = winwing_input_configured,
.report_fixup = winwing_report_fixup,
.raw_event = winwing_raw_event,
};
module_hid_driver(winwing_driver);
MODULE_LICENSE("GPL");
......@@ -163,6 +163,24 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct)
return quirks;
}
static int i2c_hid_probe_address(struct i2c_hid *ihid)
{
int ret;
/*
* Some STM-based devices need 400µs after a rising clock edge to wake
* from deep sleep, in which case the first read will fail. Try after a
* short sleep to see if the device came alive on the bus. Certain
* Weida Tech devices also need this.
*/
ret = i2c_smbus_read_byte(ihid->client);
if (ret < 0) {
usleep_range(400, 500);
ret = i2c_smbus_read_byte(ihid->client);
}
return ret < 0 ? ret : 0;
}
static int i2c_hid_xfer(struct i2c_hid *ihid,
u8 *send_buf, int send_len, u8 *recv_buf, int recv_len)
{
......@@ -384,26 +402,11 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state)
i2c_hid_dbg(ihid, "%s\n", __func__);
/*
* Some devices require to send a command to wakeup before power on.
* The call will get a return value (EREMOTEIO) but device will be
* triggered and activated. After that, it goes like a normal device.
*/
if (power_state == I2C_HID_PWR_ON) {
ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON);
/* Device was already activated */
if (!ret)
goto set_pwr_exit;
}
ret = i2c_hid_set_power_command(ihid, power_state);
if (ret)
dev_err(&ihid->client->dev,
"failed to change power setting.\n");
set_pwr_exit:
/*
* The HID over I2C specification states that if a DEVICE needs time
* after the PWR_ON request, it should utilise CLOCK stretching.
......@@ -959,6 +962,14 @@ static int i2c_hid_core_resume(struct i2c_hid *ihid)
enable_irq(client->irq);
/* Make sure the device is awake on the bus */
ret = i2c_hid_probe_address(ihid);
if (ret < 0) {
dev_err(&client->dev, "nothing at address after resume: %d\n",
ret);
return -ENXIO;
}
/* Instead of resetting device, simply powers the device on. This
* solves "incomplete reports" on Raydium devices 2386:3118 and
* 2386:4B33 and fixes various SIS touchscreens no longer sending
......@@ -992,8 +1003,7 @@ static int __i2c_hid_core_probe(struct i2c_hid *ihid)
struct hid_device *hid = ihid->hid;
int ret;
/* Make sure there is something at this address */
ret = i2c_smbus_read_byte(client);
ret = i2c_hid_probe_address(ihid);
if (ret < 0) {
i2c_hid_dbg(ihid, "nothing at this address: %d\n", ret);
return -ENXIO;
......
......@@ -11,6 +11,7 @@ intel-ishtp-objs += ishtp/client.o
intel-ishtp-objs += ishtp/bus.o
intel-ishtp-objs += ishtp/dma-if.o
intel-ishtp-objs += ishtp/client-buffers.o
intel-ishtp-objs += ishtp/loader.o
obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-ipc.o
intel-ish-ipc-objs := ipc/ipc.o
......
......@@ -13,28 +13,29 @@
#include "hw-ish-regs.h"
#include "ishtp-dev.h"
#define CHV_DEVICE_ID 0x22D8
#define BXT_Ax_DEVICE_ID 0x0AA2
#define BXT_Bx_DEVICE_ID 0x1AA2
#define APL_Ax_DEVICE_ID 0x5AA2
#define SPT_Ax_DEVICE_ID 0x9D35
#define CNL_Ax_DEVICE_ID 0x9DFC
#define GLK_Ax_DEVICE_ID 0x31A2
#define CNL_H_DEVICE_ID 0xA37C
#define ICL_MOBILE_DEVICE_ID 0x34FC
#define SPT_H_DEVICE_ID 0xA135
#define CML_LP_DEVICE_ID 0x02FC
#define CMP_H_DEVICE_ID 0x06FC
#define EHL_Ax_DEVICE_ID 0x4BB3
#define TGL_LP_DEVICE_ID 0xA0FC
#define TGL_H_DEVICE_ID 0x43FC
#define ADL_S_DEVICE_ID 0x7AF8
#define ADL_P_DEVICE_ID 0x51FC
#define ADL_N_DEVICE_ID 0x54FC
#define RPL_S_DEVICE_ID 0x7A78
#define MTL_P_DEVICE_ID 0x7E45
#define ARL_H_DEVICE_ID 0x7745
#define ARL_S_DEVICE_ID 0x7F78
#define PCI_DEVICE_ID_INTEL_ISH_CHV 0x22D8
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Ax 0x0AA2
#define PCI_DEVICE_ID_INTEL_ISH_BXT_Bx 0x1AA2
#define PCI_DEVICE_ID_INTEL_ISH_APL_Ax 0x5AA2
#define PCI_DEVICE_ID_INTEL_ISH_SPT_Ax 0x9D35
#define PCI_DEVICE_ID_INTEL_ISH_CNL_Ax 0x9DFC
#define PCI_DEVICE_ID_INTEL_ISH_GLK_Ax 0x31A2
#define PCI_DEVICE_ID_INTEL_ISH_CNL_H 0xA37C
#define PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE 0x34FC
#define PCI_DEVICE_ID_INTEL_ISH_SPT_H 0xA135
#define PCI_DEVICE_ID_INTEL_ISH_CML_LP 0x02FC
#define PCI_DEVICE_ID_INTEL_ISH_CMP_H 0x06FC
#define PCI_DEVICE_ID_INTEL_ISH_EHL_Ax 0x4BB3
#define PCI_DEVICE_ID_INTEL_ISH_TGL_LP 0xA0FC
#define PCI_DEVICE_ID_INTEL_ISH_TGL_H 0x43FC
#define PCI_DEVICE_ID_INTEL_ISH_ADL_S 0x7AF8
#define PCI_DEVICE_ID_INTEL_ISH_ADL_P 0x51FC
#define PCI_DEVICE_ID_INTEL_ISH_ADL_N 0x54FC
#define PCI_DEVICE_ID_INTEL_ISH_RPL_S 0x7A78
#define PCI_DEVICE_ID_INTEL_ISH_MTL_P 0x7E45
#define PCI_DEVICE_ID_INTEL_ISH_ARL_H 0x7745
#define PCI_DEVICE_ID_INTEL_ISH_ARL_S 0x7F78
#define PCI_DEVICE_ID_INTEL_ISH_LNL_M 0xA845
#define REVISION_ID_CHT_A0 0x6
#define REVISION_ID_CHT_Ax_SI 0x0
......
......@@ -78,7 +78,7 @@ static bool check_generated_interrupt(struct ishtp_device *dev)
bool interrupt_generated = true;
uint32_t pisr_val = 0;
if (dev->pdev->device == CHV_DEVICE_ID) {
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB);
interrupt_generated =
IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val);
......@@ -117,7 +117,7 @@ static bool ish_is_input_ready(struct ishtp_device *dev)
*/
static void set_host_ready(struct ishtp_device *dev)
{
if (dev->pdev->device == CHV_DEVICE_ID) {
if (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV) {
if (dev->pdev->revision == REVISION_ID_CHT_A0 ||
(dev->pdev->revision & REVISION_ID_SI_MASK) ==
REVISION_ID_CHT_Ax_SI)
......@@ -546,11 +546,11 @@ static int ish_fw_reset_handler(struct ishtp_device *dev)
/**
* fw_reset_work_fn() - FW reset worker function
* @unused: not used
* @work: Work item
*
* Call ish_fw_reset_handler to complete FW reset
*/
static void fw_reset_work_fn(struct work_struct *unused)
static void fw_reset_work_fn(struct work_struct *work)
{
int rv;
......@@ -562,7 +562,8 @@ static void fw_reset_work_fn(struct work_struct *unused)
wake_up_interruptible(&ishtp_dev->wait_hw_ready);
/* ISHTP notification in IPC_RESET sequence completion */
ishtp_reset_compl_handler(ishtp_dev);
if (!work_pending(work))
ishtp_reset_compl_handler(ishtp_dev);
} else
dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n",
rv);
......@@ -909,11 +910,11 @@ static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length,
*/
static bool _dma_no_cache_snooping(struct ishtp_device *dev)
{
return (dev->pdev->device == EHL_Ax_DEVICE_ID ||
dev->pdev->device == TGL_LP_DEVICE_ID ||
dev->pdev->device == TGL_H_DEVICE_ID ||
dev->pdev->device == ADL_S_DEVICE_ID ||
dev->pdev->device == ADL_P_DEVICE_ID);
return (dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_LP ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_TGL_H ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_S ||
dev->pdev->device == PCI_DEVICE_ID_INTEL_ISH_ADL_P);
}
static const struct ishtp_hw_ops ish_hw_ops = {
......
......@@ -23,30 +23,44 @@
#include "ishtp-dev.h"
#include "hw-ish.h"
enum ishtp_driver_data_index {
ISHTP_DRIVER_DATA_NONE,
ISHTP_DRIVER_DATA_LNL_M,
};
#define ISH_FW_FILENAME_LNL_M "intel/ish/ish_lnlm.bin"
static struct ishtp_driver_data ishtp_driver_data[] = {
[ISHTP_DRIVER_DATA_LNL_M] = {
.fw_filename = ISH_FW_FILENAME_LNL_M,
},
};
static const struct pci_device_id ish_pci_tbl[] = {
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CHV_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, BXT_Bx_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, APL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, GLK_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CNL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ICL_MOBILE_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, SPT_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CML_LP_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, CMP_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, EHL_Ax_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_LP_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, TGL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_S_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_P_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ADL_N_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, RPL_S_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, MTL_P_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_H_DEVICE_ID)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, ARL_S_DEVICE_ID)},
{0, }
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CHV)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_BXT_Bx)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_APL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_GLK_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CNL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ICL_MOBILE)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_SPT_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CML_LP)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_CMP_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_LP)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_TGL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_P)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ADL_N)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_RPL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_MTL_P)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_H)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_ARL_S)},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ISH_LNL_M), .driver_data = ISHTP_DRIVER_DATA_LNL_M},
{}
};
MODULE_DEVICE_TABLE(pci, ish_pci_tbl);
......@@ -105,19 +119,19 @@ static int ish_init(struct ishtp_device *dev)
static const struct pci_device_id ish_invalid_pci_ids[] = {
/* Mehlow platform special pci ids */
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA309)},
{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0xA30A)},
{PCI_VDEVICE(INTEL, 0xA309)},
{PCI_VDEVICE(INTEL, 0xA30A)},
{}
};
static inline bool ish_should_enter_d0i3(struct pci_dev *pdev)
{
return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID;
return !pm_suspend_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
static inline bool ish_should_leave_d0i3(struct pci_dev *pdev)
{
return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID;
return !pm_resume_via_firmware() || pdev->device == PCI_DEVICE_ID_INTEL_ISH_CHV;
}
/**
......@@ -166,6 +180,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
}
hw = to_ish_hw(ishtp);
ishtp->print_log = ish_event_tracer;
ishtp->driver_data = &ishtp_driver_data[ent->driver_data];
/* mapping IO device memory */
hw->mem_addr = pcim_iomap_table(pdev)[0];
......@@ -173,6 +188,11 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
/* request and enable interrupt */
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_ALL_TYPES);
if (ret < 0) {
dev_err(dev, "ISH: Failed to allocate IRQ vectors\n");
return ret;
}
if (!pdev->msi_enabled && !pdev->msix_enabled)
irq_flag = IRQF_SHARED;
......@@ -189,7 +209,7 @@ static int ish_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
init_waitqueue_head(&ishtp->resume_wait);
/* Enable PME for EHL */
if (pdev->device == EHL_Ax_DEVICE_ID)
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
device_init_wakeup(dev, true);
ret = ish_init(ishtp);
......@@ -222,7 +242,7 @@ static void ish_remove(struct pci_dev *pdev)
*/
static void ish_shutdown(struct pci_dev *pdev)
{
if (pdev->device == EHL_Ax_DEVICE_ID)
if (pdev->device == PCI_DEVICE_ID_INTEL_ISH_EHL_Ax)
pci_prepare_to_sleep(pdev);
}
......@@ -376,3 +396,5 @@ MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Intel(R) Integrated Sensor Hub PCI Device Driver");
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(ISH_FW_FILENAME_LNL_M);
......@@ -13,6 +13,7 @@
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
#include "loader.h"
/**
* ishtp_hbm_fw_cl_allocate() - Allocate FW clients
......@@ -570,6 +571,10 @@ void ishtp_hbm_dispatch(struct ishtp_device *dev,
return;
}
/* Start firmware loading process if it has loader capability */
if (version_res->host_version_supported & ISHTP_SUPPORT_CAP_LOADER)
schedule_work(&dev->work_fw_loader);
dev->version.major_version = HBM_MAJOR_VERSION;
dev->version.minor_version = HBM_MINOR_VERSION;
if (dev->dev_state == ISHTP_DEV_INIT_CLIENTS &&
......@@ -864,6 +869,20 @@ void recv_hbm(struct ishtp_device *dev, struct ishtp_msg_hdr *ishtp_hdr)
return;
}
/**
* ishtp_loader_recv_msg() - Receive a message from the ISHTP device
* @dev: The ISHTP device
* @buf: The buffer containing the message
*/
static void ishtp_loader_recv_msg(struct ishtp_device *dev, void *buf)
{
if (dev->fw_loader_rx_buf)
memcpy(dev->fw_loader_rx_buf, buf, dev->fw_loader_rx_size);
dev->fw_loader_received = true;
wake_up_interruptible(&dev->wait_loader_recvd_msg);
}
/**
* recv_fixed_cl_msg() - Receive fixed client message
* @dev: ISHTP device instance
......@@ -890,6 +909,8 @@ void recv_fixed_cl_msg(struct ishtp_device *dev,
else
dev_err(dev->devc, "unknown fixed client msg [%02X]\n",
msg_hdr->cmd);
} else if (ishtp_hdr->fw_addr == ISHTP_LOADER_CLIENT_ADDR) {
ishtp_loader_recv_msg(dev, rd_msg_buf);
}
}
......
......@@ -5,12 +5,14 @@
* Copyright (c) 2003-2016, Intel Corporation.
*/
#include <linux/devm-helpers.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "ishtp-dev.h"
#include "hbm.h"
#include "client.h"
#include "loader.h"
/**
* ishtp_dev_state_str() -Convert to string format
......@@ -51,6 +53,8 @@ const char *ishtp_dev_state_str(int state)
*/
void ishtp_device_init(struct ishtp_device *dev)
{
int ret;
dev->dev_state = ISHTP_DEV_INITIALIZING;
INIT_LIST_HEAD(&dev->cl_list);
INIT_LIST_HEAD(&dev->device_list);
......@@ -59,6 +63,7 @@ void ishtp_device_init(struct ishtp_device *dev)
spin_lock_init(&dev->rd_msg_spinlock);
init_waitqueue_head(&dev->wait_hbm_recvd_msg);
init_waitqueue_head(&dev->wait_loader_recvd_msg);
spin_lock_init(&dev->read_list_spinlock);
spin_lock_init(&dev->device_lock);
spin_lock_init(&dev->device_list_lock);
......@@ -76,6 +81,9 @@ void ishtp_device_init(struct ishtp_device *dev)
INIT_LIST_HEAD(&dev->read_list.list);
ret = devm_work_autocancel(dev->devc, &dev->work_fw_loader, ishtp_loader_work);
if (ret)
dev_err_probe(dev->devc, ret, "Failed to initialise FW loader work\n");
}
EXPORT_SYMBOL(ishtp_device_init);
......
......@@ -122,12 +122,29 @@ struct ishtp_hw_ops {
bool (*dma_no_cache_snooping)(struct ishtp_device *dev);
};
/**
* struct ishtp_driver_data - Driver-specific data for ISHTP devices
*
* This structure holds driver-specific data that can be associated with each
* ISHTP device instance. It allows for the storage of data that is unique to
* a particular driver or hardware variant.
*
* @fw_filename: The firmware filename associated with a specific hardware
* variant of the Intel Integrated Sensor Hub (ISH). This allows
* the driver to load the correct firmware based on the device's
* hardware variant.
*/
struct ishtp_driver_data {
char *fw_filename;
};
/**
* struct ishtp_device - ISHTP private device struct
*/
struct ishtp_device {
struct device *devc; /* pointer to lowest device */
struct pci_dev *pdev; /* PCI device to get device ids */
struct ishtp_driver_data *driver_data; /* pointer to driver-specific data */
/* waitq for waiting for suspend response */
wait_queue_head_t suspend_wait;
......@@ -147,6 +164,17 @@ struct ishtp_device {
struct hbm_version version;
int transfer_path; /* Choice of transfer path: IPC or DMA */
/* work structure for scheduling firmware loading tasks */
struct work_struct work_fw_loader;
/* waitq for waiting for command response from the firmware loader */
wait_queue_head_t wait_loader_recvd_msg;
/* indicating whether a message from the firmware loader has been received */
bool fw_loader_received;
/* pointer to a buffer for receiving messages from the firmware loader */
void *fw_loader_rx_buf;
/* size of the buffer pointed to by fw_loader_rx_buf */
int fw_loader_rx_size;
/* ishtp device states */
enum ishtp_dev_state dev_state;
enum ishtp_hbm_state hbm_state;
......
// SPDX-License-Identifier: GPL-2.0-only
/*
* ISHTP firmware loader function
*
* Copyright (c) 2024, Intel Corporation.
*
* This module implements the functionality to load the main ISH firmware from the host, starting
* with the Lunar Lake generation. It leverages a new method that enhances space optimization and
* flexibility by dividing the ISH firmware into a bootloader and main firmware.
*
* Please refer to the [Documentation](Documentation/hid/intel-ish-hid.rst) for the details on
* flows.
*
* Additionally, address potential error scenarios to ensure graceful failure handling.
* - Firmware Image Not Found:
* Occurs when `request_firmware()` cannot locate the firmware image. The ISH firmware will
* remain in a state awaiting firmware loading from the host, with no further action from
* the ISHTP driver.
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
*
* - DMA Buffer Allocation Failure:
* This happens if allocating a DMA buffer during `prepare_dma_bufs()` fails. The ISH firmware
* will stay in a waiting state, and the ISHTP driver will release any allocated DMA buffers and
* firmware without further actions.
* Recovery: Re-insmod the ISH drivers allows for a retry of the firmware loading from the host.
*
* - Incorrect Firmware Image:
* Using an incorrect firmware image will initiate the firmware loading process but will
* eventually be refused by the ISH firmware after three unsuccessful attempts, indicated by
* returning an error code. The ISHTP driver will stop attempting after three tries.
* Recovery: A platform reset is required to retry firmware loading from the host.
*/
#define dev_fmt(fmt) "ISH loader: " fmt
#include <linux/cacheflush.h>
#include <linux/container_of.h>
#include <linux/dev_printk.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/firmware.h>
#include <linux/gfp_types.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/pfn.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "hbm.h"
#include "loader.h"
/**
* loader_write_message() - Write a message to the ISHTP device
* @dev: The ISHTP device
* @buf: The buffer containing the message
* @len: The length of the message
*
* Return: 0 on success, negative error code on failure
*/
static int loader_write_message(struct ishtp_device *dev, void *buf, int len)
{
struct ishtp_msg_hdr ishtp_hdr = {
.fw_addr = ISHTP_LOADER_CLIENT_ADDR,
.length = len,
.msg_complete = 1,
};
dev->fw_loader_received = false;
return ishtp_write_message(dev, &ishtp_hdr, buf);
}
/**
* loader_xfer_cmd() - Transfer a command to the ISHTP device
* @dev: The ISHTP device
* @req: The request buffer
* @req_len: The length of the request
* @resp: The response buffer
* @resp_len: The length of the response
*
* Return: 0 on success, negative error code on failure
*/
static int loader_xfer_cmd(struct ishtp_device *dev, void *req, int req_len,
void *resp, int resp_len)
{
struct loader_msg_header *req_hdr = req;
struct loader_msg_header *resp_hdr = resp;
struct device *devc = dev->devc;
int rv;
dev->fw_loader_rx_buf = resp;
dev->fw_loader_rx_size = resp_len;
rv = loader_write_message(dev, req, req_len);
if (rv < 0) {
dev_err(devc, "write cmd %u failed:%d\n", req_hdr->command, rv);
return rv;
}
/* Wait the ACK */
wait_event_interruptible_timeout(dev->wait_loader_recvd_msg, dev->fw_loader_received,
ISHTP_LOADER_TIMEOUT);
dev->fw_loader_rx_size = 0;
dev->fw_loader_rx_buf = NULL;
if (!dev->fw_loader_received) {
dev_err(devc, "wait response of cmd %u timeout\n", req_hdr->command);
return -ETIMEDOUT;
}
if (!resp_hdr->is_response) {
dev_err(devc, "not a response for %u\n", req_hdr->command);
return -EBADMSG;
}
if (req_hdr->command != resp_hdr->command) {
dev_err(devc, "unexpected cmd response %u:%u\n", req_hdr->command,
resp_hdr->command);
return -EBADMSG;
}
if (resp_hdr->status) {
dev_err(devc, "cmd %u failed %u\n", req_hdr->command, resp_hdr->status);
return -EIO;
}
return 0;
}
/**
* release_dma_bufs() - Release the DMA buffer for transferring firmware fragments
* @dev: The ISHTP device
* @fragment: The ISHTP firmware fragment descriptor
* @dma_bufs: The array of DMA fragment buffers
* @fragment_size: The size of a single DMA fragment
*/
static void release_dma_bufs(struct ishtp_device *dev,
struct loader_xfer_dma_fragment *fragment,
void **dma_bufs, u32 fragment_size)
{
int i;
for (i = 0; i < FRAGMENT_MAX_NUM; i++) {
if (dma_bufs[i]) {
dma_free_coherent(dev->devc, fragment_size, dma_bufs[i],
fragment->fragment_tbl[i].ddr_adrs);
dma_bufs[i] = NULL;
}
}
}
/**
* prepare_dma_bufs() - Prepare the DMA buffer for transferring firmware fragments
* @dev: The ISHTP device
* @ish_fw: The ISH firmware
* @fragment: The ISHTP firmware fragment descriptor
* @dma_bufs: The array of DMA fragment buffers
* @fragment_size: The size of a single DMA fragment
*
* Return: 0 on success, negative error code on failure
*/
static int prepare_dma_bufs(struct ishtp_device *dev,
const struct firmware *ish_fw,
struct loader_xfer_dma_fragment *fragment,
void **dma_bufs, u32 fragment_size)
{
u32 offset = 0;
int i;
for (i = 0; i < fragment->fragment_cnt && offset < ish_fw->size; i++) {
dma_bufs[i] = dma_alloc_coherent(dev->devc, fragment_size,
&fragment->fragment_tbl[i].ddr_adrs, GFP_KERNEL);
if (!dma_bufs[i])
return -ENOMEM;
fragment->fragment_tbl[i].length = clamp(ish_fw->size - offset, 0, fragment_size);
fragment->fragment_tbl[i].fw_off = offset;
memcpy(dma_bufs[i], ish_fw->data + offset, fragment->fragment_tbl[i].length);
clflush_cache_range(dma_bufs[i], fragment_size);
offset += fragment->fragment_tbl[i].length;
}
return 0;
}
/**
* ishtp_loader_work() - Load the ISHTP firmware
* @work: The work structure
*
* The ISH Loader attempts to load firmware by sending a series of commands
* to the ISH device. If a command fails to be acknowledged by the ISH device,
* the loader will retry sending the command, up to a maximum of
* ISHTP_LOADER_RETRY_TIMES.
*
* After the maximum number of retries has been reached without success, the
* ISH bootloader will return an error status code and will no longer respond
* to the driver's commands. This behavior indicates that the ISH Loader has
* encountered a critical error during the firmware loading process.
*
* In such a case, where the ISH bootloader is unresponsive after all retries
* have been exhausted, a platform reset is required to restore communication
* with the ISH device and to recover from this error state.
*/
void ishtp_loader_work(struct work_struct *work)
{
DEFINE_RAW_FLEX(struct loader_xfer_dma_fragment, fragment, fragment_tbl, FRAGMENT_MAX_NUM);
struct ishtp_device *dev = container_of(work, struct ishtp_device, work_fw_loader);
struct loader_xfer_query query = {
.header.command = LOADER_CMD_XFER_QUERY,
};
struct loader_start start = {
.header.command = LOADER_CMD_START,
};
union loader_recv_message recv_msg;
char *filename = dev->driver_data->fw_filename;
const struct firmware *ish_fw;
void *dma_bufs[FRAGMENT_MAX_NUM] = {};
u32 fragment_size;
int retry = ISHTP_LOADER_RETRY_TIMES;
int rv;
rv = request_firmware(&ish_fw, filename, dev->devc);
if (rv < 0) {
dev_err(dev->devc, "request firmware %s failed:%d\n", filename, rv);
return;
}
fragment->fragment.header.command = LOADER_CMD_XFER_FRAGMENT;
fragment->fragment.xfer_mode = LOADER_XFER_MODE_DMA;
fragment->fragment.is_last = 1;
fragment->fragment.size = ish_fw->size;
/* Calculate the size of a single DMA fragment */
fragment_size = PFN_ALIGN(DIV_ROUND_UP(ish_fw->size, FRAGMENT_MAX_NUM));
/* Calculate the count of DMA fragments */
fragment->fragment_cnt = DIV_ROUND_UP(ish_fw->size, fragment_size);
rv = prepare_dma_bufs(dev, ish_fw, fragment, dma_bufs, fragment_size);
if (rv) {
dev_err(dev->devc, "prepare DMA buffer failed.\n");
goto out;
}
do {
query.image_size = ish_fw->size;
rv = loader_xfer_cmd(dev, &query, sizeof(query), recv_msg.raw_data,
sizeof(struct loader_xfer_query_ack));
if (rv)
continue; /* try again if failed */
dev_dbg(dev->devc, "ISH Version %u.%u.%u.%u\n",
recv_msg.query_ack.version_major,
recv_msg.query_ack.version_minor,
recv_msg.query_ack.version_hotfix,
recv_msg.query_ack.version_build);
rv = loader_xfer_cmd(dev, fragment,
struct_size(fragment, fragment_tbl, fragment->fragment_cnt),
recv_msg.raw_data, sizeof(struct loader_xfer_fragment_ack));
if (rv)
continue; /* try again if failed */
rv = loader_xfer_cmd(dev, &start, sizeof(start), recv_msg.raw_data,
sizeof(struct loader_start_ack));
if (rv)
continue; /* try again if failed */
dev_info(dev->devc, "firmware loaded. size:%zu\n", ish_fw->size);
break;
} while (--retry);
out:
release_dma_bufs(dev, fragment, dma_bufs, fragment_size);
release_firmware(ish_fw);
}
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ISHTP firmware loader header
*
* Copyright (c) 2024, Intel Corporation.
*/
#ifndef _ISHTP_LOADER_H_
#define _ISHTP_LOADER_H_
#include <linux/bits.h>
#include <linux/jiffies.h>
#include <linux/types.h>
#include "ishtp-dev.h"
struct work_struct;
#define LOADER_MSG_SIZE \
(IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr))
/*
* ISHTP firmware loader protocol definition
*/
#define LOADER_CMD_XFER_QUERY 0 /* SW -> FW */
#define LOADER_CMD_XFER_FRAGMENT 1 /* SW -> FW */
#define LOADER_CMD_START 2 /* SW -> FW */
/* Only support DMA mode */
#define LOADER_XFER_MODE_DMA BIT(0)
/**
* struct loader_msg_header - ISHTP firmware loader message header
* @command: Command type
* @is_response: Indicates if the message is a response
* @has_next: Indicates if there is a next message
* @reserved: Reserved for future use
* @status: Status of the message
*/
struct loader_msg_header {
__le32 command:7;
__le32 is_response:1;
__le32 has_next:1;
__le32 reserved:15;
__le32 status:8;
};
/**
* struct loader_xfer_query - ISHTP firmware loader transfer query packet
* @header: Header of the message
* @image_size: Size of the image
*/
struct loader_xfer_query {
struct loader_msg_header header;
__le32 image_size;
};
/**
* struct loader_version - ISHTP firmware loader version
* @value: Value of the version
* @major: Major version
* @minor: Minor version
* @hotfix: Hotfix version
* @build: Build version
*/
struct loader_version {
union {
__le32 value;
struct {
__u8 major;
__u8 minor;
__u8 hotfix;
__u8 build;
};
};
};
/**
* struct loader_capability - ISHTP firmware loader capability
* @max_fw_image_size: Maximum firmware image size
* @support_mode: Support mode
* @reserved: Reserved for future use
* @platform: Platform
* @max_dma_buf_size: Maximum DMA buffer size, multiples of 4096
*/
struct loader_capability {
__le32 max_fw_image_size;
__le16 support_mode;
__u8 reserved;
__u8 platform;
__le32 max_dma_buf_size;
};
/**
* struct loader_xfer_query_ack - ISHTP firmware loader transfer query acknowledgment
* @header: Header of the message
* @version_major: ISH Major version
* @version_minor: ISH Minor version
* @version_hotfix: ISH Hotfix version
* @version_build: ISH Build version
* @protocol_version: Protocol version
* @loader_version: Loader version
* @capability: Loader capability
*/
struct loader_xfer_query_ack {
struct loader_msg_header header;
__le16 version_major;
__le16 version_minor;
__le16 version_hotfix;
__le16 version_build;
__le32 protocol_version;
struct loader_version loader_version;
struct loader_capability capability;
};
/**
* struct loader_xfer_fragment - ISHTP firmware loader transfer fragment
* @header: Header of the message
* @xfer_mode: Transfer mode
* @offset: Offset
* @size: Size
* @is_last: Is last
*/
struct loader_xfer_fragment {
struct loader_msg_header header;
__le32 xfer_mode;
__le32 offset;
__le32 size;
__le32 is_last;
};
/**
* struct loader_xfer_fragment_ack - ISHTP firmware loader transfer fragment acknowledgment
* @header: Header of the message
*/
struct loader_xfer_fragment_ack {
struct loader_msg_header header;
};
/**
* struct fragment_dscrpt - ISHTP firmware loader fragment descriptor
* @ddr_adrs: The address in host DDR
* @fw_off: The offset of the fragment in the fw image
* @length: The length of the fragment
*/
struct fragment_dscrpt {
__le64 ddr_adrs;
__le32 fw_off;
__le32 length;
};
#define FRAGMENT_MAX_NUM \
((LOADER_MSG_SIZE - sizeof(struct loader_xfer_dma_fragment)) / \
sizeof(struct fragment_dscrpt))
/**
* struct loader_xfer_dma_fragment - ISHTP firmware loader transfer DMA fragment
* @fragment: Fragment
* @fragment_cnt: How many descriptors in the fragment_tbl
* @fragment_tbl: Fragment table
*/
struct loader_xfer_dma_fragment {
struct loader_xfer_fragment fragment;
__le32 fragment_cnt;
struct fragment_dscrpt fragment_tbl[] __counted_by(fragment_cnt);
};
/**
* struct loader_start - ISHTP firmware loader start
* @header: Header of the message
*/
struct loader_start {
struct loader_msg_header header;
};
/**
* struct loader_start_ack - ISHTP firmware loader start acknowledgment
* @header: Header of the message
*/
struct loader_start_ack {
struct loader_msg_header header;
};
union loader_recv_message {
struct loader_xfer_query_ack query_ack;
struct loader_xfer_fragment_ack fragment_ack;
struct loader_start_ack start_ack;
__u8 raw_data[LOADER_MSG_SIZE];
};
/*
* ISHTP firmware loader internal use
*/
/* ISHTP firmware loader command timeout */
#define ISHTP_LOADER_TIMEOUT msecs_to_jiffies(100)
/* ISHTP firmware loader retry times */
#define ISHTP_LOADER_RETRY_TIMES 3
/**
* struct ish_firmware_variant - ISH firmware variant
* @device: PCI Device ID
* @filename: The firmware file name
*/
struct ish_firmware_variant {
unsigned short device;
const char *filename;
};
/*
* ISHTP firmware loader API for ISHTP hbm
*/
/* ISHTP capability bit for firmware loader */
#define ISHTP_SUPPORT_CAP_LOADER BIT(4)
/* Firmware loader address */
#define ISHTP_LOADER_CLIENT_ADDR 16
/**
* ishtp_loader_work - The work function to start the firmware loading process
* @work: The work structure
*/
void ishtp_loader_work(struct work_struct *work);
#endif /* _ISHTP_LOADER_H_ */
......@@ -271,10 +271,9 @@ static int surface_kbd_probe(struct platform_device *pdev)
return surface_hid_device_add(shid);
}
static int surface_kbd_remove(struct platform_device *pdev)
static void surface_kbd_remove(struct platform_device *pdev)
{
surface_hid_device_destroy(platform_get_drvdata(pdev));
return 0;
}
static const struct acpi_device_id surface_kbd_match[] = {
......@@ -285,7 +284,7 @@ MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
static struct platform_driver surface_kbd_driver = {
.probe = surface_kbd_probe,
.remove = surface_kbd_remove,
.remove_new = surface_kbd_remove,
.driver = {
.name = "surface_keyboard",
.acpi_match_table = surface_kbd_match,
......
......@@ -474,9 +474,9 @@ struct hid_usage {
__s8 wheel_factor; /* 120/resolution_multiplier */
__u16 code; /* input driver code */
__u8 type; /* input driver type */
__s8 hat_min; /* hat switch fun */
__s8 hat_max; /* ditto */
__s8 hat_dir; /* ditto */
__s16 hat_min; /* hat switch fun */
__s16 hat_max; /* ditto */
__s16 hat_dir; /* ditto */
__s16 wheel_accumulated; /* hi-res wheel */
};
......
This diff is collapsed.
......@@ -238,3 +238,4 @@ CONFIG_VLAN_8021Q=y
CONFIG_XFRM_SUB_POLICY=y
CONFIG_XFRM_USER=y
CONFIG_ZEROPLUS_FF=y
CONFIG_KASAN=y
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment