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

Bastien Nocera <[email protected]>
Newsgroups org.kernel.vger.linux-kernel,org.kernel.vger.linux-input
Message-ID <[email protected]>
On Wed, 2026-08-12 at 12:58 -0700, Elliot Douglas wrote:
> 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.
> 
> Link:
> https://lekensteyn.nl/files/logitech/x1b04_specialkeysmsebuttons.html
> Signed-off-by: Elliot Douglas <[email protected]>

My earlier
Reviewed-by: Bastien Nocera <[email protected]>
was for both patches in the patch set :)

Thanks for v3.

> ---
>  drivers/hid/hid-logitech-hidpp.c | 205
> +++++++++++++++++++++++++++++++
>  1 file changed, 205 insertions(+)
> 
> diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-
> logitech-hidpp.c
> index 70ba1a5e40d8..f9189e14fb78 100644
> --- a/drivers/hid/hid-logitech-hidpp.c
> +++ b/drivers/hid/hid-logitech-hidpp.c
> @@ -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);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.