All of lore.kernel.org
 help / color / mirror / Atom feed
From: DevExalt <exalt.dev.team@gmail.com>
To: jikos@kernel.org, bentiss@kernel.org
Cc: lains@riseup.net, hadess@hadess.net, linux-input@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	sari.kreitem@exalt.corp-partner.google.com, hbarnor@google.com,
	"Baraa Atta (Dev Exalt)" <exalt.dev.team@gmail.com>
Subject: [PATCH] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
Date: Mon, 15 Dec 2025 14:53:19 +0200	[thread overview]
Message-ID: <20251215125319.33261-1-exalt.dev.team@gmail.com> (raw)

From: "Baraa Atta (Dev Exalt)" <exalt.dev.team@gmail.com>

Add support in the Logitech HID++ driver for the HID++ Multi-Platform
feature (0x4531), which enables HID++ devices to adjust their behavior
based on the host operating system (Linux, ChromeOS, Android).

This patch:
 * Adds device IDs for MX Keys S (046d:b378) and Casa Keys (046d:b371).
 * Introduces the module parameter "hidpp_platform" to allow selecting a
   target platform.
 * Detects whether a device implements feature 0x4531.
 * Validates that the requested platform is supported by the device.
 * Applies the platform index when valid, otherwise leaves the device
   unchanged.
 * Keeps default behavior when "hidpp_platform" is unset or invalid.

Supported values for hidpp_platform:
   Android, Linux, Chrome

TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
     * Feature 0x4531 is detected.
     * Valid platform values are accepted and applied.
     * Invalid platform values result in no update.
     * Devices without 0x4531 retain default behavior.
     * Platform-specific key behavior is observed once applied.

Signed-off-by: Baraa Atta (Dev Exalt) <exalt.dev.team@gmail.com>
---
 drivers/hid/hid-ids.h            |   2 +
 drivers/hid/hid-logitech-hidpp.c | 280 +++++++++++++++++++++++++++++++
 drivers/hid/hid-quirks.c         |   2 +
 3 files changed, 284 insertions(+)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index d31711f1aaec..12de1194d7fa 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -866,6 +866,8 @@
 #define USB_DEVICE_ID_LOGITECH_T651	0xb00c
 #define USB_DEVICE_ID_LOGITECH_DINOVO_EDGE_KBD	0xb309
 #define USB_DEVICE_ID_LOGITECH_CASA_TOUCHPAD	0xbb00
+#define USB_DEVICE_ID_LOGITECH_CASA_KEYS_KEYBOARD	0xb371
+#define USB_DEVICE_ID_LOGITECH_MX_KEYS_S_KEYBOARD	0xb378
 #define USB_DEVICE_ID_LOGITECH_C007	0xc007
 #define USB_DEVICE_ID_LOGITECH_C077	0xc077
 #define USB_DEVICE_ID_LOGITECH_RECEIVER	0xc101
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index d5011a5d0890..e94daed31981 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -4373,6 +4373,280 @@ static bool hidpp_application_equals(struct hid_device *hdev,
 	return report && report->application == application;
 }
 
