* Re: [PATCH] Input: rmi4 - release F54 queue on video registration failure
From: Hans Verkuil @ 2026-05-27 7:38 UTC (permalink / raw)
To: Dmitry Torokhov, Myeonghun Pak, Hans Verkuil
Cc: linux-input, linux-kernel, stable, Ijae Kim
In-Reply-To: <ahXYreASLGSPuIe_@google.com>
On 5/26/26 7:36 PM, Dmitry Torokhov wrote:
> On Mon, May 25, 2026 at 03:23:45AM +0900, Myeonghun Pak wrote:
>> rmi_f54_probe() initializes the videobuf2 queue before registering the
>> video device. If video_register_device() fails, probe only unregisters
>> the V4L2 device and leaves the initialized queue unwound by neither
>> remove nor file release paths.
>>
>> Release the queue before continuing through the existing probe error
>> path.
>>
>> This issue was identified during our ongoing static-analysis research while
>> reviewing kernel code.
>>
>> Fixes: 3a762dbd5347 ("[media] Input: synaptics-rmi4 - add support for F54 diagnostics")
>> Cc: stable@vger.kernel.org
>> Co-developed-by: Ijae Kim <ae878000@gmail.com>
>> Signed-off-by: Ijae Kim <ae878000@gmail.com>
>> Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
>> ---
>> drivers/input/rmi4/rmi_f54.c | 1 +
>> 1 file changed, 1 insertion(+)
>>
>> diff --git a/drivers/input/rmi4/rmi_f54.c b/drivers/input/rmi4/rmi_f54.c
>> index 61909e1a39..fca7b9fec5 100644
>> --- a/drivers/input/rmi4/rmi_f54.c
>> +++ b/drivers/input/rmi4/rmi_f54.c
>> @@ -722,6 +722,7 @@ static int rmi_f54_probe(struct rmi_function *fn)
>> ret = video_register_device(&f54->vdev, VFL_TYPE_TOUCH, -1);
>> if (ret) {
>> dev_err(&fn->dev, "Unable to register video subdevice.");
>> + vb2_queue_release(&f54->queue);
vb2_queue_release is not needed here: since the video device was never
created, it also never started streaming, and this call is only needed
if streaming is in progress.
Looking at other drivers I see that in most cases they shouldn't call
vb2_queue_release at all. I need to go through the media drivers and
fix them.
In any case:
Rejected-by: Hans Verkuil <hverkuil+cisco@kernel.org>
Regards,
Hans
>> goto remove_v4l2;
>> }
>>
>
> Hans, could you please Ack or Nak it? It is unclear to me if this
> cleanup is mandatory and whether it is also needed in rmi_f54_remove().
>
> Thanks.
>
^ permalink raw reply
* Re: [stable backport request] linux-6.19.y: cac61b58a3b6 ("HID: playstation: Clamp num_touch_reports")
From: Greg KH @ 2026-05-27 8:09 UTC (permalink / raw)
To: sin99xx
Cc: stable, sashal, jikos, bentiss, roderick.colenbrander, tjmercier,
bsevens, linux-input
In-Reply-To: <CAMX0No2KAENnb=w-0R_8vGsnW+Ux0B+LMZKHt44YPJNjxJeP7w@mail.gmail.com>
On Tue, May 26, 2026 at 01:30:51PM -0400, sin99xx wrote:
> Hi stable team,
>
> linux-6.19.y is still missing the backport of
>
> cac61b58a3b6 "HID: playstation: Clamp num_touch_reports"
> (T.J. Mercier, 2026-05-12, Cc: stable@vger.kernel.org)
>
> and its precursor
>
> 82a4fc463309 "HID: playstation: validate num_touch_reports in
> DualShock 4 reports"
> (Benoît Sevens, 2026-04-09)
>
> Both are present in mainline, linux-6.6.y and linux-6.12.y. As of
> v6.19.14 (released 2026-04-22), linux-6.19.y still carries the
> unclamped loop. Function-level disassembly of hid-playstation.ko on a
> 6.19.14 build confirms neither patch is applied:
6.19.y is long end-of-life, which is perhaps why it's not in that tree :)
Always check the front page of kernel.org for the list of kernels that
are still being supported.
thanks,
greg k-h
^ permalink raw reply
* [PATCH 1/1] HID: wacom: Fix multiple Use-After-Free issues in shared state
From: Lee Jones @ 2026-05-27 14:07 UTC (permalink / raw)
To: lee, Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires,
Dmitry Torokhov, linux-input, linux-kernel
The Wacom driver coordinates state between sibling interfaces of the same
physical device (like Pen, Touch, Pad) using a shared structure
'wacom_shared' inside 'wacom_hdev_data'. The driver kept a volatile
representative pointer 'data->dev' pointing to a sibling 'hid_device'
for physical path comparisons during sibling matching.
This pointer management is fragile. When the representative device is
disconnected, wacom_remove_shared_data() failed to clear/update
'data->dev' or wacom_wac->shared->touch_input, leading to two Use-After-Free
vulnerabilities:
1. dangling 'touch_input' dereferenced during touch switch sync.
2. dangling 'data->dev' dereferenced during subsequent sibling probes.
Instead of adding complex pointer handover logic to keep 'data->dev'
updated (which has logic gaps with Pad siblings and introduces race
conditions), completely eliminate 'data->dev' pointer.
Redesign 'wacom_hdev_data' to store stable static copies of the required
representative attributes when it is first allocated:
- Copy 'phys' path string (stored in data->phys) for stable path comparison.
- Copy 'vendor' and 'product' IDs.
- Copy and accumulate 'device_type' capabilities as siblings are probed.
Also explicitly clear 'touch_input = NULL' in wacom_remove_shared_data()
under wacom_udev_list_lock to safely avoid the touch_input UAF.
This resolves all vulnerabilities permanently at the design level without
complex pointer lifecycles or race-prone swaps on device removal.
Fixes: 471d17148c8b ("Input: wacom - move the USB (now hid) Wacom driver in drivers/hid")
Signed-off-by: Lee Jones <lee@kernel.org>
---
drivers/hid/wacom_sys.c | 58 ++++++++++++++++++++++++++++-------------
1 file changed, 40 insertions(+), 18 deletions(-)
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index a32320b351e3..9e4819c197c1 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -753,27 +753,40 @@ static void wacom_retrieve_hid_descriptor(struct hid_device *hdev,
struct wacom_hdev_data {
struct list_head list;
struct kref kref;
- struct hid_device *dev;
+ char phys[64];
+ __u32 vendor;
+ __u32 product;
+ __u32 device_type;
struct wacom_shared shared;
};
+static bool wacom_compare_device_paths(struct hid_device *hdev_a,
+ const char *phys_b, char separator)
+{
+ int n1 = strrchr(hdev_a->phys, separator) - hdev_a->phys;
+ int n2 = strrchr(phys_b, separator) - phys_b;
+
+ if (n1 != n2 || n1 <= 0 || n2 <= 0)
+ return false;
+
+ return !strncmp(hdev_a->phys, phys_b, n1);
+}
+
static LIST_HEAD(wacom_udev_list);
static DEFINE_MUTEX(wacom_udev_list_lock);
static bool wacom_are_sibling(struct hid_device *hdev,
- struct hid_device *sibling)
+ struct wacom_hdev_data *data)
{
struct wacom *wacom = hid_get_drvdata(hdev);
struct wacom_features *features = &wacom->wacom_wac.features;
- struct wacom *sibling_wacom = hid_get_drvdata(sibling);
- struct wacom_features *sibling_features = &sibling_wacom->wacom_wac.features;
__u32 oVid = features->oVid ? features->oVid : hdev->vendor;
__u32 oPid = features->oPid ? features->oPid : hdev->product;
/* The defined oVid/oPid must match that of the sibling */
- if (features->oVid != HID_ANY_ID && sibling->vendor != oVid)
+ if (features->oVid != HID_ANY_ID && data->vendor != oVid)
return false;
- if (features->oPid != HID_ANY_ID && sibling->product != oPid)
+ if (features->oPid != HID_ANY_ID && data->product != oPid)
return false;
/*
@@ -781,11 +794,11 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* device path, while those with different VID/PID must share
* the same physical parent device path.
*/
- if (hdev->vendor == sibling->vendor && hdev->product == sibling->product) {
- if (!hid_compare_device_paths(hdev, sibling, '/'))
+ if (hdev->vendor == data->vendor && hdev->product == data->product) {
+ if (!wacom_compare_device_paths(hdev, data->phys, '/'))
return false;
} else {
- if (!hid_compare_device_paths(hdev, sibling, '.'))
+ if (!wacom_compare_device_paths(hdev, data->phys, '.'))
return false;
}
@@ -798,7 +811,7 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if ((features->device_type & WACOM_DEVICETYPE_DIRECT) &&
- !(sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
+ !(data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/*
@@ -806,17 +819,17 @@ static bool wacom_are_sibling(struct hid_device *hdev,
* devices.
*/
if (!(features->device_type & WACOM_DEVICETYPE_DIRECT) &&
- (sibling_features->device_type & WACOM_DEVICETYPE_DIRECT))
+ (data->device_type & WACOM_DEVICETYPE_DIRECT))
return false;
/* Pen devices may only be siblings of touch devices */
if ((features->device_type & WACOM_DEVICETYPE_PEN) &&
- !(sibling_features->device_type & WACOM_DEVICETYPE_TOUCH))
+ !(data->device_type & WACOM_DEVICETYPE_TOUCH))
return false;
/* Touch devices may only be siblings of pen devices */
if ((features->device_type & WACOM_DEVICETYPE_TOUCH) &&
- !(sibling_features->device_type & WACOM_DEVICETYPE_PEN))
+ !(data->device_type & WACOM_DEVICETYPE_PEN))
return false;
/*
@@ -832,7 +845,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Try to find an already-probed interface from the same device */
list_for_each_entry(data, &wacom_udev_list, list) {
- if (hid_compare_device_paths(hdev, data->dev, '/')) {
+ if (wacom_compare_device_paths(hdev, data->phys, '/')) {
kref_get(&data->kref);
return data;
}
@@ -840,7 +853,7 @@ static struct wacom_hdev_data *wacom_get_hdev_data(struct hid_device *hdev)
/* Fallback to finding devices that appear to be "siblings" */
list_for_each_entry(data, &wacom_udev_list, list) {
- if (wacom_are_sibling(hdev, data->dev)) {
+ if (wacom_are_sibling(hdev, data)) {
kref_get(&data->kref);
return data;
}
@@ -871,10 +884,14 @@ static void wacom_remove_shared_data(void *res)
data = container_of(wacom_wac->shared, struct wacom_hdev_data,
shared);
- if (wacom_wac->shared->touch == wacom->hdev)
+ mutex_lock(&wacom_udev_list_lock);
+ if (wacom_wac->shared->touch == wacom->hdev) {
wacom_wac->shared->touch = NULL;
- else if (wacom_wac->shared->pen == wacom->hdev)
+ wacom_wac->shared->touch_input = NULL;
+ } else if (wacom_wac->shared->pen == wacom->hdev) {
wacom_wac->shared->pen = NULL;
+ }
+ mutex_unlock(&wacom_udev_list_lock);
kref_put(&data->kref, wacom_release_shared_data);
wacom_wac->shared = NULL;
@@ -899,8 +916,13 @@ static int wacom_add_shared_data(struct hid_device *hdev)
}
kref_init(&data->kref);
- data->dev = hdev;
+ strscpy(data->phys, hdev->phys, sizeof(data->phys));
+ data->vendor = hdev->vendor;
+ data->product = hdev->product;
+ data->device_type = wacom_wac->features.device_type;
list_add_tail(&data->list, &wacom_udev_list);
+ } else {
+ data->device_type |= wacom_wac->features.device_type;
}
mutex_unlock(&wacom_udev_list_lock);
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* Re: [PATCH v7 0/7] mfd: Add support for Asus Transformer embedded controller
From: Lee Jones @ 2026-05-27 14:46 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Dmitry Torokhov,
Pavel Machek, Sebastian Reichel, Ion Agorria,
Michał Mirosław, devicetree, linux-kernel, linux-input,
linux-leds, linux-pm
In-Reply-To: <20260518095907.36158-1-clamor95@gmail.com>
On Mon, 18 May 2026, Svyatoslav Ryhel wrote:
> Add support for embedded controller used in Asus Transformers for
> managing power and input functions.
Once you have your Input Acks, I'll merge everything through MFD.
--
Lee Jones
^ permalink raw reply
* Re: [PATCH 1/1] HID: wacom: Fix multiple Use-After-Free issues in shared state
From: sashiko-bot @ 2026-05-27 14:55 UTC (permalink / raw)
To: Lee Jones; +Cc: dmitry.torokhov, linux-input
In-Reply-To: <20260527140731.642783-1-lee@kernel.org>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch fails to fully resolve the Use-After-Free vulnerability for `touch_input` because interrupt handlers access it locklessly, leading to a Time-of-Check to Time-of-Use (TOCTOU) race condition.
- [High] A race condition exists between `kref_put()` and concurrent `kref_get()` due to missing list-level locking during object removal.
- [High] Unprotected and temporally separated assignments of `shared->touch` and `shared->touch_input` during probe allow concurrent `uhid` devices to desynchronize the shared state.
--
commit 007cb667e9154320e49d7d42911e8193520da3fe
Author: Lee Jones <lee@kernel.org>
HID: wacom: Fix multiple Use-After-Free issues in shared state
This commit redesigns the wacom_hdev_data structure to store stable static
copies of required attributes instead of a fragile representative pointer.
It explicitly clears the touch_input pointer in wacom_remove_shared_data()
under the wacom_udev_list_lock to address use-after-free vulnerabilities.
> diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
> index a32320b351e3e..9e4819c197c1b 100644
> --- a/drivers/hid/wacom_sys.c
> +++ b/drivers/hid/wacom_sys.c
[ ... ]
> @@ -871,10 +884,14 @@ static void wacom_remove_shared_data(void *res)
> data = container_of(wacom_wac->shared, struct wacom_hdev_data,
> shared);
>
> - if (wacom_wac->shared->touch == wacom->hdev)
> + mutex_lock(&wacom_udev_list_lock);
> + if (wacom_wac->shared->touch == wacom->hdev) {
> wacom_wac->shared->touch = NULL;
> - else if (wacom_wac->shared->pen == wacom->hdev)
> + wacom_wac->shared->touch_input = NULL;
[Severity: High]
This isn't a bug introduced by this patch, but does clearing this pointer under
the lock fully prevent use-after-free conditions?
Interrupt handlers read shared->touch_input locklessly (for example, in
wacom_wireless_irq()). An interrupt handler could check that
shared->touch_input is valid, but then get preempted.
Concurrently, wacom_remove_shared_data() clears the pointer here, and the
input device is subsequently unregistered and freed by devres.
When the interrupt handler resumes, could it pass the freed pointer to
input_report_switch(), causing a use-after-free? The lock protects writers,
but it appears readers remain vulnerable to this time-of-check to
time-of-use race.
> + } else if (wacom_wac->shared->pen == wacom->hdev) {
> wacom_wac->shared->pen = NULL;
> + }
> + mutex_unlock(&wacom_udev_list_lock);
>
> kref_put(&data->kref, wacom_release_shared_data);
[Severity: High]
This is a pre-existing issue, but can this kref_put() race with concurrent
list traversal?
It is called outside of wacom_udev_list_lock. If the reference count drops to 0,
another thread probing a sibling device could simultaneously acquire the list
lock in wacom_add_shared_data(), find the logically dead object before it is
removed from the list, and call kref_get().
This would attempt to increment a 0 refcount, returning a pointer to an object
that will unconditionally be freed as soon as the first thread acquires the
lock in wacom_release_shared_data(), leading to a use-after-free.
> wacom_wac->shared = NULL;
[ ... ]
> @@ -899,8 +916,13 @@ static int wacom_add_shared_data(struct hid_device *hdev)
> }
>
> kref_init(&data->kref);
> - data->dev = hdev;
> + strscpy(data->phys, hdev->phys, sizeof(data->phys));
> + data->vendor = hdev->vendor;
> + data->product = hdev->product;
> + data->device_type = wacom_wac->features.device_type;
> list_add_tail(&data->list, &wacom_udev_list);
> + } else {
> + data->device_type |= wacom_wac->features.device_type;
> }
>
> mutex_unlock(&wacom_udev_list_lock);
[Severity: High]
This is a pre-existing issue, but do the assignments to shared->touch and
shared->touch_input that follow this unlock need protection?
They are performed sequentially outside of any lock during device probe:
if (wacom_wac->features.device_type & WACOM_DEVICETYPE_TOUCH)
wacom_wac->shared->touch = hdev;
If two virtual devices with the same physical path are created concurrently
(for example, via uhid), they will share the same wacom_hdev_data structure.
Could a race in the probe sequence result in shared->touch pointing to
device B, while shared->touch_input points to device A?
If device A is then disconnected, wacom_remove_shared_data() would see
shared->touch == wacom->hdev as false and skip clearing shared->touch_input.
Device A's input device would be freed, leaving a dangling pointer that could
be dereferenced by sibling devices.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260527140731.642783-1-lee@kernel.org?part=1
^ permalink raw reply
* Re: [PATCH] Input: ims-pcu - fix use-after-free in probe error path
From: Dmitry Torokhov @ 2026-05-27 14:58 UTC (permalink / raw)
To: Zhian Liang; +Cc: linux-input, linux-kernel, stable
In-Reply-To: <20260525151410.42750-1-liangzhan5dev@gmail.com>
On Mon, May 25, 2026 at 11:14:10PM +0800, Zhian Liang wrote:
> If the driver fails during init (e.g. in ims_pcu_init_application_mode),the error path frees the pcu struct without clearing the interface data.
>
> If the device is disconnected while in this state, the disconnect handler will retrieve the stale pointer from
> usb_get_intfdata() and trigger a use-after-free
This does not make sense. How will disconnect handler run if probe has
not completed?
>
> Fix this by setting the interface data to NULL in the probe before freeing the pcu struct.
>
> Fixes: 628329d52474 ("Input: add IMS Passenger Control Unit driver")
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Zhian Liang <liangzhan5dev@gmail.com>
> ---
> drivers/input/misc/ims-pcu.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c
> index 4c022a36dbe8..fce3232ebf07 100644
> --- a/drivers/input/misc/ims-pcu.c
> +++ b/drivers/input/misc/ims-pcu.c
> @@ -2063,6 +2063,10 @@ static int ims_pcu_probe(struct usb_interface *intf,
> ims_pcu_buffers_free(pcu);
> err_unclaim_intf:
> usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf);
> + goto err_clear_intfdata;
> +err_clear_intfdata:
> + if (pcu->ctrl_intf)
> + usb_set_intfdata(pcu->ctrl_intf, NULL);
> err_free_mem:
> kfree(pcu);
> return error;
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v6 0/6] mfd: cpcap: convert documentation to schema and add Mot board support
From: Lee Jones @ 2026-05-27 15:14 UTC (permalink / raw)
To: Svyatoslav Ryhel
Cc: Dmitry Torokhov, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Pavel Machek, linux-input, devicetree, linux-kernel, linux-leds
In-Reply-To: <20260520182124.117863-1-clamor95@gmail.com>
On Wed, 20 May 2026, Svyatoslav Ryhel wrote:
> The initial goal was only to add support for the CPCAP used in the Mot
> Tegra20 board; however, since the documentation was already partially
> converted, I decided to complete the conversion to schema too.
>
> The CPCAP regulator, leds, rtc, pwrbutton and core files were converted
> from TXT to YAML while preserving the original structure. Mot board
> compatibility was added to the regulator and core schema. Since these
> were one-line patches, they were not separated into dedicated commits;
> however, the commit message notes this for both cases.
>
> Finally, the CPCAP MFD was slightly refactored to improve support for
> multiple subcell compositions.
Once you've fixed the DT issues that Rob pointed out, I'll merge the set.
--
Lee Jones
^ permalink raw reply
* [PATCH] HID: i2c-hid-acpi: Add PRP0001 to match table and OF alias
From: 谢致邦 (XIE Zhibang) @ 2026-05-27 15:17 UTC (permalink / raw)
To: linux-input
Cc: 谢致邦 (XIE Zhibang), Jiri Kosina,
Benjamin Tissoires, Mario Limonciello (AMD), Hans de Goede,
Douglas Anderson, linux-kernel
Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
declare their I2C HID ACPI touchpad in the DSDT as _HID "PRP0001" with
_DSD compatible "hid-over-i2c" instead of the standard "PNP0C50". This
worked before commit b33752c30023 ("HID: i2c-hid: Reorganize so ACPI and
OF are separate modules"), but after the split, PRP0001 devices on the
ACPI bus are no longer probed by either driver.
Fix this by adding PRP0001 to i2c_hid_acpi_match so the driver probes
these devices. The existing _DSM HID descriptor call in probe()
naturally rejects any PRP0001 device that does not implement the
protocol.
A MODULE_ALIAS is also needed for autoloading: when an ACPI device has a
_DSD "compatible" property, the uevent modalias uses the OF format
(of:N<name>T<compatible>) instead of the ACPI format (acpi:<HID>), so
udev would otherwise load only i2c-hid-of, which fails to probe because
these devices lack the "hid-descr-addr" property.
Fixes: b33752c30023 ("HID: i2c-hid: Reorganize so ACPI and OF are separate modules")
Signed-off-by: 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
---
drivers/hid/i2c-hid/i2c-hid-acpi.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
index abd700a101f4..515ced22c978 100644
--- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
+++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
@@ -119,10 +119,30 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
static const struct acpi_device_id i2c_hid_acpi_match[] = {
{ "ACPI0C50" },
{ "PNP0C50" },
+ /*
+ * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
+ * declare their I2C HID ACPI touchpad in the DSDT as _HID "PRP0001"
+ * with _DSD compatible "hid-over-i2c" instead of the standard
+ * "PNP0C50". This worked before i2c-hid was split into i2c-hid-acpi
+ * and i2c-hid-of, but PRP0001 devices on the ACPI bus are no longer
+ * probed after the split. The _DSM call in probe() naturally rejects
+ * PRP0001 devices that are not actually I2C HID, so matching PRP0001
+ * here is safe.
+ */
+ { "PRP0001" },
{ }
};
MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
+ /*
+ * When an ACPI device has a _DSD "compatible" property, the uevent
+ * modalias uses the OF format (of:N<name>T<compatible>) instead of
+ * the ACPI format (acpi:<HID>). Add an OF alias so udev can autoload
+ * this module for such devices. probe() will reject pure DT devices
+ * via the _DSM HID descriptor call.
+ */
+MODULE_ALIAS("of:N*TChid-over-i2c");
+
static struct i2c_driver i2c_hid_acpi_driver = {
.driver = {
.name = "i2c_hid_acpi",
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] HID: i2c-hid-acpi: Add PRP0001 to match table and OF alias
From: Hans de Goede @ 2026-05-27 15:44 UTC (permalink / raw)
To: 谢致邦 (XIE Zhibang), linux-input
Cc: Jiri Kosina, Benjamin Tissoires, Mario Limonciello (AMD),
Douglas Anderson, linux-kernel
In-Reply-To: <tencent_61698D7A14AC9BFE6C9F3FDB797FCA02E309@qq.com>
Hi,
On 27-May-26 17:17, 谢致邦 (XIE Zhibang) wrote:
> Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
> declare their I2C HID ACPI touchpad in the DSDT as _HID "PRP0001" with
> _DSD compatible "hid-over-i2c" instead of the standard "PNP0C50". This
> worked before commit b33752c30023 ("HID: i2c-hid: Reorganize so ACPI and
> OF are separate modules"), but after the split, PRP0001 devices on the
> ACPI bus are no longer probed by either driver.
>
> Fix this by adding PRP0001 to i2c_hid_acpi_match so the driver probes
> these devices. The existing _DSM HID descriptor call in probe()
> naturally rejects any PRP0001 device that does not implement the
> protocol.
>
> A MODULE_ALIAS is also needed for autoloading: when an ACPI device has a
> _DSD "compatible" property, the uevent modalias uses the OF format
> (of:N<name>T<compatible>) instead of the ACPI format (acpi:<HID>), so
> udev would otherwise load only i2c-hid-of, which fails to probe because
> these devices lack the "hid-descr-addr" property.
Ok, so first of all please contact the vendors of these devices to fix
their firmware.
Either a _HID "PRP0001" value should be used with a *full* of description
matching the binding from Documentation/devicetree/bindings/input/hid-over-i2c.yaml
including hid-descr-addr. Or the firmware should use PNP0C50 + the _DSM
method to get the hid-desc-addr. Mixing and matching these 2 is bad,
very very bad.
IMHO the fix here as is is not acceptable this will make the i2c_hid_acpi
module load and worse *probe* every ACPI device with a "PRP0001" HID.
You claim the existing _DSM HID descriptor call will save the driver from
actually doing much of anything but IMHO that should not be relied on.
Currently the i2c_hid_of driver will get automatically loaded + try
to probe the device, but as you say this will fail due to lacking
hid-desc-addr. Have you tried adding DMI quirks to i2c_hid_of to
provide the hid-desc-addr through a quirk? (I wonder if the IRQ will
get picked up ok)
That seems a better solution than making 2 drivers probe the same
"hid-over-i2c" compatible and let one fail (with an ugly error msg
in the logs), while also making i2c_hid_acpi probe all PRP0001 devices
and make that fail (with more err logging) on all other devices.
I see the 2 laptops here are both using a Loongson architecture,
so any fix for this should IMHO also be wrapped in
#ifdef CONFIG_LOONGARCH ... #endif
Regards,
Hans
>
> Fixes: b33752c30023 ("HID: i2c-hid: Reorganize so ACPI and OF are separate modules")
> Signed-off-by: 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
> ---
> drivers/hid/i2c-hid/i2c-hid-acpi.c | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
> index abd700a101f4..515ced22c978 100644
> --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
> +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
> @@ -119,10 +119,30 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
> static const struct acpi_device_id i2c_hid_acpi_match[] = {
> { "ACPI0C50" },
> { "PNP0C50" },
> + /*
> + * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
> + * declare their I2C HID ACPI touchpad in the DSDT as _HID "PRP0001"
> + * with _DSD compatible "hid-over-i2c" instead of the standard
> + * "PNP0C50". This worked before i2c-hid was split into i2c-hid-acpi
> + * and i2c-hid-of, but PRP0001 devices on the ACPI bus are no longer
> + * probed after the split. The _DSM call in probe() naturally rejects
> + * PRP0001 devices that are not actually I2C HID, so matching PRP0001
> + * here is safe.
> + */
> + { "PRP0001" },
> { }
> };
> MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
>
> + /*
> + * When an ACPI device has a _DSD "compatible" property, the uevent
> + * modalias uses the OF format (of:N<name>T<compatible>) instead of
> + * the ACPI format (acpi:<HID>). Add an OF alias so udev can autoload
> + * this module for such devices. probe() will reject pure DT devices
> + * via the _DSM HID descriptor call.
> + */
> +MODULE_ALIAS("of:N*TChid-over-i2c");
> +
> static struct i2c_driver i2c_hid_acpi_driver = {
> .driver = {
> .name = "i2c_hid_acpi",
^ permalink raw reply
* Re: [PATCH 1/1] HID: wacom: Fix OOB write in wacom_hid_set_device_mode()
From: Lee Jones @ 2026-05-27 15:57 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Ping Cheng, Ping Cheng, Jason Gerecke, Jiri Kosina, linux-input,
linux-kernel, stable
In-Reply-To: <20260521162212.GF3591266@google.com>
On Thu, 21 May 2026, Lee Jones wrote:
> On Thu, 21 May 2026, Benjamin Tissoires wrote:
>
> > On May 19 2026, Lee Jones wrote:
> > > On Wed, 13 May 2026, Ping Cheng wrote:
> > >
> > > > On Wed, May 13, 2026 at 1:05 AM Lee Jones <lee@kernel.org> wrote:
> > > > >
> > > > > wacom_hid_set_device_mode() currently assumes that the HID_DG_INPUTMODE
> > > > > usage is always located in the first field (field[0]) of the feature report.
> > > > > However, a device can specify HID_DG_INPUTMODE in a different field.
> > > > >
> > > > > If HID_DG_INPUTMODE is in a field other than the first one and the first
> > > > > field has a report_count smaller than the usage_index of HID_DG_INPUTMODE,
> > > > > this leads to an out-of-bounds write to r->field[0]->value.
> > > > >
> > > > > Fix this by storing the field index of HID_DG_INPUTMODE in 'struct
> > > > > hid_data' during feature mapping. In wacom_hid_set_device_mode(), use
> > > > > this stored field index to access the correct field and add bounds
> > > > > checks to ensure both the field index and the value index are within
> > > > > valid ranges before writing.
> > > > >
> > > > > Cc: stable@vger.kernel.org
> > > > > Fixes: 5ae6e89f7409 ("HID: wacom: implement the finger part of the HID generic handling")
> > > > > Signed-off-by: Lee Jones <lee@kernel.org>
> > > >
> > > > Patch looks sensible to me. Thank you for your effort, Lee!
> > > >
> > > > Tested-by: Ping Cheng <ping.cheng@wacom.com>
> > > > Reviewed-by: Ping Cheng <ping.cheng@wacom.com>
> > >
> > > Thank you Ping, I appreciate your review.
> > >
> > > HID folks - any movement on this please?
> > >
> >
> > I wanted to apply it today, but the patch conflicts with our current
> > for-7.1/upstream-fixes.
> >
> > Could you rebase on top of this branch so we can take this without me
> > messing with your patch?
>
> Sure. Leave it with me. Probably be early next week.
Are you sure this conflicted?
I just rebased it onto hid/for-7.1/upstream-fixes without issue.
--
Lee Jones
^ permalink raw reply
* Re: [PATCH] HID: i2c-hid-acpi: Add PRP0001 to match table and OF alias
From: sashiko-bot @ 2026-05-27 16:00 UTC (permalink / raw)
Cc: linux-input, dmitry.torokhov
In-Reply-To: <tencent_61698D7A14AC9BFE6C9F3FDB797FCA02E309@qq.com>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Adding `PRP0001` directly to the ACPI match table incorrectly bypasses `compatible` string matching, causing the driver to intercept all PRP0001 I2C devices and spam the kernel log.
--
commit 7d0ee8c935eaadd4f5e66e197d5712a960fd31f5
Author: 谢致邦 (XIE Zhibang) <Yeking@Red54.com>
HID: i2c-hid-acpi: Add PRP0001 to match table and OF alias
This commit adds the PRP0001 ACPI ID to the i2c-hid-acpi driver's match
table and manually defines an OF module alias. This is intended to allow the
driver to probe devices that declare themselves as PRP0001 with a "hid-over-i2c"
compatible string in their _DSD.
> diff --git a/drivers/hid/i2c-hid/i2c-hid-acpi.c b/drivers/hid/i2c-hid/i2c-hid-acpi.c
> index abd700a101f46..515ced22c978d 100644
> --- a/drivers/hid/i2c-hid/i2c-hid-acpi.c
> +++ b/drivers/hid/i2c-hid/i2c-hid-acpi.c
> @@ -119,10 +119,30 @@ static int i2c_hid_acpi_probe(struct i2c_client *client)
> static const struct acpi_device_id i2c_hid_acpi_match[] = {
> { "ACPI0C50" },
> { "PNP0C50" },
> + /*
> + * Some devices, for example the Lenovo KaiTian N60d and Inspur CP300L3,
> + * declare their I2C HID ACPI touchpad in the DSDT as _HID "PRP0001"
> + * with _DSD compatible "hid-over-i2c" instead of the standard
> + * "PNP0C50". This worked before i2c-hid was split into i2c-hid-acpi
> + * and i2c-hid-of, but PRP0001 devices on the ACPI bus are no longer
> + * probed after the split. The _DSM call in probe() naturally rejects
> + * PRP0001 devices that are not actually I2C HID, so matching PRP0001
> + * here is safe.
> + */
> + { "PRP0001" },
[Severity: Medium]
Does adding PRP0001 directly to the ACPI match table bypass the OF compatible
string check in __acpi_match_device()?
PRP0001 is a special identifier used by the ACPI subsystem to defer matching
to the OF compatible string within the device's _DSD. When PRP0001 is
explicitly added here, the match returns true immediately without checking the
compatible string.
If a system has other unrelated I2C devices using PRP0001 (like temperature
sensors or RTCs utilizing compatible strings), won't the driver core attempt
to bind i2c-hid-acpi to all of them?
Because i2c_hid_acpi_probe() unconditionally evaluates the HID _DSM which will
fail for these unrelated devices, does this introduce boot delays and pollute
the kernel log with false-positive "Error _DSM call to get HID descriptor
address failed" messages for every non-HID PRP0001 device?
> { }
> };
> MODULE_DEVICE_TABLE(acpi, i2c_hid_acpi_match);
>
> + /*
> + * When an ACPI device has a _DSD "compatible" property, the uevent
> + * modalias uses the OF format (of:N<name>T<compatible>) instead of
> + * the ACPI format (acpi:<HID>). Add an OF alias so udev can autoload
> + * this module for such devices. probe() will reject pure DT devices
> + * via the _DSM HID descriptor call.
> + */
> +MODULE_ALIAS("of:N*TChid-over-i2c");
[Severity: Medium]
Instead of manually defining this MODULE_ALIAS, could we define an
of_device_id match table containing { .compatible = "hid-over-i2c" }?
Using an of_device_id table would naturally solve the matching logic by
allowing the ACPI core to correctly evaluate the _DSD compatible string.
This prevents intercepting unrelated PRP0001 devices, and the driver core
would automatically generate the correct, robust module alias for autoloading.
--
Sashiko AI review · https://sashiko.dev/#/patchset/tencent_61698D7A14AC9BFE6C9F3FDB797FCA02E309@qq.com?part=1
^ permalink raw reply
* [PATCH v2] HID: nintendo: add support for HORI Wireless Switch Pad
From: Hector Zelaya @ 2026-05-27 16:01 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires, Daniel J. Ogorchock
Cc: linux-input, linux-kernel, Hector Zelaya, Joshua Peisach
Add support for the HORI Wireless Switch Pad (vendor 0x0f0d, product
0x00f6), a licensed third-party Nintendo Switch Pro Controller.
The controller reports controller type 0x06 (vs 0x03 for first-party
Pro Controllers) and has the following quirks:
- SPI flash calibration data is incompatible; use default stick
calibration values instead.
- X and Y button bits are swapped compared to first-party controllers;
add a dedicated button mapping table.
- Rumble and IMU enable may timeout (no vibration motor in hardware);
treat as non-fatal for licensed controllers.
Tested over Bluetooth on NixOS with kernel 7.0.5 and 7.0.10:
- All 14 buttons map correctly
- Player LED sets on connect
- Sticks report correctly with default calibration
- IMU/gyro data streams at 60Hz
- D-pad reports on ABS_HAT0X/HAT0Y
Device information:
Bluetooth name: Lic Pro Controller
Bluetooth HID: 0005:0F0D:00F6
Assisted-by: Kiro:Auto [Amazon Kiro IDE]
Signed-off-by: Hector Zelaya <hector@hectorzelaya.dev>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
Changes in v2:
- Clear ret = 0 after non-fatal IMU/rumble timeouts to prevent
unintentional probe failure.
- Link to v1: https://patch.msgid.link/20260526-hori-support-v1-1-1861c0abc2e0@hectorzelaya.dev
To: Jiri Kosina <jikos@kernel.org>
To: Benjamin Tissoires <bentiss@kernel.org>
To: "Daniel J. Ogorchock" <djogorchock@gmail.com>
Cc: linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
drivers/hid/hid-ids.h | 3 ++
drivers/hid/hid-nintendo.c | 80 +++++++++++++++++++++++++++++++++++++++-------
2 files changed, 71 insertions(+), 12 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index a1cfa436344a..3b0767cc47fd 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -683,6 +683,9 @@
#define USB_DEVICE_ID_HARMONIX_WII_RB3_KEYBOARD 0x3330
#define USB_DEVICE_ID_HARMONIX_WII_RB3_MPA_KEYBOARD_MODE 0x3338
+#define USB_VENDOR_ID_HORI 0x0f0d
+#define USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD 0x00f6
+
#define USB_VENDOR_ID_HP 0x03f0
#define USB_PRODUCT_ID_HP_ELITE_PRESENTER_MOUSE_464A 0x464a
#define USB_PRODUCT_ID_HP_LOGITECH_OEM_USB_OPTICAL_MOUSE_0A4A 0x0a4a
diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
index 29008c2cc530..2d37ddeffdb6 100644
--- a/drivers/hid/hid-nintendo.c
+++ b/drivers/hid/hid-nintendo.c
@@ -316,6 +316,7 @@ enum joycon_ctlr_type {
JOYCON_CTLR_TYPE_JCL = 0x01,
JOYCON_CTLR_TYPE_JCR = 0x02,
JOYCON_CTLR_TYPE_PRO = 0x03,
+ JOYCON_CTLR_TYPE_LIC_PRO = 0x06,
JOYCON_CTLR_TYPE_NESL = 0x09,
JOYCON_CTLR_TYPE_NESR = 0x0A,
JOYCON_CTLR_TYPE_SNES = 0x0B,
@@ -433,6 +434,25 @@ static const struct joycon_ctlr_button_mapping procon_button_mappings[] = {
{ /* sentinel */ },
};
+/* Licensed Pro Controllers (e.g. HORI) swap X/Y bits in the report */
+static const struct joycon_ctlr_button_mapping lic_procon_button_mappings[] = {
+ { BTN_EAST, JC_BTN_A, },
+ { BTN_SOUTH, JC_BTN_B, },
+ { BTN_NORTH, JC_BTN_Y, },
+ { BTN_WEST, JC_BTN_X, },
+ { BTN_TL, JC_BTN_L, },
+ { BTN_TR, JC_BTN_R, },
+ { BTN_TL2, JC_BTN_ZL, },
+ { BTN_TR2, JC_BTN_ZR, },
+ { BTN_SELECT, JC_BTN_MINUS, },
+ { BTN_START, JC_BTN_PLUS, },
+ { BTN_THUMBL, JC_BTN_LSTICK, },
+ { BTN_THUMBR, JC_BTN_RSTICK, },
+ { BTN_MODE, JC_BTN_HOME, },
+ { BTN_Z, JC_BTN_CAP, },
+ { /* sentinel */ },
+};
+
static const struct joycon_ctlr_button_mapping nescon_button_mappings[] = {
{ BTN_SOUTH, JC_BTN_A, },
{ BTN_EAST, JC_BTN_B, },
@@ -695,7 +715,8 @@ static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr)
static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr)
{
- return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO;
+ return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO ||
+ ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO;
}
static inline bool joycon_type_is_snescon(struct joycon_ctlr *ctlr)
@@ -1710,7 +1731,10 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
joycon_report_left_stick(ctlr, rep);
joycon_report_right_stick(ctlr, rep);
joycon_report_dpad(ctlr, rep);
- joycon_report_buttons(ctlr, rep, procon_button_mappings);
+ if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO)
+ joycon_report_buttons(ctlr, rep, lic_procon_button_mappings);
+ else
+ joycon_report_buttons(ctlr, rep, procon_button_mappings);
} else if (joycon_type_is_any_nescon(ctlr)) {
joycon_report_dpad(ctlr, rep);
joycon_report_buttons(ctlr, rep, nescon_button_mappings);
@@ -2156,7 +2180,10 @@ static int joycon_input_create(struct joycon_ctlr *ctlr)
joycon_config_left_stick(ctlr->input);
joycon_config_right_stick(ctlr->input);
joycon_config_dpad(ctlr->input);
- joycon_config_buttons(ctlr->input, procon_button_mappings);
+ if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO)
+ joycon_config_buttons(ctlr->input, lic_procon_button_mappings);
+ else
+ joycon_config_buttons(ctlr->input, procon_button_mappings);
} else if (joycon_type_is_any_nescon(ctlr)) {
joycon_config_dpad(ctlr->input);
joycon_config_buttons(ctlr->input, nescon_button_mappings);
@@ -2503,13 +2530,30 @@ static int joycon_init(struct hid_device *hdev)
if (joycon_has_joysticks(ctlr)) {
/* get controller calibration data, and parse it */
- ret = joycon_request_calibration(ctlr);
- if (ret) {
+ if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
/*
- * We can function with default calibration, but it may be
- * inaccurate. Provide a warning, and continue on.
+ * Licensed controllers may have incompatible SPI flash
+ * layouts. Use default calibration values.
*/
- hid_warn(hdev, "Analog stick positions may be inaccurate\n");
+ hid_info(hdev, "using default cal for licensed controller\n");
+ joycon_use_default_calibration(hdev,
+ &ctlr->left_stick_cal_x,
+ &ctlr->left_stick_cal_y,
+ "left", 0);
+ joycon_use_default_calibration(hdev,
+ &ctlr->right_stick_cal_x,
+ &ctlr->right_stick_cal_y,
+ "right", 0);
+ } else {
+ ret = joycon_request_calibration(ctlr);
+ if (ret) {
+ /*
+ * We can function with default calibration, but
+ * it may be inaccurate. Provide a warning, and
+ * continue on.
+ */
+ hid_warn(hdev, "Analog stick positions may be inaccurate\n");
+ }
}
}
@@ -2527,8 +2571,13 @@ static int joycon_init(struct hid_device *hdev)
/* Enable the IMU */
ret = joycon_enable_imu(ctlr);
if (ret) {
- hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
- goto out_unlock;
+ if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
+ hid_dbg(hdev, "IMU enable failed for licensed controller, continuing\n");
+ ret = 0;
+ } else {
+ hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
+ goto out_unlock;
+ }
}
}
@@ -2543,8 +2592,13 @@ static int joycon_init(struct hid_device *hdev)
/* Enable rumble */
ret = joycon_enable_rumble(ctlr);
if (ret) {
- hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
- goto out_unlock;
+ if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
+ hid_dbg(hdev, "rumble enable failed for licensed controller, continuing\n");
+ ret = 0;
+ } else {
+ hid_err(hdev, "Failed to enable rumble; ret=%d\n", ret);
+ goto out_unlock;
+ }
}
}
@@ -2813,6 +2867,8 @@ static const struct hid_device_id nintendo_hid_devices[] = {
USB_DEVICE_ID_NINTENDO_GENCON) },
{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO,
USB_DEVICE_ID_NINTENDO_N64CON) },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_HORI,
+ USB_DEVICE_ID_HORI_WIRELESS_SWITCH_PAD) },
{ }
};
MODULE_DEVICE_TABLE(hid, nintendo_hid_devices);
---
base-commit: e71bac24ec1f517f399a9eb471255b8f1c330b93
change-id: 20260526-hori-support-08b08bca40d8
Best regards,
--
Hector Zelaya <hector@hectorzelaya.dev>
^ permalink raw reply related
* [RESEND 1/1] HID: wacom: Fix OOB write in wacom_hid_set_device_mode()
From: Lee Jones @ 2026-05-27 16:05 UTC (permalink / raw)
To: lee, Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires,
linux-input, linux-kernel
Cc: stable
wacom_hid_set_device_mode() currently assumes that the HID_DG_INPUTMODE
usage is always located in the first field (field[0]) of the feature report.
However, a device can specify HID_DG_INPUTMODE in a different field.
If HID_DG_INPUTMODE is in a field other than the first one and the first
field has a report_count smaller than the usage_index of HID_DG_INPUTMODE,
this leads to an out-of-bounds write to r->field[0]->value.
Fix this by storing the field index of HID_DG_INPUTMODE in 'struct
hid_data' during feature mapping. In wacom_hid_set_device_mode(), use
this stored field index to access the correct field and add bounds
checks to ensure both the field index and the value index are within
valid ranges before writing.
Cc: stable@vger.kernel.org
Fixes: 5ae6e89f7409 ("HID: wacom: implement the finger part of the HID generic handling")
Tested-by: Ping Cheng <ping.cheng@wacom.com>
Reviewed-by: Ping Cheng <ping.cheng@wacom.com>
Signed-off-by: Lee Jones <lee@kernel.org>
---
drivers/hid/wacom_sys.c | 13 ++++++++++---
drivers/hid/wacom_wac.h | 1 +
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
index a32320b351e3..2220168bf116 100644
--- a/drivers/hid/wacom_sys.c
+++ b/drivers/hid/wacom_sys.c
@@ -356,6 +356,7 @@ static void wacom_feature_mapping(struct hid_device *hdev,
hid_data->inputmode = field->report->id;
hid_data->inputmode_index = usage->usage_index;
+ hid_data->inputmode_field_index = field->index;
break;
case HID_UP_DIGITIZER:
@@ -571,9 +572,14 @@ static int wacom_hid_set_device_mode(struct hid_device *hdev)
re = &(hdev->report_enum[HID_FEATURE_REPORT]);
r = re->report_id_hash[hid_data->inputmode];
- if (r) {
- r->field[0]->value[hid_data->inputmode_index] = 2;
- hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
+ if (r && hid_data->inputmode_field_index >= 0 &&
+ hid_data->inputmode_field_index < r->maxfield) {
+ struct hid_field *field = r->field[hid_data->inputmode_field_index];
+
+ if (field && hid_data->inputmode_index < field->report_count) {
+ field->value[hid_data->inputmode_index] = 2;
+ hid_hw_request(hdev, r, HID_REQ_SET_REPORT);
+ }
}
return 0;
}
@@ -2846,6 +2852,7 @@ static int wacom_probe(struct hid_device *hdev,
return -ENODEV;
wacom_wac->hid_data.inputmode = -1;
+ wacom_wac->hid_data.inputmode_field_index = -1;
wacom_wac->mode_report = -1;
if (hid_is_usb(hdev)) {
diff --git a/drivers/hid/wacom_wac.h b/drivers/hid/wacom_wac.h
index d4f7d8ca1e7e..126bec6e5c0c 100644
--- a/drivers/hid/wacom_wac.h
+++ b/drivers/hid/wacom_wac.h
@@ -295,6 +295,7 @@ struct wacom_shared {
struct hid_data {
__s16 inputmode; /* InputMode HID feature, -1 if non-existent */
__s16 inputmode_index; /* InputMode HID feature index in the report */
+ __s16 inputmode_field_index; /* InputMode HID feature field index in the report */
bool sense_state;
bool inrange_state;
bool eraser;
--
2.54.0.746.g67dd491aae-goog
^ permalink raw reply related
* Re: [PATCH 1/1] HID: wacom: Fix OOB write in wacom_hid_set_device_mode()
From: Lee Jones @ 2026-05-27 16:07 UTC (permalink / raw)
To: Benjamin Tissoires
Cc: Ping Cheng, Ping Cheng, Jason Gerecke, Jiri Kosina, linux-input,
linux-kernel, stable
In-Reply-To: <20260527155753.GD712405@google.com>
On Wed, 27 May 2026, Lee Jones wrote:
> On Thu, 21 May 2026, Lee Jones wrote:
>
> > On Thu, 21 May 2026, Benjamin Tissoires wrote:
> >
> > > On May 19 2026, Lee Jones wrote:
> > > > On Wed, 13 May 2026, Ping Cheng wrote:
> > > >
> > > > > On Wed, May 13, 2026 at 1:05 AM Lee Jones <lee@kernel.org> wrote:
> > > > > >
> > > > > > wacom_hid_set_device_mode() currently assumes that the HID_DG_INPUTMODE
> > > > > > usage is always located in the first field (field[0]) of the feature report.
> > > > > > However, a device can specify HID_DG_INPUTMODE in a different field.
> > > > > >
> > > > > > If HID_DG_INPUTMODE is in a field other than the first one and the first
> > > > > > field has a report_count smaller than the usage_index of HID_DG_INPUTMODE,
> > > > > > this leads to an out-of-bounds write to r->field[0]->value.
> > > > > >
> > > > > > Fix this by storing the field index of HID_DG_INPUTMODE in 'struct
> > > > > > hid_data' during feature mapping. In wacom_hid_set_device_mode(), use
> > > > > > this stored field index to access the correct field and add bounds
> > > > > > checks to ensure both the field index and the value index are within
> > > > > > valid ranges before writing.
> > > > > >
> > > > > > Cc: stable@vger.kernel.org
> > > > > > Fixes: 5ae6e89f7409 ("HID: wacom: implement the finger part of the HID generic handling")
> > > > > > Signed-off-by: Lee Jones <lee@kernel.org>
> > > > >
> > > > > Patch looks sensible to me. Thank you for your effort, Lee!
> > > > >
> > > > > Tested-by: Ping Cheng <ping.cheng@wacom.com>
> > > > > Reviewed-by: Ping Cheng <ping.cheng@wacom.com>
> > > >
> > > > Thank you Ping, I appreciate your review.
> > > >
> > > > HID folks - any movement on this please?
> > > >
> > >
> > > I wanted to apply it today, but the patch conflicts with our current
> > > for-7.1/upstream-fixes.
> > >
> > > Could you rebase on top of this branch so we can take this without me
> > > messing with your patch?
> >
> > Sure. Leave it with me. Probably be early next week.
>
> Are you sure this conflicted?
>
> I just rebased it onto hid/for-7.1/upstream-fixes without issue.
Rebased patch here (not sure if it's actually different:
https://lore.kernel.org/all/20260527160528.847928-1-lee@kernel.org/
--
Lee Jones
^ permalink raw reply
* Re: [PATCH 1/2] Input: userio - Update maintainer name
From: thatslyude @ 2026-05-27 16:32 UTC (permalink / raw)
To: Vicki Pfau, Dmitry Torokhov, linux-input
In-Reply-To: <20260522015040.3953472-1-vi@endrift.com>
Thank you ♥
Reviewed-by: Lyude Paul <lyude@redhat.com>
On Thu, 2026-05-21 at 18:50 -0700, Vicki Pfau wrote:
> She's been committing under the name Lyude Paul for a while
>
> Signed-off-by: Vicki Pfau <vi@endrift.com>
> ---
> Documentation/input/userio.rst | 2 +-
> MAINTAINERS | 2 +-
> drivers/input/serio/userio.c | 4 ++--
> include/uapi/linux/userio.h | 2 +-
> 4 files changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/input/userio.rst
> b/Documentation/input/userio.rst
> index f780c77931fe..415962152815 100644
> --- a/Documentation/input/userio.rst
> +++ b/Documentation/input/userio.rst
> @@ -5,7 +5,7 @@ The userio Protocol
> ===================
>
>
> -:Copyright: |copy| 2015 Stephen Chandler Paul <thatslyude@gmail.com>
> +:Copyright: |copy| 2015 Lyude Paul <thatslyude@gmail.com>
>
> Sponsored by Red Hat
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 7cf5b55c5973..ceacb1c1d679 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -28105,7 +28105,7 @@ F: sound/drivers/pcmtest.c
> F: tools/testing/selftests/alsa/test-pcmtest-driver.c
>
> VIRTUAL SERIO DEVICE DRIVER
> -M: Stephen Chandler Paul <thatslyude@gmail.com>
> +M: Lyude Paul <thatslyude@gmail.com>
> S: Maintained
> F: drivers/input/serio/userio.c
> F: include/uapi/linux/userio.h
> diff --git a/drivers/input/serio/userio.c
> b/drivers/input/serio/userio.c
> index 91cb7a177b2d..abca8cb6aca5 100644
> --- a/drivers/input/serio/userio.c
> +++ b/drivers/input/serio/userio.c
> @@ -1,7 +1,7 @@
> /*
> * userio kernel serio device emulation module
> * Copyright (C) 2015 Red Hat
> - * Copyright (C) 2015 Stephen Chandler Paul <thatslyude@gmail.com>
> + * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com>
> *
> * This program is free software; you can redistribute it and/or
> modify it
> * under the terms of the GNU Lesser General Public License as
> published by
> @@ -278,6 +278,6 @@ module_driver(userio_misc, misc_register,
> misc_deregister);
> MODULE_ALIAS_MISCDEV(USERIO_MINOR);
> MODULE_ALIAS("devname:" USERIO_NAME);
>
> -MODULE_AUTHOR("Stephen Chandler Paul <thatslyude@gmail.com>");
> +MODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>");
> MODULE_DESCRIPTION("Virtual Serio Device Support");
> MODULE_LICENSE("GPL");
> diff --git a/include/uapi/linux/userio.h
> b/include/uapi/linux/userio.h
> index 74c9951d2cd0..98fe7e9089c4 100644
> --- a/include/uapi/linux/userio.h
> +++ b/include/uapi/linux/userio.h
> @@ -2,7 +2,7 @@
> /*
> * userio: virtual serio device support
> * Copyright (C) 2015 Red Hat
> - * Copyright (C) 2015 Lyude (Stephen Chandler Paul)
> <cpaul@redhat.com>
> + * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com>
> *
> * This program is free software; you can redistribute it and/or
> modify it
> * under the terms of the GNU Lesser General Public License as
> published by the
^ permalink raw reply
* Re: [PATCH v2] HID: nintendo: add support for HORI Wireless Switch Pad
From: sashiko-bot @ 2026-05-27 16:51 UTC (permalink / raw)
To: Hector Zelaya; +Cc: linux-input, dmitry.torokhov
In-Reply-To: <20260527-hori-support-v2-1-195c2f832112@hectorzelaya.dev>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Incompatible SPI flash data is parsed as IMU calibration for licensed controllers, leading to invalid sensor scaling and broken IMU output.
- [Medium] The controller implicitly and falsely advertises force-feedback (rumble) capabilities to user-space, despite physically lacking a vibration motor.
- [Low] Bypassing stick calibration triggers a misleading error warning on every connection.
Pre-existing issues:
- [High] The IMU report parser reads out of bounds if a truncated `JC_INPUT_IMU_DATA` report is received.
--
commit 376e2cc446988e8ea86a066e021e9f1adc2e0081
Author: Hector Zelaya <hector@hectorzelaya.dev>
Subject: HID: nintendo: add support for HORI Wireless Switch Pad
This commit adds support for the HORI Wireless Switch Pad (vendor 0x0f0d,
product 0x00f6). It configures quirks for the third-party controller, such as
handling incompatible SPI flash calibration data, swapped X/Y buttons, and the
lack of hardware vibration.
> diff --git a/drivers/hid/hid-nintendo.c b/drivers/hid/hid-nintendo.c
> index 29008c2cc5304..2d37ddeffdb6e 100644
> --- a/drivers/hid/hid-nintendo.c
> +++ b/drivers/hid/hid-nintendo.c
[ ... ]
> @@ -695,7 +715,8 @@ static inline bool joycon_type_is_right_joycon(struct joycon_ctlr *ctlr)
>
> static inline bool joycon_type_is_procon(struct joycon_ctlr *ctlr)
> {
> - return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO;
> + return ctlr->ctlr_type == JOYCON_CTLR_TYPE_PRO ||
> + ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO;
> }
[Severity: Medium]
Since the commit message states there is "no vibration motor in hardware",
does returning true here incorrectly advertise force-feedback capabilities to
userspace?
Because joycon_type_is_procon() returns true, joycon_has_rumble() will also
return true. This causes joycon_input_create() to call joycon_config_rumble(),
which registers an FF_RUMBLE capability with the input subsystem, creating a
phantom rumble device.
[ ... ]
> @@ -1710,7 +1731,10 @@ static void joycon_parse_report(struct joycon_ctlr *ctlr,
> joycon_report_left_stick(ctlr, rep);
> joycon_report_right_stick(ctlr, rep);
> joycon_report_dpad(ctlr, rep);
> - joycon_report_buttons(ctlr, rep, procon_button_mappings);
> + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO)
> + joycon_report_buttons(ctlr, rep, lic_procon_button_mappings);
> + else
> + joycon_report_buttons(ctlr, rep, procon_button_mappings);
> } else if (joycon_type_is_any_nescon(ctlr)) {
[Severity: High]
This is a pre-existing issue, but can parsing the IMU data from this
controller lead to an out-of-bounds read if a truncated report is received?
In joycon_ctlr_read_handler(), the length check only verifies size >= 12
before calling joycon_parse_report(). If the report ID is JC_INPUT_IMU_DATA,
joycon_parse_report() delegates to joycon_parse_imu_report().
The IMU parsing logic assumes 36 bytes of IMU data starting at offset 13.
Processing a truncated IMU report over Bluetooth could lead to an
out-of-bounds read from the socket buffer.
[ ... ]
> @@ -2503,13 +2530,30 @@ static int joycon_init(struct hid_device *hdev)
>
> if (joycon_has_joysticks(ctlr)) {
> /* get controller calibration data, and parse it */
> - ret = joycon_request_calibration(ctlr);
> - if (ret) {
> + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
> /*
> - * We can function with default calibration, but it may be
> - * inaccurate. Provide a warning, and continue on.
> + * Licensed controllers may have incompatible SPI flash
> + * layouts. Use default calibration values.
> */
> - hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> + hid_info(hdev, "using default cal for licensed controller\n");
> + joycon_use_default_calibration(hdev,
> + &ctlr->left_stick_cal_x,
> + &ctlr->left_stick_cal_y,
> + "left", 0);
[Severity: Low]
Does this trigger a misleading warning on every connection?
joycon_use_default_calibration() unconditionally prints "Failed to read ...
stick cal, using defaults; e=%d" using hid_warn(). Calling it directly with
ret=0 will print a confusing failure message even though no read was actually
attempted.
> + joycon_use_default_calibration(hdev,
> + &ctlr->right_stick_cal_x,
> + &ctlr->right_stick_cal_y,
> + "right", 0);
> + } else {
> + ret = joycon_request_calibration(ctlr);
> + if (ret) {
> + /*
> + * We can function with default calibration, but
> + * it may be inaccurate. Provide a warning, and
> + * continue on.
> + */
> + hid_warn(hdev, "Analog stick positions may be inaccurate\n");
> + }
> }
> }
>
> @@ -2527,8 +2571,13 @@ static int joycon_init(struct hid_device *hdev)
[Severity: High]
Should we also bypass joycon_request_imu_calibration() for licensed
controllers?
The patch acknowledges the SPI flash layout is incompatible and bypasses
analog stick calibration. However, joycon_has_imu() will return true for
LIC_PRO, meaning joycon_request_imu_calibration() is still called right
before this hunk.
If the SPI read succeeds but returns unexpected data or zeroes from the
incompatible flash, the IMU calibration scales and offsets could be populated
with garbage. If the read scales are parsed as 0, the computed divisors are
adjusted to 1, and the resulting IMU values in joycon_parse_imu_report() will
always evaluate to 0, breaking IMU functionality.
> /* Enable the IMU */
> ret = joycon_enable_imu(ctlr);
> if (ret) {
> - hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> - goto out_unlock;
> + if (ctlr->ctlr_type == JOYCON_CTLR_TYPE_LIC_PRO) {
> + hid_dbg(hdev, "IMU enable failed for licensed controller, continuing\n");
> + ret = 0;
> + } else {
> + hid_err(hdev, "Failed to enable the IMU; ret=%d\n", ret);
> + goto out_unlock;
> + }
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260527-hori-support-v2-1-195c2f832112@hectorzelaya.dev?part=1
^ permalink raw reply
* Re: [PATCH 1/1] HID: wacom: Fix multiple Use-After-Free issues in shared state
From: Dmitry Torokhov @ 2026-05-27 19:18 UTC (permalink / raw)
To: Lee Jones
Cc: Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires,
linux-input, linux-kernel
In-Reply-To: <20260527140731.642783-1-lee@kernel.org>
Hi Lee,
On Wed, May 27, 2026 at 03:07:30PM +0100, Lee Jones wrote:
> The Wacom driver coordinates state between sibling interfaces of the same
> physical device (like Pen, Touch, Pad) using a shared structure
> 'wacom_shared' inside 'wacom_hdev_data'. The driver kept a volatile
> representative pointer 'data->dev' pointing to a sibling 'hid_device'
> for physical path comparisons during sibling matching.
>
> This pointer management is fragile. When the representative device is
> disconnected, wacom_remove_shared_data() failed to clear/update
> 'data->dev' or wacom_wac->shared->touch_input, leading to two Use-After-Free
> vulnerabilities:
>
> 1. dangling 'touch_input' dereferenced during touch switch sync.
> 2. dangling 'data->dev' dereferenced during subsequent sibling probes.
>
> Instead of adding complex pointer handover logic to keep 'data->dev'
> updated (which has logic gaps with Pad siblings and introduces race
> conditions), completely eliminate 'data->dev' pointer.
>
> Redesign 'wacom_hdev_data' to store stable static copies of the required
> representative attributes when it is first allocated:
>
> - Copy 'phys' path string (stored in data->phys) for stable path comparison.
> - Copy 'vendor' and 'product' IDs.
This I think makes sense.
> - Copy and accumulate 'device_type' capabilities as siblings are probed.
This (accumulation) I unconvinced is safe. In any case I think it should
be a separate patch as it may change the behavior.
>
> Also explicitly clear 'touch_input = NULL' in wacom_remove_shared_data()
> under wacom_udev_list_lock to safely avoid the touch_input UAF.
The fix is incomplete and should be split out. It is not enough to take
the lock, you need to make sure you are not racing with URB/IRQ
handling. Maybe RCU can help here.
>
> This resolves all vulnerabilities permanently at the design level without
> complex pointer lifecycles or race-prone swaps on device removal.
>
> Fixes: 471d17148c8b ("Input: wacom - move the USB (now hid) Wacom driver in drivers/hid")
This is not the commit that introduced this behavior IIRC.
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH] HID: wacom: stop hardware after post-start probe failures
From: Dmitry Torokhov @ 2026-05-27 19:34 UTC (permalink / raw)
To: Myeonghun Pak
Cc: Ping Cheng, Jason Gerecke, Jiri Kosina, Benjamin Tissoires,
linux-input, linux-kernel, stable, Ijae Kim
In-Reply-To: <20260524175552.1973-1-mhun512@gmail.com>
Hi Myeonghun,
On Mon, May 25, 2026 at 02:53:33AM +0900, Myeonghun Pak wrote:
> wacom_parse_and_register() starts HID hardware before registering inputs
> and initializing pad LEDs/remotes. Those later steps can fail, but their
> error paths currently release Wacom resources without stopping the HID
> hardware.
>
> Route post-hid_hw_start() failures through hid_hw_stop() before
> releasing driver resources.
>
> This issue was identified during our ongoing static-analysis research while
> reviewing kernel code.
>
> Fixes: c1d6708bf0d3 ("HID: wacom: Do not register input devices until after hid_hw_start")
> Cc: stable@vger.kernel.org
> Co-developed-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Ijae Kim <ae878000@gmail.com>
> Signed-off-by: Myeonghun Pak <mhun512@gmail.com>
> ---
> drivers/hid/wacom_sys.c | 7 ++++---
> 1 file changed, 4 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
> index 0d1c6d90fe..c824d9c224 100644
> --- a/drivers/hid/wacom_sys.c
> +++ b/drivers/hid/wacom_sys.c
> @@ -2456,16 +2456,16 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
>
> error = wacom_register_inputs(wacom);
> if (error)
> - goto fail;
> + goto fail_hw_stop;
>
> if (wacom->wacom_wac.features.device_type & WACOM_DEVICETYPE_PAD) {
> error = wacom_initialize_leds(wacom);
> if (error)
> - goto fail;
> + goto fail_hw_stop;
>
> error = wacom_initialize_remotes(wacom);
> if (error)
> - goto fail;
> + goto fail_hw_stop;
> }
>
> if (!wireless) {
> @@ -2496,6 +2496,7 @@ static int wacom_parse_and_register(struct wacom *wacom, bool wireless)
> return 0;
>
> fail_quirks:
> +fail_hw_stop:
> hid_hw_stop(hdev);
> fail:
> wacom_release_resources(wacom);
I'd get rid of 'fail_quirks' and use 'fail_hw_stop' everywhere,
otherwise:
Reviewed-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/1] HID: wacom: validate report size before kfifo insert
From: Dmitry Torokhov @ 2026-05-27 19:47 UTC (permalink / raw)
To: Jinmo Yang
Cc: Jason Gerecke, Ping Cheng, Jiri Kosina, Benjamin Tissoires,
linux-input, linux-kernel, stable
In-Reply-To: <20260524135203.1996265-2-jinmo44.yang@gmail.com>
On Sun, May 24, 2026 at 10:52:03PM +0900, Jinmo Yang wrote:
> wacom_wac_queue_insert() passes the report size directly to kfifo_in()
> without checking whether the report fits in the kfifo buffer.
>
> Since commit 5e013ad20689 ("HID: wacom: Remove static WACOM_PKGLEN_MAX
> limit"), the kfifo is sized dynamically as min(PAGE_SIZE, 10 * pktlen),
> which can be as small as 256 bytes. However, reports received via
> UHID_INPUT2 can be up to UHID_DATA_MAX (4096) bytes. When such an
> oversized report reaches wacom_wac_queue_insert(), the existing
> kfifo_avail() loop cannot make room for a record larger than the total
> buffer, causing kfifo_copy_in() to memcpy up to 3840 bytes past the
> slab allocation.
Does it? Or maybe spins there indefinitely? Also, doesn't
kfifo_copy_in() return 0 if a record it too big and not copy anything?
>
> Add a size check at the top of wacom_wac_queue_insert() to reject
> reports that exceed the kfifo capacity.
>
> Fixes: 5e013ad20689 ("HID: wacom: Remove static WACOM_PKGLEN_MAX limit")
> Cc: stable@vger.kernel.org
> Signed-off-by: Jinmo Yang <jinmo44.yang@gmail.com>
> ---
> drivers/hid/wacom_sys.c | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/drivers/hid/wacom_sys.c b/drivers/hid/wacom_sys.c
> index a32320b..cc82c6f 100644
> --- a/drivers/hid/wacom_sys.c
> +++ b/drivers/hid/wacom_sys.c
> @@ -54,6 +54,12 @@ static void wacom_wac_queue_insert(struct hid_device *hdev,
> {
> bool warned = false;
>
> + if (size > kfifo_size(fifo)) {
> + hid_warn(hdev, "%s: report too large (%d > %u) for kfifo\n",
> + __func__, size, kfifo_size(fifo));
> + return;
> + }
> +
> while (kfifo_avail(fifo) < size) {
> if (!warned)
> hid_warn(hdev, "%s: kfifo has filled, starting to drop events\n", __func__);
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH 1/1] HID: wacom: validate report size before kfifo insert
From: Dmitry Torokhov @ 2026-05-27 21:41 UTC (permalink / raw)
To: Jinmo Yang
Cc: Jason Gerecke, Ping Cheng, Jiri Kosina, Benjamin Tissoires,
linux-input, linux-kernel, stable
In-Reply-To: <ahdJzWhVFm7mWu-v@google.com>
On Wed, May 27, 2026 at 12:47:03PM -0700, Dmitry Torokhov wrote:
> On Sun, May 24, 2026 at 10:52:03PM +0900, Jinmo Yang wrote:
> > wacom_wac_queue_insert() passes the report size directly to kfifo_in()
> > without checking whether the report fits in the kfifo buffer.
> >
> > Since commit 5e013ad20689 ("HID: wacom: Remove static WACOM_PKGLEN_MAX
> > limit"), the kfifo is sized dynamically as min(PAGE_SIZE, 10 * pktlen),
> > which can be as small as 256 bytes. However, reports received via
> > UHID_INPUT2 can be up to UHID_DATA_MAX (4096) bytes. When such an
> > oversized report reaches wacom_wac_queue_insert(), the existing
> > kfifo_avail() loop cannot make room for a record larger than the total
> > buffer, causing kfifo_copy_in() to memcpy up to 3840 bytes past the
> > slab allocation.
>
> Does it? Or maybe spins there indefinitely? Also, doesn't
> kfifo_copy_in() return 0 if a record it too big and not copy anything?
OK, so the root cause is that kfifo_skip() must not be called on an
empty fifo. I think you want the code to look something like this:
static void wacom_wac_queue_insert(struct hid_device *hdev,
struct kfifo_rec_ptr_2 *fifo,
u8 *raw_data, int size)
{
bool warned = false;
while (kfifo_avail(fifo) < size && !kfifo_is_empty(fifo)) {
if (!warned)
hid_warn(hdev, "%s: kfifo has filled, starting to drop events\n", __func__);
warned = true;
kfifo_skip(fifo);
}
if (!kfifo_in(fifo, raw_data, size))
hid_warn_ratelimited(hdev, "%s: report is too large (%d)\n",
__func__, size);
}
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2 2/2] arm64: dts: qcom: sdm845-oneplus: Update compatible to include model
From: Dmitry Torokhov @ 2026-05-27 21:56 UTC (permalink / raw)
To: david
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jason A. Donenfeld, Matthias Schiffer, Vincent Huang,
Bjorn Andersson, Konrad Dybcio, linux-input, devicetree,
linux-kernel, linux-arm-msm, phone-devel, Krzysztof Kozlowski,
Konrad Dybcio
In-Reply-To: <20260523-synaptics-rmi4-dt-v2-2-0645122babdc@ixit.cz>
Hi David,
On Sat, May 23, 2026 at 11:45:35AM +0200, David Heidelberg via B4 Relay wrote:
> From: David Heidelberg <david@ixit.cz>
>
> We know the driver is reporting s3706b, introduce the compatible so we
> can more easily introduce quirks for weird touchscreen replacements in
> followup series.
>
> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
> index 6b7378cf4d493..148164d456a5a 100644
> --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
> +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
> @@ -475,17 +475,17 @@ bq27441_fg: bq27441-battery@55 {
> };
> };
>
> &i2c12 {
> status = "okay";
> clock-frequency = <400000>;
>
> synaptics-rmi4-i2c@20 {
> - compatible = "syna,rmi4-i2c";
> + compatible = "syna,rmi4-s3706b", "syna,rmi4-i2c";
So I believe we established that this device (s3706b) does not in fact
implement rmi4 protocol properly. Why do we have "syna,rmi4-i2c" as a
fallback? Shouldn't it be just "syna,rmi4-s3706b"?
Thanks.
--
Dmitry
^ permalink raw reply
* Re: [PATCH v2 2/2] arm64: dts: qcom: sdm845-oneplus: Update compatible to include model
From: David Heidelberg @ 2026-05-27 22:13 UTC (permalink / raw)
To: Dmitry Torokhov
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jason A. Donenfeld, Matthias Schiffer, Vincent Huang,
Bjorn Andersson, Konrad Dybcio, linux-input, devicetree,
linux-kernel, linux-arm-msm, phone-devel, Krzysztof Kozlowski,
Konrad Dybcio
In-Reply-To: <ahdoBl3qCTyvlYJf@google.com>
On 27/05/2026 23:56, Dmitry Torokhov wrote:
> Hi David,
>
> On Sat, May 23, 2026 at 11:45:35AM +0200, David Heidelberg via B4 Relay wrote:
>> From: David Heidelberg <david@ixit.cz>
>>
>> We know the driver is reporting s3706b, introduce the compatible so we
>> can more easily introduce quirks for weird touchscreen replacements in
>> followup series.
>>
>> Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
>> Signed-off-by: David Heidelberg <david@ixit.cz>
>> ---
>> arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
>> index 6b7378cf4d493..148164d456a5a 100644
>> --- a/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
>> +++ b/arch/arm64/boot/dts/qcom/sdm845-oneplus-common.dtsi
>> @@ -475,17 +475,17 @@ bq27441_fg: bq27441-battery@55 {
>> };
>> };
>>
>> &i2c12 {
>> status = "okay";
>> clock-frequency = <400000>;
>>
>> synaptics-rmi4-i2c@20 {
>> - compatible = "syna,rmi4-i2c";
>> + compatible = "syna,rmi4-s3706b", "syna,rmi4-i2c";
>
> So I believe we established that this device (s3706b) does not in fact
> implement rmi4 protocol properly. Why do we have "syna,rmi4-i2c" as a
> fallback? Shouldn't it be just "syna,rmi4-s3706b"?
The vendor supplies s3706b which does implement the RMI4 properly.
The 3rd party replacement impersonating original parts may not implement it
properly, but I don't address this issue in this initial submission.
With this compatible we know which original part is used by the vendor and
installed in the phones, so later we can deduct specific sequences for the
replacement aftermarket parts to keep phone touchscreen working same as they do
on Android without affecting other devices.
David
>
> Thanks.
>
--
David Heidelberg
^ permalink raw reply
* [PATCH v10 0/4] Add MSI Claw HID Configuration Driver
From: Derek J. Clark @ 2026-05-27 22:21 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
This series adds an HID Configuration driver for the MSI Claw line of
Handheld Gaming PC's. The MSI Claw HID interface provides multiple
features, such as the ability to switch between xinput, dinput, and a
desktop mode, RGB control, rumble intensity, and mapping of the rear "M"
keys. There are additional gamepad modes that are not included in this
driver as they appear to be used in assembly line testing or are
incomplete in the firmware. During my testing I found them to be unstable.
The initial version of this driver was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes and additional features. Finally, I
refactored the entire driver, fixed multiple bugs, and refined the overall
format to conform to kernel driver best practices and style guide.
Claude was used initially by Zhouwang Huang to quickly parse HID captures
during the reverse-engineering of some of the features. Since Claude had
already been used, as a test of its capabilities I had it implement the
rumble intensity attribute after I had already rewritten most of the
driver, which I then manually edited to fix some mistakes. I also used
Claude to review the driver and these patches for any mistakes and bugs.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v10:
- Remove additional gamepad_registered variable left over after rename
to gp_registered.
v9: https://lore.kernel.org/linux-input/20260525020543.519082-1-derekjohn.clark@gmail.com/
- Don't use devm_* functions in cfg_setup_fn, do manual adding
and cleanup to prevent possible use after free.
- Use scoped_guard instead of guard in claw_remove.
- Rename gamepad_registered to gp_registered for brevity.
- Check for drvdata in rgb_queue_fn to avoid use after free during
teardown.
- Ensure rgb_queue work is canceled during suspend.
- Limit guard usage in cfg_setup_fn to avoid holding a lock during
registration and add group events.
v8: https://lore.kernel.org/linux-input/20260522015518.1111290-1-derekjohn.clark@gmail.com/
- Use spinlock when accessing gamepad_registered.
- Clear state machine on all errors in claw_hw_output_report.
- Wrap all branches under single cmd_lock guard in claw_raw_event.
- Reject generic ACK in claw_raw_event if waiting_cmd is for another
branch.
- Wrap all branches under single cmd_lock guard in claw_raw_event.
- Reject generic ACK in claw_raw_event if waiting_cmd is for another
branch.
- Don't close hid devices that couldn't have been opened.
- Ensure led_classdev is unregistered if adding attribute group fails.
- Reorder remove actions to ensure no use-after free or rearming cleared
flags.
v7: https://lore.kernel.org/linux-input/20260520013158.3633277-1-derekjohn.clark@gmail.com/
- Use smp_[store_release|load_acquire] pattern for checking
gamepad_registered and rgb_registered to avoid possible races during
teardown.
- Reorder reinit_completion in claw_hw_output_report to avoid race
with possible incoming ACKs.
- Reorder cancel_delayed_work_sync to ensure setup can't be re-armed
after cancel.
- Reset command state machine if hw_output_report has an error.
- Add comments to (hopefully) silence sashinko-bot warnings about the
use of endpoint matching and the impossible scenario of switching to
the alternate endpoint from userspace while the driver is bound.
- Don't use spinlock_irqsave when already in irq context.
- Add profile_lock for read/write profile_pending.
- Use struct for mkey reports and rumble reports, following the
pattern established by rgb reports previously.
- Add gating to cfg_setup_fn, allowing either gamepad settings or rgb
settings to populate if the other fails for any reason.
- Match on write address for rumble and mkey reports to prevent late
ACK from causing synchronization errors.
v6: https://lore.kernel.org/linux-input/20260518222935.1802071-1-derekjohn.clark@gmail.com/
- Add send/ack pattern to ensure synchronous acks.
- Use spinlock_irqsave instead of mutex for read/write MODE event
data.
- add select NEW_LEDS to kconfig.
- Make all timeouts 25ms to ensure at least 2 jiffies in a 100Hz
config.
- Gate all attribute show/store functions with gamepad_registered or
rgb_registered, enabling use of devm_device_add_group and ending
the need to hold a mutex during remove.
- Don't set gamepad_mode on resume, MCU preserves state.
- Ensure all count variables are checked for > 0 characters before
setting buf - 1 to \n.
- Re-arm cfg_setup in resume if it was canceled in an early suspend.
- Remove duplicated argv_free macro.
- Add spinlock_irqsave vice mutex for read/write access on attribute
variables.
v5: https://lore.kernel.org/linux-input/20260517013925.3120314-1-derekjohn.clark@gmail.com/
- Swap disabled & combination mkeys_function enum values.
- Fix bug introduced in v5 where claw_buttons_store would return
-EINVAL on all valid key entries.
- Ensure mode_mutex is properly init.
- Ensure claw_remove is calling hid_hw_close and not hid_hw_stop for
all paths.
- Ensure adding "DISABLED" key to valid entries is done in the correct
patch.
- Re-enable sending an empty string to clear button mappings in
addition to setting DISABLED.
- Move adding the RGB device into cfg_setup to prevent led core
attributes from being written to prior to setup completing.
- Ensure frame_lock is properly init.
- Change variable names in RGB functions from frame and zone to f and
z respectively to fit all scoped_guard actions in 100 columns.
v4: https://lore.kernel.org/linux-input/20260516042841.500299-1-derekjohn.clark@gmail.com/
- Add msi_suspend/claw_suspend.
- Reorder claw_remove to cancel all work before removing sysfs.
- Add mutex lock for removing sysfs attributes.
- Add mutex lock for MODE command data read/write.
- Change dev_warn to dev_dbg in claw_profile_event.
- use __free with DEFINE_FREE macro for argv instead of manually
running argv_free, cleaining up scoped_guard goto.
- Fix frame_calc validity check to use >=.
- Use spinlock instead of mutex in raw_event and related attribute
_store function.
- Ensure delayed work is canceled in suspend & canceled before sysfs
attribute removal.
v3: https://lore.kernel.org/linux-input/20260515033622.2095277-1-derekjohn.clark@gmail.com/
- Add mutex for read/write if rgb frame data.
- Ensure claw_hw_output_report is properly guarded.
- Remove setting rgb_frame_count when reading rgb profiles as it always
returns garbage data.
- Ensure rgb_speed is getting drvdata from a valid lookup (not hdev).
- Use scoped_guard where necessary.
- Reoder claw_probe to ensure all mutex, completion, and variable
assignments are in place prior to setting drvdata.
- Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2: https://lore.kernel.org/linux-input/20260513231445.3213501-1-derekjohn.clark@gmail.com/
- Use mutexes to guard SYNC_TO_ROM calls and pending_profile calls.
- Rename driver to hid-msi and add generic entrypoints for
probe/resume/remove that call claw specific functions in order to
future proof the driver for other MSI HID interfaces.
- Fix various bugs and formatting issues.
v1: https://lore.kernel.org/linux-input/20260510043510.442807-1-derekjohn.clark@gmail.com/
Derek J. Clark (4):
HID: hid-msi: Add MSI Claw configuration driver
HID: hid-msi: Add M-key mapping attributes
HID: hid-msi: Add RGB control interface
HID: hid-msi: Add Rumble Intensity Attributes
MAINTAINERS | 6 +
drivers/hid/Kconfig | 13 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 5 +
drivers/hid/hid-msi.c | 1813 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 1838 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
--
2.53.0
^ permalink raw reply
* [PATCH v10 1/4] HID: hid-msi: Add MSI Claw configuration driver
From: Derek J. Clark @ 2026-05-27 22:21 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260527222122.10620-1-derekjohn.clark@gmail.com>
Adds configuration HID driver for the MSI Claw series of handheld PC's.
In this initial patch add the initial driver outline and attributes for
changing the gamepad mode, M-key behavior, and add a WO reset function.
Sending the SWITCH_MODE and RESET commands causes a USB disconnect in
the device. The completion will therefore never get hit and would trigger
an -EIO. To avoid showing the user an error for every write to these
attrs a bypass for the completion handling is introduced when timeout ==
0.
The initial version of this patch was written by Denis Benato, which
contained the initial reverse-engineering and implementation for the
gamepad mode switching. This work was later expanded by Zhouwang Huang
to include more gamepad modes. Finally, I refactored the drivers data
in/out flow and overall format to conform to kernel driver best
practices and style guides. Claude was used as an initial reviewer of
this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Denis Benato <denis.benato@linux.dev>
Signed-off-by: Denis Benato <denis.benato@linux.dev>
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v9:
- Don't use devm_device_add_group in cfg_setup_fn, do manual adding
and cleanup to prevent possible use after free.
- Use scoped_guard instead of guard in claw_remove.
- Rename gamepad_registered to gp_registered for brevity.
v8:
- Use spinlock when accessing gamepad_registered.
- Clear state machine on all errors in claw_hw_output_report.
- Wrap all branches under single cmd_lock guard in claw_raw_event.
- Reject generic ACK in claw_raw_event if waiting_cmd is for another
branch.
- Don't close hid devices that couldn't have been opened.
v7:
- Use smp_[store_release|load_acquire] pattern for checking
gamepad_registered to avoid possible races during teardown.
- Reorder reinit_completion in claw_hw_output_report to avoid race
with possible incoming ACKs.
- Reorder cancel_delayed_work_sync to ensure setup can't be re-armed
after cancel.
- Reset command state machine if hw_output_report has an error.
- Add comments to (hopefully) silence sashinko-bot warnings about the
use of endpoint matching and the impossible scenario of switching to
the alternate endpoint from userspace while the driver is bound.
- Don't use spinlock_irqsave when already in irq context.
v6:
- Add send/ack pattern to ensure synchronous acks.
- Use spinlock_irqsave instead of mutex for read/write MODE event
data.
- add select NEW_LEDS to kconfig.
- Make all timeouts 25ms to ensure at least 2 jiffies in a 100Hz
config.
- Gate all attribute show/store functions with gamepad_registered,
enabling use of devm_device_add_group.
- Re-arm cfg_setup in resume if it was canceled in an early suspend.
- Don't set gamepad_mode on resume, MCU preserves state.
- Ensure all count variables are checked for > 0 characters before
setting buf - 1 to \n.
v5:
- Swap disabled & combination mkeys_function enum values.
- Ensure mode_mutex is properly init.
- Ensure claw_remove is calling hid_hw_close and not hid_hw_stop for
all paths.
v4:
- Add msi_suspend/claw_suspend.
- Reorder claw_remove to cancel all work before removing sysfs.
- Add mutex lock for removing sysfs attributes.
- Add mutex lock for MODE command data read/write.
v3:
- Ensure claw_hw_output_report is properly guarded.
- Reoder claw_probe to ensure all mutex, completion, and variable
assignments are in place prior to setting drvdata.
- Ensure gamepad_mode is set to a valid enum value in claw_probe.
v2:
- Rename driver to hid-msi from hid-msi-claw.
- Rename reusable/generic functions to msi_* from claw_*, retaining
claw specific functions.
- Add generic entrypoints for probe, remove, and raw event that route
to claw specific functions.
---
MAINTAINERS | 6 +
drivers/hid/Kconfig | 13 +
drivers/hid/Makefile | 1 +
drivers/hid/hid-ids.h | 5 +
drivers/hid/hid-msi.c | 710 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 735 insertions(+)
create mode 100644 drivers/hid/hid-msi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 6f6517bf4f970..8e2de98b768f7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17965,6 +17965,12 @@ S: Odd Fixes
F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt
F: drivers/net/ieee802154/mrf24j40.c
+MSI HID DRIVER
+M: Derek J. Clark <derekjohn.clark@gmail.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/hid/hid-msi.c
+
MSI EC DRIVER
M: Nikita Kravets <teackot@gmail.com>
L: platform-driver-x86@vger.kernel.org
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 10c12d8e65579..7766676051a52 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -492,6 +492,19 @@ config HID_GT683R
Currently the following devices are know to be supported:
- MSI GT683R
+config HID_MSI
+ tristate "MSI Claw Gamepad Support"
+ depends on USB_HID
+ select NEW_LEDS
+ select LEDS_CLASS
+ select LEDS_CLASS_MULTICOLOR
+ help
+ Support for the MSI Claw RGB and controller configuration
+
+ Say Y here to include configuration interface support for the MSI Claw Line
+ of Handheld Console Controllers. Say M here to compile this driver as a
+ module. The module will be called hid-msi.
+
config HID_KEYTOUCH
tristate "Keytouch HID devices"
help
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 07dfdb6a49c59..80925a17b059c 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o
obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o
obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o
obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o
+obj-$(CONFIG_HID_MSI) += hid-msi.o
obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o
obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o
obj-$(CONFIG_HID_NTI) += hid-nti.o
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 933b7943bdb50..94a9b89dc240a 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1047,7 +1047,12 @@
#define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010
#define USB_VENDOR_ID_MSI 0x1770
+#define USB_VENDOR_ID_MSI_2 0x0db0
#define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00
+#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901
+#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902
+#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903
+#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904
#define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400
#define USB_DEVICE_ID_N_S_HARMONY 0xc359
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
new file mode 100644
index 0000000000000..3a176f44f2c43
--- /dev/null
+++ b/drivers/hid/hid-msi.c
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * HID driver for MSI Claw Handheld PC gamepads.
+ *
+ * Provides configuration support for the MSI Claw series of handheld PC
+ * gamepads. Multiple iterations of the device firmware has led to some
+ * quirks for how certain attributes are handled. The original firmware
+ * did not support remapping of the M1 (right) and M2 (left) rear paddles.
+ * Additionally, the MCU RAM address for writing configuration data has
+ * changed twice. Checks are done during probe to enumerate these variances.
+ *
+ * Copyright (c) 2026 Zhouwang Huang <honjow311@gmail.com>
+ * Copyright (c) 2026 Denis Benato <denis.benato@linux.dev>
+ * Copyright (c) 2026 Valve Corporation
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/hid.h>
+#include <linux/kobject.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/spinlock.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#include "hid-ids.h"
+
+#define CLAW_OUTPUT_REPORT_ID 0x0f
+#define CLAW_INPUT_REPORT_ID 0x10
+
+#define CLAW_PACKET_SIZE 64
+
+#define CLAW_DINPUT_CFG_INTF_IN 0x82
+#define CLAW_XINPUT_CFG_INTF_IN 0x83
+
+enum claw_command_index {
+ CLAW_COMMAND_TYPE_NONE = 0x00,
+ CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
+ CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05,
+ CLAW_COMMAND_TYPE_ACK = 0x06,
+ CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA = 0x21,
+ CLAW_COMMAND_TYPE_SYNC_TO_ROM = 0x22,
+ CLAW_COMMAND_TYPE_SWITCH_MODE = 0x24,
+ CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 0x26,
+ CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK = 0x27,
+ CLAW_COMMAND_TYPE_RESET_DEVICE = 0x28,
+};
+
+enum claw_gamepad_mode_index {
+ CLAW_GAMEPAD_MODE_XINPUT = 0x01,
+ CLAW_GAMEPAD_MODE_DINPUT = 0x02,
+ CLAW_GAMEPAD_MODE_DESKTOP = 0x04,
+};
+
+static const char * const claw_gamepad_mode_text[] = {
+ [CLAW_GAMEPAD_MODE_XINPUT] = "xinput",
+ [CLAW_GAMEPAD_MODE_DINPUT] = "dinput",
+ [CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",
+};
+
+enum claw_mkeys_function_index {
+ CLAW_MKEY_FUNCTION_MACRO,
+ CLAW_MKEY_FUNCTION_DISABLED,
+ CLAW_MKEY_FUNCTION_COMBO,
+};
+
+static const char * const claw_mkeys_function_text[] = {
+ [CLAW_MKEY_FUNCTION_MACRO] = "macro",
+ [CLAW_MKEY_FUNCTION_DISABLED] = "disabled",
+ [CLAW_MKEY_FUNCTION_COMBO] = "combination",
+};
+
+struct claw_command_report {
+ u8 report_id;
+ u8 padding[2];
+ u8 header_tail;
+ u8 cmd;
+ u8 data[59];
+} __packed;
+
+struct claw_drvdata {
+ /* MCU General Variables */
+ struct completion send_cmd_complete;
+ struct delayed_work cfg_resume;
+ struct delayed_work cfg_setup;
+ spinlock_t registration_lock; /* Lock for registration read/write */
+ struct hid_device *hdev;
+ struct mutex cfg_mutex; /* mutex for synchronous data */
+ bool waiting_for_ack;
+ spinlock_t cmd_lock; /* Lock for cmd data read/write */
+ u8 waiting_cmd;
+ int cmd_status;
+ u8 ep;
+
+ /* Gamepad Variables */
+ enum claw_mkeys_function_index mkeys_function;
+ enum claw_gamepad_mode_index gamepad_mode;
+ spinlock_t mode_lock; /* Lock for mode data read/write */
+ bool gp_registered;
+};
+
+static int get_endpoint_address(struct hid_device *hdev)
+{
+ struct usb_host_endpoint *ep;
+ struct usb_interface *intf;
+
+ intf = to_usb_interface(hdev->dev.parent);
+ ep = intf->cur_altsetting->endpoint;
+ if (ep)
+ return ep->desc.bEndpointAddress;
+
+ return -ENODEV;
+}
+
+static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
+ struct claw_command_report *cmd_rep)
+{
+ if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) ||
+ !claw_gamepad_mode_text[cmd_rep->data[0]] ||
+ cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text))
+ return -EINVAL;
+
+ scoped_guard(spinlock, &drvdata->mode_lock) {
+ drvdata->gamepad_mode = cmd_rep->data[0];
+ drvdata->mkeys_function = cmd_rep->data[1];
+ }
+
+ return 0;
+}
+
+static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct claw_command_report *cmd_rep;
+ int ret = 0;
+
+ if (size != CLAW_PACKET_SIZE)
+ return 0;
+
+ cmd_rep = (struct claw_command_report *)data;
+
+ if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c)
+ return 0;
+
+ dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n",
+ CLAW_PACKET_SIZE, data);
+
+ guard(spinlock)(&drvdata->cmd_lock);
+ switch (cmd_rep->cmd) {
+ case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK:
+ ret = claw_gamepad_mode_event(drvdata, cmd_rep);
+ if (drvdata->waiting_for_ack &&
+ drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) {
+ drvdata->cmd_status = ret;
+ drvdata->waiting_for_ack = false;
+ complete(&drvdata->send_cmd_complete);
+ }
+
+ break;
+ case CLAW_COMMAND_TYPE_ACK:
+ if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE)
+ break;
+ if (drvdata->waiting_for_ack) {
+ drvdata->cmd_status = 0;
+ drvdata->waiting_for_ack = false;
+ complete(&drvdata->send_cmd_complete);
+ }
+ dev_dbg(&drvdata->hdev->dev, "Waiting CMD: %x\n", drvdata->waiting_cmd);
+
+ break;
+ default:
+ dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd);
+ return 0;
+ }
+
+ return ret;
+}
+
+static int msi_raw_event(struct hid_device *hdev, struct hid_report *report,
+ u8 *data, int size)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN &&
+ drvdata->ep != CLAW_DINPUT_CFG_INTF_IN))
+ return 0;
+
+ return claw_raw_event(drvdata, report, data, size);
+}
+
+static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
+ size_t len, unsigned int timeout)
+{
+ unsigned char *dmabuf __free(kfree) = NULL;
+ u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index };
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ size_t header_size = ARRAY_SIZE(header);
+ int ret;
+
+ if (header_size + len > CLAW_PACKET_SIZE)
+ return -EINVAL;
+
+ /* We can't use a devm_alloc reusable buffer without side effects during suspend */
+ dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL);
+ if (!dmabuf)
+ return -ENOMEM;
+
+ memcpy(dmabuf, header, header_size);
+ if (data && len)
+ memcpy(dmabuf + header_size, data, len);
+
+ guard(mutex)(&drvdata->cfg_mutex);
+ if (timeout) {
+ reinit_completion(&drvdata->send_cmd_complete);
+ scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) {
+ drvdata->waiting_cmd = index;
+ drvdata->waiting_for_ack = true;
+ drvdata->cmd_status = -ETIMEDOUT;
+ }
+ }
+
+ dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n",
+ CLAW_PACKET_SIZE, dmabuf);
+
+ ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE);
+ if (ret < 0)
+ goto err_clear_state;
+
+ ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO;
+ if (ret)
+ goto err_clear_state;
+
+ if (timeout) {
+ ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete,
+ msecs_to_jiffies(timeout));
+
+ dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret);
+ ret = ret > 0 ? drvdata->cmd_status : ret ?: -EBUSY;
+ }
+
+err_clear_state:
+ if (timeout) {
+ guard(spinlock_irqsave)(&drvdata->cmd_lock);
+ drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE;
+ drvdata->waiting_for_ack = false;
+ }
+
+ return ret;
+}
+
+static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int i, ret = -EINVAL;
+ u8 data[2];
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+ if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) {
+ ret = i;
+ break;
+ }
+ }
+ if (ret < 0)
+ return ret;
+
+ data[0] = ret;
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock)
+ data[1] = drvdata->mkeys_function;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t gamepad_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret, i;
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25);
+ if (ret)
+ return ret;
+
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock)
+ i = drvdata->gamepad_mode;
+
+ if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+ return sysfs_emit(buf, "unsupported\n");
+
+ return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]);
+}
+static DEVICE_ATTR_RW(gamepad_mode);
+
+static ssize_t gamepad_mode_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t count = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) {
+ if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0')
+ continue;
+ count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]);
+ }
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(gamepad_mode_index);
+
+static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int i, ret = -EINVAL;
+ u8 data[2];
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) {
+ if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) {
+ ret = i;
+ break;
+ }
+ }
+ if (ret < 0)
+ return ret;
+
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock)
+ data[0] = drvdata->gamepad_mode;
+ data[1] = ret;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ int ret, i;
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25);
+ if (ret)
+ return ret;
+
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock)
+ i = drvdata->mkeys_function;
+
+ if (i >= ARRAY_SIZE(claw_mkeys_function_text))
+ return sysfs_emit(buf, "unsupported\n");
+
+ return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]);
+}
+static DEVICE_ATTR_RW(mkeys_function);
+
+static ssize_t mkeys_function_index_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(mkeys_function_index);
+
+static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ bool val;
+ int ret;
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ if (!val)
+ return -EINVAL;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0);
+ if (ret)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(reset);
+
+static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
+ int n)
+{
+ struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj));
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata) {
+ dev_warn(&hdev->dev,
+ "Failed to get drvdata from kobj. Gamepad attributes are not available.\n");
+ return 0;
+ }
+
+ return attr->mode;
+}
+
+static struct attribute *claw_gamepad_attrs[] = {
+ &dev_attr_gamepad_mode.attr,
+ &dev_attr_gamepad_mode_index.attr,
+ &dev_attr_mkeys_function.attr,
+ &dev_attr_mkeys_function_index.attr,
+ &dev_attr_reset.attr,
+ NULL,
+};
+
+static const struct attribute_group claw_gamepad_attr_group = {
+ .attrs = claw_gamepad_attrs,
+ .is_visible = claw_gamepad_attr_is_visible,
+};
+
+static void cfg_setup_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup);
+ int ret;
+
+ ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE,
+ NULL, 0, 25);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't read gamepad mode: %d\n", ret);
+ return;
+ }
+
+ /* Add sysfs attributes after we get the device state */
+ ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group);
+ if (ret) {
+ dev_err(&drvdata->hdev->dev,
+ "Failed to setup device, can't create gamepad attrs: %d\n", ret);
+ return;
+ }
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock)
+ /* Pairs with smp_load_acquire in attribute show/store functions */
+ smp_store_release(&drvdata->gp_registered, true);
+
+ kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+}
+
+static void cfg_resume_fn(struct work_struct *work)
+{
+ struct delayed_work *dwork = container_of(work, struct delayed_work, work);
+ struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume);
+
+ guard(spinlock_irqsave)(&drvdata->registration_lock);
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
+}
+
+static int claw_probe(struct hid_device *hdev, u8 ep)
+{
+ struct claw_drvdata *drvdata;
+ int ret;
+
+ drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT;
+ drvdata->hdev = hdev;
+ drvdata->ep = ep;
+
+ mutex_init(&drvdata->cfg_mutex);
+ spin_lock_init(&drvdata->registration_lock);
+ spin_lock_init(&drvdata->cmd_lock);
+ spin_lock_init(&drvdata->mode_lock);
+ init_completion(&drvdata->send_cmd_complete);
+ INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
+ INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
+
+ /* For control interface: open the HID transport for sending commands. */
+ ret = hid_hw_open(hdev);
+ if (ret)
+ return ret;
+
+ hid_set_drvdata(hdev, drvdata);
+ schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
+
+ return 0;
+}
+
+static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id)
+{
+ int ret;
+ u8 ep;
+
+ if (!hid_is_usb(hdev)) {
+ ret = -ENODEV;
+ goto err_probe;
+ }
+
+ ret = hid_parse(hdev);
+ if (ret)
+ goto err_probe;
+
+ /* Set quirk to create separate input devices per HID application */
+ hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT;
+ ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+ if (ret)
+ goto err_probe;
+
+ /* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */
+ ret = get_endpoint_address(hdev);
+ if (ret < 0)
+ goto err_stop_hw;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) {
+ ret = claw_probe(hdev, ep);
+ if (ret)
+ goto err_stop_hw;
+ }
+
+ return 0;
+
+err_stop_hw:
+ hid_hw_stop(hdev);
+err_probe:
+ return dev_err_probe(&hdev->dev, ret, "Failed to init device\n");
+}
+
+static void claw_remove(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ bool gp_registered;
+
+ if (!drvdata)
+ return;
+
+ cancel_delayed_work_sync(&drvdata->cfg_resume);
+ cancel_delayed_work_sync(&drvdata->cfg_setup);
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ gp_registered = smp_load_acquire(&drvdata->gp_registered);
+ /* Pairs with smp_load_acquire in attribute show/store functions */
+ smp_store_release(&drvdata->gp_registered, false);
+ }
+
+ if (gp_registered)
+ device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
+
+ hid_hw_close(hdev);
+}
+
+static void msi_remove(struct hid_device *hdev)
+{
+ int ret;
+ u8 ep;
+
+ /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ goto hw_stop;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+ claw_remove(hdev);
+
+hw_stop:
+ hid_hw_stop(hdev);
+}
+
+static int claw_resume(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata)
+ return -ENODEV;
+
+ /* MCU can take up to 500ms to be ready after resume */
+ schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500));
+ return 0;
+}
+
+static int msi_resume(struct hid_device *hdev)
+{
+ int ret;
+ u8 ep;
+
+ /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ return 0;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+ return claw_resume(hdev);
+
+ return 0;
+}
+
+static int claw_suspend(struct hid_device *hdev)
+{
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (!drvdata)
+ return -ENODEV;
+
+ cancel_delayed_work_sync(&drvdata->cfg_resume);
+ cancel_delayed_work_sync(&drvdata->cfg_setup);
+
+ return 0;
+}
+
+static int msi_suspend(struct hid_device *hdev, pm_message_t msg)
+{
+ int ret;
+ u8 ep;
+
+ /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */
+ ret = get_endpoint_address(hdev);
+ if (ret <= 0)
+ return 0;
+
+ ep = ret;
+ if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN)
+ return claw_suspend(hdev);
+
+ return 0;
+}
+
+static const struct hid_device_id msi_devices[] = {
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) },
+ { }
+};
+MODULE_DEVICE_TABLE(hid, msi_devices);
+
+static struct hid_driver msi_driver = {
+ .name = "hid-msi",
+ .id_table = msi_devices,
+ .raw_event = msi_raw_event,
+ .probe = msi_probe,
+ .remove = msi_remove,
+ .resume = msi_resume,
+ .suspend = pm_ptr(msi_suspend),
+};
+module_hid_driver(msi_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Denis Benato <denis.benato@linux.dev>");
+MODULE_AUTHOR("Zhouwang Huang <honjow311@gmail.com>");
+MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads");
--
2.53.0
^ permalink raw reply related
* [PATCH v10 2/4] HID: hid-msi: Add M-key mapping attributes
From: Derek J. Clark @ 2026-05-27 22:21 UTC (permalink / raw)
To: Jiri Kosina, Benjamin Tissoires
Cc: Pierre-Loup A . Griffais, Denis Benato, Zhouwang Huang,
Derek J . Clark, linux-input, linux-doc, linux-kernel
In-Reply-To: <20260527222122.10620-1-derekjohn.clark@gmail.com>
Adds attributes that allow for remapping the M-keys with up to 5 values
when in macro mode. There are 2 mappable buttons on the rear of the
device, M1 on the right and M2 on the left. When mapped, the events will
fire from one of three event devices: gamepad buttons will fire from the
device handled by xpad, while keyboard and mouse events will fire from
respectively typed evdevs provided by the input core. Names of each
mapping have been kept as close to the event that will fire from the evdev
as possible, with context added to the ABS_ events on the direction of the
movement.
Initial reverse-engineering and implementation of this feature was done
by Zhouwang Huang. I refactored the overall format to conform to kernel
driver best practices and style guides. Claude was used as an initial
reviewer of this patch.
Assisted-by: Claude:claude-sonnet-4-6
Co-developed-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Zhouwang Huang <honjow311@gmail.com>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v10:
- Remove additional gamepad_registered variable left over after rename
to gp_registered.
v8:
- Wrap all branches under single cmd_lock guard in claw_raw_event.
- Reject generic ACK in claw_raw_event if waiting_cmd is for another
branch.
v7:
- Use smp_[store_release|load_acquire] pattern for checking
gamepad_registered to avoid possible races during teardown.
- Add profile_lock for read/write profile_pending.
- Match on write address for mkey reports to prevent late ACK
from causing synchronization errors.
- Use struct for mkey reports.
v6:
- Make all timeouts 25ms to ensure at least 2 jiffies in a 100Hz
config.
- Gate all attribute show/store functions with gamepad_registered.
- Remove duplicated argv_free macro.
v5:
- Ensure adding "DISABLED" key to valid entries is done in the correct
patch.
- Re-enable sending an empty string to clear button mappings in
addition to setting DISABLED.
v4:
- Change dev_warn to dev_dbg in claw_profile_event.
- use __free with DEFINE_FREE macro for argv instead of manually
running argv_free, cleaining up scoped_guard goto.
v3:
- Use scoped_guard where necessary.
v2:
- Add mutex for SYNC_TO_ROM commands to ensure every SYNC is completed
before more data is written to the MCU volatile memory.
- Add mutex for profile_pending to ensure every profile action
response is serialized to the generating command.
---
drivers/hid/hid-msi.c | 448 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 446 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 3a176f44f2c43..f099d6cb7ea8d 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -42,6 +42,8 @@
#define CLAW_DINPUT_CFG_INTF_IN 0x82
#define CLAW_XINPUT_CFG_INTF_IN 0x83
+#define CLAW_KEYS_MAX 5
+
enum claw_command_index {
CLAW_COMMAND_TYPE_NONE = 0x00,
CLAW_COMMAND_TYPE_READ_PROFILE = 0x04,
@@ -67,6 +69,17 @@ static const char * const claw_gamepad_mode_text[] = {
[CLAW_GAMEPAD_MODE_DESKTOP] = "desktop",
};
+enum claw_profile_ack_pending {
+ CLAW_NO_PENDING,
+ CLAW_M1_PENDING,
+ CLAW_M2_PENDING,
+};
+
+enum claw_key_index {
+ CLAW_KEY_M1,
+ CLAW_KEY_M2,
+};
+
enum claw_mkeys_function_index {
CLAW_MKEY_FUNCTION_MACRO,
CLAW_MKEY_FUNCTION_DISABLED,
@@ -79,6 +92,155 @@ static const char * const claw_mkeys_function_text[] = {
[CLAW_MKEY_FUNCTION_COMBO] = "combination",
};
+static const struct {
+ u8 code;
+ const char *name;
+} claw_button_mapping_key_map[] = {
+ /* Gamepad buttons */
+ { 0x01, "ABS_HAT0Y_UP" },
+ { 0x02, "ABS_HAT0Y_DOWN" },
+ { 0x03, "ABS_HAT0X_LEFT" },
+ { 0x04, "ABS_HAT0X_RIGHT" },
+ { 0x05, "BTN_TL" },
+ { 0x06, "BTN_TR" },
+ { 0x07, "BTN_THUMBL" },
+ { 0x08, "BTN_THUMBR" },
+ { 0x09, "BTN_SOUTH" },
+ { 0x0a, "BTN_EAST" },
+ { 0x0b, "BTN_NORTH" },
+ { 0x0c, "BTN_WEST" },
+ { 0x0d, "BTN_MODE" },
+ { 0x0e, "BTN_SELECT" },
+ { 0x0f, "BTN_START" },
+ { 0x13, "BTN_TL2"},
+ { 0x14, "BTN_TR2"},
+ { 0x15, "ABS_Y_UP"},
+ { 0x16, "ABS_Y_DOWN"},
+ { 0x17, "ABS_X_LEFT"},
+ { 0x18, "ABS_X_RIGHT"},
+ { 0x19, "ABS_RY_UP"},
+ { 0x1a, "ABS_RY_DOWN"},
+ { 0x1b, "ABS_RX_LEFT"},
+ { 0x1c, "ABS_RX_RIGHT"},
+ /* Keyboard keys */
+ { 0x32, "KEY_ESC" },
+ { 0x33, "KEY_F1" },
+ { 0x34, "KEY_F2" },
+ { 0x35, "KEY_F3" },
+ { 0x36, "KEY_F4" },
+ { 0x37, "KEY_F5" },
+ { 0x38, "KEY_F6" },
+ { 0x39, "KEY_F7" },
+ { 0x3a, "KEY_F8" },
+ { 0x3b, "KEY_F9" },
+ { 0x3c, "KEY_F10" },
+ { 0x3d, "KEY_F11" },
+ { 0x3e, "KEY_F12" },
+ { 0x3f, "KEY_GRAVE" },
+ { 0x40, "KEY_1" },
+ { 0x41, "KEY_2" },
+ { 0x42, "KEY_3" },
+ { 0x43, "KEY_4" },
+ { 0x44, "KEY_5" },
+ { 0x45, "KEY_6" },
+ { 0x46, "KEY_7" },
+ { 0x47, "KEY_8" },
+ { 0x48, "KEY_9" },
+ { 0x49, "KEY_0" },
+ { 0x4a, "KEY_MINUS" },
+ { 0x4b, "KEY_EQUAL" },
+ { 0x4c, "KEY_BACKSPACE" },
+ { 0x4d, "KEY_TAB" },
+ { 0x4e, "KEY_Q" },
+ { 0x4f, "KEY_W" },
+ { 0x50, "KEY_E" },
+ { 0x51, "KEY_R" },
+ { 0x52, "KEY_T" },
+ { 0x53, "KEY_Y" },
+ { 0x54, "KEY_U" },
+ { 0x55, "KEY_I" },
+ { 0x56, "KEY_O" },
+ { 0x57, "KEY_P" },
+ { 0x58, "KEY_LEFTBRACE" },
+ { 0x59, "KEY_RIGHTBRACE" },
+ { 0x5a, "KEY_BACKSLASH" },
+ { 0x5b, "KEY_CAPSLOCK" },
+ { 0x5c, "KEY_A" },
+ { 0x5d, "KEY_S" },
+ { 0x5e, "KEY_D" },
+ { 0x5f, "KEY_F" },
+ { 0x60, "KEY_G" },
+ { 0x61, "KEY_H" },
+ { 0x62, "KEY_J" },
+ { 0x63, "KEY_K" },
+ { 0x64, "KEY_L" },
+ { 0x65, "KEY_SEMICOLON" },
+ { 0x66, "KEY_APOSTROPHE" },
+ { 0x67, "KEY_ENTER" },
+ { 0x68, "KEY_LEFTSHIFT" },
+ { 0x69, "KEY_Z" },
+ { 0x6a, "KEY_X" },
+ { 0x6b, "KEY_C" },
+ { 0x6c, "KEY_V" },
+ { 0x6d, "KEY_B" },
+ { 0x6e, "KEY_N" },
+ { 0x6f, "KEY_M" },
+ { 0x70, "KEY_COMMA" },
+ { 0x71, "KEY_DOT" },
+ { 0x72, "KEY_SLASH" },
+ { 0x73, "KEY_RIGHTSHIFT" },
+ { 0x74, "KEY_LEFTCTRL" },
+ { 0x75, "KEY_LEFTMETA" },
+ { 0x76, "KEY_LEFTALT" },
+ { 0x77, "KEY_SPACE" },
+ { 0x78, "KEY_RIGHTALT" },
+ { 0x79, "KEY_RIGHTCTRL" },
+ { 0x7a, "KEY_INSERT" },
+ { 0x7b, "KEY_HOME" },
+ { 0x7c, "KEY_PAGEUP" },
+ { 0x7d, "KEY_DELETE" },
+ { 0x7e, "KEY_END" },
+ { 0x7f, "KEY_PAGEDOWN" },
+ { 0x8a, "KEY_KPENTER" },
+ { 0x8b, "KEY_KP0" },
+ { 0x8c, "KEY_KP1" },
+ { 0x8d, "KEY_KP2" },
+ { 0x8e, "KEY_KP3" },
+ { 0x8f, "KEY_KP4" },
+ { 0x90, "KEY_KP5" },
+ { 0x91, "KEY_KP6" },
+ { 0x92, "KEY_KP7" },
+ { 0x93, "KEY_KP8" },
+ { 0x94, "KEY_KP9" },
+ { 0x95, "MD_PLAY" },
+ { 0x96, "MD_STOP" },
+ { 0x97, "MD_NEXT" },
+ { 0x98, "MD_PREV" },
+ { 0x99, "MD_VOL_UP" },
+ { 0x9a, "MD_VOL_DOWN" },
+ { 0x9b, "MD_VOL_MUTE" },
+ { 0x9c, "KEY_F23" },
+ /* Mouse events */
+ { 0xc8, "BTN_LEFT" },
+ { 0xc9, "BTN_MIDDLE" },
+ { 0xca, "BTN_RIGHT" },
+ { 0xcb, "BTN_SIDE" },
+ { 0xcc, "BTN_EXTRA" },
+ { 0xcd, "REL_WHEEL_UP" },
+ { 0xce, "REL_WHEEL_DOWN" },
+ { 0xff, "DISABLED" },
+};
+
+static const u16 button_mapping_addr_old[] = {
+ 0x007a, /* M1 */
+ 0x011f, /* M2 */
+};
+
+static const u16 button_mapping_addr_new[] = {
+ 0x00bb, /* M1 */
+ 0x0164, /* M2 */
+};
+
struct claw_command_report {
u8 report_id;
u8 padding[2];
@@ -87,25 +249,47 @@ struct claw_command_report {
u8 data[59];
} __packed;
+struct claw_profile_report {
+ u8 profile;
+ __be16 read_addr;
+} __packed;
+
+struct claw_mkey_report {
+ struct claw_profile_report;
+ u8 padding_0;
+ u8 padding_1;
+ u8 padding_2;
+ u8 codes[5];
+} __packed;
+
struct claw_drvdata {
/* MCU General Variables */
+ enum claw_profile_ack_pending profile_pending;
struct completion send_cmd_complete;
struct delayed_work cfg_resume;
struct delayed_work cfg_setup;
spinlock_t registration_lock; /* Lock for registration read/write */
+ struct mutex profile_mutex; /* mutex for profile_pending calls */
+ spinlock_t profile_lock; /* Lock for profile_pending read/write */
struct hid_device *hdev;
struct mutex cfg_mutex; /* mutex for synchronous data */
+ struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */
bool waiting_for_ack;
spinlock_t cmd_lock; /* Lock for cmd data read/write */
u8 waiting_cmd;
int cmd_status;
+ u16 bcd_device;
u8 ep;
/* Gamepad Variables */
enum claw_mkeys_function_index mkeys_function;
enum claw_gamepad_mode_index gamepad_mode;
+ u8 m1_codes[CLAW_KEYS_MAX];
+ u8 m2_codes[CLAW_KEYS_MAX];
+ const u16 *bmap_addr;
spinlock_t mode_lock; /* Lock for mode data read/write */
bool gp_registered;
+ bool bmap_support;
};
static int get_endpoint_address(struct hid_device *hdev)
@@ -137,6 +321,39 @@ static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
return 0;
}
+static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep)
+{
+ enum claw_profile_ack_pending profile;
+ struct claw_mkey_report *mkeys;
+ u8 *codes, key;
+ int i;
+
+ scoped_guard(spinlock, &drvdata->profile_lock)
+ profile = drvdata->profile_pending;
+
+ switch (profile) {
+ case CLAW_M1_PENDING:
+ case CLAW_M2_PENDING:
+ key = (profile == CLAW_M1_PENDING) ? CLAW_KEY_M1 : CLAW_KEY_M2;
+ mkeys = (struct claw_mkey_report *)cmd_rep->data;
+ if (be16_to_cpu(mkeys->read_addr) != drvdata->bmap_addr[key])
+ return -EINVAL;
+ codes = (profile == CLAW_M1_PENDING) ? drvdata->m1_codes : drvdata->m2_codes;
+ for (i = 0; i < CLAW_KEYS_MAX; i++)
+ codes[i] = (mkeys->codes[i]);
+ break;
+ default:
+ dev_dbg(&drvdata->hdev->dev,
+ "Got profile event without changes pending from command: %x\n",
+ cmd_rep->cmd);
+ return -EINVAL;
+ }
+ scoped_guard(spinlock, &drvdata->profile_lock)
+ drvdata->profile_pending = CLAW_NO_PENDING;
+
+ return 0;
+}
+
static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report,
u8 *data, int size)
{
@@ -165,9 +382,20 @@ static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *repor
complete(&drvdata->send_cmd_complete);
}
+ break;
+ case CLAW_COMMAND_TYPE_READ_PROFILE_ACK:
+ ret = claw_profile_event(drvdata, cmd_rep);
+ if (drvdata->waiting_for_ack &&
+ drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE) {
+ drvdata->cmd_status = ret;
+ drvdata->waiting_for_ack = false;
+ complete(&drvdata->send_cmd_complete);
+ }
+
break;
case CLAW_COMMAND_TYPE_ACK:
- if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE)
+ if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE ||
+ drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE)
break;
if (drvdata->waiting_for_ack) {
drvdata->cmd_status = 0;
@@ -442,6 +670,177 @@ static ssize_t reset_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_WO(reset);
+static int mkey_mapping_name_to_code(const char *name)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (!strcmp(name, claw_button_mapping_key_map[i].name))
+ return claw_button_mapping_key_map[i].code;
+ }
+
+ return -EINVAL;
+}
+
+static const char *mkey_mapping_code_to_name(u8 code)
+{
+ int i;
+
+ if (code == 0xff)
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) {
+ if (claw_button_mapping_key_map[i].code == code)
+ return claw_button_mapping_key_map[i].name;
+ }
+
+ return NULL;
+}
+
+static int claw_mkey_store(struct device *dev, const char *buf, u8 mkey)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[mkey])},
+ 0x07, 0x04, 0x00, {0xff, 0xff, 0xff, 0xff, 0xff} };
+ char **raw_keys __free(argv_free) = NULL;
+ int ret, key_count, i;
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ raw_keys = argv_split(GFP_KERNEL, buf, &key_count);
+ if (!raw_keys)
+ return -ENOMEM;
+
+ if (key_count > CLAW_KEYS_MAX)
+ return -EINVAL;
+
+ if (key_count == 0)
+ goto set_buttons;
+
+ for (i = 0; i < key_count; i++) {
+ ret = mkey_mapping_name_to_code(raw_keys[i]);
+ if (ret < 0)
+ return ret;
+
+ report.codes[i] = ret;
+ }
+
+set_buttons:
+ scoped_guard(mutex, &drvdata->rom_mutex) {
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA,
+ (u8 *)&report, sizeof(report), 25);
+ if (ret)
+ return ret;
+ /* MCU will not send ACK until the USB transaction completes. ACK is sent
+ * immediately after and will hit the stale state machine, before the next
+ * command re-arms the state machine. Timeout 0 ensures no deadlock waiting
+ * for ACK that ill never come.
+ */
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0);
+ }
+
+ return ret;
+}
+
+static int claw_mkey_show(struct device *dev, char *buf, enum claw_key_index m_key)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[m_key])}, 0x07 };
+ int i, ret, count = 0;
+ const char *name;
+ u8 *codes;
+
+ scoped_guard(spinlock_irqsave, &drvdata->registration_lock) {
+ /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */
+ if (!smp_load_acquire(&drvdata->gp_registered))
+ return -ENODEV;
+ }
+
+ codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes;
+
+ guard(mutex)(&drvdata->profile_mutex);
+ scoped_guard(spinlock_irqsave, &drvdata->profile_lock)
+ drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING
+ : CLAW_M2_PENDING;
+
+ ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE,
+ (u8 *)&report, sizeof(report), 25);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < CLAW_KEYS_MAX; i++) {
+ name = mkey_mapping_code_to_name(codes[i]);
+ if (name)
+ count += sysfs_emit_at(buf, count, "%s ", name);
+ }
+
+ if (!count)
+ return sysfs_emit(buf, "(not set)\n");
+
+ buf[count - 1] = '\n';
+
+ return count;
+}
+
+static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_mkey_store(dev, buf, CLAW_KEY_M1);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_mkey_show(dev, buf, CLAW_KEY_M1);
+}
+static DEVICE_ATTR_RW(button_m1);
+
+static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = claw_mkey_store(dev, buf, CLAW_KEY_M2);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return claw_mkey_show(dev, buf, CLAW_KEY_M2);
+}
+static DEVICE_ATTR_RW(button_m2);
+
+static ssize_t button_mapping_options_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, count = 0;
+
+ for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++)
+ count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name);
+
+ if (count)
+ buf[count - 1] = '\n';
+
+ return count;
+}
+static DEVICE_ATTR_RO(button_mapping_options);
+
static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
@@ -454,10 +853,22 @@ static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribu
return 0;
}
- return attr->mode;
+ /* Always show attrs available on all firmware */
+ if (attr == &dev_attr_gamepad_mode.attr ||
+ attr == &dev_attr_gamepad_mode_index.attr ||
+ attr == &dev_attr_mkeys_function.attr ||
+ attr == &dev_attr_mkeys_function_index.attr ||
+ attr == &dev_attr_reset.attr)
+ return attr->mode;
+
+ /* Hide button mapping attrs if it isn't supported */
+ return drvdata->bmap_support ? attr->mode : 0;
}
static struct attribute *claw_gamepad_attrs[] = {
+ &dev_attr_button_m1.attr,
+ &dev_attr_button_m2.attr,
+ &dev_attr_button_mapping_options.attr,
&dev_attr_gamepad_mode.attr,
&dev_attr_gamepad_mode_index.attr,
&dev_attr_mkeys_function.attr,
@@ -510,8 +921,31 @@ static void cfg_resume_fn(struct work_struct *work)
schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500));
}
+static void claw_features_supported(struct claw_drvdata *drvdata)
+{
+ u8 major = (drvdata->bcd_device >> 8) & 0xff;
+ u8 minor = drvdata->bcd_device & 0xff;
+
+ if (major == 0x01) {
+ drvdata->bmap_support = true;
+ if (minor >= 0x66)
+ drvdata->bmap_addr = button_mapping_addr_new;
+ else
+ drvdata->bmap_addr = button_mapping_addr_old;
+ return;
+ }
+
+ if ((major == 0x02 && minor >= 0x17) || major >= 0x03) {
+ drvdata->bmap_support = true;
+ drvdata->bmap_addr = button_mapping_addr_new;
+ return;
+ }
+}
+
static int claw_probe(struct hid_device *hdev, u8 ep)
{
+ struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+ struct usb_device *udev = interface_to_usbdev(intf);
struct claw_drvdata *drvdata;
int ret;
@@ -523,10 +957,20 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
drvdata->hdev = hdev;
drvdata->ep = ep;
+ /* Determine feature level from firmware version */
+ drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice);
+ claw_features_supported(drvdata);
+
+ if (!drvdata->bmap_support)
+ dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n");
+
mutex_init(&drvdata->cfg_mutex);
+ mutex_init(&drvdata->profile_mutex);
+ mutex_init(&drvdata->rom_mutex);
spin_lock_init(&drvdata->registration_lock);
spin_lock_init(&drvdata->cmd_lock);
spin_lock_init(&drvdata->mode_lock);
+ spin_lock_init(&drvdata->profile_lock);
init_completion(&drvdata->send_cmd_complete);
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox