Skip to contentSkip to conversation

HID: logitech-hidpp: fix Signature M650 side button timing

PATCH Superseded Elliot Douglas 4 participants 16 messages Jun 13 – Aug 12

Subsystems (3)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

cover HID: logitech-hidpp: fix Signature M650 side button timing

The Logitech Signature M650 over Bluetooth exposes its side buttons in the normal mouse report, but the reported BTN_SIDE/BTN_EXTRA events are short click-like events emitted around button release rather than physical press/release events with the real hold duration. The device appears to reserve the held side-button state for a built-in gesture mode: holding a side button long enough, or holding it while using the wheel for horizontal scrolling, can mean the normal mouse report never emits a usable side-button press at all. That makes the buttons unusable for standard Linux hold actions such as push-to-talk, drag modifiers, or remapping rules that depend on key-up timing.

When HID++ 2.0 feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, temporarily diverts the same controls, the device sends diverted-control notifications with real press and release timing. This series adds quirk-gated support for those notifications and enables it for the Bluetooth Signature M650.

Before enabling diversion, the driver verifies that each mapped control is present in the device's HID++ control table and is advertised as a divertable mouse control.

The diverted M650 controls are reported as BTN_BACK and BTN_FORWARD. Logitech's Signature M650 getting-started page labels these physical controls as Back/Forward buttons and describes their default page-navigation behavior:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

The reprogrammable-control support is per-product and parses the full HID++ divertedButtonsEvent pressed-control list, so it can support devices with more buttons without relying on a single last-control release heuristic. Only the Signature M650 opts in for now. Other Logitech devices should only be enabled after their HID++ control IDs and divertedButtonsEvent behavior are captured and verified.

There is evidence that this is not unique to the M650. A prior MX Anywhere 3 patch used the same HID++ feature to fix thumb buttons that only activated on release, and Logitech documents side-button + wheel horizontal scrolling for both the MX Anywhere 3/3S and Signature M650. Solaar's device reports and rules documentation also show HID++ divertable back/forward controls on MX Master 3 and MX Master 3S class devices. This series remains conservative and only enables the device tested here.

Tested with a Logitech Signature M650 L over Bluetooth, HID ID 0005:046D:B02A. Baseline evtest showed short release-time BTN_SIDE/BTN_EXTRA events. Earlier local testing of the same HID++ diversion path showed real hold-duration press/release events, including holds longer than 4 seconds for both buttons.

Elliot Douglas (2):
HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

 drivers/hid/hid-logitech-hidpp.c | 236 ++++++++++++++++++++++++++++++-
 1 file changed, 235 insertions(+), 1 deletion(-)

base-commit: f0866517be9345d8245d32b722574b8aecccb348

Signature · 1 line
2.54.0
#1 lore raw

PATCH 1/2 HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control.

Keep the control mappings in per-product profiles so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices.

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here.

---
 drivers/hid/hid-logitech-hidpp.c | 215 +++++++++++++++++++++++++++++++
 1 file changed, 215 insertions(+)
drivers/hid/hid-logitech-hidpp.c +215 -0
index 70ba1a5e40d8..24c9cfaa4f37 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0		BIT(28)
 #define HIDPP_QUIRK_WIRELESS_STATUS		BIT(29)
 #define HIDPP_QUIRK_RESET_HI_RES_SCROLL		BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS	BIT(31)
 
 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
 	struct hidpp_scroll_counter vertical_wheel_counter;
 
 	u8 wireless_feature_index;
+	u8 reprog_controls_feature_index;
 
 	int hires_wheel_multiplier;
 	u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 	return 1;
 }
 
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls                                           */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4			0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT			0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO		0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING	0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE		BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT		BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED	BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT	BIT(1)
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED		0x00
+
+struct hidpp_reprog_control_mapping {
+	u16 control;
+	u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+	const struct hidpp_reprog_control_mapping *mappings;
+	unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
+{
+	return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+					  &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+						u8 index, u16 *control,
+						u8 *flags)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+					  sizeof(index), &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	*control = get_unaligned_be16(&response.fap.params[0]);
+	*flags = response.fap.params[4];
+
+	return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+						 u16 control)
+{
+	int count, ret;
+	u16 cid;
+	u8 flags;
+	int i;
+
+	count = hidpp20_reprog_controls_get_count(hidpp);
+	if (count < 0)
+		return false;
+
+	for (i = 0; i < count; i++) {
+		ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+							   &flags);
+		if (ret)
+			return false;
+
+		if (cid == control)
+			return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+			       (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+	}
+
+	return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+							 u16 control, u8 flags)
+{
+	struct hidpp_report response;
+	u8 params[5];
+
+	put_unaligned_be16(control, &params[0]);
+	params[2] = flags;
+	put_unaligned_be16(control, &params[3]);
+
+	return hidpp_send_fap_command_sync(hidpp,
+					   hidpp->reprog_controls_feature_index,
+					   HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+					   params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+		   HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+	unsigned int i;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+		return;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);
+	if (!profile)
+		return;
+
+	if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+				   &hidpp->reprog_controls_feature_index))
+		return;
+
+	for (i = 0; i < profile->mapping_count; i++) {
+		u16 control = profile->mappings[i].control;
+
+		if (!hidpp20_reprog_controls_find_control(hidpp, control))
+			continue;
+
+		hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+	}
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+					     u8 *data, int size)
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	const struct hidpp_reprog_control_mapping *mapping;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	u16 controls[4];
+	bool pressed;
+	unsigned int i, j;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+	    !hidpp->input ||
+	    hidpp->reprog_controls_feature_index == 0xff)
+		return 0;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);
+	if (!profile)
+		return 0;
+
+	if (size < HIDPP_REPORT_LONG_LENGTH ||
+	    report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+	    report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(controls); i++)
+		controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+	for (i = 0; i < profile->mapping_count; i++) {
+		mapping = &profile->mappings[i];
+		pressed = false;
+
+		for (j = 0; j < ARRAY_SIZE(controls); j++) {
+			if (controls[j] == mapping->control) {
+				pressed = true;
+				break;
+			}
+		}
+
+		input_report_key(hidpp->input, mapping->code, pressed);
+	}
+
+	input_sync(hidpp->input);
+
+	return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+						   struct input_dev *input_dev)
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	unsigned int i;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);
+	if (!profile)
+		return;
+
+	for (i = 0; i < profile->mapping_count; i++)
+		input_set_capability(input_dev, EV_KEY, profile->mappings[i].code);
+}
+
 static void hidpp10_extra_mouse_buttons_populate_input(
 			struct hidpp_device *hidpp, struct input_dev *input_dev)
 {
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
 
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
 		hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
+
+	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+		hidpp20_reprog_controls_populate_input(hidpp, input);
 }
 
 static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 			return ret;
 	}
 
+	ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+	if (ret != 0)
+		return ret;
+
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
 		ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
 		if (ret != 0)
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct work_struct *work)
 			return;
 	}
 
+	hidpp20_reprog_controls_connect(hidpp);
+
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
 		ret = hidpp10_consumer_keys_connect(hidpp);
 		if (ret)
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	hidpp->hid_dev = hdev;
 	hidpp->name = hdev->name;
 	hidpp->quirks = id->driver_data;
+	hidpp->reprog_controls_feature_index = 0xff;
 	hid_set_drvdata(hdev, hidpp);
 
 	ret = hid_parse(hdev);
Signature · 1 line
2.54.0
#2 lore raw

Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Quoted 4 lines · Elliot Douglas

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Quoted 8 lines

Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control.

Keep the control mappings in per-product profiles so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices.

How does this forced setting work/clash with the programmable buttons in Solaar?

I've added some inline comments below.

Quoted 51 lines

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here.

---
 drivers/hid/hid-logitech-hidpp.c | 215 +++++++++++++++++++++++++++++++

 1 file changed, 215 insertions(+)
drivers/hid/hid-logitech-hidpp.c +1 -0
index 70ba1a5e40d8..24c9cfaa4f37 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
  #define HIDPP_QUIRK_HI_RES_SCROLL_1P0		BIT(28)
  #define HIDPP_QUIRK_WIRELESS_STATUS		BIT(29)
  #define HIDPP_QUIRK_RESET_HI_RES_SCROLL		BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS	BIT(31)


 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -205,6 +206,7 @@ struct hidpp_device {
  	struct hidpp_scroll_counter vertical_wheel_counter;


  u8 wireless_feature_index;

drivers/hid/hid-logitech-hidpp.c +1 -0
+	u8 reprog_controls_feature_index;


  int hires_wheel_multiplier;
  u8 hires_wheel_feature_index;

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3601,6 +3603,209 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
  	return 1;
  }

