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] HID: hid-msi: Send a uevent when the MCU changes the mode by itself
Date: Sat, 14 Nov 2026 22:14:40 +0000 [thread overview]
Message-ID: <20261114221440.12580-1-green.koala2979@fastmail.com> (raw)
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.
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.
drivers/hid/hid-msi.c | 42 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c
index 3225d3b..e99f749 100644
--- a/drivers/hid/hid-msi.c
+++ b/drivers/hid/hid-msi.c
@@ -350,6 +350,7 @@ 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 */
u8 m1_codes[CLAW_KEYS_MAX];
u8 m2_codes[CLAW_KEYS_MAX];
u8 rumble_intensity_right;
@@ -387,19 +388,41 @@ 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];
}
+ /*
+ * 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;
}
@@ -657,6 +680,7 @@ static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field,
{
struct claw_drvdata *drvdata = hid_get_drvdata(hdev);
u8 data[2];
+ int ret;
guard(mutex)(&drvdata->cfg_mutex);
@@ -673,8 +697,18 @@ 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 */
+ scoped_guard(spinlock_irqsave, &drvdata->mode_lock) {
+ 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 +1905,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 +1981,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
reply other threads:[~2026-11-14 22:14 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
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=20261114221440.12580-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®