+/* -------------------------------------------------------------------------- */
+/* 0x4531: Multi-Platform Support                                             */
+/* -------------------------------------------------------------------------- */
+
+/*
+ * Some Logitech devices expose the HID++ feature 0x4531 (Multi-Platform) allowing
+ * the host to specify which operating system platform to use on the device. Changing device's
+ * platform may alter the behavior of the device to match the specified platform.
+ */
+
+static char *hidpp_platform;
+module_param(hidpp_platform, charp, 0644);
+MODULE_PARM_DESC(hidpp_platform, "Select host platform type for Logitech HID++ Multi-Platform feature "
+		 "0x4531, valid values: (linux|chrome|android).  If unset, no "
+		 "change is applied.");
+
+#define HIDPP_MULTIPLATFORM_FEAT_ID			0x4531
+#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO		0x0F
+#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR	0x1F
+#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM	0x3F
+
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX		BIT(10)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME	BIT(11)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID	BIT(12)
+
+struct hidpp_platform_desc {
+	u8 plat_idx;
+	u8 desc_idx;
+	u16 plat_mask;
+};
+
+/**
+ * hidpp_multiplatform_mask_from_str() - Convert platform name to an HID++ platform mask
+ * @pname: Platform name string
+ *
+ * Converts a platform name string to its corresponding HID++ platform mask based on
+ * the Multi-Platform feature specification.
+ *
+ * Return: Platform mask corresponding to @pname on success,
+ * or 0 if @pname is NULL or unsupported.
+ */
+static u16 hidpp_multiplatform_mask_from_str(const char *pname)
+{
+	if (!pname)
+		return 0;
+
+	if (!strcasecmp(pname, "linux"))
+		return HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX;
+	if (!strcasecmp(pname, "chrome"))
+		return HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME;
+	if (!strcasecmp(pname, "android"))
+		return HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID;
+
+	return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_num_pdesc() - Retrieve number of platform descriptors
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @num_desc: Pointer to store the number of platform descriptors
+ *
+ * Retrieves the number of platform descriptors supported by the device through
+ * the Multi-Platform feature and stores it in @num_desc.
+ *
+ * Return: 0 on success, or non-zero on failure.
+ */
+static int hidpp_multiplatform_get_num_pdesc(struct hidpp_device *hidpp,
+					     u8 feat_index, u8 *num_desc)
+{
+	int ret;
+	struct hidpp_report response;
+	struct hid_device *hdev = hidpp->hid_dev;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feat_index,
+					  HIDPP_MULTIPLATFORM_GET_FEATURE_INFO,
+					  NULL, 0, &response);
+	if (ret) {
+		hid_warn(hdev, "Multiplatform: GET_FEATURE_INFO failed (err=%d)", ret);
+		return ret;
+	}
+
+	*num_desc = response.fap.params[3];
+	hid_dbg(hdev, "Multiplatform: Device supports %d platform descriptors", *num_desc);
+
+	return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_platform_desc() - Retrieve a platform descriptor entry
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @platform_idx: Index of the platform descriptor to retrieve
+ * @pdesc: Pointer to store the retrieved platform descriptor
+ *
+ * Retrieves a single platform descriptor identified by @platform_idx from the
+ * device and stores the parsed descriptor fields in @pdesc.
+ *
+ * Return: 0 on success, or non-zero on failure.
+ */
+static int hidpp_multiplatform_get_platform_desc(struct hidpp_device *hidpp, u8 feat_index,
+						 u8 platform_idx, struct hidpp_platform_desc *pdesc)
+{
+	int ret;
+	struct hidpp_report response;
+	u8 params[1] = { platform_idx };
+	struct hid_device *hdev = hidpp->hid_dev;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feat_index,
+					  HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR,
+					  params, sizeof(params), &response);
+
+	if (ret) {
+		hid_warn(hdev,
+			 "Multiplatform: GET_PLATFORM_DESCRIPTOR failed for index %d (err=%d)",
+			 platform_idx, ret);
+		return ret;
+	}
+
+	pdesc->plat_idx = response.fap.params[0];
+	pdesc->desc_idx = response.fap.params[1];
+	pdesc->plat_mask = get_unaligned_be16(&response.fap.params[2]);
+
+	hid_dbg(hdev,
+		"Multiplatform: descriptor %d: plat_idx=%d, desc_idx=%d, plat_mask=0x%04x",
+		platform_idx, pdesc->plat_idx, pdesc->desc_idx, pdesc->plat_mask);
+
+	return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_platform_index() - Find platform index for a mask
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_mask: Platform mask to search for
+ * @plat_index: Pointer to store the matched platform index
+ *
+ * Iterates through all platform descriptors exposed by the device via the
+ * Multi-Platform feature, retrieving each descriptor and comparing its
+ * platform mask to @plat_mask. A descriptor matches if its mask overlaps with
+ * the requested @plat_mask (i.e. (pdesc.plat_mask & plat_mask) is non-zero).
+ *
+ * When a matching descriptor is found, its platform index (plat_idx) is
+ * written to @plat_index and the function returns success.
+ *
+ * If no descriptor matches, -ENOENT is returned.
+ *
+ * Return: 0 on success; -ENOENT if no matching descriptor exists;
+ *         or non-zero on failure.
+ */
+static int hidpp_multiplatform_get_platform_index(struct hidpp_device *hidpp,
+						  u8 feat_index, u16 plat_mask,
+						  u8 *plat_index)
+{
+	int i;
+	int ret;
+	u8 num_desc;
+	struct hidpp_platform_desc pdesc;
+	struct hid_device *hdev = hidpp->hid_dev;
+
+	ret = hidpp_multiplatform_get_num_pdesc(hidpp, feat_index, &num_desc);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_desc; i++) {
+		ret = hidpp_multiplatform_get_platform_desc(hidpp, feat_index, i, &pdesc);
+		if (ret)
+			return ret;
+
+		if (pdesc.plat_mask & plat_mask) {
+			*plat_index = pdesc.plat_idx;
+			hid_dbg(hdev,
+				"Multiplatform: Selected platform index %d for platform '%s'",
+				*plat_index, hidpp_platform);
+			return 0;
+		}
+	}
+
+	hid_dbg(hdev,
+		"Multiplatform: No matching platform descriptor found for platform '%s'",
+		hidpp_platform);
+	return -ENOENT;
+}
+
+/**
+ * hidpp_multiplatform_update_device_platform() - Update the device platform
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_index: Platform index to set on the device
+ *
+ * Sends the HID++ Multi-Platform 'SET_CURRENT_PLATFORM' command to the device to
+ * update its platform index to @plat_index.
+ *
+ * Return: 0 on success, or non-zero on failure.
+ */
+static int hidpp_multiplatform_update_device_platform(struct hidpp_device *hidpp,
+						      u8 feat_index, u8 plat_index)
+{
+	int ret;
+	struct hidpp_report response;
+	/* Byte 0 (hostIndex): 0xFF selects the current host. */
+	u8 params[2] = { 0xFF, plat_index };
+
+	ret = hidpp_send_fap_command_sync(hidpp, feat_index,
+					  HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM,
+					  params, sizeof(params), &response);
+
+	if (ret)
+		hid_warn(hidpp->hid_dev,
+			 "Multiplatform: SET_CURRENT_PLATFORM failed for index %d (err=%d)",
+			 plat_index, ret);
+
+	return ret;
+}
+
+/**
+ * hidpp_multiplatform_init() - Apply the HID++ Multi-Platform (0x4531) feature
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Initializes the Multi-Platform feature by selecting the device platform
+ * corresponding to the module parameter @hidpp_platform, if provided.
+ *
+ * The function performs the following steps:
+ *   1. Convert the @hidpp_platform string into a platform mask.
+ *   2. Check whether the device supports the Multi-Platform feature (0x4531).
+ *   3. Look up the device's platform index whose mask matches the host
+ *      platform mask.
+ *   4. Apply that platform index to the device via 'SET_CURRENT_PLATFORM'.
+ *
+ * If the module parameter is unset or invalid, or the device does not support
+ * the feature, or no matching platform descriptor is found, the function exits
+ * silently without modifying the device state.
+ *
+ * On success, the device's platform configuration is updated.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+	int ret;
+	u8 feat_index;
+	u8 plat_index;
+	u16 host_plat_mask;
+	struct hid_device *hdev = hidpp->hid_dev;
+
+	if (!hidpp_platform)
+		return;
+
+	host_plat_mask = hidpp_multiplatform_mask_from_str(hidpp_platform);
+	if (!host_plat_mask) {
+		hid_warn(hdev,
+			 "Multiplatform: Invalid or unsupported platform name '%s'",
+			 hidpp_platform);
+		return;
+	}
+
+	ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+	if (ret) {
+		hid_warn(hdev,
+			 "Multiplatform: Failed to get the HID++ multiplatform feature 0x4531");
+		return;
+	}
+
+	ret = hidpp_multiplatform_get_platform_index(hidpp, feat_index, host_plat_mask,
+						     &plat_index);
+	if (ret)
+		return;
+
+	ret = hidpp_multiplatform_update_device_platform(hidpp, feat_index, plat_index);
+	if (ret)
+		return;
+
+	hid_info(hdev,
+		 "Multiplatform: Device platform successfully set to '%s'", hidpp_platform);
+}
+
 static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 {
 	struct hidpp_device *hidpp;
@@ -4467,6 +4741,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	if (hidpp->quirks & HIDPP_QUIRK_DELAYED_INIT)
 		connect_mask &= ~HID_CONNECT_HIDINPUT;
 
+	hidpp_multiplatform_init(hidpp);
+
 	/* Now export the actual inputs and hidraw nodes to the world */
 	hid_device_io_stop(hdev);
 	ret = hid_connect(hdev, connect_mask);
