From: Ana Veto <green.koala2979@fastmail.com>
To: derekjohn.clark@gmail.com, Jiri Kosina <jikos@kernel.org>,
Benjamin Tissoires <bentiss@kernel.org>
Cc: Ana Veto <green.koala2979@fastmail.com>,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v3] HID: hid-msi: Send a uevent when the MCU changes the mode by itself
Date: Sun, 15 Nov 2026 03:23:14 +0000 [thread overview]
Message-ID: <20261115032314.7094-1-green.koala2979@fastmail.com> (raw)
In-Reply-To: <20261114221440.12580-1-green.koala2979@fastmail.com>
The MCU of the MSI Claw changes the M-key function by itself once a
second while View+LB is held, and the controller can end up in desktop
mode, where the sticks drive the mouse. The MCU reports the current
mode and M-key function in a GAMEPAD_MODE_ACK report, unsolicited after
such a change and in reply to a query, and the driver stores both, but
userspace is not told. udev rules that keep the controller usable run
only on add, change and bind (for example InputPlumber's rules that
switch desktop mode back to xinput and set the M-keys to macro), so the
controller stays in desktop mode or with M1/M2 disabled until it is
probed again.
Emit KOBJ_CHANGE from a work item whenever a report changes the stored
mode or M-key function. Store the new values after a successful
SWITCH_MODE write as well, so that the driver's own writes raise no
event, unless a report came in during the write: that report may
already show a newer state, so it stays.
Tested on an MSI Claw 8 EX AI+ (0db0:1901) with a v7.3.3-based kernel and
InputPlumber's rules. The controller was switched to desktop mode by a
SWITCH_MODE report written to hidraw, bypassing the driver. A read of
gamepad_mode then returned desktop and raised a change uevent, and the
rule wrote xinput 66 ms later. After another such switch, View+LB made
the MCU report mode 4 by itself, and the rule wrote xinput within
0.3 s. While View+LB was held in xinput mode, the MCU reported the
M-keys disabled once a second, and the M-key rule set macro again each
time.
Fixes: a9517958e828 ("HID: hid-msi: Add MSI Claw configuration driver")
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Ana Veto <green.koala2979@fastmail.com>
---
Notes:
Based on hid.git for-next. This changes lines near "HID: hid-msi: Drain orphan MCU ACKs by issuing a query" (v2, posted 2026-11-14); the two patches apply in either order.
v3: a GAMEPAD_MODE_ACK that arrives during the SWITCH_MODE write is no longer overwritten by the values the driver sent (claw_switch_mode now stores them only if no report was stored meanwhile).
v2: https://lore.kernel.org/r/20261114221440.12580-1-green.koala2979@fastmail.com
drivers/hid/hid-msi.c | 51 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 49 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 3225d3b..6404de8 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -350,6 +350,8 @@ struct claw_drvdata {
/* Gamepad Variables */
enum claw_mkeys_function_index mkeys_function;
enum claw_gamepad_mode_index gamepad_mode;
+ struct work_struct mode_event; /* uevent when the MCU changes the mode */
+ unsigned int mode_reports; /* Mode reports stored, guarded by mode_lock */
u8 m1_codes[CLAW_KEYS_MAX];
u8 m2_codes[CLAW_KEYS_MAX];
u8 rumble_intensity_right;
@@ -387,17 +389,40 @@ static int get_endpoint_address(struct hid_device *hdev)
return -ENODEV;
}
+static void claw_mode_event_fn(struct work_struct *work)
+{
+ struct claw_drvdata *drvdata = container_of(work, struct claw_drvdata, mode_event);
+
+ kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE);
+}
+
static int claw_gamepad_mode_event(struct claw_drvdata *drvdata,
struct claw_command_report *cmd_rep)
{
+ bool changed;
+
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_irqsave, &drvdata->mode_lock) {
+ changed = drvdata->gamepad_mode != cmd_rep->data[0] ||
+ drvdata->mkeys_function != cmd_rep->data[1];
drvdata->gamepad_mode = cmd_rep->data[0];
drvdata->mkeys_function = cmd_rep->data[1];
+ drvdata->mode_reports++;
+ }
+
+ /*
+ * The MCU changes the mode or M-key function by itself, e.g. while
+ * View+LB is held. Userspace learns of changes only through uevents.
+ */
+ if (changed) {
+ guard(spinlock_irqsave)(&drvdata->registration_lock);
+ /* Pairs with smp_store_release from cfg_setup_fn and claw_remove */
+ if (smp_load_acquire(&drvdata->gp_registered))
+ schedule_work(&drvdata->mode_event);
}
return 0;
@@ -656,11 +681,14 @@ static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data,
static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field, u8 val)
{
struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
+ unsigned int reports;
u8 data[2];
+ int ret;
guard(mutex)(&drvdata->cfg_mutex);
scoped_guard(spinlock_irqsave, &drvdata->mode_lock) {
+ reports = drvdata->mode_reports;
switch (field) {
case CLAW_FIELD_GAMEPAD_MODE:
data[0] = val;
@@ -673,8 +701,23 @@ static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field,
}
}
- return __claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
- ARRAY_SIZE(data), 0);
+ ret = __claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data,
+ ARRAY_SIZE(data), 0);
+ if (ret)
+ return ret;
+
+ /*
+ * The driver's own change is no change to report. A report that came
+ * in during the transfer may already show a newer state; keep it.
+ */
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock) {
+ if (drvdata->mode_reports == reports) {
+ drvdata->gamepad_mode = data[0];
+ drvdata->mkeys_function = data[1];
+ }
+ }
+
+ return 0;
}
static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr,
@@ -1871,6 +1914,7 @@ static int claw_probe(struct hid_device *hdev, u8 ep)
INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn);
INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn);
INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn);
+ INIT_WORK(&drvdata->mode_event, &claw_mode_event_fn);
/* For control interface: open the HID transport for sending commands. */
ret = hid_hw_open(hdev);
@@ -1946,6 +1990,9 @@ static void claw_remove(struct hid_device *hdev)
smp_store_release(&drvdata->rgb_registered, false);
}
+ /* gp_registered is false now, so no new mode event can be queued */
+ cancel_work_sync(&drvdata->mode_event);
+
if (gp_registered)
device_remove_group(&hdev->dev, &claw_gamepad_attr_group);
--
2.56.0
next prev parent reply other threads:[~2026-11-15 3:23 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 22:14 [PATCH] " Ana Veto
2026-11-15 3:23 ` Ana Veto [this message]
2026-11-15 14:21 ` [PATCH v4] " Ana Veto
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261115032314.7094-1-green.koala2979@fastmail.com \
--to=green.koala2979@fastmail.com \
--cc=bentiss@kernel.org \
--cc=derekjohn.clark@gmail.com \
--cc=jikos@kernel.org \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®