Re: [PATCH v3 1/2] HID: logitech-hidpp: add HID++ 2.0 reprogrammable button support
Bastien Nocera <[email protected]> Thu, 13 Aug 2026 00:26:45 +0200
| Newsgroups | org.kernel.vger.linux-input,org.kernel.vger.linux-kernel |
|---|---|
| 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, ¶ms[0]); > + params[2] = flags; > + put_unaligned_be16(control, ¶ms[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);