@@ -4664,6 +4940,10 @@ static const struct hid_device_id hidpp_devices[] = {
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb034) },
 	{ /* MX Anywhere 3SB mouse over Bluetooth */
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb038) },
+	{ /* Casa Keys keyboard over Bluetooth */
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CASA_KEYS_KEYBOARD) },
+	{ /* MX Keys S keyboard over Bluetooth */
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MX_KEYS_S_KEYBOARD) },
 	{}
 };
 
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index c89a015686c0..99ca04b61bda 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -520,6 +520,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
 #endif
 #if IS_ENABLED(CONFIG_HID_LOGITECH_HIDPP)
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_CASA_KEYS_KEYBOARD) },
+	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_MX_KEYS_S_KEYBOARD) },
 #endif
 #if IS_ENABLED(CONFIG_HID_MAGICMOUSE)
 	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGICMOUSE) },
-- 
2.34.1


             reply	other threads:[~2025-12-15 12:53 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-15 12:53 DevExalt [this message]
     [not found] ` <CAJaUH_8A70=_Cb8yCWqJxbjpW-BnK958fExnC1kSgyhVaydbUw@mail.gmail.com>
2026-03-08  8:08   ` [PATCH] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) dev exalt
2026-03-09  9:53 ` Bastien Nocera
2026-03-19 10:05   ` dev exalt
2026-05-10  6:36     ` dev exalt
2026-05-21  9:27       ` dev exalt
2026-05-23  4:12         ` Greg KH

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=20251215125319.33261-1-exalt.dev.team@gmail.com \
    --to=exalt.dev.team@gmail.com \
    --cc=bentiss@kernel.org \
    --cc=hadess@hadess.net \
    --cc=hbarnor@google.com \
    --cc=jikos@kernel.org \
    --cc=lains@riseup.net \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=sari.kreitem@exalt.corp-partner.google.com \
    /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.