drivers/hid/hid-logitech-hidpp.c +15 -0
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls                                           */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4			0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT			0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO		0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING	0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE		BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT		BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED	BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT	BIT(1)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED		0x00
+
+struct hidpp_reprog_control_mapping {
+	u16 control;
+	u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+	const struct hidpp_reprog_control_mapping *mappings;

probably needs a __counted_by(), or maybe as it's static, it might be better to not require an intermediate struct, and return a NULL- terminated array instead.

Quoted 91 lines
drivers/hid/hid-logitech-hidpp.c +91 -0
+	unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
+{
+	return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+					  &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+						u8 index, u16 *control,
+						u8 *flags)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+					  sizeof(index), &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	*control = get_unaligned_be16(&response.fap.params[0]);
+	*flags = response.fap.params[4];
+
+	return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+						 u16 control)
+{
+	int count, ret;
+	u16 cid;
+	u8 flags;
+	int i;
+
+	count = hidpp20_reprog_controls_get_count(hidpp);
+	if (count < 0)
+		return false;
+
+	for (i = 0; i < count; i++) {
+		ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+							   &flags);
+		if (ret)
+			return false;
+
+		if (cid == control)
+			return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+			       (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+	}
+
+	return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+							 u16 control, u8 flags)
+{
+	struct hidpp_report response;
+	u8 params[5];
+
+	put_unaligned_be16(control, &params[0]);
+	params[2] = flags;
+	put_unaligned_be16(control, &params[3]);
+
+	return hidpp_send_fap_command_sync(hidpp,
+					   hidpp->reprog_controls_feature_index,
+					   HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+					   params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3731

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+		   HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+	unsigned int i;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+		return;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);

Could the profile be cached in the hidpp_device struct?

Quoted 118 lines
drivers/hid/hid-logitech-hidpp.c +77 -0
+	if (!profile)
+		return;
+
+	if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+				   &hidpp->reprog_controls_feature_index))
+		return;
+
+	for (i = 0; i < profile->mapping_count; i++) {
+		u16 control = profile->mappings[i].control;
+
+		if (!hidpp20_reprog_controls_find_control(hidpp, control))
+			continue;
+
+		hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+	}
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+					     u8 *data, int size)
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	const struct hidpp_reprog_control_mapping *mapping;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	u16 controls[4];
+	bool pressed;
+	unsigned int i, j;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+	    !hidpp->input ||
+	    hidpp->reprog_controls_feature_index == 0xff)
+		return 0;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);
+	if (!profile)
+		return 0;
+
+	if (size < HIDPP_REPORT_LONG_LENGTH ||
+	    report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+	    report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(controls); i++)
+		controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+	for (i = 0; i < profile->mapping_count; i++) {
+		mapping = &profile->mappings[i];
+		pressed = false;
+
+		for (j = 0; j < ARRAY_SIZE(controls); j++) {
+			if (controls[j] == mapping->control) {
+				pressed = true;
+				break;
+			}
+		}
+
+		input_report_key(hidpp->input, mapping->code, pressed);
+	}
+
+	input_sync(hidpp->input);
+
+	return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+						   struct input_dev *input_dev)
+{
+	const struct hidpp_reprog_controls_profile *profile;
+	unsigned int i;
+
+	profile = hidpp20_reprog_controls_get_profile(hidpp);
+	if (!profile)
+		return;
+
+	for (i = 0; i < profile->mapping_count; i++)
+		input_set_capability(input_dev, EV_KEY, profile->mappings[i].code);
+}
+

 static void hidpp10_extra_mouse_buttons_populate_input(
  struct hidpp_device *hidpp, struct input_dev *input_dev) {

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,


  if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) hidpp10_extra_mouse_buttons_populate_input(hidpp, input);

drivers/hid/hid-logitech-hidpp.c +3 -0
+
+	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+		hidpp20_reprog_controls_populate_input(hidpp, input);

 }

 static int hidpp_input_configured(struct hid_device *hdev,

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
  			return ret;
  	}

drivers/hid/hid-logitech-hidpp.c +4 -0
+	ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+	if (ret != 0)
+		return ret;
+

  if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); if (ret != 0)

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct work_struct *work)
  			return;
  	}

drivers/hid/hid-logitech-hidpp.c +2 -0
+	hidpp20_reprog_controls_connect(hidpp);
+

  if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { ret = hidpp10_consumer_keys_connect(hidpp); if (ret)

drivers/hid/hid-logitech-hidpp.c +1 -0
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
  	hidpp->hid_dev = hdev;
  	hidpp->name = hdev->name;
  	hidpp->quirks = id->driver_data;
+	hidpp->reprog_controls_feature_index = 0xff;
  	hid_set_drvdata(hdev, hidpp);


  ret = hid_parse(hdev);

#3 lore raw

Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Thanks, that makes sense.

For Solaar, this is not continuously forced. The kernel only programs temporary diversion when the device connects. Solaar can still issue HID++ commands through hidraw, so if Solaar changes reporting for the same controls afterwards, the last writer wins.

If Solaar takes over those controls for custom actions, the kernel would stop receiving the diverted button notifications for normal evdev reporting until the kernel diverts the controls again, for example after reconnect. While the controls remain diverted, hidraw clients should still receive the raw HID++ reports.

I have addressed the inline comments locally for v2:
- replaced the profile/count wrapper with NULL-terminated mapping arrays
- cached the selected mapping pointer in struct hidpp_device

I'll wait for Benjamin's input to send Patch v2.

Quoted 308 lines · Bastien Nocera
Quoted 4 lines · Elliot Douglas

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Quoted 8 lines

Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control.

Keep the control mappings in per-product profiles so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices.

How does this forced setting work/clash with the programmable buttons in Solaar?

I've added some inline comments below.

Quoted 51 lines

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here.

---
 drivers/hid/hid-logitech-hidpp.c | 215 +++++++++++++++++++++++++++++++
 1 file changed, 215 insertions(+)
drivers/hid/hid-logitech-hidpp.c +17 -0
index 70ba1a5e40d8..24c9cfaa4f37 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0                BIT(28)
 #define HIDPP_QUIRK_WIRELESS_STATUS          BIT(29)
 #define HIDPP_QUIRK_RESET_HI_RES_SCROLL              BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS       BIT(31)
 
 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
      struct hidpp_scroll_counter vertical_wheel_counter;
 
      u8 wireless_feature_index;
+     u8 reprog_controls_feature_index;
 
      int hires_wheel_multiplier;
      u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
      return 1;
 }
 
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls                                           */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4                        0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT                      0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO           0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING  0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE             BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT            BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED     BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT        BIT(1)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED         0x00
+
+struct hidpp_reprog_control_mapping {
+     u16 control;
+     u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+     const struct hidpp_reprog_control_mapping *mappings;

probably needs a __counted_by(), or maybe as it's static, it might be better to not require an intermediate struct, and return a NULL- terminated array instead.

Quoted 91 lines
drivers/hid/hid-logitech-hidpp.c +91 -0
+     unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
+{
+     return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+                                       &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+                                             u8 index, u16 *control,
+                                             u8 *flags)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+                                       sizeof(index), &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     *control = get_unaligned_be16(&response.fap.params[0]);
+     *flags = response.fap.params[4];
+
+     return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+                                              u16 control)
+{
+     int count, ret;
+     u16 cid;
+     u8 flags;
+     int i;
+
+     count = hidpp20_reprog_controls_get_count(hidpp);
+     if (count < 0)
+             return false;
+
+     for (i = 0; i < count; i++) {
+             ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+                                                        &flags);
+             if (ret)
+                     return false;
+
+             if (cid == control)
+                     return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+                            (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+     }
+
+     return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+                                                      u16 control, u8 flags)
+{
+     struct hidpp_report response;
+     u8 params[5];
+
+     put_unaligned_be16(control, &params[0]);
+     params[2] = flags;
+     put_unaligned_be16(control, &params[3]);
+
+     return hidpp_send_fap_command_sync(hidpp,
+                                        hidpp->reprog_controls_feature_index,
+                                        HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+                                        params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3731

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+                HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+     unsigned int i;
+
+     if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+             return;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);

Could the profile be cached in the hidpp_device struct?

Quoted 118 lines
drivers/hid/hid-logitech-hidpp.c +87 -0
+     if (!profile)
+             return;
+
+     if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+                                &hidpp->reprog_controls_feature_index))
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             u16 control = profile->mappings[i].control;
+
+             if (!hidpp20_reprog_controls_find_control(hidpp, control))
+                     continue;
+
+             hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+     }
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+                                          u8 *data, int size)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     const struct hidpp_reprog_control_mapping *mapping;
+     struct hidpp_report *report = (struct hidpp_report *)data;
+     u16 controls[4];
+     bool pressed;
+     unsigned int i, j;
+
+     if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+         !hidpp->input ||
+         hidpp->reprog_controls_feature_index == 0xff)
+             return 0;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return 0;
+
+     if (size < HIDPP_REPORT_LONG_LENGTH ||
+         report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+         report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+             return 0;
+
+     for (i = 0; i < ARRAY_SIZE(controls); i++)
+             controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             mapping = &profile->mappings[i];
+             pressed = false;
+
+             for (j = 0; j < ARRAY_SIZE(controls); j++) {
+                     if (controls[j] == mapping->control) {
+                             pressed = true;
+                             break;
+                     }
+             }
+
+             input_report_key(hidpp->input, mapping->code, pressed);
+     }
+
+     input_sync(hidpp->input);
+
+     return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+                                                struct input_dev *input_dev)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     unsigned int i;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++)
+             input_set_capability(input_dev, EV_KEY, profile->mappings[i].code);
+}
+
 static void hidpp10_extra_mouse_buttons_populate_input(
                      struct hidpp_device *hidpp, struct input_dev *input_dev)
 {
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
 
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
              hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
+
+     if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+             hidpp20_reprog_controls_populate_input(hidpp, input);
 }
 
 static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
                      return ret;
      }
 
+     ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+     if (ret != 0)
+             return ret;
+
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
              if (ret != 0)
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct work_struct *work)
                      return;
      }
 
+     hidpp20_reprog_controls_connect(hidpp);
+
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_connect(hidpp);
              if (ret)
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
      hidpp->hid_dev = hdev;
      hidpp->name = hdev->name;
      hidpp->quirks = id->driver_data;
+     hidpp->reprog_controls_feature_index = 0xff;
      hid_set_drvdata(hdev, hidpp);
 
      ret = hid_parse(hdev);
#4 lore raw

Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Just wanted to poke on this thread again, Benjamin or Bastien, what is needed to push this forward or should I send the v2 at this point?

Thanks,
Elliot

Quoted 330 lines · Elliot Douglas

Thanks, that makes sense.

For Solaar, this is not continuously forced. The kernel only programs temporary diversion when the device connects. Solaar can still issue HID++ commands through hidraw, so if Solaar changes reporting for the same controls afterwards, the last writer wins.

If Solaar takes over those controls for custom actions, the kernel would stop receiving the diverted button notifications for normal evdev reporting until the kernel diverts the controls again, for example after reconnect. While the controls remain diverted, hidraw clients should still receive the raw HID++ reports.

I have addressed the inline comments locally for v2:
- replaced the profile/count wrapper with NULL-terminated mapping arrays
- cached the selected mapping pointer in struct hidpp_device

I'll wait for Benjamin's input to send Patch v2.

Quoted 308 lines · Bastien Nocera
Quoted 4 lines · Elliot Douglas

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Quoted 8 lines

Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control.

Keep the control mappings in per-product profiles so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices.

How does this forced setting work/clash with the programmable buttons in Solaar?

I've added some inline comments below.

Quoted 51 lines

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here.

---
 drivers/hid/hid-logitech-hidpp.c | 215 +++++++++++++++++++++++++++++++
 1 file changed, 215 insertions(+)
drivers/hid/hid-logitech-hidpp.c +17 -0
index 70ba1a5e40d8..24c9cfaa4f37 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0                BIT(28)
 #define HIDPP_QUIRK_WIRELESS_STATUS          BIT(29)
 #define HIDPP_QUIRK_RESET_HI_RES_SCROLL              BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS       BIT(31)
 
 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
      struct hidpp_scroll_counter vertical_wheel_counter;
 
      u8 wireless_feature_index;
+     u8 reprog_controls_feature_index;
 
      int hires_wheel_multiplier;
      u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
      return 1;
 }
 
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls                                           */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4                        0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT                      0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO           0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING  0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE             BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT            BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED     BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT        BIT(1)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED         0x00
+
+struct hidpp_reprog_control_mapping {
+     u16 control;
+     u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+     const struct hidpp_reprog_control_mapping *mappings;

probably needs a __counted_by(), or maybe as it's static, it might be better to not require an intermediate struct, and return a NULL- terminated array instead.

Quoted 91 lines
drivers/hid/hid-logitech-hidpp.c +91 -0
+     unsigned int mapping_count;
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
+{
+     return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+                                       &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+                                             u8 index, u16 *control,
+                                             u8 *flags)
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+                                       sizeof(index), &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;
+
+     *control = get_unaligned_be16(&response.fap.params[0]);
+     *flags = response.fap.params[4];
+
+     return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+                                              u16 control)
+{
+     int count, ret;
+     u16 cid;
+     u8 flags;
+     int i;
+
+     count = hidpp20_reprog_controls_get_count(hidpp);
+     if (count < 0)
+             return false;
+
+     for (i = 0; i < count; i++) {
+             ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+                                                        &flags);
+             if (ret)
+                     return false;
+
+             if (cid == control)
+                     return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+                            (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+     }
+
+     return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+                                                      u16 control, u8 flags)
+{
+     struct hidpp_report response;
+     u8 params[5];
+
+     put_unaligned_be16(control, &params[0]);
+     params[2] = flags;
+     put_unaligned_be16(control, &params[3]);
+
+     return hidpp_send_fap_command_sync(hidpp,
+                                        hidpp->reprog_controls_feature_index,
+                                        HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+                                        params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3731

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+                HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+     unsigned int i;
+
+     if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+             return;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);

Could the profile be cached in the hidpp_device struct?

Quoted 118 lines
drivers/hid/hid-logitech-hidpp.c +87 -0
+     if (!profile)
+             return;
+
+     if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+                                &hidpp->reprog_controls_feature_index))
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             u16 control = profile->mappings[i].control;
+
+             if (!hidpp20_reprog_controls_find_control(hidpp, control))
+                     continue;
+
+             hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+     }
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+                                          u8 *data, int size)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     const struct hidpp_reprog_control_mapping *mapping;
+     struct hidpp_report *report = (struct hidpp_report *)data;
+     u16 controls[4];
+     bool pressed;
+     unsigned int i, j;
+
+     if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+         !hidpp->input ||
+         hidpp->reprog_controls_feature_index == 0xff)
+             return 0;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return 0;
+
+     if (size < HIDPP_REPORT_LONG_LENGTH ||
+         report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+         report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+             return 0;
+
+     for (i = 0; i < ARRAY_SIZE(controls); i++)
+             controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             mapping = &profile->mappings[i];
+             pressed = false;
+
+             for (j = 0; j < ARRAY_SIZE(controls); j++) {
+                     if (controls[j] == mapping->control) {
+                             pressed = true;
+                             break;
+                     }
+             }
+
+             input_report_key(hidpp->input, mapping->code, pressed);
+     }
+
+     input_sync(hidpp->input);
+
+     return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+                                                struct input_dev *input_dev)
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     unsigned int i;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++)
+             input_set_capability(input_dev, EV_KEY, profile->mappings[i].code);
+}
+
 static void hidpp10_extra_mouse_buttons_populate_input(
                      struct hidpp_device *hidpp, struct input_dev *input_dev)
 {
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
 
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
              hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
+
+     if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+             hidpp20_reprog_controls_populate_input(hidpp, input);
 }
 
 static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
                      return ret;
      }
 
+     ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+     if (ret != 0)
+             return ret;
+
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
              if (ret != 0)
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct work_struct *work)
                      return;
      }
 
+     hidpp20_reprog_controls_connect(hidpp);
+
      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_connect(hidpp);
              if (ret)
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
      hidpp->hid_dev = hdev;
      hidpp->name = hdev->name;
      hidpp->quirks = id->driver_data;
+     hidpp->reprog_controls_feature_index = 0xff;
      hid_set_drvdata(hdev, hidpp);
 
      ret = hid_parse(hdev);
#5 lore raw

Re: [PATCH 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Quoted 3 lines · Elliot Douglas

Just wanted to poke on this thread again, Benjamin or Bastien, what is needed to
push this forward or should I send the v2 at this point?

You're definitely better off sending a new version when there's no feedback incoming, it acts as a gentle reminder and avoids another back and forth if there's no further comments (apart from the ones in the original review).

Quoted 412 lines

Thanks,
Elliot

Quoted 406 lines · Elliot Douglas

Thanks, that makes sense.

For Solaar, this is not continuously forced. The kernel only programs
temporary diversion when the device connects. Solaar can still issue HID++
commands through hidraw, so if Solaar changes reporting for the same controls
afterwards, the last writer wins.

If Solaar takes over those controls for custom actions, the kernel would stop
receiving the diverted button notifications for normal evdev reporting until
the kernel diverts the controls again, for example after reconnect. While the
controls remain diverted, hidraw clients should still receive the raw HID++
reports.

I have addressed the inline comments locally for v2:
- replaced the profile/count wrapper with NULL-terminated mapping arrays
- cached the selected mapping pointer in struct hidpp_device

I'll wait for Benjamin's input to send Patch v2.

Quoted 375 lines · Bastien Nocera
Quoted 12 lines · Elliot Douglas

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Add a quirk-gated event path for those controls. The handler temporarily
diverts verified per-product controls, parses divertedButtonsEvent as the
current pressed-control list, and reports the corresponding evdev key state
for every mapped control.

Quoted 5 lines

Keep the control mappings in per-product profiles so adding support for
another mouse does not change the evdev capabilities advertised by
already-supported devices.

How does this forced setting work/clash with the programmable buttons
in Solaar?

I've added some inline comments below.

Quoted 64 lines

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list
of currently pressed diverted buttons, which is the event format handled
here.

Link:
https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html

---
 drivers/hid/hid-logitech-hidpp.c | 215
+++++++++++++++++++++++++++++++

 1 file changed, 215 insertions(+)
drivers/hid/hid-logitech-hidpp.c +2 -0
index 70ba1a5e40d8..24c9cfaa4f37 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
  #define HIDPP_QUIRK_HI_RES_SCROLL_1P0                BIT(28)
  #define HIDPP_QUIRK_WIRELESS_STATUS          BIT(29)
  #define HIDPP_QUIRK_RESET_HI_RES_SCROLL              BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS       BIT(31)
 
  /* These are just aliases for now */
  #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -205,6 +206,7 @@ struct hidpp_device {
       struct hidpp_scroll_counter vertical_wheel_counter;
 
       u8 wireless_feature_index;
+     u8 reprog_controls_feature_index;
 
       int hires_wheel_multiplier;
       u8 hires_wheel_feature_index;
@@ -3601,6 +3603,209 @@ static int

hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
      return 1;
 }

drivers/hid/hid-logitech-hidpp.c +2 -1
+/* -----------------------------------------------------------
--------------- */
+/* HID++2.0 reprogrammable

controls                                           */

drivers/hid/hid-logitech-hidpp.c +3 -1
+/* -----------------------------------------------------------
--------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4                       

0x1b04

drivers/hid/hid-logitech-hidpp.c +2 -0
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT                     

0x00

drivers/hid/hid-logitech-hidpp.c +8 -0
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO           0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING  0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE             BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT            BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED     BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT       

BIT(1)

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 10 lines
drivers/hid/hid-logitech-hidpp.c +10 -0
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED         0x00
+
+struct hidpp_reprog_control_mapping {
+     u16 control;
+     u16 code;
+};
+
+struct hidpp_reprog_controls_profile {
+     const struct hidpp_reprog_control_mapping *mappings;

probably needs a __counted_by(), or maybe as it's static, it might be
better to not require an intermediate struct, and return a NULL- terminated array instead.

Quoted 117 lines

+     unsigned int mapping_count;

drivers/hid/hid-logitech-hidpp.c +4 -0
+};
+
+static const struct hidpp_reprog_controls_profile *
+hidpp20_reprog_controls_get_profile(struct hidpp_device

*hidpp)

drivers/hid/hid-logitech-hidpp.c +5 -0
+{
+     return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct

hidpp_device *hidpp)

drivers/hid/hid-logitech-hidpp.c +7 -0
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index,

cmd, NULL, 0,
+                                       &response); +     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;

drivers/hid/hid-logitech-hidpp.c +5 -0
+
+     return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct

hidpp_device *hidpp,
+                                             u8 index, u16 *control,
+                                             u8 *flags)

drivers/hid/hid-logitech-hidpp.c +7 -0
+{
+     struct hidpp_report response;
+     u8 feature_index = hidpp->reprog_controls_feature_index;
+     u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+     int ret;
+
+     ret = hidpp_send_fap_command_sync(hidpp, feature_index,

cmd, &index,
+                                       sizeof(index), &response);
+     if (ret > 0)
+             return -EPROTO;
+     if (ret)
+             return ret;

drivers/hid/hid-logitech-hidpp.c +8 -0
+
+     *control = get_unaligned_be16(&response.fap.params[0]);
+     *flags = response.fap.params[4];
+
+     return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct

hidpp_device *hidpp,
+                                              u16 control)

drivers/hid/hid-logitech-hidpp.c +12 -0
+{
+     int count, ret;
+     u16 cid;
+     u8 flags;
+     int i;
+
+     count = hidpp20_reprog_controls_get_count(hidpp);
+     if (count < 0)
+             return false;
+
+     for (i = 0; i < count; i++) {
+             ret = hidpp20_reprog_controls_get_cid_info(hidpp,

i, &cid,
+
&flags);
+             if (ret)
+                     return false;
+
+             if (cid == control)
+                     return (flags &
HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+                            (flags &
HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+     }

drivers/hid/hid-logitech-hidpp.c +5 -0
+
+     return false;
+}
+
+static int

hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+                                                      u16 control, u8 flags)

drivers/hid/hid-logitech-hidpp.c +10 -0
+{
+     struct hidpp_report response;
+     u8 params[5];
+
+     put_unaligned_be16(control, &params[0]);
+     params[2] = flags;
+     put_unaligned_be16(control, &params[3]);
+
+     return hidpp_send_fap_command_sync(hidpp,
+                                        hidpp-
Quoted 1 line

reprog_controls_feature_index,

+
HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+                                        params,
sizeof(params), &response);

drivers/hid/hid-logitech-hidpp.c +3 -0
+}
+
+static void hidpp20_reprog_controls_connect(struct

hidpp_device *hidpp)

drivers/hid/hid-logitech-hidpp.c +7 -0
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+                HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+     unsigned int i;
+
+     if (!(hidpp->quirks &
Quoted 4 lines

HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+             return;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);

Could the profile be cached in the hidpp_device struct?

Quoted 145 lines

+     if (!profile)
+             return;
+
+     if (hidpp_root_get_feature(hidpp,
HIDPP_PAGE_REPROG_CONTROLS_V4,
+                                &hidpp-

Quoted 1 line

reprog_controls_feature_index))

+             return;

drivers/hid/hid-logitech-hidpp.c +5 -0
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             u16 control = profile->mappings[i].control;
+
+             if (!hidpp20_reprog_controls_find_control(hidpp,

control))
+                     continue;
+
+ hidpp20_reprog_controls_set_control_reporting(hidpp, control, flags);
+     }

drivers/hid/hid-logitech-hidpp.c +3 -0
+}
+
+static int hidpp20_reprog_controls_raw_event(struct

hidpp_device *hidpp,
+                                          u8 *data, int size) +{
+     const struct hidpp_reprog_controls_profile *profile; +     const struct hidpp_reprog_control_mapping *mapping; +     struct hidpp_report *report = (struct hidpp_report *)data;
+     u16 controls[4];
+     bool pressed;
+     unsigned int i, j;
+
+     if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+         !hidpp->input ||
+         hidpp->reprog_controls_feature_index == 0xff) +             return 0;

drivers/hid/hid-logitech-hidpp.c +7 -0
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return 0;
+
+     if (size < HIDPP_REPORT_LONG_LENGTH ||
+         report->fap.feature_index != hidpp-
Quoted 1 line

reprog_controls_feature_index ||

+         report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+             return 0;
+
+     for (i = 0; i < ARRAY_SIZE(controls); i++) +             controls[i] = get_unaligned_be16(&report-

Quoted 1 line

fap.params[i * 2]);

drivers/hid/hid-logitech-hidpp.c +13 -0
+
+     for (i = 0; i < profile->mapping_count; i++) {
+             mapping = &profile->mappings[i];
+             pressed = false;
+
+             for (j = 0; j < ARRAY_SIZE(controls); j++) {
+                     if (controls[j] == mapping->control) {
+                             pressed = true;
+                             break;
+                     }
+             }
+
+             input_report_key(hidpp->input, mapping->code,

pressed);
+     }

drivers/hid/hid-logitech-hidpp.c +7 -0
+
+     input_sync(hidpp->input);
+
+     return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct

hidpp_device *hidpp,
+                                                struct input_dev *input_dev)

drivers/hid/hid-logitech-hidpp.c +10 -0
+{
+     const struct hidpp_reprog_controls_profile *profile;
+     unsigned int i;
+
+     profile = hidpp20_reprog_controls_get_profile(hidpp);
+     if (!profile)
+             return;
+
+     for (i = 0; i < profile->mapping_count; i++)
+             input_set_capability(input_dev, EV_KEY, profile-
Quoted 1 line

mappings[i].code);

drivers/hid/hid-logitech-hidpp.c +2 -0
+}
+

 static void hidpp10_extra_mouse_buttons_populate_input( struct hidpp_device *hidpp, struct input_dev *input_dev)
 {

drivers/hid/hid-logitech-hidpp.c +3 -0
@@ -3859,6 +4064,9 @@ static void hidpp_populate_input(struct
 hidpp_device *hidpp,
 
       if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
               hidpp10_extra_mouse_buttons_populate_input(hidpp,
 input);
+
+     if (hidpp->quirks &
 HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+             hidpp20_reprog_controls_populate_input(hidpp,

input);
 }

 static int hidpp_input_configured(struct hid_device *hdev,

drivers/hid/hid-logitech-hidpp.c +4 -0
@@ -3971,6 +4179,10 @@ static int hidpp_raw_hidpp_event(struct
 hidpp_device *hidpp, u8 *data,
                       return ret;
       }
 
+     ret = hidpp20_reprog_controls_raw_event(hidpp, data,
 size);
+     if (ret != 0)
+             return ret;
+

      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
              if (ret != 0)

drivers/hid/hid-logitech-hidpp.c +2 -0
@@ -4264,6 +4476,8 @@ static void hidpp_connect_event(struct
 work_struct *work)
                       return;
       }
 
+     hidpp20_reprog_controls_connect(hidpp);
+

      if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
              ret = hidpp10_consumer_keys_connect(hidpp); if (ret)

drivers/hid/hid-logitech-hidpp.c +1 -0
@@ -4436,6 +4650,7 @@ static int hidpp_probe(struct hid_device
 *hdev, const struct hid_device_id *id)
       hidpp->hid_dev = hdev;
       hidpp->name = hdev->name;
       hidpp->quirks = id->driver_data;
+     hidpp->reprog_controls_feature_index = 0xff;
       hid_set_drvdata(hdev, hidpp);
 

      ret = hid_parse(hdev);

#6 lore raw

PATCH 2/2 HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

In reply to Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal mouse report, but the observed events are short click-like events emitted around release rather than physical press/release state.

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough can prevent the normal mouse report from emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

---
 drivers/hid/hid-logitech-hidpp.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)
drivers/hid/hid-logitech-hidpp.c +20 -1
index 24c9cfaa4f37..80108778ee80 100644
@@ -3621,6 +3621,9 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 
 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED		0x00
 
+#define HIDPP_REPROG_CONTROL_M650_BACK			0x0053
+#define HIDPP_REPROG_CONTROL_M650_FORWARD		0x0056
+
 struct hidpp_reprog_control_mapping {
 	u16 control;
 	u16 code;
@@ -3631,9 +3634,24 @@ struct hidpp_reprog_controls_profile {
 	unsigned int mapping_count;
 };
 
+static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
+	{ HIDPP_REPROG_CONTROL_M650_BACK, BTN_BACK },
+	{ HIDPP_REPROG_CONTROL_M650_FORWARD, BTN_FORWARD },
+};
+
+static const struct hidpp_reprog_controls_profile m650_reprog_controls_profile = {
+	.mappings = m650_reprog_control_mappings,
+	.mapping_count = ARRAY_SIZE(m650_reprog_control_mappings),
+};
+
 static const struct hidpp_reprog_controls_profile *
 hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp)
 {
+	switch (hidpp->hid_dev->product) {
+	case 0xb02a:
+		return &m650_reprog_controls_profile;
+	}
+
 	return NULL;
 }
 
@@ -4921,7 +4939,8 @@ static const struct hid_device_id hidpp_devices[] = {
 	{ /* MX Vertical mouse over Bluetooth */
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
 	{ /* Signature M650 over Bluetooth */
-	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a),
+	  .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
 	{ /* MX Master 3 mouse over Bluetooth */
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
 	{ /* MX Anywhere 3 mouse over Bluetooth */
Signature · 1 line
2.54.0
#7 lore raw

Re: [PATCH 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

Hey Elliot,

I have an m650 on hand on with which I should be able to test your patch next week, once we've gone through a first round of reviews.

Benjamin, is there something I can capture locally that could make it into the HID tests for this feature?

Inline comments below

Quoted 36 lines · Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal
mouse report, but the observed events are short click-like events emitted
around release rather than physical press/release state.

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough can prevent the normal mouse report from
emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech
documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

Link:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

---
 drivers/hid/hid-logitech-hidpp.c | 21 ++++++++++++++++++++-

 1 file changed, 20 insertions(+), 1 deletion(-)
drivers/hid/hid- renamed from drivers/hid/hid-logitech-hidpp.c +0 -0

logitech-hidpp.c
index 24c9cfaa4f37..80108778ee80 100644

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3621,6 +3621,9 @@ static int
 hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,


 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3625

Quoted 2 lines
drivers/hid/hid-logitech-hidpp.c +2 -0
+#define HIDPP_REPROG_CONTROL_M650_BACK			0x0053
+#define HIDPP_REPROG_CONTROL_M650_FORWARD		0x0056

I read through the 0x1b04 docs, and I'm trying to understand whether those CID numbers change in different hardware, or if they're hardcoded (a back button will always have the 83/0x53 value).

Seems to me that it's the latter? The back button is listed in the Example Control ID table in the 0x1b04 docs.

Quoted 23 lines

+
 struct hidpp_reprog_control_mapping {
  u16 control;
  u16 code;

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3631,9 +3634,24 @@ struct hidpp_reprog_controls_profile {
  	unsigned int mapping_count;
  };


+static const struct hidpp_reprog_control_mapping
m650_reprog_control_mappings[] = {

drivers/hid/hid-logitech-hidpp.c +5 -0
+	{ HIDPP_REPROG_CONTROL_M650_BACK, BTN_BACK },
+	{ HIDPP_REPROG_CONTROL_M650_FORWARD, BTN_FORWARD },
+};
+
+static const struct hidpp_reprog_controls_profile

m650_reprog_controls_profile = {

drivers/hid/hid-logitech-hidpp.c +4 -0
+	.mappings = m650_reprog_control_mappings,
+	.mapping_count = ARRAY_SIZE(m650_reprog_control_mappings),
+};
+

 static const struct hidpp_reprog_controls_profile * hidpp20_reprog_controls_get_profile(struct hidpp_device *hidpp) {

drivers/hid/hid-logitech-hidpp.c struct hidpp_reprog_controls_profile { · line 3651

Quoted 2 lines
drivers/hid/hid-logitech-hidpp.c +2 -0
+	switch (hidpp->hid_dev->product) {
+	case 0xb02a:

You probably want a define for that constant.

Quoted 17 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+		return &m650_reprog_controls_profile;
+	}
+

  return NULL;
 }

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -4921,7 +4939,8 @@ static const struct hid_device_id
 hidpp_devices[] = {

  { /* MX Vertical mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) }, { /* Signature M650 over Bluetooth */

drivers/hid/hid-logitech-hidpp.c +2 -1
-	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a),
+	  .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },

  { /* MX Master 3 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, { /* MX Anywhere 3 mouse over Bluetooth */

#8 lore raw

v2 cover HID: logitech-hidpp: fix Signature M650 side button timing

In reply to Elliot Douglas

The Logitech Signature M650 over Bluetooth exposes its side buttons in the normal mouse report, but the reported BTN_SIDE/BTN_EXTRA events are short click-like events emitted around button release rather than physical press/release events with the real hold duration. The device appears to reserve the held side-button state for a built-in gesture mode: holding a side button long enough, or holding it while using the wheel for horizontal scrolling, can mean the normal mouse report never emits a usable side-button press at all. That makes the buttons unusable for standard Linux hold actions such as push-to-talk, drag modifiers, or remapping rules that depend on key-up timing.

When HID++ 2.0 feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, temporarily diverts the same controls, the device sends diverted-control notifications with real press and release timing. This series adds quirk-gated support for those notifications and enables it for the Bluetooth Signature M650.

Before enabling diversion, the driver verifies that each mapped control is present in the device's HID++ control table and is advertised as a divertable mouse control.

The kernel only programs temporary diversion when the device connects. It does not continuously force the setting. A userspace HID++ tool such as Solaar can still issue HID++ commands through hidraw; if Solaar changes the reporting mode for the same controls afterwards, the last writer wins. That means Solaar can still take over those controls for custom actions, but the kernel will no longer receive the diverted button notifications for normal evdev reporting until the kernel diverts the controls again, for example after reconnect. While the controls remain diverted, hidraw clients should still receive the raw reports and the kernel reports the matching evdev state.

The diverted M650 controls are reported as BTN_BACK and BTN_FORWARD. Logitech's Signature M650 getting-started page labels these physical controls as Back/Forward buttons and describes their default page-navigation behavior:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

The reprogrammable-control support is per-product and parses the full HID++ divertedButtonsEvent pressed-control list, so it can support devices with more buttons without relying on a single last-control release heuristic. Only the Signature M650 opts in for now. Other Logitech devices should only be enabled after their HID++ control IDs and divertedButtonsEvent behavior are captured and verified.

There is evidence that this is not unique to the M650. A prior MX Anywhere 3 patch used the same HID++ feature to fix thumb buttons that only activated on release, and Logitech documents side-button + wheel horizontal scrolling for both the MX Anywhere 3/3S and Signature M650. Solaar's device reports and rules documentation also show HID++ divertable back/forward controls on MX Master 3 and MX Master 3S class devices. This series remains conservative and only enables the device tested here.

Tested with a Logitech Signature M650 L over Bluetooth, HID ID 0005:046D:B02A. Baseline evtest showed short release-time BTN_SIDE/BTN_EXTRA events. Earlier local testing of the same HID++ diversion path showed real hold-duration press/release events, including holds longer than 4 seconds for both buttons.

Changes in v2:
- Replace the profile/count wrapper with NULL-terminated mapping arrays.
- Cache the selected reprogrammable-control mapping in struct hidpp_device.
- Add a named constant for the M650 Bluetooth product ID.
- Use common Back/Forward names for the 0x0053/0x0056 HID++ control IDs.

Elliot Douglas (2):
HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

 drivers/hid/hid-logitech-hidpp.c | 225 ++++++++++++++++++++++++++++++-
 1 file changed, 224 insertions(+), 1 deletion(-)

base-commit: f0866517be9345d8245d32b722574b8aecccb348

Signature · 1 line
2.54.0
#9 lore raw

PATCH v2 1/2 HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Add a quirk-gated event path for those controls. The handler temporarily diverts verified per-product controls, parses divertedButtonsEvent as the current pressed-control list, and reports the corresponding evdev key state for every mapped control.

Keep the control mappings in per-product arrays so adding support for another mouse does not change the evdev capabilities advertised by already-supported devices.

Documentation for feature 0x1b04 describes divertedButtonsEvent as a list of currently pressed diverted buttons, which is the event format handled here.

---
 drivers/hid/hid-logitech-hidpp.c | 205 +++++++++++++++++++++++++++++++
 1 file changed, 205 insertions(+)
drivers/hid/hid-logitech-hidpp.c +205 -0
index 70ba1a5e40d8..f9189e14fb78 100644
@@ -76,6 +76,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
 #define HIDPP_QUIRK_HI_RES_SCROLL_1P0		BIT(28)
 #define HIDPP_QUIRK_WIRELESS_STATUS		BIT(29)
 #define HIDPP_QUIRK_RESET_HI_RES_SCROLL		BIT(30)
+#define HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS	BIT(31)
 
 /* These are just aliases for now */
 #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS
@@ -178,6 +179,8 @@ struct hidpp_scroll_counter {
 	unsigned long long last_time;
 };
 
+struct hidpp_reprog_control_mapping;
+
 struct hidpp_device {
 	struct hid_device *hid_dev;
 	struct input_dev *input;
@@ -205,6 +208,8 @@ struct hidpp_device {
 	struct hidpp_scroll_counter vertical_wheel_counter;
 
 	u8 wireless_feature_index;
+	u8 reprog_controls_feature_index;
+	const struct hidpp_reprog_control_mapping *reprog_controls;
 
 	int hires_wheel_multiplier;
 	u8 hires_wheel_feature_index;
@@ -3601,6 +3606,195 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 	return 1;
 }
 
+/* -------------------------------------------------------------------------- */
+/* HID++2.0 reprogrammable controls                                           */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_REPROG_CONTROLS_V4			0x1b04
+
+#define HIDPP_REPROG_CONTROLS_GET_COUNT			0x00
+#define HIDPP_REPROG_CONTROLS_GET_CID_INFO		0x10
+#define HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING	0x30
+
+#define HIDPP_REPROG_CONTROLS_FLAG_MOUSE		BIT(0)
+#define HIDPP_REPROG_CONTROLS_FLAG_DIVERT		BIT(5)
+
+#define HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED	BIT(0)
+#define HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT	BIT(1)
+
+#define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED		0x00
+
+struct hidpp_reprog_control_mapping {
+	u16 control;
+	u16 code;
+};
+
+static const struct hidpp_reprog_control_mapping *
+hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
+{
+	return NULL;
+}
+
+static int hidpp20_reprog_controls_get_count(struct hidpp_device *hidpp)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_COUNT;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, NULL, 0,
+					  &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	return response.fap.params[0];
+}
+
+static int hidpp20_reprog_controls_get_cid_info(struct hidpp_device *hidpp,
+						u8 index, u16 *control,
+						u8 *flags)
+{
+	struct hidpp_report response;
+	u8 feature_index = hidpp->reprog_controls_feature_index;
+	u8 cmd = HIDPP_REPROG_CONTROLS_GET_CID_INFO;
+	int ret;
+
+	ret = hidpp_send_fap_command_sync(hidpp, feature_index, cmd, &index,
+					  sizeof(index), &response);
+	if (ret > 0)
+		return -EPROTO;
+	if (ret)
+		return ret;
+
+	*control = get_unaligned_be16(&response.fap.params[0]);
+	*flags = response.fap.params[4];
+
+	return 0;
+}
+
+static bool hidpp20_reprog_controls_find_control(struct hidpp_device *hidpp,
+						 u16 control)
+{
+	int count, ret;
+	u16 cid;
+	u8 flags;
+	int i;
+
+	count = hidpp20_reprog_controls_get_count(hidpp);
+	if (count < 0)
+		return false;
+
+	for (i = 0; i < count; i++) {
+		ret = hidpp20_reprog_controls_get_cid_info(hidpp, i, &cid,
+							   &flags);
+		if (ret)
+			return false;
+
+		if (cid == control)
+			return (flags & HIDPP_REPROG_CONTROLS_FLAG_MOUSE) &&
+			       (flags & HIDPP_REPROG_CONTROLS_FLAG_DIVERT);
+	}
+
+	return false;
+}
+
+static int hidpp20_reprog_controls_set_control_reporting(struct hidpp_device *hidpp,
+							 u16 control, u8 flags)
+{
+	struct hidpp_report response;
+	u8 params[5];
+
+	put_unaligned_be16(control, &params[0]);
+	params[2] = flags;
+	put_unaligned_be16(control, &params[3]);
+
+	return hidpp_send_fap_command_sync(hidpp,
+					   hidpp->reprog_controls_feature_index,
+					   HIDPP_REPROG_CONTROLS_SET_CONTROL_REPORTING,
+					   params, sizeof(params), &response);
+}
+
+static void hidpp20_reprog_controls_connect(struct hidpp_device *hidpp)
+{
+	const struct hidpp_reprog_control_mapping *mapping;
+	u8 flags = HIDPP_REPROG_CONTROLS_TEMPORARY_DIVERTED |
+		   HIDPP_REPROG_CONTROLS_CHANGE_TEMPORARY_DIVERT;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS))
+		return;
+
+	if (!hidpp->reprog_controls)
+		return;
+
+	if (hidpp_root_get_feature(hidpp, HIDPP_PAGE_REPROG_CONTROLS_V4,
+				   &hidpp->reprog_controls_feature_index))
+		return;
+
+	for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
+		if (!hidpp20_reprog_controls_find_control(hidpp, mapping->control))
+			continue;
+
+		hidpp20_reprog_controls_set_control_reporting(hidpp,
+							      mapping->control,
+							      flags);
+	}
+}
+
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+					     u8 *data, int size)
+{
+	const struct hidpp_reprog_control_mapping *mapping;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	u16 controls[4];
+	bool pressed;
+	unsigned int i, j;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+	    !hidpp->input ||
+	    !hidpp->reprog_controls ||
+	    hidpp->reprog_controls_feature_index == 0xff)
+		return 0;
+
+	if (size < HIDPP_REPORT_LONG_LENGTH ||
+	    report->fap.feature_index != hidpp->reprog_controls_feature_index ||
+	    report->fap.funcindex_clientid != HIDPP_REPROG_CONTROLS_EVENT_DIVERTED)
+		return 0;
+
+	for (i = 0; i < ARRAY_SIZE(controls); i++)
+		controls[i] = get_unaligned_be16(&report->fap.params[i * 2]);
+
+	for (mapping = hidpp->reprog_controls; mapping->control; mapping++) {
+		pressed = false;
+
+		for (j = 0; j < ARRAY_SIZE(controls); j++) {
+			if (controls[j] == mapping->control) {
+				pressed = true;
+				break;
+			}
+		}
+
+		input_report_key(hidpp->input, mapping->code, pressed);
+	}
+
+	input_sync(hidpp->input);
+
+	return 1;
+}
+
+static void hidpp20_reprog_controls_populate_input(struct hidpp_device *hidpp,
+						   struct input_dev *input_dev)
+{
+	const struct hidpp_reprog_control_mapping *mapping;
+
+	if (!hidpp->reprog_controls)
+		return;
+
+	for (mapping = hidpp->reprog_controls; mapping->control; mapping++)
+		input_set_capability(input_dev, EV_KEY, mapping->code);
+}
+
 static void hidpp10_extra_mouse_buttons_populate_input(
 			struct hidpp_device *hidpp, struct input_dev *input_dev)
 {
@@ -3859,6 +4053,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp,
 
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS)
 		hidpp10_extra_mouse_buttons_populate_input(hidpp, input);
+
+	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS)
+		hidpp20_reprog_controls_populate_input(hidpp, input);
 }
 
 static int hidpp_input_configured(struct hid_device *hdev,
@@ -3971,6 +4168,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 			return ret;
 	}
 
+	ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+	if (ret != 0)
+		return ret;
+
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
 		ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
 		if (ret != 0)
@@ -4264,6 +4465,8 @@ static void hidpp_connect_event(struct work_struct *work)
 			return;
 	}
 
+	hidpp20_reprog_controls_connect(hidpp);
+
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
 		ret = hidpp10_consumer_keys_connect(hidpp);
 		if (ret)
@@ -4436,6 +4639,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
 	hidpp->hid_dev = hdev;
 	hidpp->name = hdev->name;
 	hidpp->quirks = id->driver_data;
+	hidpp->reprog_controls_feature_index = 0xff;
+	hidpp->reprog_controls = hidpp20_reprog_controls_get_mappings(hidpp);
 	hid_set_drvdata(hdev, hidpp);
 
 	ret = hid_parse(hdev);
Signature · 1 line
2.54.0
#10 lore raw

Re: [PATCH v2 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Dangling pointer hidpp->input leads to Use-After-Free if input_register_device() fails.
- [High] Unbounded 64-byte structure copy in hidpp_raw_hidpp_event causes a Kernel Heap Out-of-Bounds Read. --

commit 13dc21807e8eb6f76d9029e89353e4bd2e6aa1c3
Author: Elliot Douglas <[email protected]>
Subject: HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support

Some Logitech HID++ 2.0 mice can report diverted reprogrammable controls through HID++ feature 0x1b04, SpecialKeysMseButtons / REPROG_CONTROLS_V4, instead of the normal HID mouse report.

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +0 -0
index 70ba1a5e40d8e..f9189e14fb780 100644

[ ... ]

Quoted 1 line
drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3601,6 +3606,195 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,

[ ... ]

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3755

Quoted 11 lines
drivers/hid/hid-logitech-hidpp.c +11 -0
+static int hidpp20_reprog_controls_raw_event(struct hidpp_device *hidpp,
+					     u8 *data, int size)
+{
+	const struct hidpp_reprog_control_mapping *mapping;
+	struct hidpp_report *report = (struct hidpp_report *)data;
+	u16 controls[4];
+	bool pressed;
+	unsigned int i, j;
+
+	if (!(hidpp->quirks & HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS) ||
+	    !hidpp->input ||

[Severity: High]
This is a pre-existing issue, but could this lead to a use-after-free?

Looking at hidpp_connect_event(), if input_register_device() fails, it cleans up by calling input_free_device() but leaves the hidpp->input pointer dangling:

    ret = input_register_device(input);
    if (ret) {
            input_free_device(input);
            return;
    }

Because hid_device_io_start() has already enabled incoming packets before this point, could a newly arriving event trigger this new hidpp20_reprog_controls_raw_event() handler, pass the !hidpp->input check, and dereference the freed pointer?

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +3 -0
+	    !hidpp->reprog_controls ||
+	    hidpp->reprog_controls_feature_index == 0xff)
+		return 0;

[ ... ]

drivers/hid/hid-logitech-hidpp.c static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, · line 4174

Quoted 8 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
@@ -3971,6 +4168,10 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
 			return ret;
 	}
 
+	ret = hidpp20_reprog_controls_raw_event(hidpp, data, size);
+	if (ret != 0)
+		return ret;
+

[Severity: High]
This is a pre-existing issue, but does hidpp_raw_hidpp_event() perform an out-of-bounds read on the incoming data buffer?

Earlier in this function, if the packet matches a pending question, it does a direct structure assignment:

    if (hidpp_match_answer(question, report) ||
            hidpp_match_error(question, report)) {
        *answer = *report;

Since struct hidpp_report is a 64-byte packed structure, won't this pointer assignment perform a fixed 64-byte copy regardless of the actual size of the received buffer (which could be as small as 7 bytes for a REPORT_ID_HIDPP_SHORT packet)?

Could this copy adjacent heap memory into the answer buffer, potentially leaking it when the data is parsed later?

Quoted 3 lines
drivers/hid/hid-logitech-hidpp.c +0 -0
 	if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) {
 		ret = hidpp10_consumer_keys_raw_event(hidpp, data, size);
 		if (ret != 0)
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#11 lore raw

PATCH v2 2/2 HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

In reply to Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal mouse report, but the observed events are short click-like events emitted around release rather than physical press/release state.

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough can prevent the normal mouse report from emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

The HID++ 0x1b04 documentation lists those control IDs as Back and Forward. The driver still verifies that the controls are present in the device control table and advertised as divertable before changing their reporting mode.

---
 drivers/hid/hid-logitech-hidpp.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
drivers/hid/hid-logitech-hidpp.c +19 -1
index f9189e14fb78..e71dd2457e80 100644
@@ -3624,14 +3624,30 @@ static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 
 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED		0x00
 
+#define HIDPP_REPROG_CONTROL_BACK			0x0053
+#define HIDPP_REPROG_CONTROL_FORWARD			0x0056
+
+#define HIDPP_PRODUCT_SIGNATURE_M650			0xb02a
+
 struct hidpp_reprog_control_mapping {
 	u16 control;
 	u16 code;
 };
 
+static const struct hidpp_reprog_control_mapping m650_reprog_control_mappings[] = {
+	{ HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
+	{ HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
+	{ }
+};
+
 static const struct hidpp_reprog_control_mapping *
 hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
 {
+	switch (hidpp->hid_dev->product) {
+	case HIDPP_PRODUCT_SIGNATURE_M650:
+		return m650_reprog_control_mappings;
+	}
+
 	return NULL;
 }
 
@@ -4911,7 +4927,9 @@ static const struct hid_device_id hidpp_devices[] = {
 	{ /* MX Vertical mouse over Bluetooth */
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
 	{ /* Signature M650 over Bluetooth */
-	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+			       HIDPP_PRODUCT_SIGNATURE_M650),
+	  .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
 	{ /* MX Master 3 mouse over Bluetooth */
 	  HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },
 	{ /* MX Anywhere 3 mouse over Bluetooth */
Signature · 1 line
2.54.0
reviewer Reviewed-by on v2 2/2
#12 lore raw

Re: [PATCH v2 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

Quoted 9 lines · Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal
mouse report, but the observed events are short click-like events emitted
around release rather than physical press/release state.

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough

s/long enough/more than approximately 2 seconds/ ?

Quoted 16 lines

can prevent the normal mouse report from
emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech
documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

The HID++ 0x1b04 documentation lists those control IDs as Back and Forward. The driver still verifies that the controls are present in the
device control table and advertised as divertable before changing their
reporting mode.

Quoted 3 lines

Link:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

Tested with my M650 mouse connected through Bluetooth.

Quoted 14 lines

---
 drivers/hid/hid-logitech-hidpp.c | 20 +++++++++++++++++++-

 1 file changed, 19 insertions(+), 1 deletion(-)
drivers/hid/hid- renamed from drivers/hid/hid-logitech-hidpp.c +0 -0

logitech-hidpp.c
index f9189e14fb78..e71dd2457e80 100644

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3624,14 +3624,30 @@ static int
 hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,


 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+#define HIDPP_REPROG_CONTROL_BACK 0x0053
+#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
+
+#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a

If you want to add a product ID constant, please do it in hid-ids.h instead.

Quoted 36 lines

+
 struct hidpp_reprog_control_mapping {
  u16 control;
  u16 code;
 };

+static const struct hidpp_reprog_control_mapping
m650_reprog_control_mappings[] = {
+ { HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
+ { HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
+ { }

drivers/hid/hid-logitech-hidpp.c +2 -0
+};
+

 static const struct hidpp_reprog_control_mapping * hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp) {
+ switch (hidpp->hid_dev->product) {
+ case HIDPP_PRODUCT_SIGNATURE_M650:
+ return m650_reprog_control_mappings;
+ }
+
  return NULL;
 }

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -4911,7 +4927,9 @@ static const struct hid_device_id
 hidpp_devices[] = {

  { /* MX Vertical mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) }, { /* Signature M650 over Bluetooth */
-   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+        HIDPP_PRODUCT_SIGNATURE_M650),
+   .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS }, { /* MX Master 3 mouse over Bluetooth */ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) }, { /* MX Anywhere 3 mouse over Bluetooth */

#13 lore raw

Re: [PATCH v2 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

Thanks for the feedback. I just left for a trip, but I'll be back and should be able to work on it on Tuesday, August 11th.

Quoted 96 lines · Bastien Nocera
Quoted 9 lines · Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal
mouse report, but the observed events are short click-like events emitted
around release rather than physical press/release state.

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough

s/long enough/more than approximately 2 seconds/ ?

Quoted 16 lines

can prevent the normal mouse report from
emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech
documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

The HID++ 0x1b04 documentation lists those control IDs as Back and Forward. The driver still verifies that the controls are present in the
device control table and advertised as divertable before changing their
reporting mode.

Quoted 3 lines

Link:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

Tested with my M650 mouse connected through Bluetooth.

Quoted 10 lines
---
 drivers/hid/hid-logitech-hidpp.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
drivers/hid/hid- renamed from drivers/hid/hid-logitech-hidpp.c +0 -0

logitech-hidpp.c
index f9189e14fb78..e71dd2457e80 100644

drivers/hid/hid-logitech-hidpp.c static int · line 3631

Quoted 8 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
@@ -3624,14 +3624,30 @@ static int
 hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 
 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
 
+#define HIDPP_REPROG_CONTROL_BACK 0x0053
+#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
+
+#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a

If you want to add a product ID constant, please do it in hid-ids.h instead.

Quoted 36 lines
drivers/hid/hid-logitech-hidpp.c +12 -0
+
 struct hidpp_reprog_control_mapping {
  u16 control;
  u16 code;
 };
 
+static const struct hidpp_reprog_control_mapping
 m650_reprog_control_mappings[] = {
+ { HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
+ { HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
+ { }
+};
+
 static const struct hidpp_reprog_control_mapping *
 hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
 {
+ switch (hidpp->hid_dev->product) {
+ case HIDPP_PRODUCT_SIGNATURE_M650:
+ return m650_reprog_control_mappings;
+ }
+
  return NULL;

}

drivers/hid/hid-logitech-hidpp.c +3 -1
@@ -4911,7 +4927,9 @@ static const struct hid_device_id
 hidpp_devices[] = {
  { /* MX Vertical mouse over Bluetooth */
    HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
  { /* Signature M650 over Bluetooth */
-   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+        HIDPP_PRODUCT_SIGNATURE_M650),
+   .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
  { /* MX Master 3 mouse over Bluetooth */
    HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },

{ /* MX Anywhere 3 mouse over Bluetooth */

#14 lore raw

Re: [PATCH v2 2/2] HID: logitech-hidpp: enable reprogrammable buttons on Signature M650

In reply to Bastien Nocera
Quoted 8 lines · Bastien Nocera
Quoted 6 lines · Elliot Douglas

The Bluetooth Signature M650 exposes its side buttons through the normal
mouse report, but the observed events are short click-like events emitted
around release rather than physical press/release state.

Quoted 5 lines
Quoted 3 lines

The device appears to use the held side-button state for its built-in gesture and side-button + wheel horizontal-scroll mode. As a result, holding a side button long enough

s/long enough/more than approximately 2 seconds/ ?

It was about 2 seconds, I measured it.

Quoted 38 lines
Quoted 16 lines

can prevent the normal mouse report from
emitting a usable button event at all.

HID++ REPROG_CONTROLS_V4 diversion for control IDs 0x0053 and 0x0056 provides real press and release timing for those same controls. Logitech
documents the Signature M650 side buttons as Back/Forward buttons, so report the diverted controls as BTN_BACK and BTN_FORWARD.

The HID++ 0x1b04 documentation lists those control IDs as Back and Forward. The driver still verifies that the controls are present in the
device control table and advertised as divertable before changing their
reporting mode.

Quoted 3 lines

Link:
https://support.logi.com/hc/en-nz/articles/4414473810583-Getting-Started-Signature-M650

Tested with my M650 mouse connected through Bluetooth.

Quoted 14 lines
---
 drivers/hid/hid-logitech-hidpp.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)
drivers/hid/hid- renamed from drivers/hid/hid-logitech-hidpp.c +0 -0

logitech-hidpp.c
index f9189e14fb78..e71dd2457e80 100644

drivers/hid/hid-logitech-hidpp.c +0 -0
@@ -3624,14 +3624,30 @@ static int
 hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp,
 
 #define HIDPP_REPROG_CONTROLS_EVENT_DIVERTED 0x00
 
Quoted 7 lines

drivers/hid/hid-logitech-hidpp.c static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, · line 3630

Quoted 4 lines
drivers/hid/hid-logitech-hidpp.c +4 -0
+#define HIDPP_REPROG_CONTROL_BACK 0x0053
+#define HIDPP_REPROG_CONTROL_FORWARD 0x0056
+
+#define HIDPP_PRODUCT_SIGNATURE_M650 0xb02a

If you want to add a product ID constant, please do it in hid-ids.h instead.

I'll address this in the next patch revision.

Quoted 36 lines
Quoted 36 lines
drivers/hid/hid-logitech-hidpp.c +12 -0
+
 struct hidpp_reprog_control_mapping {
  u16 control;
  u16 code;
 };
 
+static const struct hidpp_reprog_control_mapping
 m650_reprog_control_mappings[] = {
+ { HIDPP_REPROG_CONTROL_BACK, BTN_BACK },
+ { HIDPP_REPROG_CONTROL_FORWARD, BTN_FORWARD },
+ { }
+};
+
 static const struct hidpp_reprog_control_mapping *
 hidpp20_reprog_controls_get_mappings(struct hidpp_device *hidpp)
 {
+ switch (hidpp->hid_dev->product) {
+ case HIDPP_PRODUCT_SIGNATURE_M650:
+ return m650_reprog_control_mappings;
+ }
+
  return NULL;

}

drivers/hid/hid-logitech-hidpp.c +3 -1
@@ -4911,7 +4927,9 @@ static const struct hid_device_id
 hidpp_devices[] = {
  { /* MX Vertical mouse over Bluetooth */
    HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb020) },
  { /* Signature M650 over Bluetooth */
-   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb02a) },
+   HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
+        HIDPP_PRODUCT_SIGNATURE_M650),
+   .driver_data = HIDPP_QUIRK_HIDPP_REPROG_CONTROLS_BTNS },
  { /* MX Master 3 mouse over Bluetooth */
    HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb023) },

{ /* MX Anywhere 3 mouse over Bluetooth */