From: Denis Benato <denis.benato@linux.dev>
To: linux-kernel@vger.kernel.org
Cc: platform-driver-x86@vger.kernel.org, linux-input@vger.kernel.org,
kernel@lists.opengamingcollective.org,
"Benjamin Tissoires" <bentiss@kernel.org>,
"Jiri Kosina" <jikos@kernel.org>,
"Luke D . Jones" <luke@ljones.dev>,
"Mateusz Schwartz" <matthew.schwartz@linux.dev>,
"Denis Benato" <benato.denis96@gmail.com>,
"Jonathan LoBue" <jlobue10@gmail.com>,
"Khamunetri Clark" <khamunetriclark@gmail.com>,
"Derek J. Clark" <derekjohn.clark@gmail.com>,
"Joshua Leivenzon" <hacker1024@users.sourceforge.net>,
"Nathan Chancellor" <nathan@kernel.org>,
"Rebecca Mara Müller" <rebecca.mara@posteo.de>,
"Ivan Levchenko" <me@livan.pro>,
"Corentin Chary" <corentin.chary@gmail.com>,
"Hans de Goede" <hansg@kernel.org>,
"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
"Denis Benato" <denis.benato@linux.dev>
Subject: [PATCH v11 13/18] HID: asus: identify XG Mobile stations and bind the LED to the reported model
Date: Sun, 25 Oct 2026 23:52:26 +0000 [thread overview]
Message-ID: <20261025235232.129945-14-denis.benato@linux.dev> (raw)
In-Reply-To: <20261025235232.129945-1-denis.benato@linux.dev>
From: Denis Benato <benato.denis96@gmail.com>
The XG Mobile LED interface is currently bound to two USB product IDs
(0x1970, 0x1a9a). The stations identify themselves through the vendor
report 0x5E: after the usual "ASUS Tech.Inc." unlock, the 0xCE 0x01
query answers with a NUL terminated string of the form
MB:<mainboard>;SN:<serial>;PRO:<model>;PNUM:<part number>;
Run this query at probe time to initialize the LED interface only on
supported models (GC31, GC32 and GC33), and name the LED after the
reported model (asus:xgm-<model>:led).
Signed-off-by: Denis Benato <denis.benato@linux.dev>
---
drivers/hid/hid-asus.c | 183 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 182 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index fe2a326ccd86..e751bf163434 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -63,6 +63,12 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
+/* XG Mobile station protocol on report 0x5E */
+#define ASUS_XGM_CMD_QUERY 0xce
+#define ASUS_XGM_QUERY_IDENTITY 0x01
+#define ASUS_XGM_QUERY_VERSION 0x03
+#define ASUS_XGM_INFO_LEN 32
+
#define ROG_ALLY_REPORT_SIZE 64
#define ROG_ALLY_X_MIN_MCU 313
#define ROG_ALLY_MIN_MCU 319
@@ -174,6 +180,13 @@ struct asus_xgm_led {
struct hid_device *hdev;
};
+struct asus_xgm_info {
+ char mainboard[ASUS_XGM_INFO_LEN];
+ char serial[ASUS_XGM_INFO_LEN];
+ char model[ASUS_XGM_INFO_LEN];
+ char part_number[ASUS_XGM_INFO_LEN];
+};
+
struct asus_touchpad_info {
int max_x;
int max_y;
@@ -471,6 +484,7 @@ struct asus_drvdata {
struct asus_hid_listener listener;
bool fn_lock;
struct asus_xgm_led *xgm_led;
+ struct asus_xgm_info *xgm_info;
};
static int asus_report_battery(struct asus_drvdata *, u8 *, int);
@@ -6049,6 +6063,147 @@ static int asus_battery_probe(struct hid_device *hdev)
return ret;
}
+/**
+ * asus_xgm_query() - Run a query command against an XG Mobile station
+ * @hdev: HID device
+ * @subcmd: query sub-command
+ * @buf: buffer to store the answer in
+ * @buf_size: size of @buf
+ *
+ * Windows re-sends the "ASUS Tech.Inc." unlock before every command
+ * burst, so do the same.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int asus_xgm_query(struct hid_device *hdev, u8 subcmd,
+ u8 *buf, size_t buf_size)
+{
+ const u8 unlock[] = {
+ FEATURE_KBD_LED_REPORT_ID2,
+ 'A', 'S', 'U', 'S', ' ', 'T', 'e', 'c', 'h', '.', 'I', 'n', 'c', '.'
+ };
+ const u8 cmd[] = { FEATURE_KBD_LED_REPORT_ID2, ASUS_XGM_CMD_QUERY, subcmd };
+ int ret;
+
+ ret = asus_kbd_set_report(hdev, unlock, sizeof(unlock));
+ if (ret < 0)
+ return ret;
+
+ ret = asus_kbd_set_report(hdev, cmd, sizeof(cmd));
+ if (ret < 0)
+ return ret;
+
+ return asus_kbd_get_report(hdev, FEATURE_KBD_LED_REPORT_ID2, buf, buf_size);
+}
+
+/**
+ * asus_xgm_info_assign() - Copy an identity field up to its ';'
+ * terminator
+ * @dst: destination buffer
+ * @dst_len: size of @dst, including the trailing NUL
+ * @src: start of the field inside the identity string
+ * @src_len: number of bytes available at @src
+ *
+ * Copies up to @dst_len - 1 bytes from @src, stopping at the first ';'
+ * field separator, and NUL terminates @dst.
+ */
+static void asus_xgm_info_assign(char *dst, size_t dst_len,
+ const char *src, size_t src_len)
+{
+ const char *sep = strnchr(src, src_len, ';');
+ size_t n = sep ? (size_t)(sep - src) : src_len;
+
+ if (n >= dst_len)
+ n = dst_len - 1;
+
+ memcpy(dst, src, n);
+ dst[n] = '\0';
+}
+
+/**
+ * asus_xgm_get_identity() - Query the identity of an XG Mobile station
+ * @hdev: HID device
+ * @info: identity structure to fill in
+ *
+ * The identity query answers with a NUL terminated string of the form
+ * "MB:<mainboard>;SN:<serial>;PRO:<model>;PNUM:<part number>;", which is
+ * split into the fields of @info. The model field is mandatory: a station
+ * that does not report one is treated as absent.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int asus_xgm_get_identity(struct hid_device *hdev,
+ struct asus_xgm_info *info)
+{
+ size_t len = asus_report_len(hdev, FEATURE_KBD_LED_REPORT_ID2);
+ const char *start, *p;
+ size_t rem;
+ int ret;
+
+ if (!len)
+ return -EINVAL;
+
+ u8 *buf __free(kfree) = kzalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = asus_xgm_query(hdev, ASUS_XGM_QUERY_IDENTITY, buf, len);
+ if (ret < 0)
+ return ret;
+
+ if (len < 4 || buf[1] != ASUS_XGM_CMD_QUERY ||
+ buf[2] != ASUS_XGM_QUERY_IDENTITY)
+ return -ENODEV;
+
+ start = (char *)&buf[3];
+ rem = strnlen(start, len - 3);
+ if (!rem)
+ return -ENODEV;
+
+ p = strnstr(start, "MB:", rem);
+ if (p)
+ asus_xgm_info_assign(info->mainboard, sizeof(info->mainboard),
+ p + 3, rem - (p + 3 - start));
+ p = strnstr(start, "SN:", rem);
+ if (p)
+ asus_xgm_info_assign(info->serial, sizeof(info->serial),
+ p + 3, rem - (p + 3 - start));
+ p = strnstr(start, "PRO:", rem);
+ if (p)
+ asus_xgm_info_assign(info->model, sizeof(info->model),
+ p + 4, rem - (p + 4 - start));
+ p = strnstr(start, "PNUM:", rem);
+ if (p)
+ asus_xgm_info_assign(info->part_number, sizeof(info->part_number),
+ p + 5, rem - (p + 5 - start));
+
+ if (!info->model[0])
+ return -ENODEV;
+
+ return 0;
+}
+
+static const char * const asus_xgm_models[] = { "GC31", "GC32", "GC33" };
+
+/**
+ * asus_xgm_is_supported_model() - Check that a station is supported
+ * @info: identity of the station
+ *
+ * The supported families are the GC31, GC32 and GC33 stations.
+ *
+ * Return: true if the reported model belongs to one of the supported
+ * families, false otherwise
+ */
+static bool asus_xgm_is_supported_model(const struct asus_xgm_info *info)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(asus_xgm_models); i++)
+ if (strncmp(info->model, asus_xgm_models[i],
+ strlen(asus_xgm_models[i])) == 0)
+ return true;
+
+ return false;
+}
+
static int asus_xgm_led_set(struct led_classdev *led_cdev, enum led_brightness value)
{
const u8 buf[] = { FEATURE_KBD_LED_REPORT_ID2, 0xC5, (value) ? 0x50 : 0x00 };
@@ -6315,15 +6470,41 @@ static void asus_initialize_reports(struct hid_device *hdev)
static int asus_xgm_init(struct hid_device *hdev, struct asus_drvdata *drvdata)
{
+ struct asus_xgm_info *info;
const char *name;
int ret;
+ info = devm_kzalloc(&hdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ /*
+ * The station reports its identity through the query protocol: the
+ * reported model decides whether the LED interface is registered
+ * and what name it gets.
+ */
+ ret = asus_xgm_get_identity(hdev, info);
+ if (ret < 0) {
+ hid_warn(hdev, "Failed to query XG Mobile identity: %d\n", ret);
+ return 0;
+ }
+
+ hid_info(hdev, "XG Mobile: model %s, serial %s\n",
+ info->model, info->serial);
+
+ if (!asus_xgm_is_supported_model(info)) {
+ hid_warn(hdev, "Unsupported XG Mobile model %s\n", info->model);
+ return 0;
+ }
+
+ drvdata->xgm_info = info;
+
drvdata->xgm_led = devm_kzalloc(&hdev->dev, sizeof(*drvdata->xgm_led), GFP_KERNEL);
if (drvdata->xgm_led == NULL)
return -ENOMEM;
name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "asus:xgm-%s:led",
- strlen(hdev->uniq) ? hdev->uniq : dev_name(&hdev->dev));
+ info->model);
if (name == NULL) {
ret = -ENOMEM;
--
2.47.3
next prev parent reply other threads:[~2026-10-25 23:53 UTC|newest]
Thread overview: 44+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-25 23:52 [PATCH v11 00/18] HID: asus: add support for ROG Ally handhelds Denis Benato
2026-10-25 23:52 ` [PATCH v11 01/18] " Denis Benato
2026-10-26 0:10 ` sashiko-bot
2026-10-26 12:38 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 02/18] HID: asus: add gamepad configuration Denis Benato
2026-10-26 0:10 ` sashiko-bot
2026-10-26 12:56 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 03/18] HID: asus: add vibration strength configuration Denis Benato
2026-10-25 23:52 ` [PATCH v11 04/18] HID: asus: add joysticks inner and outer range configuration Denis Benato
2026-10-26 13:00 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 05/18] HID: asus: add triggers " Denis Benato
2026-10-25 23:52 ` [PATCH v11 06/18] HID: asus: add joysticks anti-deadzone configuration Denis Benato
2026-10-25 23:52 ` [PATCH v11 07/18] HID: asus: add support for response curve Denis Benato
2026-10-26 13:06 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 08/18] HID: asus: add support to force feedback Denis Benato
2026-10-26 0:19 ` sashiko-bot
2026-10-26 12:36 ` Sakari Ailus
2026-10-26 18:21 ` Ilpo Järvinen
2026-10-26 18:27 ` Sakari Ailus
2026-10-26 13:13 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 09/18] HID: asus: add support for gamepad mode Denis Benato
2026-10-26 13:20 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 10/18] HID: asus: add support for turbo buttons Denis Benato
2026-10-26 13:29 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 11/18] HID: asus: add support for btn remapping Denis Benato
2026-10-26 0:06 ` sashiko-bot
2026-10-26 14:03 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 12/18] HID: asus: size feature reports from the HID report descriptor Denis Benato
2026-10-26 0:06 ` sashiko-bot
2026-10-26 13:06 ` Rebecca Mara Müller
2026-10-26 19:38 ` Denis Benato
2026-10-25 23:52 ` Denis Benato [this message]
2026-10-25 23:52 ` [PATCH v11 14/18] HID: asus: expose XG Mobile identity and firmware information Denis Benato
2026-10-26 0:05 ` sashiko-bot
2026-10-26 14:10 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 15/18] HID: asus: size ally gamepad commands from the HID report descriptor Denis Benato
2026-10-26 0:10 ` sashiko-bot
2026-10-25 23:52 ` [PATCH v11 16/18] platform/x86: asus-armoury: pick the eGPU activation from the reported GPU Denis Benato
2026-10-25 23:52 ` [PATCH v11 17/18] HID: asus: report the XG Mobile GPU vendor to asus-wmi Denis Benato
2026-10-26 0:03 ` sashiko-bot
2026-10-26 14:15 ` Ilpo Järvinen
2026-10-25 23:52 ` [PATCH v11 18/18] MAINTAINERS: add maintainer for the ASUS HID driver Denis Benato
2026-10-26 14:24 ` [PATCH v11 00/18] HID: asus: add support for ROG Ally handhelds Ilpo Järvinen
2026-10-26 18:03 ` Denis Benato
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=20261025235232.129945-14-denis.benato@linux.dev \
--to=denis.benato@linux.dev \
--cc=benato.denis96@gmail.com \
--cc=bentiss@kernel.org \
--cc=corentin.chary@gmail.com \
--cc=derekjohn.clark@gmail.com \
--cc=hacker1024@users.sourceforge.net \
--cc=hansg@kernel.org \
--cc=ilpo.jarvinen@linux.intel.com \
--cc=jikos@kernel.org \
--cc=jlobue10@gmail.com \
--cc=kernel@lists.opengamingcollective.org \
--cc=khamunetriclark@gmail.com \
--cc=linux-input@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luke@ljones.dev \
--cc=matthew.schwartz@linux.dev \
--cc=me@livan.pro \
--cc=nathan@kernel.org \
--cc=platform-driver-x86@vger.kernel.org \
--cc=rebecca.mara@posteo.de \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.