* [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
@ 2026-06-25 8:08 DevExalt
2026-06-25 8:18 ` sashiko-bot
` (5 more replies)
0 siblings, 6 replies; 13+ messages in thread
From: DevExalt @ 2026-06-25 8:08 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
.../testing/sysfs-driver-hid-logitech-hidpp | 29 ++
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-logitech-hidpp.c | 382 ++++++++++++++++++
drivers/hid/hid-quirks.c | 2 +
4 files changed, 415 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..520b9a735bbc 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,32 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: May, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (WO) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..a6c2dea79397 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -913,6 +913,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 90b0184df777..a41fac1b4d82 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,10 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ /* HID++ Multi-Platform (0x4531) index; feature_index 0 means unsupported. */
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4427,379 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device;
+ * reading reports the platform last applied through this attribute, or an empty
+ * line if none has been set.
+ */
+
+#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_TIZEN BIT(0)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute. The order of
+ * this array must stay in sync with multiplatform_masks[] below, as the index
+ * returned by sysfs_match_string() is used to look up the matching mask.
+ */
+static const char * const multiplatform_names[] = {
+ "windows", "winemb", "linux", "chrome",
+ "android", "macos", "ios", "webos", "tizen", NULL
+};
+
+static const u16 multiplatform_masks[] = {
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single HIDPP_MULTIPLATFORM_PLATFORM_MASK_* bit to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ idx = sysfs_match_string(multiplatform_names, platform);
+ if (idx < 0)
+ return idx;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ ret = devm_device_add_group(&hdev->dev, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4478,6 +4855,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
INIT_WORK(&hidpp->work, hidpp_connect_event);
INIT_WORK(&hidpp->reset_hi_res_work, hidpp_reset_hi_res_handler);
mutex_init(&hidpp->send_mutex);
+ mutex_init(&hidpp->multiplatform_lock);
init_waitqueue_head(&hidpp->wait);
/* indicates we are handling the battery properties in the kernel */
@@ -4517,6 +4895,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);
@@ -4560,6 +4940,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
+ mutex_destroy(&hidpp->multiplatform_lock);
return ret;
}
@@ -4576,6 +4957,7 @@ static void hidpp_remove(struct hid_device *hdev)
cancel_work_sync(&hidpp->work);
cancel_work_sync(&hidpp->reset_hi_res_work);
mutex_destroy(&hidpp->send_mutex);
+ mutex_destroy(&hidpp->multiplatform_lock);
}
#define LDJ_DEVICE(product) \
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 57d8efdd9b89..577af0075e27 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -537,6 +537,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
@ 2026-06-25 8:18 ` sashiko-bot
2026-06-30 9:48 ` [PATCH v3] " DevExalt
` (4 subsequent siblings)
5 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-25 8:18 UTC (permalink / raw)
To: DevExalt; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Use-After-Destroy of `multiplatform_lock` and race with hardware teardown due to deferred sysfs cleanup.
- [Medium] The `platform` sysfs attribute is exposed before IO is re-enabled, causing writes to time out and drop packets.
- [Low] The `platform` sysfs attribute documentation incorrectly claims it supports read operations.
--
commit b016243fdd25010c24333aa504f62dd4e1905e09
Author: Baraa Atta (Dev Exalt) <exalt.dev.team@gmail.com>
HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
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.
> diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> index 90b0184df777f..a41fac1b4d82d 100644
> --- a/drivers/hid/hid-logitech-hidpp.c
> +++ b/drivers/hid/hid-logitech-hidpp.c
[ ... ]
> @@ -4423,6 +4427,379 @@ 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.
> + *
> + * Devices that implement this feature expose a per-device sysfs attribute
> + * "platform". Writing one of (windows|winemb|linux|chrome|android|
> + * macos|ios|webos|tizen) selects the matching platform descriptor on the device;
> + * reading reports the platform last applied through this attribute, or an empty
> + * line if none has been set.
> + */
[Severity: Low]
This isn't a bug, but the comment mentions that reading reports the platform
last applied. However, the sysfs attribute is declared as Write-Only using
DEVICE_ATTR_WO(platform) further below.
Should this block comment be updated, or was a read function intended to be
included?
[ ... ]
> +static ssize_t platform_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct hidpp_device *hidpp = hid_get_drvdata(hdev);
> + char platform[16];
> + int idx;
> + int ret;
> +
> + strscpy(platform, buf, sizeof(platform));
> + string_lower(platform, platform);
> +
> + idx = sysfs_match_string(multiplatform_names, platform);
> + if (idx < 0)
> + return idx;
> +
> + mutex_lock(&hidpp->multiplatform_lock);
[Severity: High]
Does this create a use-after-destroy regression?
Because devm_device_add_group() is used in hidpp_multiplatform_init(), the
sysfs attribute group is removed only after hidpp_remove() returns.
If hidpp_remove() destroys the multiplatform_lock via mutex_destroy(), could a
concurrent userspace write to the platform sysfs attribute attempt to lock
the destroyed mutex here?
> + ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
> + mutex_unlock(&hidpp->multiplatform_lock);
> + if (ret)
> + return ret;
> +
> + hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
> + multiplatform_names[idx]);
> +
> + return count;
> +}
[ ... ]
> @@ -4517,6 +4895,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);
[Severity: Medium]
Will writes to the sysfs attribute time out here?
By initializing the sysfs attribute group in hidpp_multiplatform_init() and
immediately stopping IO with hid_device_io_stop(), the driver creates a
window where writes from userspace are possible but IO responses are blocked.
If a write happens before hid_device_io_start() is called later in probe,
could hidpp_send_fap_command_sync() wait for a response that never arrives,
causing a -ETIMEDOUT error and dropping packets?
> ret = hid_connect(hdev, connect_mask);
[ ... ]
> @@ -4576,6 +4957,7 @@ static void hidpp_remove(struct hid_device *hdev)
> cancel_work_sync(&hidpp->work);
> cancel_work_sync(&hidpp->reset_hi_res_work);
> mutex_destroy(&hidpp->send_mutex);
> + mutex_destroy(&hidpp->multiplatform_lock);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260625080807.74157-1-exalt.dev.team@gmail.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
2026-06-25 8:18 ` sashiko-bot
@ 2026-06-30 9:48 ` DevExalt
2026-06-30 10:13 ` sashiko-bot
2026-07-01 6:30 ` [PATCH v4] " DevExalt
` (3 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: DevExalt @ 2026-06-30 9:48 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
Changes in v3:
* Address Sashiko review comments.
* Switch to devm_mutex_init() to handle mutex lifecycle management
automatically.
* Move hidpp_multiplatform_init() to the end of hidpp_probe() after
hid_device_io_start() to guarantee that the hardware I/O loop is fully
active when the sysfs attribute becomes visible.
.../testing/sysfs-driver-hid-logitech-hidpp | 29 ++
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-logitech-hidpp.c | 384 ++++++++++++++++++
drivers/hid/hid-quirks.c | 2 +
4 files changed, 417 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..9b963407a975 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,32 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: Jun, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (WO) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..a6c2dea79397 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -913,6 +913,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 90b0184df777..33904f309380 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,9 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4426,385 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
+ */
+
+#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_TIZEN BIT(0)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute. The order of
+ * this array must stay in sync with multiplatform_masks[] below, as the index
+ * returned by sysfs_match_string() is used to look up the matching mask.
+ */
+static const char * const multiplatform_names[] = {
+ "windows", "winemb", "linux", "chrome",
+ "android", "macos", "ios", "webos", "tizen", NULL
+};
+
+static const u16 multiplatform_masks[] = {
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single HIDPP_MULTIPLATFORM_PLATFORM_MASK_* bit to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ idx = sysfs_match_string(multiplatform_names, platform);
+ if (idx < 0)
+ return idx;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ ret = devm_mutex_init(&hdev->dev, &hidpp->multiplatform_lock);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to init mutex (err=%d)\n", ret);
+ hidpp->multiplatform_feature_index = 0;
+ return;
+ }
+
+ ret = devm_device_add_group(&hdev->dev, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4545,6 +4927,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
}
+ hidpp_multiplatform_init(hidpp);
+
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 57d8efdd9b89..577af0075e27 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -537,6 +537,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v3] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-30 9:48 ` [PATCH v3] " DevExalt
@ 2026-06-30 10:13 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-30 10:13 UTC (permalink / raw)
To: DevExalt; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Use-after-destroy of `hidpp->send_mutex` and operations on stopped hardware due to delayed sysfs group removal.
--
commit 55433f57873d505e62b905c239d0015edd306ae5
Author: Baraa Atta (Dev Exalt) <exalt.dev.team@gmail.com>
HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
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.
> diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> index 90b0184df777f..33904f3093803 100644
> --- a/drivers/hid/hid-logitech-hidpp.c
> +++ b/drivers/hid/hid-logitech-hidpp.c
[ ... ]
> @@ -4780,6 +4780,26 @@ static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
> {
> struct hid_device *hdev = hidpp->hid_dev;
> u8 feat_index;
> int ret;
>
> ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
> if (ret)
> return;
>
> hidpp->multiplatform_feature_index = feat_index;
>
> ret = devm_mutex_init(&hdev->dev, &hidpp->multiplatform_lock);
> if (ret) {
> hid_warn(hdev,
> "Multiplatform: Failed to init mutex (err=%d)\n", ret);
> hidpp->multiplatform_feature_index = 0;
> return;
> }
>
> ret = devm_device_add_group(&hdev->dev, &multiplatform_attribute_group);
[Severity: High]
Does this create a use-after-destroy race condition during device unbind?
Because devm_device_add_group() is used, the sysfs group is scheduled for
removal during the devres phase, which runs after the driver's remove callback
returns.
In hidpp_remove(), the hardware state is explicitly torn down and the mutex is
destroyed:
drivers/hid/hid-logitech-hidpp.c:hidpp_remove() {
...
hid_hw_stop(hdev);
cancel_work_sync(&hidpp->work);
cancel_work_sync(&hidpp->reset_hi_res_work);
mutex_destroy(&hidpp->send_mutex);
}
The sysfs attribute remains accessible to userspace during and shortly after
hidpp_remove() runs. A concurrent write to the platform sysfs attribute could
trigger platform_store(), which eventually attempts to lock the destroyed
hidpp->send_mutex and perform I/O on stopped hardware.
> if (ret) {
> hid_warn(hdev,
> "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
> hidpp->multiplatform_feature_index = 0;
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260630094838.50471-1-exalt.dev.team@gmail.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v4] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
2026-06-25 8:18 ` sashiko-bot
2026-06-30 9:48 ` [PATCH v3] " DevExalt
@ 2026-07-01 6:30 ` DevExalt
2026-07-01 6:43 ` sashiko-bot
2026-07-01 7:37 ` [PATCH v5] " DevExalt
` (2 subsequent siblings)
5 siblings, 1 reply; 13+ messages in thread
From: DevExalt @ 2026-07-01 6:30 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
Changes in v3:
* Address Sashiko review comments.
* Switch to devm_mutex_init() to handle mutex lifecycle management
automatically.
* Move hidpp_multiplatform_init() to the end of hidpp_probe() after
hid_device_io_start() to guarantee that the hardware I/O loop is fully
active when the sysfs attribute becomes visible.
Changes in v4:
* Address Sashiko review comments.
* Fix a potential use-after-destroy race condition during device unbind
by replacing devm_device_add_group() with manual sysfs_create_group()
management, ensuring the sysfs attribute is removed before the
associated device resources and send_mutex are destroyed.
.../testing/sysfs-driver-hid-logitech-hidpp | 29 ++
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-logitech-hidpp.c | 399 ++++++++++++++++++
drivers/hid/hid-quirks.c | 2 +
4 files changed, 432 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..41f0fd8c2192 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,32 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: Jul, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (WO) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..a6c2dea79397 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -913,6 +913,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 90b0184df777..c631f5fe0153 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,9 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4426,398 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
+ */
+
+#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_TIZEN BIT(0)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute. The order of
+ * this array must stay in sync with multiplatform_masks[] below, as the index
+ * returned by sysfs_match_string() is used to look up the matching mask.
+ */
+static const char * const multiplatform_names[] = {
+ "windows", "winemb", "linux", "chrome",
+ "android", "macos", "ios", "webos", "tizen", NULL
+};
+
+static const u16 multiplatform_masks[] = {
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single HIDPP_MULTIPLATFORM_PLATFORM_MASK_* bit to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ idx = sysfs_match_string(multiplatform_names, platform);
+ if (idx < 0)
+ return idx;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ mutex_init(&hidpp->multiplatform_lock);
+
+ ret = sysfs_create_group(&hdev->dev.kobj, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ mutex_destroy(&hidpp->multiplatform_lock);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
+/**
+ * hidpp_multiplatform_cleanup() - Cleanup HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Removes the sysfs attribute group for platform selection and destroys the mutex
+ * used for synchronizing access to the Multi-Platform feature.
+ * This function should be called during device removal or driver cleanup to ensure
+ * proper resource management.
+ */
+static void hidpp_multiplatform_cleanup(struct hidpp_device *hidpp)
+{
+ if (!hidpp->multiplatform_feature_index)
+ return;
+
+ sysfs_remove_group(&hidpp->hid_dev->dev.kobj, &multiplatform_attribute_group);
+ mutex_destroy(&hidpp->multiplatform_lock);
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4545,6 +4940,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
}
+ hidpp_multiplatform_init(hidpp);
+
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
@@ -4557,6 +4954,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_hw_open_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
@@ -4570,6 +4968,7 @@ static void hidpp_remove(struct hid_device *hdev)
if (!hidpp)
return hid_hw_stop(hdev);
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
hid_hw_stop(hdev);
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 57d8efdd9b89..577af0075e27 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -537,6 +537,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v4] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-07-01 6:30 ` [PATCH v4] " DevExalt
@ 2026-07-01 6:43 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-07-01 6:43 UTC (permalink / raw)
To: DevExalt; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] FAP command macros incorrectly embed a hardcoded Client ID, violating the subsystem's software ID convention.
--
commit 18d7a2d2e6622c1a347a9e072d766882d23cb46c
Author: Baraa Atta (Dev Exalt) <exalt.dev.team@gmail.com>
HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
Adds support in the Logitech HID++ driver for the HID++ Multi-Platform
feature (0x4531), introducing a per-device sysfs attribute "platform" to
allow selecting a target operating system platform.
> diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
> index 90b0184df777f..b3b846c8bdaf8 100644
> --- a/drivers/hid/hid-logitech-hidpp.c
> +++ b/drivers/hid/hid-logitech-hidpp.c
[ ... ]
> @@ -4423,6 +4426,398 @@ static bool hidpp_application_equals(struct hid_device *hdev,
> return report && report->application == application;
> }
>
> +/* -------------------------------------------------------------------------- */
> +/* 0x4531: Multi-Platform Support */
> +/* -------------------------------------------------------------------------- */
[ ... ]
> +#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
[Severity: Medium]
Do these FAP command constants incorrectly have the Client ID 0xF baked into
the lower nibble? By subsystem design, they should specify only the function
index (e.g., 0x00, 0x10, 0x30).
Because these constants hardcode 0xF, when hidpp_send_fap_command_sync() is
called to format the message:
drivers/hid/hid-logitech-hidpp.c:hidpp_send_fap_command_sync() {
...
message->fap.funcindex_clientid = funcindex_clientid | LINUX_KERNEL_SW_ID;
...
}
The bitwise OR with LINUX_KERNEL_SW_ID (which is 1) will result in 0xF,
bypassing the driver's intentional Linux Kernel Software ID allocation.
Could this incorrect Client ID cause the device to misattribute state changes
or fail to route events back to the kernel properly?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701063022.29547-1-exalt.dev.team@gmail.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v5] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
` (2 preceding siblings ...)
2026-07-01 6:30 ` [PATCH v4] " DevExalt
@ 2026-07-01 7:37 ` DevExalt
2026-07-15 14:01 ` dev exalt
` (2 more replies)
2026-08-02 11:50 ` [PATCH v6] " DevExalt
2026-08-02 12:01 ` [PATCH v7] " DevExalt
5 siblings, 3 replies; 13+ messages in thread
From: DevExalt @ 2026-07-01 7:37 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
Changes in v3:
* Address Sashiko review comments.
* Switch to devm_mutex_init() to handle mutex lifecycle management
automatically.
* Move hidpp_multiplatform_init() to the end of hidpp_probe() after
hid_device_io_start() to guarantee that the hardware I/O loop is fully
active when the sysfs attribute becomes visible.
Changes in v4:
* Address Sashiko review comments.
* Fix a potential use-after-destroy race condition during device unbind
by replacing devm_device_add_group() with manual sysfs_create_group()
management, ensuring the sysfs attribute is removed before the
associated device resources and send_mutex are destroyed.
Changes in v5:
* Address Sashiko review comments.
* Fix a sub system design violation by removing the hardcoded 0xF client
ID from the lower nibble of the FAP command macros.
.../testing/sysfs-driver-hid-logitech-hidpp | 29 ++
drivers/hid/hid-ids.h | 2 +
drivers/hid/hid-logitech-hidpp.c | 399 ++++++++++++++++++
drivers/hid/hid-quirks.c | 2 +
4 files changed, 432 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..41f0fd8c2192 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,32 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: Jul, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (WO) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 1059922baaac..a6c2dea79397 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -913,6 +913,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 90b0184df777..39edb22b55e1 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,9 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4426,398 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
+ */
+
+#define HIDPP_MULTIPLATFORM_FEAT_ID 0x4531
+#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO 0x00
+#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR 0x10
+#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM 0x30
+
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN BIT(0)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
+#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute. The order of
+ * this array must stay in sync with multiplatform_masks[] below, as the index
+ * returned by sysfs_match_string() is used to look up the matching mask.
+ */
+static const char * const multiplatform_names[] = {
+ "windows", "winemb", "linux", "chrome",
+ "android", "macos", "ios", "webos", "tizen", NULL
+};
+
+static const u16 multiplatform_masks[] = {
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
+ HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single HIDPP_MULTIPLATFORM_PLATFORM_MASK_* bit to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ idx = sysfs_match_string(multiplatform_names, platform);
+ if (idx < 0)
+ return idx;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ mutex_init(&hidpp->multiplatform_lock);
+
+ ret = sysfs_create_group(&hdev->dev.kobj, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ mutex_destroy(&hidpp->multiplatform_lock);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
+/**
+ * hidpp_multiplatform_cleanup() - Cleanup HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Removes the sysfs attribute group for platform selection and destroys the mutex
+ * used for synchronizing access to the Multi-Platform feature.
+ * This function should be called during device removal or driver cleanup to ensure
+ * proper resource management.
+ */
+static void hidpp_multiplatform_cleanup(struct hidpp_device *hidpp)
+{
+ if (!hidpp->multiplatform_feature_index)
+ return;
+
+ sysfs_remove_group(&hidpp->hid_dev->dev.kobj, &multiplatform_attribute_group);
+ mutex_destroy(&hidpp->multiplatform_lock);
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4545,6 +4940,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
}
+ hidpp_multiplatform_init(hidpp);
+
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
@@ -4557,6 +4954,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_hw_open_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
@@ -4570,6 +4968,7 @@ static void hidpp_remove(struct hid_device *hdev)
if (!hidpp)
return hid_hw_stop(hdev);
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
hid_hw_stop(hdev);
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 57d8efdd9b89..577af0075e27 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -537,6 +537,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
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v5] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-07-01 7:37 ` [PATCH v5] " DevExalt
@ 2026-07-15 14:01 ` dev exalt
2026-07-27 14:04 ` Bastien Nocera
2026-07-29 15:02 ` Bastien Nocera
2 siblings, 0 replies; 13+ messages in thread
From: dev exalt @ 2026-07-15 14:01 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor
Dear maintainers,
We would like to kindly follow up on the latest revision of this
patch, as we have not yet received any feedback.
This patch is the latest revision of the work previously discussed here:
https://lore.kernel.org/linux-input/20251215125319.33261-1-exalt.dev.team@gmail.com/
This version incorporates the feedback and suggestions from the
previous discussion and updates the implementation accordingly.
We understand that maintainers are busy, so we are just sending a
gentle reminder in case this patch has not yet had a chance to be
reviewed.
Thank you very much for your time and review.
Best regards,
Baraa Atta
On Wed, Jul 1, 2026 at 10:37 AM DevExalt <exalt.dev.team@gmail.com> wrote:
>
> 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.
>
> This patch:
> * Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
> * Introduces the per-device sysfs attribute "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 selected platform when valid.
> * Leaves the device unchanged when an unsupported platform is requested.
>
> Supported values for the platform sysfs attribute:
>
> windows, winemb, linux, chrome, android,
> macos, ios, webos, tizen
>
> TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
> * Feature 0x4531 is detected.
> * Valid platform values written through sysfs 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>
> ---
> Changes in v2:
> * Replace the global hidpp_platform module parameter with a per-device
> sysfs attribute
> * Expose all platforms supported by the HID++ Multi-Platform feature
> * Update documentation and testing description
>
> Changes in v3:
> * Address Sashiko review comments.
> * Switch to devm_mutex_init() to handle mutex lifecycle management
> automatically.
> * Move hidpp_multiplatform_init() to the end of hidpp_probe() after
> hid_device_io_start() to guarantee that the hardware I/O loop is fully
> active when the sysfs attribute becomes visible.
>
> Changes in v4:
> * Address Sashiko review comments.
> * Fix a potential use-after-destroy race condition during device unbind
> by replacing devm_device_add_group() with manual sysfs_create_group()
> management, ensuring the sysfs attribute is removed before the
> associated device resources and send_mutex are destroyed.
>
> Changes in v5:
> * Address Sashiko review comments.
> * Fix a sub system design violation by removing the hardcoded 0xF client
> ID from the lower nibble of the FAP command macros.
>
> .../testing/sysfs-driver-hid-logitech-hidpp | 29 ++
> drivers/hid/hid-ids.h | 2 +
> drivers/hid/hid-logitech-hidpp.c | 399 ++++++++++++++++++
> drivers/hid/hid-quirks.c | 2 +
> 4 files changed, 432 insertions(+)
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
> index d8f831f2d6b5..41f0fd8c2192 100644
> --- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
> +++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
> @@ -17,3 +17,32 @@ Description:
> handling battery properties in the kernel. This way, upower can
> add a udev rule to decide whether or not it should use the
> internal unifying support or the generic kernel one.
> +
> +What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
> +Date: Jul, 2026
> +KernelVersion: 7.2
> +Contact: linux-input@vger.kernel.org
> +Description:
> + (WO) This attribute is present only on Logitech HID++ 2.0 devices
> + that implement feature 0x4531 (Multi-Platform). It allows the host
> + to select which operating-system platform the device should emulate,
> + altering its key mapping and behaviour accordingly.
> +
> + Writing one of the following platform names programs the device:
> +
> + =========== ======================================================
> + windows Standard Windows key layout
> + winemb Windows Embedded key layout
> + linux Linux key layout
> + chrome ChromeOS key layout
> + android Android key layout
> + macos macOS key layout
> + ios iOS key layout
> + webos webOS key layout
> + tizen Tizen key layout
> + =========== ======================================================
> +
> + Only platforms advertised by the device's own descriptors are
> + accepted. The input is case-insensitive. Writing an unknown
> + platform name returns -EINVAL; writing a valid name that the
> + device does not expose in its descriptors returns -EOPNOTSUPP.
> diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
> index 1059922baaac..a6c2dea79397 100644
> --- a/drivers/hid/hid-ids.h
> +++ b/drivers/hid/hid-ids.h
> @@ -913,6 +913,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 90b0184df777..39edb22b55e1 100644
> --- a/drivers/hid/hid-logitech-hidpp.c
> +++ b/drivers/hid/hid-logitech-hidpp.c
> @@ -209,6 +209,9 @@ struct hidpp_device {
> int hires_wheel_multiplier;
> u8 hires_wheel_feature_index;
>
> + u8 multiplatform_feature_index;
> + struct mutex multiplatform_lock;
> +
> bool connected_once;
> };
>
> @@ -4423,6 +4426,398 @@ 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.
> + *
> + * Devices that implement this feature expose a per-device sysfs attribute
> + * "platform". Writing one of (windows|winemb|linux|chrome|android|
> + * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
> + */
> +
> +#define HIDPP_MULTIPLATFORM_FEAT_ID 0x4531
> +#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO 0x00
> +#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR 0x10
> +#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM 0x30
> +
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN BIT(0)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
> +
> +struct hidpp_platform_desc {
> + u8 plat_idx;
> + u8 desc_idx;
> + u16 plat_mask;
> +};
> +
> +/*
> + * Platform names exposed through the "platform" sysfs attribute. The order of
> + * this array must stay in sync with multiplatform_masks[] below, as the index
> + * returned by sysfs_match_string() is used to look up the matching mask.
> + */
> +static const char * const multiplatform_names[] = {
> + "windows", "winemb", "linux", "chrome",
> + "android", "macos", "ios", "webos", "tizen", NULL
> +};
> +
> +static const u16 multiplatform_masks[] = {
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
> +};
> +
> +/**
> + * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
> + * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
> + *
> + * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
> + * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
> + * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
> + * based on the HID++ specification error codes.
> + *
> + * This is used to ensure that functions interacting with the Multi-Platform feature can
> + * return consistent Linux error codes even when they encounter errors defined by the HID++
> + * protocol when the platform is set from the sysfs attribute.
> + *
> + * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
> + * already a Linux errno.
> + */
> +static int hidpp_multiplatform_errno(int err)
> +{
> + if (err <= 0)
> + return err;
> +
> + switch (err) {
> + case HIDPP20_ERROR_INVALID_ARGS:
> + case HIDPP20_ERROR_OUT_OF_RANGE:
> + case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
> + case HIDPP20_ERROR_INVALID_FUNCTION_ID:
> + return -EINVAL;
> + case HIDPP20_ERROR_NOT_ALLOWED:
> + return -EPERM;
> + case HIDPP20_ERROR_BUSY:
> + return -EBUSY;
> + case HIDPP20_ERROR_UNSUPPORTED:
> + return -EOPNOTSUPP;
> + case HIDPP20_ERROR_HW_ERROR:
> + case HIDPP20_ERROR_UNKNOWN:
> + default:
> + return -EIO;
> + }
> +}
> +
> +/**
> + * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
> + *
> + * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
> + * matching @plat_mask; or another negative Linux errno on transport
> + * 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 mask 0x%04x",
> + *plat_index, plat_mask);
> + return 0;
> + }
> + }
> +
> + hid_dbg(hdev,
> + "Multiplatform: No matching platform descriptor for mask 0x%04x",
> + plat_mask);
> + return -EOPNOTSUPP;
> +}
> +
> +/**
> + * 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 a negative Linux errno 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 hidpp_multiplatform_errno(ret);
> +}
> +
> +/**
> + * hidpp_multiplatform_set_platform() - Apply a platform to the device
> + * @hidpp: Pointer to the hidpp_device instance
> + * @mask: A single HIDPP_MULTIPLATFORM_PLATFORM_MASK_* bit to apply
> + *
> + * Looks up the device's platform descriptor whose platform mask matches @mask
> + * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
> + *
> + * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
> + * 0x4531 or exposes no descriptor matching @mask, or another negative
> + * Linux errno from the underlying HID++ command.
> + */
> +static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
> +{
> + u8 plat_index;
> + int ret;
> +
> + if (!hidpp->multiplatform_feature_index)
> + return -EOPNOTSUPP;
> +
> + ret = hidpp_multiplatform_get_platform_index(hidpp,
> + hidpp->multiplatform_feature_index, mask, &plat_index);
> + if (ret)
> + return ret;
> +
> + ret = hidpp_multiplatform_update_device_platform(hidpp,
> + hidpp->multiplatform_feature_index, plat_index);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * platform_store() - Set the device platform based on user input
> + * @dev: Pointer to the device instance
> + * @attr: Pointer to the device attribute
> + * @buf: Buffer containing the platform name string
> + * @count: Size of the input buffer
> + *
> + * Parses the platform name from the input buffer, converts it to a platform mask,
> + * and applies it to the device using the HID++ Multi-Platform feature. The function
> + * handles errors gracefully, returning appropriate Linux errno values if the input
> + * is invalid or if the device does not support the requested platform.
> + *
> + * Return: Number of bytes consumed from the input buffer on success, or a negative
> + * Linux errno on failure.
> + */
> +static ssize_t platform_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct hid_device *hdev = to_hid_device(dev);
> + struct hidpp_device *hidpp = hid_get_drvdata(hdev);
> + char platform[16];
> + int idx;
> + int ret;
> +
> + strscpy(platform, buf, sizeof(platform));
> + string_lower(platform, platform);
> +
> + idx = sysfs_match_string(multiplatform_names, platform);
> + if (idx < 0)
> + return idx;
> +
> + mutex_lock(&hidpp->multiplatform_lock);
> + ret = hidpp_multiplatform_set_platform(hidpp, multiplatform_masks[idx]);
> + mutex_unlock(&hidpp->multiplatform_lock);
> + if (ret)
> + return ret;
> +
> + hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
> + multiplatform_names[idx]);
> +
> + return count;
> +}
> +
> +static DEVICE_ATTR_WO(platform);
> +
> +static struct attribute *multiplatform_attrs[] = {
> + &dev_attr_platform.attr,
> + NULL
> +};
> +
> +static const struct attribute_group multiplatform_attribute_group = {
> + .attrs = multiplatform_attrs,
> +};
> +
> +/**
> + * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
> + * @hidpp: Pointer to the hidpp_device instance
> + *
> + * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
> + * initializes the hidpp_device structure to track the feature index and creates the
> + * corresponding sysfs attribute group for platform selection.
> + */
> +static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
> +{
> + struct hid_device *hdev = hidpp->hid_dev;
> + u8 feat_index;
> + int ret;
> +
> + ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
> + if (ret)
> + return;
> +
> + hidpp->multiplatform_feature_index = feat_index;
> +
> + mutex_init(&hidpp->multiplatform_lock);
> +
> + ret = sysfs_create_group(&hdev->dev.kobj, &multiplatform_attribute_group);
> + if (ret) {
> + hid_warn(hdev,
> + "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
> + mutex_destroy(&hidpp->multiplatform_lock);
> + hidpp->multiplatform_feature_index = 0;
> + }
> +}
> +
> +/**
> + * hidpp_multiplatform_cleanup() - Cleanup HID++ Multi-Platform support
> + * @hidpp: Pointer to the hidpp_device instance
> + *
> + * Removes the sysfs attribute group for platform selection and destroys the mutex
> + * used for synchronizing access to the Multi-Platform feature.
> + * This function should be called during device removal or driver cleanup to ensure
> + * proper resource management.
> + */
> +static void hidpp_multiplatform_cleanup(struct hidpp_device *hidpp)
> +{
> + if (!hidpp->multiplatform_feature_index)
> + return;
> +
> + sysfs_remove_group(&hidpp->hid_dev->dev.kobj, &multiplatform_attribute_group);
> + mutex_destroy(&hidpp->multiplatform_lock);
> +}
> +
> static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
> {
> struct hidpp_device *hidpp;
> @@ -4545,6 +4940,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
> }
> }
>
> + hidpp_multiplatform_init(hidpp);
> +
> /*
> * This relies on logi_dj_ll_close() being a no-op so that DJ connection
> * events will still be received.
> @@ -4557,6 +4954,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
> hid_hw_open_fail:
> hid_hw_stop(hdev);
> hid_hw_start_fail:
> + hidpp_multiplatform_cleanup(hidpp);
> sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
> cancel_work_sync(&hidpp->work);
> mutex_destroy(&hidpp->send_mutex);
> @@ -4570,6 +4968,7 @@ static void hidpp_remove(struct hid_device *hdev)
> if (!hidpp)
> return hid_hw_stop(hdev);
>
> + hidpp_multiplatform_cleanup(hidpp);
> sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
>
> hid_hw_stop(hdev);
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index 57d8efdd9b89..577af0075e27 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -537,6 +537,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
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v5] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-07-01 7:37 ` [PATCH v5] " DevExalt
2026-07-15 14:01 ` dev exalt
@ 2026-07-27 14:04 ` Bastien Nocera
2026-07-29 15:02 ` Bastien Nocera
2 siblings, 0 replies; 13+ messages in thread
From: Bastien Nocera @ 2026-07-27 14:04 UTC (permalink / raw)
To: DevExalt, jikos, bentiss
Cc: lains, linux-input, linux-kernel, sari.kreitem, hbarnor
On Wed, 2026-07-01 at 10:37 +0300, DevExalt wrote:
> From: "Baraa Atta (Dev Exalt)" <exalt.dev.team@gmail.com>
>
<snip>
> +
> +#define HIDPP_MULTIPLATFORM_FEAT_ID 0x4531
> +#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO 0x00
> +#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR 0x10
> +#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM 0x30
> +
> +#define
> HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN BIT(0)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS BIT(8)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB BIT(9)
> +#define
> HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX BIT(10)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME BIT(11)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID BIT(12)
> +#define
> HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS BIT(13)
> +#define HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS BIT(14)
> +#define
> HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS BIT(15)
> +
> +struct hidpp_platform_desc {
> + u8 plat_idx;
> + u8 desc_idx;
> + u16 plat_mask;
> +};
> +
> +/*
> + * Platform names exposed through the "platform" sysfs attribute.
> The order of
> + * this array must stay in sync with multiplatform_masks[] below, as
> the index
> + * returned by sysfs_match_string() is used to look up the matching
> mask.
> + */
> +static const char * const multiplatform_names[] = {
> + "windows", "winemb", "linux", "chrome",
> + "android", "macos", "ios", "webos", "tizen", NULL
> +};
> +
> +static const u16 multiplatform_masks[] = {
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_LINUX,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_CHROME,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_ANDROID,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_MACOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_IOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_WEBOS,
> + HIDPP_MULTIPLATFORM_PLATFORM_MASK_TIZEN,
Wouldn't something like:
static enum multiplatform_masks = {
HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS = 8,
HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINEMB = 9,
...
};
followed by:
static const char * const multiplatform_names[] = {
[HIDPP_MULTIPLATFORM_PLATFORM_MASK_WINDOWS] = "windows",
...
};
work? It makes the array a little bit bigger, but not hugely.
That way it completely negates the need to keep 2 arrays, and both of
them in sync.
> diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
> index 57d8efdd9b89..577af0075e27 100644
> --- a/drivers/hid/hid-quirks.c
> +++ b/drivers/hid/hid-quirks.c
> @@ -537,6 +537,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) },
That hunk shouldn't be necessary (since
e04a0442d33b8cf183bba38646447b891bb02123) unless the device was
absolutely unusable without the HID++ driver.
Rest looks fine, I'll try to test it on my Slim Solar+.
Cheers
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v5] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-07-01 7:37 ` [PATCH v5] " DevExalt
2026-07-15 14:01 ` dev exalt
2026-07-27 14:04 ` Bastien Nocera
@ 2026-07-29 15:02 ` Bastien Nocera
2 siblings, 0 replies; 13+ messages in thread
From: Bastien Nocera @ 2026-07-29 15:02 UTC (permalink / raw)
To: DevExalt, jikos, bentiss
Cc: lains, linux-input, linux-kernel, sari.kreitem, hbarnor
Hey,
On Wed, 2026-07-01 at 10:37 +0300, DevExalt wrote:
> +static DEVICE_ATTR_WO(platform);
> +
> +static struct attribute *multiplatform_attrs[] = {
> + &dev_attr_platform.attr,
> + NULL
> +};
> +
> +static const struct attribute_group multiplatform_attribute_group =
> {
> + .attrs = multiplatform_attrs,
> +};
I just realised that even though you retrieve the list of supported
platforms in the code to check whether it's supported, we don't have
any ways to read it. It would really be useful if that was fixed before
merging, it makes verifying that it works as expected much easier,
including whether it can tell user-space that the value changed too.
I tested this with a recent K980 Slim Solar+ keyboard, using the info
in this PDF to switch between macOS, Windows and Chrome using the
keyboard:
https://gzhls.at/blob/ldb/e/3/7/1/4d91f22c1050443f6235092b49270fd021e9.pdf
Shortcuts to switch between platforms:
macos: fn + O
windows: fn + P
chrome: fn + C
The F7 / screenshot key looks like it's something that changes usefully
between platforms:
- macOS: Shift+Win+4
- Windows: Shift+Win+S
- Chrome: SysRq
Testing by between all 3 platforms using both the keyboard keys, and
the sysfs attribute yielded the same results with the changing key
combination generated by the F7 key.
Happy to test again when you have read support added to the sysfs
attribute.
Cheers
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v6] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
` (3 preceding siblings ...)
2026-07-01 7:37 ` [PATCH v5] " DevExalt
@ 2026-08-02 11:50 ` DevExalt
2026-08-02 11:58 ` sashiko-bot
2026-08-02 12:01 ` [PATCH v7] " DevExalt
5 siblings, 1 reply; 13+ messages in thread
From: DevExalt @ 2026-08-02 11:50 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
Changes in v3:
* Address Sashiko review comments.
* Switch to devm_mutex_init() to handle mutex lifecycle management
automatically.
* Move hidpp_multiplatform_init() to the end of hidpp_probe() after
hid_device_io_start() to guarantee that the hardware I/O loop is fully
active when the sysfs attribute becomes visible.
Changes in v4:
* Address Sashiko review comments.
* Fix a potential use-after-destroy race condition during device unbind
by replacing devm_device_add_group() with manual sysfs_create_group()
management, ensuring the sysfs attribute is removed before the
associated device resources and send_mutex are destroyed.
Changes in v5:
* Address Sashiko review comments.
* Fix a sub system design violation by removing the hardcoded 0xF client
ID from the lower nibble of the FAP command macros.
Changes in V6:
* Address Bastien review comments.
* Index platform names by their platform mask values instead of maintaining
parallel mask and name arrays.
* Remove the Casa Keys and MX Keys S keyboard entries from hid-quirks.c
* Make the platform sysfs attribute read/write, with the read operation
exposing the operating systems corresponding to the device's current
platform index for easier verification and testing.
.../testing/sysfs-driver-hid-logitech-hidpp | 36 ++
drivers/hid/hid-logitech-hidpp.c | 579 ++++++++++++++++++
2 files changed, 615 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..3263d8473993 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,39 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: Aug, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (RW) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Reading it returns the platform the device is currently configured
+ with for this host. A platform may cover several operating systems,
+ in which case all of their names are listed, separated by spaces
+ (for example "windows linux android").
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
+ Reading returns -ENODATA when the device reports no platform
+ configured for this host.
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 90b0184df777..6ec27c5afc64 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,9 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4426,578 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
+ * Reading it reports the platform currently selected by the device as the
+ * space separated list of the platform names the selected platform descriptor covers.
+ */
+
+#define HIDPP_MULTIPLATFORM_FEAT_ID 0x4531
+#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO 0x00
+#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR 0x10
+#define HIDPP_MULTIPLATFORM_GET_CURRENT_PLATFORM 0x20
+#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM 0x30
+
+/* Host index selecting the host the device is currently connected to. */
+#define HIDPP_MULTIPLATFORM_HOST_CURRENT 0xFF
+
+/* Platform index reported for a host that has no platform configured. */
+#define HIDPP_MULTIPLATFORM_PLAT_IDX_UNDEFINED 0xFF
+
+/*
+ * Bit positions of the platforms within the 16-bit platform mask reported by
+ * the platform descriptors. The mask of a given platform is BIT(value).
+ */
+enum hidpp_multiplatform_platform {
+ HIDPP_MULTIPLATFORM_PLATFORM_TIZEN = 0,
+ HIDPP_MULTIPLATFORM_PLATFORM_WINDOWS = 8,
+ HIDPP_MULTIPLATFORM_PLATFORM_WINEMB = 9,
+ HIDPP_MULTIPLATFORM_PLATFORM_LINUX = 10,
+ HIDPP_MULTIPLATFORM_PLATFORM_CHROME = 11,
+ HIDPP_MULTIPLATFORM_PLATFORM_ANDROID = 12,
+ HIDPP_MULTIPLATFORM_PLATFORM_MACOS = 13,
+ HIDPP_MULTIPLATFORM_PLATFORM_IOS = 14,
+ HIDPP_MULTIPLATFORM_PLATFORM_WEBOS = 15,
+};
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute, indexed by
+ * their bit position in the platform mask. Bit positions the specification
+ * does not assign a platform to are left as NULL holes and must be skipped
+ * when walking the array.
+ */
+static const char * const multiplatform_names[] = {
+ [HIDPP_MULTIPLATFORM_PLATFORM_TIZEN] = "tizen",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WINDOWS] = "windows",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WINEMB] = "winemb",
+ [HIDPP_MULTIPLATFORM_PLATFORM_LINUX] = "linux",
+ [HIDPP_MULTIPLATFORM_PLATFORM_CHROME] = "chrome",
+ [HIDPP_MULTIPLATFORM_PLATFORM_ANDROID] = "android",
+ [HIDPP_MULTIPLATFORM_PLATFORM_MACOS] = "macos",
+ [HIDPP_MULTIPLATFORM_PLATFORM_IOS] = "ios",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WEBOS] = "webos",
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * hidpp_multiplatform_get_platform_mask() - Find the platform mask for a platform index
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_index: Platform index to find the platform mask of
+ * @plat_mask: Pointer to store the found platform mask
+ *
+ * A platform is the union of one or more platform descriptors, each covering
+ * its own set of operating systems. This walks all platform descriptors exposed
+ * by the device and accumulates the platform masks of every descriptor
+ * belonging to @plat_index, yielding the full set of platforms covered by it.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * belonging to @plat_index; or another negative Linux errno on
+ * transport failure.
+ */
+static int hidpp_multiplatform_get_platform_mask(struct hidpp_device *hidpp,
+ u8 feat_index, u8 plat_index,
+ u16 *plat_mask)
+{
+ int i;
+ int ret;
+ u8 num_desc;
+ u16 mask = 0;
+ 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_idx == plat_index)
+ mask |= pdesc.plat_mask;
+ }
+
+ if (!mask) {
+ hid_dbg(hdev,
+ "Multiplatform: No platform descriptor for platform index %d",
+ plat_index);
+ return -EOPNOTSUPP;
+ }
+
+ hid_dbg(hdev, "Multiplatform: Platform index %d covers mask 0x%04x",
+ plat_index, mask);
+ *plat_mask = mask;
+
+ return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_current_platform_index() - Query the device current platform index
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_index: Pointer to store the platform index reported by the device
+ *
+ * Sends the HID++ Multi-Platform 'GET_CURRENT_PLATFORM' command for the host
+ * the device is currently connected to and stores the platform index it reports
+ * in @plat_index.
+ *
+ * Return: 0 on success; -ENODATA if the host has no platform configured; or
+ * another negative Linux errno on failure.
+ */
+static int hidpp_multiplatform_get_current_platform_index(struct hidpp_device *hidpp,
+ u8 feat_index, u8 *plat_index)
+{
+ int ret;
+ struct hidpp_report response;
+ u8 params[1] = { HIDPP_MULTIPLATFORM_HOST_CURRENT };
+ struct hid_device *hdev = hidpp->hid_dev;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feat_index,
+ HIDPP_MULTIPLATFORM_GET_CURRENT_PLATFORM,
+ params, sizeof(params), &response);
+ if (ret) {
+ hid_warn(hdev, "Multiplatform: GET_CURRENT_PLATFORM failed (err=%d)", ret);
+ return hidpp_multiplatform_errno(ret);
+ }
+
+ if (response.fap.params[2] == HIDPP_MULTIPLATFORM_PLAT_IDX_UNDEFINED) {
+ hid_dbg(hdev, "Multiplatform: Current host has no platform configured");
+ return -ENODATA;
+ }
+
+ *plat_index = response.fap.params[2];
+ hid_dbg(hdev, "Multiplatform: Current platform index is %d", *plat_index);
+
+ return 0;
+}
+
+/**
+ * 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 a negative Linux errno 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;
+ u8 params[2] = { HIDPP_MULTIPLATFORM_HOST_CURRENT, 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single BIT(HIDPP_MULTIPLATFORM_PLATFORM_*) mask to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_current_platform_mask() - Query the device current platform mask
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: Pointer to store the device platform mask in use
+ *
+ * Asks the device which platform is configured for the current host and
+ * resolves it into the mask of operating systems that platform covers.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor for its current platform, or another
+ * negative Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_get_current_platform_mask(struct hidpp_device *hidpp, u16 *mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_current_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, &plat_index);
+ if (ret)
+ return ret;
+
+ return hidpp_multiplatform_get_platform_mask(hidpp,
+ hidpp->multiplatform_feature_index, plat_index, mask);
+}
+
+/**
+ * platform_show() - Report the device's current platform
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer to store the platform names in
+ *
+ * Queries the platform mask the device is configured with for the current host and
+ * emits the names of all the platforms it covers as a space separated list.
+ *
+ * Return: Number of bytes written to @buf on success, or a negative Linux errno
+ * on failure.
+ */
+static ssize_t platform_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ unsigned long mask;
+ unsigned int idx;
+ int len = 0;
+ u16 plat_mask;
+ int ret;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_get_current_platform_mask(hidpp, &plat_mask);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ /* Mask bits the specification assigns no platform to are skipped. */
+ mask = plat_mask;
+ for_each_set_bit(idx, &mask, ARRAY_SIZE(multiplatform_names))
+ if (multiplatform_names[idx])
+ len += sysfs_emit_at(buf, len, "%s%s",
+ len ? " " : "", multiplatform_names[idx]);
+
+ return len + sysfs_emit_at(buf, len, "\n");
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ unsigned int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ /*
+ * multiplatform_names[] contains NULL holes, so sysfs_match_string()
+ * cannot be used here: it stops at the first NULL entry.
+ */
+ for (idx = 0; idx < ARRAY_SIZE(multiplatform_names); idx++)
+ if (multiplatform_names[idx] &&
+ sysfs_streq(multiplatform_names[idx], platform))
+ break;
+
+ if (idx == ARRAY_SIZE(multiplatform_names))
+ return -EINVAL;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, BIT(idx));
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ mutex_init(&hidpp->multiplatform_lock);
+
+ ret = sysfs_create_group(&hdev->dev.kobj, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ mutex_destroy(&hidpp->multiplatform_lock);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
+/**
+ * hidpp_multiplatform_cleanup() - Cleanup HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Removes the sysfs attribute group for platform selection and destroys the mutex
+ * used for synchronizing access to the Multi-Platform feature.
+ * This function should be called during device removal or driver cleanup to ensure
+ * proper resource management.
+ */
+static void hidpp_multiplatform_cleanup(struct hidpp_device *hidpp)
+{
+ if (!hidpp->multiplatform_feature_index)
+ return;
+
+ sysfs_remove_group(&hidpp->hid_dev->dev.kobj, &multiplatform_attribute_group);
+ mutex_destroy(&hidpp->multiplatform_lock);
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4545,6 +5120,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
}
+ hidpp_multiplatform_init(hidpp);
+
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
@@ -4557,6 +5134,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_hw_open_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
@@ -4570,6 +5148,7 @@ static void hidpp_remove(struct hid_device *hdev)
if (!hidpp)
return hid_hw_stop(hdev);
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
hid_hw_stop(hdev);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v6] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-08-02 11:50 ` [PATCH v6] " DevExalt
@ 2026-08-02 11:58 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-08-02 11:58 UTC (permalink / raw)
To: DevExalt; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The commit message claims to add specific device IDs and remove quirks, but the patch does not modify any device ID tables or quirks file.
--
--- Patch [1]: [PATCH v6] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) ---
commit 2283951914fe82c9f82d572a2c1e9090262e500b
Author: Baraa Atta (Dev Exalt) <exalt.dev.team@gmail.com>
HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
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.
> This patch:
> * Adds device IDs for MX Keys S (046d) and Casa Keys (046d).
[Severity: Medium]
The commit message claims to add specific device IDs for MX Keys S and Casa
Keys, and the v6 changelog mentions removing them from hid-quirks.c, but the
patch diff does not modify any device ID tables or quirks files. Were these
changes accidentally dropped during a rebase or intended for a separate patch?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260802115009.90840-1-exalt.dev.team@gmail.com?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v7] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531)
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
` (4 preceding siblings ...)
2026-08-02 11:50 ` [PATCH v6] " DevExalt
@ 2026-08-02 12:01 ` DevExalt
5 siblings, 0 replies; 13+ messages in thread
From: DevExalt @ 2026-08-02 12:01 UTC (permalink / raw)
To: jikos, bentiss
Cc: lains, hadess, linux-input, linux-kernel, sari.kreitem, hbarnor,
Baraa Atta (Dev Exalt)
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.
This patch:
* Introduces the per-device sysfs attribute "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 selected platform when valid.
* Leaves the device unchanged when an unsupported platform is requested.
Supported values for the platform sysfs attribute:
windows, winemb, linux, chrome, android,
macos, ios, webos, tizen
TEST=Pair MX Keys S and Casa Keys over Bluetooth and verify:
* Feature 0x4531 is detected.
* Valid platform values written through sysfs 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>
---
Changes in v2:
* Replace the global hidpp_platform module parameter with a per-device
sysfs attribute
* Expose all platforms supported by the HID++ Multi-Platform feature
* Update documentation and testing description
Changes in v3:
* Address Sashiko review comments.
* Switch to devm_mutex_init() to handle mutex lifecycle management
automatically.
* Move hidpp_multiplatform_init() to the end of hidpp_probe() after
hid_device_io_start() to guarantee that the hardware I/O loop is fully
active when the sysfs attribute becomes visible.
Changes in v4:
* Address Sashiko review comments.
* Fix a potential use-after-destroy race condition during device unbind
by replacing devm_device_add_group() with manual sysfs_create_group()
management, ensuring the sysfs attribute is removed before the
associated device resources and send_mutex are destroyed.
Changes in v5:
* Address Sashiko review comments.
* Fix a sub system design violation by removing the hardcoded 0xF client
ID from the lower nibble of the FAP command macros.
Changes in V6:
* Address Bastien review comments.
* Index platform names by their platform mask values instead of maintaining
parallel mask and name arrays.
* Remove the Casa Keys and MX Keys S keyboard entries from hid-quirks.c
* Make the platform sysfs attribute read/write, with the read operation
exposing the operating systems corresponding to the device's current
platform index for easier verification and testing.
Changes in V7:
* Address Sashiko review comments.
* Update the commit message.
.../testing/sysfs-driver-hid-logitech-hidpp | 36 ++
drivers/hid/hid-logitech-hidpp.c | 579 ++++++++++++++++++
2 files changed, 615 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
index d8f831f2d6b5..3263d8473993 100644
--- a/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
+++ b/Documentation/ABI/testing/sysfs-driver-hid-logitech-hidpp
@@ -17,3 +17,39 @@ Description:
handling battery properties in the kernel. This way, upower can
add a udev rule to decide whether or not it should use the
internal unifying support or the generic kernel one.
+
+What: /sys/bus/hid/drivers/logitech-hidpp-device/<dev>/platform
+Date: Aug, 2026
+KernelVersion: 7.2
+Contact: linux-input@vger.kernel.org
+Description:
+ (RW) This attribute is present only on Logitech HID++ 2.0 devices
+ that implement feature 0x4531 (Multi-Platform). It allows the host
+ to select which operating-system platform the device should emulate,
+ altering its key mapping and behaviour accordingly.
+
+ Reading it returns the platform the device is currently configured
+ with for this host. A platform may cover several operating systems,
+ in which case all of their names are listed, separated by spaces
+ (for example "windows linux android").
+
+ Writing one of the following platform names programs the device:
+
+ =========== ======================================================
+ windows Standard Windows key layout
+ winemb Windows Embedded key layout
+ linux Linux key layout
+ chrome ChromeOS key layout
+ android Android key layout
+ macos macOS key layout
+ ios iOS key layout
+ webos webOS key layout
+ tizen Tizen key layout
+ =========== ======================================================
+
+ Only platforms advertised by the device's own descriptors are
+ accepted. The input is case-insensitive. Writing an unknown
+ platform name returns -EINVAL; writing a valid name that the
+ device does not expose in its descriptors returns -EOPNOTSUPP.
+ Reading returns -ENODATA when the device reports no platform
+ configured for this host.
diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c
index 90b0184df777..7f4bbb6b62ca 100644
--- a/drivers/hid/hid-logitech-hidpp.c
+++ b/drivers/hid/hid-logitech-hidpp.c
@@ -209,6 +209,9 @@ struct hidpp_device {
int hires_wheel_multiplier;
u8 hires_wheel_feature_index;
+ u8 multiplatform_feature_index;
+ struct mutex multiplatform_lock;
+
bool connected_once;
};
@@ -4423,6 +4426,578 @@ 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.
+ *
+ * Devices that implement this feature expose a per-device sysfs attribute
+ * "platform". Writing one of (windows|winemb|linux|chrome|android|
+ * macos|ios|webos|tizen) selects the matching platform descriptor on the device.
+ * Reading it reports the platform currently selected by the device as the
+ * space separated list of the platform names the selected platform descriptor covers.
+ */
+
+#define HIDPP_MULTIPLATFORM_FEAT_ID 0x4531
+#define HIDPP_MULTIPLATFORM_GET_FEATURE_INFO 0x00
+#define HIDPP_MULTIPLATFORM_GET_PLATFORM_DESCRIPTOR 0x10
+#define HIDPP_MULTIPLATFORM_GET_CURRENT_PLATFORM 0x20
+#define HIDPP_MULTIPLATFORM_SET_CURRENT_PLATFORM 0x30
+
+/* Host index selecting the host the device is currently connected to. */
+#define HIDPP_MULTIPLATFORM_HOST_CURRENT 0xFF
+
+/* Platform index reported for a host that has no platform configured. */
+#define HIDPP_MULTIPLATFORM_PLAT_IDX_UNDEFINED 0xFF
+
+/*
+ * Bit positions of the platforms within the 16-bit platform mask reported by
+ * the platform descriptors. The mask of a given platform is BIT(value).
+ */
+enum hidpp_multiplatform_platform {
+ HIDPP_MULTIPLATFORM_PLATFORM_TIZEN = 0,
+ HIDPP_MULTIPLATFORM_PLATFORM_WINDOWS = 8,
+ HIDPP_MULTIPLATFORM_PLATFORM_WINEMB = 9,
+ HIDPP_MULTIPLATFORM_PLATFORM_LINUX = 10,
+ HIDPP_MULTIPLATFORM_PLATFORM_CHROME = 11,
+ HIDPP_MULTIPLATFORM_PLATFORM_ANDROID = 12,
+ HIDPP_MULTIPLATFORM_PLATFORM_MACOS = 13,
+ HIDPP_MULTIPLATFORM_PLATFORM_IOS = 14,
+ HIDPP_MULTIPLATFORM_PLATFORM_WEBOS = 15,
+};
+
+struct hidpp_platform_desc {
+ u8 plat_idx;
+ u8 desc_idx;
+ u16 plat_mask;
+};
+
+/*
+ * Platform names exposed through the "platform" sysfs attribute, indexed by
+ * their bit position in the platform mask. Bit positions the specification
+ * does not assign a platform to are left as NULL holes and must be skipped
+ * when walking the array.
+ */
+static const char * const multiplatform_names[] = {
+ [HIDPP_MULTIPLATFORM_PLATFORM_TIZEN] = "tizen",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WINDOWS] = "windows",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WINEMB] = "winemb",
+ [HIDPP_MULTIPLATFORM_PLATFORM_LINUX] = "linux",
+ [HIDPP_MULTIPLATFORM_PLATFORM_CHROME] = "chrome",
+ [HIDPP_MULTIPLATFORM_PLATFORM_ANDROID] = "android",
+ [HIDPP_MULTIPLATFORM_PLATFORM_MACOS] = "macos",
+ [HIDPP_MULTIPLATFORM_PLATFORM_IOS] = "ios",
+ [HIDPP_MULTIPLATFORM_PLATFORM_WEBOS] = "webos",
+};
+
+/**
+ * hidpp_multiplatform_errno() - Convert HID++ protocol error codes to Linux errno
+ * @err: HID++ protocol error code (positive) or Linux errno (negative or zero)
+ *
+ * Converts a HID++ protocol error code to the corresponding Linux errno. If @err is
+ * already a negative or zero Linux errno, it is returned unchanged. Otherwise, if @err
+ * is a positive HID++ error code, it is mapped to the appropriate negative Linux errno
+ * based on the HID++ specification error codes.
+ *
+ * This is used to ensure that functions interacting with the Multi-Platform feature can
+ * return consistent Linux error codes even when they encounter errors defined by the HID++
+ * protocol when the platform is set from the sysfs attribute.
+ *
+ * Return: Negative Linux errno corresponding to the HID++ error code, or @err if it is
+ * already a Linux errno.
+ */
+static int hidpp_multiplatform_errno(int err)
+{
+ if (err <= 0)
+ return err;
+
+ switch (err) {
+ case HIDPP20_ERROR_INVALID_ARGS:
+ case HIDPP20_ERROR_OUT_OF_RANGE:
+ case HIDPP20_ERROR_INVALID_FEATURE_INDEX:
+ case HIDPP20_ERROR_INVALID_FUNCTION_ID:
+ return -EINVAL;
+ case HIDPP20_ERROR_NOT_ALLOWED:
+ return -EPERM;
+ case HIDPP20_ERROR_BUSY:
+ return -EBUSY;
+ case HIDPP20_ERROR_UNSUPPORTED:
+ return -EOPNOTSUPP;
+ case HIDPP20_ERROR_HW_ERROR:
+ case HIDPP20_ERROR_UNKNOWN:
+ default:
+ return -EIO;
+ }
+}
+
+/**
+ * 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 a negative Linux errno 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 hidpp_multiplatform_errno(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 a negative Linux errno 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 hidpp_multiplatform_errno(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.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * matching @plat_mask; or another negative Linux errno on transport
+ * 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 mask 0x%04x",
+ *plat_index, plat_mask);
+ return 0;
+ }
+ }
+
+ hid_dbg(hdev,
+ "Multiplatform: No matching platform descriptor for mask 0x%04x",
+ plat_mask);
+ return -EOPNOTSUPP;
+}
+
+/**
+ * hidpp_multiplatform_get_platform_mask() - Find the platform mask for a platform index
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_index: Platform index to find the platform mask of
+ * @plat_mask: Pointer to store the found platform mask
+ *
+ * A platform is the union of one or more platform descriptors, each covering
+ * its own set of operating systems. This walks all platform descriptors exposed
+ * by the device and accumulates the platform masks of every descriptor
+ * belonging to @plat_index, yielding the full set of platforms covered by it.
+ *
+ * Return: 0 on success; -EOPNOTSUPP if the device exposes no descriptor
+ * belonging to @plat_index; or another negative Linux errno on
+ * transport failure.
+ */
+static int hidpp_multiplatform_get_platform_mask(struct hidpp_device *hidpp,
+ u8 feat_index, u8 plat_index,
+ u16 *plat_mask)
+{
+ int i;
+ int ret;
+ u8 num_desc;
+ u16 mask = 0;
+ 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_idx == plat_index)
+ mask |= pdesc.plat_mask;
+ }
+
+ if (!mask) {
+ hid_dbg(hdev,
+ "Multiplatform: No platform descriptor for platform index %d",
+ plat_index);
+ return -EOPNOTSUPP;
+ }
+
+ hid_dbg(hdev, "Multiplatform: Platform index %d covers mask 0x%04x",
+ plat_index, mask);
+ *plat_mask = mask;
+
+ return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_current_platform_index() - Query the device current platform index
+ * @hidpp: Pointer to the hidpp_device instance
+ * @feat_index: Feature index of the Multi-Platform feature
+ * @plat_index: Pointer to store the platform index reported by the device
+ *
+ * Sends the HID++ Multi-Platform 'GET_CURRENT_PLATFORM' command for the host
+ * the device is currently connected to and stores the platform index it reports
+ * in @plat_index.
+ *
+ * Return: 0 on success; -ENODATA if the host has no platform configured; or
+ * another negative Linux errno on failure.
+ */
+static int hidpp_multiplatform_get_current_platform_index(struct hidpp_device *hidpp,
+ u8 feat_index, u8 *plat_index)
+{
+ int ret;
+ struct hidpp_report response;
+ u8 params[1] = { HIDPP_MULTIPLATFORM_HOST_CURRENT };
+ struct hid_device *hdev = hidpp->hid_dev;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feat_index,
+ HIDPP_MULTIPLATFORM_GET_CURRENT_PLATFORM,
+ params, sizeof(params), &response);
+ if (ret) {
+ hid_warn(hdev, "Multiplatform: GET_CURRENT_PLATFORM failed (err=%d)", ret);
+ return hidpp_multiplatform_errno(ret);
+ }
+
+ if (response.fap.params[2] == HIDPP_MULTIPLATFORM_PLAT_IDX_UNDEFINED) {
+ hid_dbg(hdev, "Multiplatform: Current host has no platform configured");
+ return -ENODATA;
+ }
+
+ *plat_index = response.fap.params[2];
+ hid_dbg(hdev, "Multiplatform: Current platform index is %d", *plat_index);
+
+ return 0;
+}
+
+/**
+ * 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 a negative Linux errno 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;
+ u8 params[2] = { HIDPP_MULTIPLATFORM_HOST_CURRENT, 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 hidpp_multiplatform_errno(ret);
+}
+
+/**
+ * hidpp_multiplatform_set_platform() - Apply a platform to the device
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: A single BIT(HIDPP_MULTIPLATFORM_PLATFORM_*) mask to apply
+ *
+ * Looks up the device's platform descriptor whose platform mask matches @mask
+ * and instructs the device to switch to it via SET_CURRENT_PLATFORM.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor matching @mask, or another negative
+ * Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_set_platform(struct hidpp_device *hidpp, u16 mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, mask, &plat_index);
+ if (ret)
+ return ret;
+
+ ret = hidpp_multiplatform_update_device_platform(hidpp,
+ hidpp->multiplatform_feature_index, plat_index);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * hidpp_multiplatform_get_current_platform_mask() - Query the device current platform mask
+ * @hidpp: Pointer to the hidpp_device instance
+ * @mask: Pointer to store the device platform mask in use
+ *
+ * Asks the device which platform is configured for the current host and
+ * resolves it into the mask of operating systems that platform covers.
+ *
+ * Return: 0 on success, -EOPNOTSUPP if the device does not implement feature
+ * 0x4531 or exposes no descriptor for its current platform, or another
+ * negative Linux errno from the underlying HID++ command.
+ */
+static int hidpp_multiplatform_get_current_platform_mask(struct hidpp_device *hidpp, u16 *mask)
+{
+ u8 plat_index;
+ int ret;
+
+ if (!hidpp->multiplatform_feature_index)
+ return -EOPNOTSUPP;
+
+ ret = hidpp_multiplatform_get_current_platform_index(hidpp,
+ hidpp->multiplatform_feature_index, &plat_index);
+ if (ret)
+ return ret;
+
+ return hidpp_multiplatform_get_platform_mask(hidpp,
+ hidpp->multiplatform_feature_index, plat_index, mask);
+}
+
+/**
+ * platform_show() - Report the device's current platform
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer to store the platform names in
+ *
+ * Queries the platform mask the device is configured with for the current host and
+ * emits the names of all the platforms it covers as a space separated list.
+ *
+ * Return: Number of bytes written to @buf on success, or a negative Linux errno
+ * on failure.
+ */
+static ssize_t platform_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ unsigned long mask;
+ unsigned int idx;
+ int len = 0;
+ u16 plat_mask;
+ int ret;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_get_current_platform_mask(hidpp, &plat_mask);
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ /* Mask bits the specification assigns no platform to are skipped. */
+ mask = plat_mask;
+ for_each_set_bit(idx, &mask, ARRAY_SIZE(multiplatform_names))
+ if (multiplatform_names[idx])
+ len += sysfs_emit_at(buf, len, "%s%s",
+ len ? " " : "", multiplatform_names[idx]);
+
+ return len + sysfs_emit_at(buf, len, "\n");
+}
+
+/**
+ * platform_store() - Set the device platform based on user input
+ * @dev: Pointer to the device instance
+ * @attr: Pointer to the device attribute
+ * @buf: Buffer containing the platform name string
+ * @count: Size of the input buffer
+ *
+ * Parses the platform name from the input buffer, converts it to a platform mask,
+ * and applies it to the device using the HID++ Multi-Platform feature. The function
+ * handles errors gracefully, returning appropriate Linux errno values if the input
+ * is invalid or if the device does not support the requested platform.
+ *
+ * Return: Number of bytes consumed from the input buffer on success, or a negative
+ * Linux errno on failure.
+ */
+static ssize_t platform_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hid_device *hdev = to_hid_device(dev);
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ char platform[16];
+ unsigned int idx;
+ int ret;
+
+ strscpy(platform, buf, sizeof(platform));
+ string_lower(platform, platform);
+
+ /*
+ * multiplatform_names[] contains NULL holes, so sysfs_match_string()
+ * cannot be used here: it stops at the first NULL entry.
+ */
+ for (idx = 0; idx < ARRAY_SIZE(multiplatform_names); idx++)
+ if (multiplatform_names[idx] &&
+ sysfs_streq(multiplatform_names[idx], platform))
+ break;
+
+ if (idx == ARRAY_SIZE(multiplatform_names))
+ return -EINVAL;
+
+ mutex_lock(&hidpp->multiplatform_lock);
+ ret = hidpp_multiplatform_set_platform(hidpp, BIT(idx));
+ mutex_unlock(&hidpp->multiplatform_lock);
+ if (ret)
+ return ret;
+
+ hid_dbg(hdev, "Multiplatform: Device platform set to '%s'\n",
+ multiplatform_names[idx]);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(platform);
+
+static struct attribute *multiplatform_attrs[] = {
+ &dev_attr_platform.attr,
+ NULL
+};
+
+static const struct attribute_group multiplatform_attribute_group = {
+ .attrs = multiplatform_attrs,
+};
+
+/**
+ * hidpp_multiplatform_init() - Initialize HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Checks if the device supports the HID++ Multi-Platform feature (0x4531) and, if so,
+ * initializes the hidpp_device structure to track the feature index and creates the
+ * corresponding sysfs attribute group for platform selection.
+ */
+static void hidpp_multiplatform_init(struct hidpp_device *hidpp)
+{
+ struct hid_device *hdev = hidpp->hid_dev;
+ u8 feat_index;
+ int ret;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_MULTIPLATFORM_FEAT_ID, &feat_index);
+ if (ret)
+ return;
+
+ hidpp->multiplatform_feature_index = feat_index;
+
+ mutex_init(&hidpp->multiplatform_lock);
+
+ ret = sysfs_create_group(&hdev->dev.kobj, &multiplatform_attribute_group);
+ if (ret) {
+ hid_warn(hdev,
+ "Multiplatform: Failed to create sysfs group (err=%d)\n", ret);
+ mutex_destroy(&hidpp->multiplatform_lock);
+ hidpp->multiplatform_feature_index = 0;
+ }
+}
+
+/**
+ * hidpp_multiplatform_cleanup() - Cleanup HID++ Multi-Platform support
+ * @hidpp: Pointer to the hidpp_device instance
+ *
+ * Removes the sysfs attribute group for platform selection and destroys the mutex
+ * used for synchronizing access to the Multi-Platform feature.
+ * This function should be called during device removal or driver cleanup to ensure
+ * proper resource management.
+ */
+static void hidpp_multiplatform_cleanup(struct hidpp_device *hidpp)
+{
+ if (!hidpp->multiplatform_feature_index)
+ return;
+
+ sysfs_remove_group(&hidpp->hid_dev->dev.kobj, &multiplatform_attribute_group);
+ mutex_destroy(&hidpp->multiplatform_lock);
+}
+
static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
struct hidpp_device *hidpp;
@@ -4545,6 +5120,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
}
}
+ hidpp_multiplatform_init(hidpp);
+
/*
* This relies on logi_dj_ll_close() being a no-op so that DJ connection
* events will still be received.
@@ -4557,6 +5134,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_hw_open_fail:
hid_hw_stop(hdev);
hid_hw_start_fail:
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
cancel_work_sync(&hidpp->work);
mutex_destroy(&hidpp->send_mutex);
@@ -4570,6 +5148,7 @@ static void hidpp_remove(struct hid_device *hdev)
if (!hidpp)
return hid_hw_stop(hdev);
+ hidpp_multiplatform_cleanup(hidpp);
sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group);
hid_hw_stop(hdev);
--
2.34.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-02 12:02 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-25 8:08 [PATCH v2] HID: logitech-hidpp: Add support for HID++ Multi-Platform feature (0x4531) DevExalt
2026-06-25 8:18 ` sashiko-bot
2026-06-30 9:48 ` [PATCH v3] " DevExalt
2026-06-30 10:13 ` sashiko-bot
2026-07-01 6:30 ` [PATCH v4] " DevExalt
2026-07-01 6:43 ` sashiko-bot
2026-07-01 7:37 ` [PATCH v5] " DevExalt
2026-07-15 14:01 ` dev exalt
2026-07-27 14:04 ` Bastien Nocera
2026-07-29 15:02 ` Bastien Nocera
2026-08-02 11:50 ` [PATCH v6] " DevExalt
2026-08-02 11:58 ` sashiko-bot
2026-08-02 12:01 ` [PATCH v7] " DevExalt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox