From 9576af6a95dbabd035a59fa6ffc5b88baa07f221 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 22 Mar 2019 08:41:38 +0100 Subject: HID: logitech-hidpp: simplify printing of HID++ version Simply always print the HID++ version on hidpp_root_get_protocol_version success. This also fixes the version not being printed when a HID++ device connected through a receiver is already connected when the hidpp driver is loaded. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 15ed6177a7a3..3795ae90207a 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -847,7 +847,7 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) if (ret == HIDPP_ERROR_INVALID_SUBID) { hidpp->protocol_major = 1; hidpp->protocol_minor = 0; - return 0; + goto print_version; } /* the device might not be connected */ @@ -865,18 +865,15 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) hidpp->protocol_major = response.fap.params[0]; hidpp->protocol_minor = response.fap.params[1]; - return ret; +print_version: + hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n", + hidpp->protocol_major, hidpp->protocol_minor); + return 0; } static bool hidpp_is_connected(struct hidpp_device *hidpp) { - int ret; - - ret = hidpp_root_get_protocol_version(hidpp); - if (!ret) - hid_dbg(hidpp->hid_dev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - return ret == 0; + return hidpp_root_get_protocol_version(hidpp) == 0; } /* -------------------------------------------------------------------------- */ @@ -3133,8 +3130,6 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) hid_err(hdev, "Can not get the protocol version.\n"); return; } - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); } if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) { @@ -3291,9 +3286,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) goto hid_hw_open_failed; } - hid_info(hdev, "HID++ %u.%u device connected.\n", - hidpp->protocol_major, hidpp->protocol_minor); - hidpp_overwrite_name(hdev); } -- cgit From 090760d4269d03d05341e428b6a7ef68f94c5dcc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 22 Mar 2019 08:41:39 +0100 Subject: HID: logitech-hidpp: remove hidpp_is_connected() Remove the hidpp_is_connected() function wrapper, and have the callers directly call hidpp_root_get_protocol_version() instead. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 3795ae90207a..cd4b0befc0e8 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -871,11 +871,6 @@ print_version: return 0; } -static bool hidpp_is_connected(struct hidpp_device *hidpp) -{ - return hidpp_root_get_protocol_version(hidpp) == 0; -} - /* -------------------------------------------------------------------------- */ /* 0x0005: GetDeviceNameType */ /* -------------------------------------------------------------------------- */ @@ -3125,7 +3120,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { - ret = !hidpp_is_connected(hidpp); + ret = hidpp_root_get_protocol_version(hidpp); if (ret) { hid_err(hdev, "Can not get the protocol version.\n"); return; @@ -3277,7 +3272,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (hidpp->quirks & HIDPP_QUIRK_UNIFYING) hidpp_unifying_init(hidpp); - connected = hidpp_is_connected(hidpp); + connected = hidpp_root_get_protocol_version(hidpp) == 0; atomic_set(&hidpp->connected, connected); if (!(hidpp->quirks & HIDPP_QUIRK_UNIFYING)) { if (!connected) { -- cgit From 1f87b0cd32b3456d7efdfb017fcf74d0bfe3ec29 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 22 Mar 2019 08:41:40 +0100 Subject: HID: logitech-hidpp: change low battery level threshold from 31 to 30 percent According to hidpp20_batterylevel_get_battery_info my Logitech K270 keyboard reports only 2 battery levels. This matches with what I've seen after testing with batteries at varying level of fullness, it always reports either 5% or 30%. Windows reports "battery good" for the 30% level. I've captured an USB trace of Windows reading the battery and it is getting the same info as the Linux hidpp code gets. Now that Linux handles these devices as hidpp devices, it reports the battery as being low as it treats anything under 31% as low, this leads to the user constantly getting a "Keyboard battery is low" warning from GNOME3, which is very annoying. This commit fixes this by changing the low threshold to anything under 30%, which I assume is what Windows does. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-logitech-hidpp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index cd4b0befc0e8..e5897c292e8c 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -1004,7 +1004,11 @@ static int hidpp_map_battery_level(int capacity) { if (capacity < 11) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; - else if (capacity < 31) + /* + * The spec says this should be < 31 but some devices report 30 + * with brand new batteries and Windows reports 30 as "Good". + */ + else if (capacity < 30) return POWER_SUPPLY_CAPACITY_LEVEL_LOW; else if (capacity < 81) return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -- cgit From 43cd97af70c650c4463817eb28fda3678a1956c9 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 20 Apr 2019 13:21:42 +0200 Subject: HID: logitech: Stop setting drvdata to NULL on probe failure and remove There is no need to set drvdata to NULL on probe failure and remove, the driver-core already does this for us. [hdegoede@redhat.com: Isolate Logitech changes into a separate patch] Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 29395da8f345..965479fe2736 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3231,15 +3231,15 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) { ret = wtp_allocate(hdev, id); if (ret) - goto allocate_fail; + return ret; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { ret = m560_allocate(hdev); if (ret) - goto allocate_fail; + return ret; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { ret = k400_allocate(hdev); if (ret) - goto allocate_fail; + return ret; } INIT_WORK(&hidpp->work, delayed_work_cb); @@ -3334,8 +3334,6 @@ hid_parse_fail: sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); -allocate_fail: - hid_set_drvdata(hdev, NULL); return ret; } -- cgit From fe3ee1ec007bf7b10d7c02814d4b8fbe7d9bb435 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 20 Apr 2019 13:22:04 +0200 Subject: HID: logitech-hidpp: allow non HID++ devices to be handled by this module On the gaming mice, there are 2 interfaces, one for the mouse and one for the macros. Better allow everybody to go through hid-logitech-hidpp than trying to be smarter. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 76 +++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 8 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 965479fe2736..3b4d466963af 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2783,6 +2783,9 @@ static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return 0; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) return wtp_input_mapping(hdev, hi, field, usage, bit, max); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560 && @@ -2798,6 +2801,9 @@ static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi, { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return 0; + /* Ensure that Logitech G920 is not given a default fuzz/flat value */ if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { if (usage->type == EV_ABS && (usage->code == ABS_X || @@ -2829,6 +2835,9 @@ static int hidpp_input_configured(struct hid_device *hdev, struct hidpp_device *hidpp = hid_get_drvdata(hdev); struct input_dev *input = hidinput->input; + if (!hidpp) + return 0; + hidpp_populate_input(hidpp, input, true); return 0; @@ -2898,6 +2907,9 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, struct hidpp_device *hidpp = hid_get_drvdata(hdev); int ret = 0; + if (!hidpp) + return 0; + /* Generic HID++ processing. */ switch (data[0]) { case REPORT_ID_HIDPP_VERY_LONG: @@ -2946,7 +2958,12 @@ static int hidpp_event(struct hid_device *hdev, struct hid_field *field, * restriction imposed in hidpp_usages. */ struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct hidpp_scroll_counter *counter = &hidpp->vertical_wheel_counter; + struct hidpp_scroll_counter *counter; + + if (!hidpp) + return 0; + + counter = &hidpp->vertical_wheel_counter; /* A scroll event may occur before the multiplier has been retrieved or * the input device set, or high-res scroll enabling may fail. In such * cases we must return early (falling back to default behaviour) to @@ -3202,6 +3219,41 @@ static const struct attribute_group ps_attribute_group = { .attrs = sysfs_attrs }; +static bool hidpp_validate_report(struct hid_device *hdev, int id, int size, + bool optional) +{ + struct hid_report_enum *re; + struct hid_report *report; + + if (id >= HID_MAX_IDS || id < 0) { + hid_err(hdev, "invalid HID report id %u\n", id); + return false; + } + + re = &(hdev->report_enum[HID_OUTPUT_REPORT]); + report = re->report_id_hash[id]; + + if (!report) + return optional; + + if (report->field[0]->report_count < size) { + hid_warn(hdev, "not enough values in hidpp report %d\n", id); + return false; + } + + return true; +} + +static bool hidpp_validate_device(struct hid_device *hdev) +{ + return hidpp_validate_report(hdev, REPORT_ID_HIDPP_SHORT, + HIDPP_REPORT_SHORT_LENGTH - 1, false) && + hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG, + HIDPP_REPORT_LONG_LENGTH - 1, true) && + hidpp_validate_report(hdev, REPORT_ID_HIDPP_VERY_LONG, + HIDPP_REPORT_VERY_LONG_LENGTH - 1, true); +} + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; @@ -3209,6 +3261,18 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) bool connected; unsigned int connect_mask = HID_CONNECT_DEFAULT; + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "%s:parse failed\n", __func__); + return ret; + } + + /* + * Make sure the device is HID++ capable, otherwise treat as generic HID + */ + if (!hidpp_validate_device(hdev)) + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); + hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), GFP_KERNEL); if (!hidpp) @@ -3252,12 +3316,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_warn(hdev, "Cannot allocate sysfs group for %s\n", hdev->name); - ret = hid_parse(hdev); - if (ret) { - hid_err(hdev, "%s:parse failed\n", __func__); - goto hid_parse_fail; - } - if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) connect_mask &= ~HID_CONNECT_HIDINPUT; @@ -3330,7 +3388,6 @@ hid_hw_open_failed: hid_hw_stop(hdev); } hid_hw_start_fail: -hid_parse_fail: sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); @@ -3341,6 +3398,9 @@ static void hidpp_remove(struct hid_device *hdev) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); + if (!hidpp) + return hid_hw_stop(hdev); + sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { -- cgit From 91cf9a98ae4127551d022e287e283bcd8738ed02 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 20 Apr 2019 13:22:05 +0200 Subject: HID: logitech-hidpp: make .probe usbhid capable The current custom solution for the G920 is not the best because hid_hw_start() is not called at the end of the .probe(). It means that any configuration retrieved after the initial hid_hw_start would not be exposed to user space without races. We can simply force hid_hw_start to just enable the transport layer by not using a connect_mask. This way, we can have a common path between USB, Unifying and Bluetooth devices. With this change, we can now support the non DJ receivers for low end devices, which will allow us to fetch the actual names of the paired device (instead of 'Logitech Wireless Receiver') Tested with a M185 with the non unifying receiver, a T650 and many other unifying devices, and the T651 over Bluetooth. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 86 +++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 41 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 3b4d466963af..b389fcc1a894 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3316,24 +3316,23 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hid_warn(hdev, "Cannot allocate sysfs group for %s\n", hdev->name); - if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) - connect_mask &= ~HID_CONNECT_HIDINPUT; - - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - ret = hid_hw_start(hdev, connect_mask); - if (ret) { - hid_err(hdev, "hw start failed\n"); - goto hid_hw_start_fail; - } - ret = hid_hw_open(hdev); - if (ret < 0) { - dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", - __func__, ret); - hid_hw_stop(hdev); - goto hid_hw_start_fail; - } + /* + * Plain USB connections need to actually call start and open + * on the transport driver to allow incoming data. + */ + ret = hid_hw_start(hdev, 0); + if (ret) { + hid_err(hdev, "hw start failed\n"); + goto hid_hw_start_fail; } + ret = hid_hw_open(hdev); + if (ret < 0) { + dev_err(&hdev->dev, "%s:hid_hw_open returned error:%d\n", + __func__, ret); + hid_hw_stop(hdev); + goto hid_hw_open_fail; + } /* Allow incoming packets */ hid_device_io_start(hdev); @@ -3347,7 +3346,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (!connected) { ret = -ENODEV; hid_err(hdev, "Device not connected"); - goto hid_hw_open_failed; + goto hid_hw_init_fail; } hidpp_overwrite_name(hdev); @@ -3356,37 +3355,36 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP)) { ret = wtp_get_config(hidpp); if (ret) - goto hid_hw_open_failed; + goto hid_hw_init_fail; } else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { ret = g920_get_config(hidpp); if (ret) - goto hid_hw_open_failed; + goto hid_hw_init_fail; } - /* Block incoming packets */ - hid_device_io_stop(hdev); + hidpp_connect_event(hidpp); - if (!(hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) { - ret = hid_hw_start(hdev, connect_mask); - if (ret) { - hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); - goto hid_hw_start_fail; - } - } + /* Reset the HID node state */ + hid_device_io_stop(hdev); + hid_hw_close(hdev); + hid_hw_stop(hdev); - /* Allow incoming packets */ - hid_device_io_start(hdev); + if (hidpp->quirks & HIDPP_QUIRK_NO_HIDINPUT) + connect_mask &= ~HID_CONNECT_HIDINPUT; - hidpp_connect_event(hidpp); + /* Now export the actual inputs and hidraw nodes to the world */ + ret = hid_hw_start(hdev, connect_mask); + if (ret) { + hid_err(hdev, "%s:hid_hw_start returned error\n", __func__); + goto hid_hw_start_fail; + } return ret; -hid_hw_open_failed: - hid_device_io_stop(hdev); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { - hid_hw_close(hdev); - hid_hw_stop(hdev); - } +hid_hw_init_fail: + hid_hw_close(hdev); +hid_hw_open_fail: + hid_hw_stop(hdev); hid_hw_start_fail: sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); cancel_work_sync(&hidpp->work); @@ -3403,10 +3401,9 @@ static void hidpp_remove(struct hid_device *hdev) sysfs_remove_group(&hdev->dev.kobj, &ps_attribute_group); - if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) { + if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) hidpp_ff_deinit(hdev); - hid_hw_close(hdev); - } + hid_hw_stop(hdev); cancel_work_sync(&hidpp->work); mutex_destroy(&hidpp->send_mutex); @@ -3470,7 +3467,14 @@ static const struct hid_device_id hidpp_devices[] = { { LDJ_DEVICE(HID_ANY_ID) }, - { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), + { /* Logitech G403 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, + { /* Logitech G700 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC06B) }, + { /* Logitech G900 Gaming Mouse over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC081) }, + { /* Logitech G920 Wheel over USB */ + HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, {} }; -- cgit From 22bf6bdef4a1d551fdaebbe8247e57569ea07b6a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:06 +0200 Subject: HID: logitech-hidpp: ignore very-short or empty names Some devices report an empty or very short name, in this case stick with the name generated by the logitech-dj code instead of overriding it with e.g. "Logitech ". Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index b389fcc1a894..e7f082374943 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -741,6 +741,9 @@ static char *hidpp_unifying_get_name(struct hidpp_device *hidpp_dev) if (2 + len > sizeof(response.rap.params)) return NULL; + if (len < 4) /* logitech devices are usually at least Xddd */ + return NULL; + name = kzalloc(len + 1, GFP_KERNEL); if (!name) return NULL; -- cgit From 2ddf07f388af6c8f0db3ec21e17ca0f06b9ec0cc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:07 +0200 Subject: HID: logitech-hidpp: do not make failure to get the name fatal With devices attached to a non-unifying 2.4GHz receiver we sometimes fail to get the name. This is not a fatal error, we can just continue with the original name. So instead of bailing out, continue with battery-initialization when this happens. This fixes the battery not getting registered when we fail to get the name. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e7f082374943..2a0866e6face 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3160,18 +3160,15 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) if (hidpp->name == hdev->name && hidpp->protocol_major >= 2) { name = hidpp_get_device_name(hidpp); - if (!name) { - hid_err(hdev, - "unable to retrieve the name of the device"); - return; - } - - devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, "%s", name); - kfree(name); - if (!devm_name) - return; + if (name) { + devm_name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "%s", name); + kfree(name); + if (!devm_name) + return; - hidpp->name = devm_name; + hidpp->name = devm_name; + } } hidpp_initialize_battery(hidpp); -- cgit From 205a2ab0c97bb3a532ca25a7ed1debb62706d83d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:08 +0200 Subject: HID: logitech-hidpp: remove double assignment from __hidpp_send_report The hidpp variable is already initialized with hid_get_drvdata(hdev) when it is declared, drop the second no-op assignment. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 2a0866e6face..e9bf61d21423 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -206,8 +206,6 @@ static int __hidpp_send_report(struct hid_device *hdev, struct hidpp_device *hidpp = hid_get_drvdata(hdev); int fields_count, ret; - hidpp = hid_get_drvdata(hdev); - switch (hidpp_report->report_id) { case REPORT_ID_HIDPP_SHORT: fields_count = HIDPP_REPORT_SHORT_LENGTH; -- cgit From e54abaf675ca76e026a104790e3aaa2b47ee7497 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:09 +0200 Subject: HID: logitech-hidpp: remove unused origin_is_hid_core function parameter All the various populate_input functions have an origin_is_hid_core function parameter, but none use it, remove it. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e9bf61d21423..b0da186ef5e7 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -2226,7 +2226,7 @@ static int wtp_input_mapping(struct hid_device *hdev, struct hid_input *hi, } static void wtp_populate_input(struct hidpp_device *hidpp, - struct input_dev *input_dev, bool origin_is_hid_core) + struct input_dev *input_dev) { struct wtp_data *wd = hidpp->private_data; @@ -2622,7 +2622,7 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) } static void m560_populate_input(struct hidpp_device *hidpp, - struct input_dev *input_dev, bool origin_is_hid_core) + struct input_dev *input_dev) { struct m560_private_data *mydata = hidpp->private_data; @@ -2819,12 +2819,12 @@ static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi, static void hidpp_populate_input(struct hidpp_device *hidpp, - struct input_dev *input, bool origin_is_hid_core) + struct input_dev *input) { if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) - wtp_populate_input(hidpp, input, origin_is_hid_core); + wtp_populate_input(hidpp, input); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) - m560_populate_input(hidpp, input, origin_is_hid_core); + m560_populate_input(hidpp, input); if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) hidpp->vertical_wheel_counter.dev = input; @@ -2839,7 +2839,7 @@ static int hidpp_input_configured(struct hid_device *hdev, if (!hidpp) return 0; - hidpp_populate_input(hidpp, input, true); + hidpp_populate_input(hidpp, input); return 0; } @@ -3197,7 +3197,7 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } - hidpp_populate_input(hidpp, input, false); + hidpp_populate_input(hidpp, input); ret = input_register_device(input); if (ret) -- cgit From 096377525cdb8251e4656085efc988bdf733fb4c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:10 +0200 Subject: HID: logitech-hidpp: use RAP instead of FAP to get the protocol version According to the logitech_hidpp_2.0_specification_draft_2012-06-04.pdf doc: https://lekensteyn.nl/files/logitech/logitech_hidpp_2.0_specification_draft_2012-06-04.pdf We should use a register-access-protocol request using the short input / output report ids. This is necessary because 27MHz HID++ receivers have a max-packetsize on their HIP++ endpoint of 8, so they cannot support long reports. Using a feature-access-protocol request (which is always long or very-long) with these will cause a timeout error, followed by the hidpp driver treating the device as not being HID++ capable. This commit fixes this by switching to using a rap request to get the protocol version. Besides being tested with a (046d:c517) 27MHz receiver with various 27MHz keyboards and mice, this has also been tested to not cause regressions on a non-unifying dual-HID++ nano receiver (046d:c534) with k270 and m185 HID++-2.0 devices connected and on a unifying/dj receiver (046d:c52b) with a HID++-2.0 Logitech Rechargeable Touchpad T650. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index b0da186ef5e7..243426097c51 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -837,13 +837,16 @@ static int hidpp_root_get_feature(struct hidpp_device *hidpp, u16 feature, static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) { + const u8 ping_byte = 0x5a; + u8 ping_data[3] = { 0, 0, ping_byte }; struct hidpp_report response; int ret; - ret = hidpp_send_fap_command_sync(hidpp, + ret = hidpp_send_rap_command_sync(hidpp, + REPORT_ID_HIDPP_SHORT, HIDPP_PAGE_ROOT_IDX, CMD_ROOT_GET_PROTOCOL_VERSION, - NULL, 0, &response); + ping_data, sizeof(ping_data), &response); if (ret == HIDPP_ERROR_INVALID_SUBID) { hidpp->protocol_major = 1; @@ -863,8 +866,14 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp) if (ret) return ret; - hidpp->protocol_major = response.fap.params[0]; - hidpp->protocol_minor = response.fap.params[1]; + if (response.rap.params[2] != ping_byte) { + hid_err(hidpp->hid_dev, "%s: ping mismatch 0x%02x != 0x%02x\n", + __func__, response.rap.params[2], ping_byte); + return -EPROTO; + } + + hidpp->protocol_major = response.rap.params[0]; + hidpp->protocol_minor = response.rap.params[1]; print_version: hid_info(hidpp->hid_dev, "HID++ %u.%u device connected.\n", -- cgit From 754a30884886d751ea8b5681a8ccaad59147a914 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:11 +0200 Subject: HID: logitech-hidpp: handle devices attached to 27MHz wireless receivers Logitech 27MHz devices are HID++ devices, so handle them in the hidpp driver, this enables battery monitoring on these devices (and more in follow-up patches). Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 243426097c51..e8a552c8801d 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -3420,6 +3420,10 @@ static void hidpp_remove(struct hid_device *hdev) HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_DJ_DEVICE, \ USB_VENDOR_ID_LOGITECH, (product)) +#define L27MHZ_DEVICE(product) \ + HID_DEVICE(BUS_USB, HID_GROUP_LOGITECH_27MHZ_DEVICE, \ + USB_VENDOR_ID_LOGITECH, (product)) + static const struct hid_device_id hidpp_devices[] = { { /* wireless touchpad */ LDJ_DEVICE(0x4011), @@ -3474,6 +3478,8 @@ static const struct hid_device_id hidpp_devices[] = { { LDJ_DEVICE(HID_ANY_ID) }, + { L27MHZ_DEVICE(HID_ANY_ID) }, + { /* Logitech G403 Gaming Mouse over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, 0xC082) }, { /* Logitech G700 Gaming Mouse over USB */ -- cgit From d71b18f7c7999381a9b721d761b0aceffdcd65da Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:12 +0200 Subject: HID: logitech-hidpp: do not hardcode very long report length The HID++ spec says the following about the very long report length: "n Bytes, depends on HID++ collection declaration". Hardcoding this breaks talking to some HID++ devices over BlueTooth, since they declare only 45 bytes data for the very long report, rather then the hardcoded 63. This commit fixes this by getting the actual report length from the descriptors. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 50 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 18 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index e8a552c8801d..ecf1b1e22e1a 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -51,7 +51,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_REPORT_SHORT_LENGTH 7 #define HIDPP_REPORT_LONG_LENGTH 20 -#define HIDPP_REPORT_VERY_LONG_LENGTH 64 +#define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64 #define HIDPP_QUIRK_CLASS_WTP BIT(0) #define HIDPP_QUIRK_CLASS_M560 BIT(1) @@ -106,13 +106,13 @@ MODULE_PARM_DESC(disable_tap_to_click, struct fap { u8 feature_index; u8 funcindex_clientid; - u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U]; + u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U]; }; struct rap { u8 sub_id; u8 reg_address; - u8 params[HIDPP_REPORT_VERY_LONG_LENGTH - 4U]; + u8 params[HIDPP_REPORT_VERY_LONG_MAX_LENGTH - 4U]; }; struct hidpp_report { @@ -162,6 +162,7 @@ struct hidpp_device { void *send_receive_buf; char *name; /* will never be NULL and should not be freed */ wait_queue_head_t wait; + int very_long_report_length; bool answer_available; u8 protocol_major; u8 protocol_minor; @@ -214,7 +215,7 @@ static int __hidpp_send_report(struct hid_device *hdev, fields_count = HIDPP_REPORT_LONG_LENGTH; break; case REPORT_ID_HIDPP_VERY_LONG: - fields_count = HIDPP_REPORT_VERY_LONG_LENGTH; + fields_count = hidpp->very_long_report_length; break; default: return -ENODEV; @@ -340,7 +341,7 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev, max_count = HIDPP_REPORT_LONG_LENGTH - 4; break; case REPORT_ID_HIDPP_VERY_LONG: - max_count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + max_count = hidpp_dev->very_long_report_length - 4; break; default: return -EINVAL; @@ -934,7 +935,7 @@ static int hidpp_devicenametype_get_device_name(struct hidpp_device *hidpp, switch (response.report_id) { case REPORT_ID_HIDPP_VERY_LONG: - count = HIDPP_REPORT_VERY_LONG_LENGTH - 4; + count = hidpp->very_long_report_length - 4; break; case REPORT_ID_HIDPP_LONG: count = HIDPP_REPORT_LONG_LENGTH - 4; @@ -2923,7 +2924,7 @@ static int hidpp_raw_event(struct hid_device *hdev, struct hid_report *report, /* Generic HID++ processing. */ switch (data[0]) { case REPORT_ID_HIDPP_VERY_LONG: - if (size != HIDPP_REPORT_VERY_LONG_LENGTH) { + if (size != hidpp->very_long_report_length) { hid_err(hdev, "received hid++ report of bad size (%d)", size); return 1; @@ -3226,24 +3227,34 @@ static const struct attribute_group ps_attribute_group = { .attrs = sysfs_attrs }; -static bool hidpp_validate_report(struct hid_device *hdev, int id, int size, - bool optional) +static int hidpp_get_report_length(struct hid_device *hdev, int id) { struct hid_report_enum *re; struct hid_report *report; + re = &(hdev->report_enum[HID_OUTPUT_REPORT]); + report = re->report_id_hash[id]; + if (!report) + return 0; + + return report->field[0]->report_count + 1; +} + +static bool hidpp_validate_report(struct hid_device *hdev, int id, + int expected_length, bool optional) +{ + int report_length; + if (id >= HID_MAX_IDS || id < 0) { hid_err(hdev, "invalid HID report id %u\n", id); return false; } - re = &(hdev->report_enum[HID_OUTPUT_REPORT]); - report = re->report_id_hash[id]; - - if (!report) + report_length = hidpp_get_report_length(hdev, id); + if (!report_length) return optional; - if (report->field[0]->report_count < size) { + if (report_length < expected_length) { hid_warn(hdev, "not enough values in hidpp report %d\n", id); return false; } @@ -3254,11 +3265,9 @@ static bool hidpp_validate_report(struct hid_device *hdev, int id, int size, static bool hidpp_validate_device(struct hid_device *hdev) { return hidpp_validate_report(hdev, REPORT_ID_HIDPP_SHORT, - HIDPP_REPORT_SHORT_LENGTH - 1, false) && + HIDPP_REPORT_SHORT_LENGTH, false) && hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG, - HIDPP_REPORT_LONG_LENGTH - 1, true) && - hidpp_validate_report(hdev, REPORT_ID_HIDPP_VERY_LONG, - HIDPP_REPORT_VERY_LONG_LENGTH - 1, true); + HIDPP_REPORT_LONG_LENGTH, true); } static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) @@ -3291,6 +3300,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->quirks = id->driver_data; + hidpp->very_long_report_length = + hidpp_get_report_length(hdev, REPORT_ID_HIDPP_VERY_LONG); + if (hidpp->very_long_report_length > HIDPP_REPORT_VERY_LONG_MAX_LENGTH) + hidpp->very_long_report_length = HIDPP_REPORT_VERY_LONG_MAX_LENGTH; + if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) hidpp->quirks |= HIDPP_QUIRK_UNIFYING; -- cgit From 0610430e3dea51c9a00565af685745898048fa2b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:13 +0200 Subject: HID: logitech-hidpp: add input_device ptr to struct hidpp_device Most device-class specific code needs access to the input_device, instead of storing that in the class specific data-struct, simply store this into the hidpp_device struct itself. In case of the m560 this avoids the need for having private data at all and this will also avoid the need to add private data in some upcoming patches. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 143 +++++++++++++++------------------------ 1 file changed, 56 insertions(+), 87 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index ecf1b1e22e1a..5ef3f76c3f66 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -149,7 +149,6 @@ struct hidpp_battery { * @last_time: last event time, used to reset remainder after inactivity */ struct hidpp_scroll_counter { - struct input_dev *dev; int wheel_multiplier; int remainder; int direction; @@ -158,6 +157,7 @@ struct hidpp_scroll_counter { struct hidpp_device { struct hid_device *hid_dev; + struct input_dev *input; struct mutex send_mutex; void *send_receive_buf; char *name; /* will never be NULL and should not be freed */ @@ -433,14 +433,15 @@ static void hidpp_prefix_name(char **name, int name_length) * emit low-resolution scroll events when appropriate for * backwards-compatibility with userspace input libraries. */ -static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *counter, +static void hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev, + struct hidpp_scroll_counter *counter, int hi_res_value) { int low_res_value, remainder, direction; unsigned long long now, previous; hi_res_value = hi_res_value * 120/counter->wheel_multiplier; - input_report_rel(counter->dev, REL_WHEEL_HI_RES, hi_res_value); + input_report_rel(input_dev, REL_WHEEL_HI_RES, hi_res_value); remainder = counter->remainder; direction = hi_res_value > 0 ? 1 : -1; @@ -474,7 +475,7 @@ static void hidpp_scroll_counter_handle_scroll(struct hidpp_scroll_counter *coun low_res_value = remainder / 120; if (low_res_value == 0) low_res_value = (hi_res_value > 0 ? 1 : -1); - input_report_rel(counter->dev, REL_WHEEL, low_res_value); + input_report_rel(input_dev, REL_WHEEL, low_res_value); remainder -= low_res_value * 120; } counter->remainder = remainder; @@ -2218,7 +2219,6 @@ static int hidpp_ff_deinit(struct hid_device *hid) #define WTP_MANUAL_RESOLUTION 39 struct wtp_data { - struct input_dev *input; u16 x_size, y_size; u8 finger_count; u8 mt_feature_index; @@ -2262,31 +2262,30 @@ static void wtp_populate_input(struct hidpp_device *hidpp, input_mt_init_slots(input_dev, wd->maxcontacts, INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED); - - wd->input = input_dev; } -static void wtp_touch_event(struct wtp_data *wd, +static void wtp_touch_event(struct hidpp_device *hidpp, struct hidpp_touchpad_raw_xy_finger *touch_report) { + struct wtp_data *wd = hidpp->private_data; int slot; if (!touch_report->finger_id || touch_report->contact_type) /* no actual data */ return; - slot = input_mt_get_slot_by_key(wd->input, touch_report->finger_id); + slot = input_mt_get_slot_by_key(hidpp->input, touch_report->finger_id); - input_mt_slot(wd->input, slot); - input_mt_report_slot_state(wd->input, MT_TOOL_FINGER, + input_mt_slot(hidpp->input, slot); + input_mt_report_slot_state(hidpp->input, MT_TOOL_FINGER, touch_report->contact_status); if (touch_report->contact_status) { - input_event(wd->input, EV_ABS, ABS_MT_POSITION_X, + input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_X, touch_report->x); - input_event(wd->input, EV_ABS, ABS_MT_POSITION_Y, + input_event(hidpp->input, EV_ABS, ABS_MT_POSITION_Y, wd->flip_y ? wd->y_size - touch_report->y : touch_report->y); - input_event(wd->input, EV_ABS, ABS_MT_PRESSURE, + input_event(hidpp->input, EV_ABS, ABS_MT_PRESSURE, touch_report->area); } } @@ -2294,19 +2293,18 @@ static void wtp_touch_event(struct wtp_data *wd, static void wtp_send_raw_xy_event(struct hidpp_device *hidpp, struct hidpp_touchpad_raw_xy *raw) { - struct wtp_data *wd = hidpp->private_data; int i; for (i = 0; i < 2; i++) - wtp_touch_event(wd, &(raw->fingers[i])); + wtp_touch_event(hidpp, &(raw->fingers[i])); if (raw->end_of_frame && !(hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS)) - input_event(wd->input, EV_KEY, BTN_LEFT, raw->button); + input_event(hidpp->input, EV_KEY, BTN_LEFT, raw->button); if (raw->end_of_frame || raw->finger_count <= 2) { - input_mt_sync_frame(wd->input); - input_sync(wd->input); + input_mt_sync_frame(hidpp->input); + input_sync(hidpp->input); } } @@ -2356,7 +2354,7 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) struct hidpp_report *report = (struct hidpp_report *)data; struct hidpp_touchpad_raw_xy raw; - if (!wd || !wd->input) + if (!wd || !hidpp->input) return 1; switch (data[0]) { @@ -2367,11 +2365,11 @@ static int wtp_raw_event(struct hid_device *hdev, u8 *data, int size) return 1; } if (hidpp->quirks & HIDPP_QUIRK_WTP_PHYSICAL_BUTTONS) { - input_event(wd->input, EV_KEY, BTN_LEFT, + input_event(hidpp->input, EV_KEY, BTN_LEFT, !!(data[1] & 0x01)); - input_event(wd->input, EV_KEY, BTN_RIGHT, + input_event(hidpp->input, EV_KEY, BTN_RIGHT, !!(data[1] & 0x02)); - input_sync(wd->input); + input_sync(hidpp->input); return 0; } else { if (size < 21) @@ -2489,10 +2487,6 @@ static int wtp_connect(struct hid_device *hdev, bool connected) static const u8 m560_config_parameter[] = {0x00, 0xaf, 0x03}; -struct m560_private_data { - struct input_dev *input; -}; - /* how buttons are mapped in the report */ #define M560_MOUSE_BTN_LEFT 0x01 #define M560_MOUSE_BTN_RIGHT 0x02 @@ -2520,28 +2514,12 @@ static int m560_send_config_command(struct hid_device *hdev, bool connected) ); } -static int m560_allocate(struct hid_device *hdev) -{ - struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *d; - - d = devm_kzalloc(&hdev->dev, sizeof(struct m560_private_data), - GFP_KERNEL); - if (!d) - return -ENOMEM; - - hidpp->private_data = d; - - return 0; -}; - static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) { struct hidpp_device *hidpp = hid_get_drvdata(hdev); - struct m560_private_data *mydata = hidpp->private_data; /* sanity check */ - if (!mydata || !mydata->input) { + if (!hidpp->input) { hid_err(hdev, "error in parameter\n"); return -EINVAL; } @@ -2568,24 +2546,24 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) switch (data[5]) { case 0xaf: - input_report_key(mydata->input, BTN_MIDDLE, 1); + input_report_key(hidpp->input, BTN_MIDDLE, 1); break; case 0xb0: - input_report_key(mydata->input, BTN_FORWARD, 1); + input_report_key(hidpp->input, BTN_FORWARD, 1); break; case 0xae: - input_report_key(mydata->input, BTN_BACK, 1); + input_report_key(hidpp->input, BTN_BACK, 1); break; case 0x00: - input_report_key(mydata->input, BTN_BACK, 0); - input_report_key(mydata->input, BTN_FORWARD, 0); - input_report_key(mydata->input, BTN_MIDDLE, 0); + input_report_key(hidpp->input, BTN_BACK, 0); + input_report_key(hidpp->input, BTN_FORWARD, 0); + input_report_key(hidpp->input, BTN_MIDDLE, 0); break; default: hid_err(hdev, "error in report\n"); return 0; } - input_sync(mydata->input); + input_sync(hidpp->input); } else if (data[0] == 0x02) { /* @@ -2599,33 +2577,33 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) int v; - input_report_key(mydata->input, BTN_LEFT, + input_report_key(hidpp->input, BTN_LEFT, !!(data[1] & M560_MOUSE_BTN_LEFT)); - input_report_key(mydata->input, BTN_RIGHT, + input_report_key(hidpp->input, BTN_RIGHT, !!(data[1] & M560_MOUSE_BTN_RIGHT)); if (data[1] & M560_MOUSE_BTN_WHEEL_LEFT) { - input_report_rel(mydata->input, REL_HWHEEL, -1); - input_report_rel(mydata->input, REL_HWHEEL_HI_RES, + input_report_rel(hidpp->input, REL_HWHEEL, -1); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, -120); } else if (data[1] & M560_MOUSE_BTN_WHEEL_RIGHT) { - input_report_rel(mydata->input, REL_HWHEEL, 1); - input_report_rel(mydata->input, REL_HWHEEL_HI_RES, + input_report_rel(hidpp->input, REL_HWHEEL, 1); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, 120); } v = hid_snto32(hid_field_extract(hdev, data+3, 0, 12), 12); - input_report_rel(mydata->input, REL_X, v); + input_report_rel(hidpp->input, REL_X, v); v = hid_snto32(hid_field_extract(hdev, data+3, 12, 12), 12); - input_report_rel(mydata->input, REL_Y, v); + input_report_rel(hidpp->input, REL_Y, v); v = hid_snto32(data[6], 8); if (v != 0) - hidpp_scroll_counter_handle_scroll( + hidpp_scroll_counter_handle_scroll(hidpp->input, &hidpp->vertical_wheel_counter, v); - input_sync(mydata->input); + input_sync(hidpp->input); } return 1; @@ -2634,24 +2612,20 @@ static int m560_raw_event(struct hid_device *hdev, u8 *data, int size) static void m560_populate_input(struct hidpp_device *hidpp, struct input_dev *input_dev) { - struct m560_private_data *mydata = hidpp->private_data; - - mydata->input = input_dev; - - __set_bit(EV_KEY, mydata->input->evbit); - __set_bit(BTN_MIDDLE, mydata->input->keybit); - __set_bit(BTN_RIGHT, mydata->input->keybit); - __set_bit(BTN_LEFT, mydata->input->keybit); - __set_bit(BTN_BACK, mydata->input->keybit); - __set_bit(BTN_FORWARD, mydata->input->keybit); + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(BTN_MIDDLE, input_dev->keybit); + __set_bit(BTN_RIGHT, input_dev->keybit); + __set_bit(BTN_LEFT, input_dev->keybit); + __set_bit(BTN_BACK, input_dev->keybit); + __set_bit(BTN_FORWARD, input_dev->keybit); - __set_bit(EV_REL, mydata->input->evbit); - __set_bit(REL_X, mydata->input->relbit); - __set_bit(REL_Y, mydata->input->relbit); - __set_bit(REL_WHEEL, mydata->input->relbit); - __set_bit(REL_HWHEEL, mydata->input->relbit); - __set_bit(REL_WHEEL_HI_RES, mydata->input->relbit); - __set_bit(REL_HWHEEL_HI_RES, mydata->input->relbit); + __set_bit(EV_REL, input_dev->evbit); + __set_bit(REL_X, input_dev->relbit); + __set_bit(REL_Y, input_dev->relbit); + __set_bit(REL_WHEEL, input_dev->relbit); + __set_bit(REL_HWHEEL, input_dev->relbit); + __set_bit(REL_WHEEL_HI_RES, input_dev->relbit); + __set_bit(REL_HWHEEL_HI_RES, input_dev->relbit); } static int m560_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -2831,13 +2805,12 @@ static int hidpp_input_mapped(struct hid_device *hdev, struct hid_input *hi, static void hidpp_populate_input(struct hidpp_device *hidpp, struct input_dev *input) { + hidpp->input = input; + if (hidpp->quirks & HIDPP_QUIRK_CLASS_WTP) wtp_populate_input(hidpp, input); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) m560_populate_input(hidpp, input); - - if (hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) - hidpp->vertical_wheel_counter.dev = input; } static int hidpp_input_configured(struct hid_device *hdev, @@ -2981,10 +2954,10 @@ static int hidpp_event(struct hid_device *hdev, struct hid_field *field, * avoid a crash in hidpp_scroll_counter_handle_scroll. */ if (!(hidpp->quirks & HIDPP_QUIRK_HI_RES_SCROLL) || value == 0 - || counter->dev == NULL || counter->wheel_multiplier == 0) + || hidpp->input == NULL || counter->wheel_multiplier == 0) return 0; - hidpp_scroll_counter_handle_scroll(counter, value); + hidpp_scroll_counter_handle_scroll(hidpp->input, counter, value); return 1; } @@ -3317,10 +3290,6 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) ret = wtp_allocate(hdev, id); if (ret) return ret; - } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) { - ret = m560_allocate(hdev); - if (ret) - return ret; } else if (hidpp->quirks & HIDPP_QUIRK_CLASS_K400) { ret = k400_allocate(hdev); if (ret) -- cgit From 35839f77238bccb695696bb390732339d53b8062 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:14 +0200 Subject: HID: logitech-hidpp: make hidpp10_set_register_bit a bit more generic Make hidpp10_set_register_bit() take a mask and value for the register byte being changed, rather then making it only set a single bit. While at it also at defines for the bits which we will be using. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 5ef3f76c3f66..2b769766e158 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -491,14 +491,16 @@ static void hidpp_scroll_counter_handle_scroll(struct input_dev *input_dev, #define HIDPP_GET_LONG_REGISTER 0x83 /** - * hidpp10_set_register_bit() - Sets a single bit in a HID++ 1.0 register. + * hidpp10_set_register - Modify a HID++ 1.0 register. * @hidpp_dev: the device to set the register on. * @register_address: the address of the register to modify. * @byte: the byte of the register to modify. Should be less than 3. + * @mask: mask of the bits to modify + * @value: new values for the bits in mask * Return: 0 if successful, otherwise a negative error code. */ -static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, - u8 register_address, u8 byte, u8 bit) +static int hidpp10_set_register(struct hidpp_device *hidpp_dev, + u8 register_address, u8 byte, u8 mask, u8 value) { struct hidpp_report response; int ret; @@ -514,7 +516,8 @@ static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, memcpy(params, response.rap.params, 3); - params[byte] |= BIT(bit); + params[byte] &= ~mask; + params[byte] |= value & mask; return hidpp_send_rap_command_sync(hidpp_dev, REPORT_ID_HIDPP_SHORT, @@ -523,20 +526,28 @@ static int hidpp10_set_register_bit(struct hidpp_device *hidpp_dev, params, 3, &response); } - -#define HIDPP_REG_GENERAL 0x00 +#define HIDPP_REG_ENABLE_REPORTS 0x00 +#define HIDPP_ENABLE_CONSUMER_REPORT BIT(0) +#define HIDPP_ENABLE_WHEEL_REPORT BIT(2) +#define HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT BIT(3) +#define HIDPP_ENABLE_BAT_REPORT BIT(4) +#define HIDPP_ENABLE_HWHEEL_REPORT BIT(5) static int hidpp10_enable_battery_reporting(struct hidpp_device *hidpp_dev) { - return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_GENERAL, 0, 4); + return hidpp10_set_register(hidpp_dev, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_BAT_REPORT, HIDPP_ENABLE_BAT_REPORT); } #define HIDPP_REG_FEATURES 0x01 +#define HIDPP_ENABLE_SPECIAL_BUTTON_FUNC BIT(1) +#define HIDPP_ENABLE_FAST_SCROLL BIT(6) /* On HID++ 1.0 devices, high-res scroll was called "scrolling acceleration". */ static int hidpp10_enable_scrolling_acceleration(struct hidpp_device *hidpp_dev) { - return hidpp10_set_register_bit(hidpp_dev, HIDPP_REG_FEATURES, 0, 6); + return hidpp10_set_register(hidpp_dev, HIDPP_REG_FEATURES, 0, + HIDPP_ENABLE_FAST_SCROLL, HIDPP_ENABLE_FAST_SCROLL); } #define HIDPP_REG_BATTERY_STATUS 0x07 -- cgit From 4a79bcc64a0514d630783b8d19cf9a85e46fe988 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:15 +0200 Subject: HID: logitech-hidpp: add support for HID++ 1.0 wheel reports Add a quirk for switching wheel event reporting to using the HID++ report for this. This has 2 advantages: 1) Without this tilting the scrollwheel left / right will send a scroll-lock + cursor-left/-right + scroll-lock key-sequence instead of hwheel events 2) The HID++ reports contain the device index instead of using the generic HID implementation, so this will make scroll-wheel events from the wheel on some keyboards be emitted by the right event node. 2. also fixes keyboard scroll-wheel events getting lost in the (mostly theoretical) case of there not being a mouse paired with the receiver. This commit enables this quirk for all 27Mhz mice, it cannot hurt to have it enabled and this avoids the need to keep adding more and more quirks for this. This has been tested in 5 different 27MHz mice, 3 of which have a wheel which can tilt. This commit also adds explicit quirks for 3 keyboards with a zoom-/scroll- wheel. The MX3000 keyboard scroll-wheel can also tilt. I've defined aliases to the new HIDPP_QUIRK_HIDPP_WHEELS for this, so that it is clear why the keyboard has the quirk and in case we want to handle the keyboard wheels and especially the keyboard zoom-wheels differently in the future. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 2b769766e158..7f6bebae3a78 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -53,6 +53,9 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_REPORT_LONG_LENGTH 20 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64 +#define HIDPP_SUB_ID_ROLLER 0x05 +#define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS 0x06 + #define HIDPP_QUIRK_CLASS_WTP BIT(0) #define HIDPP_QUIRK_CLASS_M560 BIT(1) #define HIDPP_QUIRK_CLASS_K400 BIT(2) @@ -68,6 +71,11 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_HI_RES_SCROLL_1P0 BIT(26) #define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27) #define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) +#define HIDPP_QUIRK_HIDPP_WHEELS BIT(29) + +/* These are just aliases for now */ +#define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS +#define HIDPP_QUIRK_KBD_ZOOM_WHEEL HIDPP_QUIRK_HIDPP_WHEELS /* Convenience constant to check for any high-res support. */ #define HIDPP_QUIRK_HI_RES_SCROLL (HIDPP_QUIRK_HI_RES_SCROLL_1P0 | \ @@ -2738,6 +2746,52 @@ static int g920_get_config(struct hidpp_device *hidpp) return 0; } +/* -------------------------------------------------------------------------- */ +/* HID++1.0 devices which use HID++ reports for their wheels */ +/* -------------------------------------------------------------------------- */ +static int hidpp10_wheel_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT, + HIDPP_ENABLE_WHEEL_REPORT | HIDPP_ENABLE_HWHEEL_REPORT); +} + +static int hidpp10_wheel_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + s8 value, hvalue; + + if (!hidpp->input) + return -EINVAL; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || data[2] != HIDPP_SUB_ID_ROLLER) + return 0; + + value = data[3]; + hvalue = data[4]; + + input_report_rel(hidpp->input, REL_WHEEL, value); + input_report_rel(hidpp->input, REL_WHEEL_HI_RES, value * 120); + input_report_rel(hidpp->input, REL_HWHEEL, hvalue); + input_report_rel(hidpp->input, REL_HWHEEL_HI_RES, hvalue * 120); + input_sync(hidpp->input); + + return 1; +} + +static void hidpp10_wheel_populate_input(struct hidpp_device *hidpp, + struct input_dev *input_dev) +{ + __set_bit(EV_REL, input_dev->evbit); + __set_bit(REL_WHEEL, input_dev->relbit); + __set_bit(REL_WHEEL_HI_RES, input_dev->relbit); + __set_bit(REL_HWHEEL, input_dev->relbit); + __set_bit(REL_HWHEEL_HI_RES, input_dev->relbit); +} + /* -------------------------------------------------------------------------- */ /* High-resolution scroll wheels */ /* -------------------------------------------------------------------------- */ @@ -2822,6 +2876,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp, wtp_populate_input(hidpp, input); else if (hidpp->quirks & HIDPP_QUIRK_CLASS_M560) m560_populate_input(hidpp, input); + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) + hidpp10_wheel_populate_input(hidpp, input); } static int hidpp_input_configured(struct hid_device *hdev, @@ -2893,6 +2950,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) { + ret = hidpp10_wheel_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -3140,6 +3203,12 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) { + ret = hidpp10_wheel_connect(hidpp); + if (ret) + return; + } + /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { @@ -3254,6 +3323,17 @@ static bool hidpp_validate_device(struct hid_device *hdev) HIDPP_REPORT_LONG_LENGTH, true); } +static bool hidpp_application_equals(struct hid_device *hdev, + unsigned int application) +{ + struct list_head *report_list; + struct hid_report *report; + + report_list = &hdev->report_enum[HID_INPUT_REPORT].report_list; + report = list_first_entry_or_null(report_list, struct hid_report, list); + return report && report->application == application; +} + static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct hidpp_device *hidpp; @@ -3292,6 +3372,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (id->group == HID_GROUP_LOGITECH_DJ_DEVICE) hidpp->quirks |= HIDPP_QUIRK_UNIFYING; + if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + hidpp_application_equals(hdev, HID_GD_MOUSE)) + hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS; + if (disable_raw_mode) { hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; @@ -3472,6 +3556,16 @@ static const struct hid_device_id hidpp_devices[] = { { LDJ_DEVICE(HID_ANY_ID) }, + { /* Keyboard LX501 (Y-RR53) */ + L27MHZ_DEVICE(0x0049), + .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL }, + { /* Keyboard MX3000 (Y-RAM74) */ + L27MHZ_DEVICE(0x0057), + .driver_data = HIDPP_QUIRK_KBD_SCROLL_WHEEL }, + { /* Keyboard MX3200 (Y-RAV80) */ + L27MHZ_DEVICE(0x005c), + .driver_data = HIDPP_QUIRK_KBD_ZOOM_WHEEL }, + { L27MHZ_DEVICE(HID_ANY_ID) }, { /* Logitech G403 Gaming Mouse over USB */ -- cgit From 7457bc1b0ebf30d98ce993ec876a5ab2b143539d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:16 +0200 Subject: HID: logitech-hidpp: add support for HID++ 1.0 extra mouse buttons reports Some mice have extra buttons which are only reported through HID++ 1.0 extra mouse buttons reports, this commit adds support for this and automatically enables this support for all 27 MHz mice. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 77 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 7f6bebae3a78..215c3a256c27 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -72,6 +72,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_HI_RES_SCROLL_X2120 BIT(27) #define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) #define HIDPP_QUIRK_HIDPP_WHEELS BIT(29) +#define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(30) /* These are just aliases for now */ #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS @@ -2792,6 +2793,64 @@ static void hidpp10_wheel_populate_input(struct hidpp_device *hidpp, __set_bit(REL_HWHEEL_HI_RES, input_dev->relbit); } +/* -------------------------------------------------------------------------- */ +/* HID++1.0 mice which use HID++ reports for extra mouse buttons */ +/* -------------------------------------------------------------------------- */ +static int hidpp10_extra_mouse_buttons_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT, + HIDPP_ENABLE_MOUSE_EXTRA_BTN_REPORT); +} + +static int hidpp10_extra_mouse_buttons_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + int i; + + if (!hidpp->input) + return -EINVAL; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || + data[2] != HIDPP_SUB_ID_MOUSE_EXTRA_BTNS) + return 0; + + /* + * Buttons are either delivered through the regular mouse report *or* + * through the extra buttons report. At least for button 6 how it is + * delivered differs per receiver firmware version. Even receivers with + * the same usb-id show different behavior, so we handle both cases. + */ + for (i = 0; i < 8; i++) + input_report_key(hidpp->input, BTN_MOUSE + i, + (data[3] & (1 << i))); + + /* Some mice report events on button 9+, use BTN_MISC */ + for (i = 0; i < 8; i++) + input_report_key(hidpp->input, BTN_MISC + i, + (data[4] & (1 << i))); + + input_sync(hidpp->input); + return 1; +} + +static void hidpp10_extra_mouse_buttons_populate_input( + struct hidpp_device *hidpp, struct input_dev *input_dev) +{ + /* BTN_MOUSE - BTN_MOUSE+7 are set already by the descriptor */ + __set_bit(BTN_0, input_dev->keybit); + __set_bit(BTN_1, input_dev->keybit); + __set_bit(BTN_2, input_dev->keybit); + __set_bit(BTN_3, input_dev->keybit); + __set_bit(BTN_4, input_dev->keybit); + __set_bit(BTN_5, input_dev->keybit); + __set_bit(BTN_6, input_dev->keybit); + __set_bit(BTN_7, input_dev->keybit); +} + /* -------------------------------------------------------------------------- */ /* High-resolution scroll wheels */ /* -------------------------------------------------------------------------- */ @@ -2879,6 +2938,9 @@ static void hidpp_populate_input(struct hidpp_device *hidpp, if (hidpp->quirks & HIDPP_QUIRK_HIDPP_WHEELS) hidpp10_wheel_populate_input(hidpp, input); + + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) + hidpp10_extra_mouse_buttons_populate_input(hidpp, input); } static int hidpp_input_configured(struct hid_device *hdev, @@ -2956,6 +3018,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) { + ret = hidpp10_extra_mouse_buttons_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -3209,6 +3277,12 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS) { + ret = hidpp10_extra_mouse_buttons_connect(hidpp); + if (ret) + return; + } + /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { @@ -3374,7 +3448,8 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && hidpp_application_equals(hdev, HID_GD_MOUSE)) - hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS; + hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS | + HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS; if (disable_raw_mode) { hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; -- cgit From 42bc4f3129e6565567ee63116850c530dc561aa0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:17 +0200 Subject: HID: logitech-hidpp: add support for HID++ 1.0 consumer keys reports All Logitech 27 MHz keyboards and also the MX5000 bluetooth keyboard use Logitech custom usages of 0x10xx in the consumer page. The descriptor for the consumer input-report only declares usages up to 652, so we end up dropping all the input-reports reporting 0x10xx usages without reporting events for these to userspace. This commit adds a descriptor_fixup function for this which changes the usage and logical maximum to 0x107f. Mapping these usages to something other then KEY_UNKNOWN is left to userspace (hwdb). Note: 1. The old descriptor_fixup for this in hid-lg.c used a maximimum of 0x104d this is not high enough, the S520 keyboard battery key sends 0x106f. 2. The descriptor_fixup is flexible so that it works with both the kbd- desc. passed by the logitech-dj code and with bluetooth descriptors. The descriptor_fixup makes most keys work on 27 MHz keyboards, but it is not enough to get all keys to work on 27 MHz keyboards and just the fixup is not enough to get the MX5000 to generate 0x10xx events: 1) The LX501 and MX3000 27 MHz kbds both have a button labelled "media" (called "Media Player" by SetPoint) and a button with a remote-control symbol ("Media Life" in SetPoint) which both send an identical consumer usage-page code (0x0183) making the 2 buttons indistinguishable, switching to HID++ 1.0 consumer keys reports makes the remote-control symbol button generate a 0x10xx Logitech specific code instead. 2) The MX5000 Bluetooth keyboard has 11 keys which report 0x10xx consumer page usages, but unlike 27 MHz devices which happily send 0x10xx codes in their normal consumer-page input-report, the MX5000 honors the maximum of 652 from its descriptor and sends a 0x0000 code (so release) whenever these keys are pressed. When switching to HID++ sub-id 0x03 HID++ 1.0 consumer keys reports these 0x10xx codes do get properly reported. This commit adds support for HID++ 1.0 consumer keys reports and enables this for all 27 MHz keyboards and for the MX5000. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 133 +++++++++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 12 deletions(-) (limited to 'drivers/hid/hid-logitech-hidpp.c') diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 215c3a256c27..72fc9c0566db 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -53,6 +53,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_REPORT_LONG_LENGTH 20 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64 +#define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS 0x03 #define HIDPP_SUB_ID_ROLLER 0x05 #define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS 0x06 @@ -73,6 +74,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) #define HIDPP_QUIRK_HIDPP_WHEELS BIT(29) #define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(30) +#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(31) /* These are just aliases for now */ #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS @@ -2851,6 +2853,71 @@ static void hidpp10_extra_mouse_buttons_populate_input( __set_bit(BTN_7, input_dev->keybit); } +/* -------------------------------------------------------------------------- */ +/* HID++1.0 kbds which only report 0x10xx consumer usages through sub-id 0x03 */ +/* -------------------------------------------------------------------------- */ + +/* Find the consumer-page input report desc and change Maximums to 0x107f */ +static u8 *hidpp10_consumer_keys_report_fixup(struct hidpp_device *hidpp, + u8 *_rdesc, unsigned int *rsize) +{ + /* Note 0 terminated so we can use strnstr to search for this. */ + const char consumer_rdesc_start[] = { + 0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */ + 0x09, 0x01, /* USAGE (Consumer Control) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x03, /* REPORT_ID = 3 */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x15, 0x01, /* LOGICAL_MIN (1) */ + 0x26, 0x00 /* LOGICAL_MAX (... */ + }; + char *consumer_rdesc, *rdesc = (char *)_rdesc; + unsigned int size; + + consumer_rdesc = strnstr(rdesc, consumer_rdesc_start, *rsize); + size = *rsize - (consumer_rdesc - rdesc); + if (consumer_rdesc && size >= 25) { + consumer_rdesc[15] = 0x7f; + consumer_rdesc[16] = 0x10; + consumer_rdesc[20] = 0x7f; + consumer_rdesc[21] = 0x10; + } + return _rdesc; +} + +static int hidpp10_consumer_keys_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_CONSUMER_REPORT, + HIDPP_ENABLE_CONSUMER_REPORT); +} + +static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + u8 consumer_report[5]; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || + data[2] != HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS) + return 0; + + /* + * Build a normal consumer report (3) out of the data, this detour + * is necessary to get some keyboards to report their 0x10xx usages. + */ + consumer_report[0] = 0x03; + memcpy(&consumer_report[1], &data[3], 4); + /* We are called from atomic context */ + hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT, + consumer_report, 5, 1); + + return 1; +} + /* -------------------------------------------------------------------------- */ /* High-resolution scroll wheels */ /* -------------------------------------------------------------------------- */ @@ -2886,6 +2953,22 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ +static u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *rsize) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (!hidpp) + return rdesc; + + /* For 27 MHz keyboards the quirk gets set after hid_parse. */ + if (hdev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE || + (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS)) + rdesc = hidpp10_consumer_keys_report_fixup(hidpp, rdesc, rsize); + + return rdesc; +} + static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -3024,6 +3107,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -3283,6 +3372,12 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_connect(hidpp); + if (ret) + return; + } + /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { @@ -3415,6 +3510,16 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) bool connected; unsigned int connect_mask = HID_CONNECT_DEFAULT; + /* report_fixup needs drvdata to be set before we call hid_parse */ + hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); + if (!hidpp) + return -ENOMEM; + + hidpp->hid_dev = hdev; + hidpp->name = hdev->name; + hidpp->quirks = id->driver_data; + hid_set_drvdata(hdev, hidpp); + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "%s:parse failed\n", __func__); @@ -3424,19 +3529,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* * Make sure the device is HID++ capable, otherwise treat as generic HID */ - if (!hidpp_validate_device(hdev)) + if (!hidpp_validate_device(hdev)) { + hid_set_drvdata(hdev, NULL); + devm_kfree(&hdev->dev, hidpp); return hid_hw_start(hdev, HID_CONNECT_DEFAULT); - - hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), - GFP_KERNEL); - if (!hidpp) - return -ENOMEM; - - hidpp->hid_dev = hdev; - hidpp->name = hdev->name; - hid_set_drvdata(hdev, hidpp); - - hidpp->quirks = id->driver_data; + } hidpp->very_long_report_length = hidpp_get_report_length(hdev, REPORT_ID_HIDPP_VERY_LONG); @@ -3451,6 +3548,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS | HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS; + if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + hidpp_application_equals(hdev, HID_GD_KEYBOARD)) + hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS; + if (disable_raw_mode) { hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; @@ -3628,6 +3729,9 @@ static const struct hid_device_id hidpp_devices[] = { { /* Solar Keyboard Logitech K750 */ LDJ_DEVICE(0x4002), .driver_data = HIDPP_QUIRK_CLASS_K750 }, + { /* Keyboard MX5000 (Bluetooth-receiver in HID proxy mode) */ + LDJ_DEVICE(0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, { LDJ_DEVICE(HID_ANY_ID) }, @@ -3652,6 +3756,10 @@ static const struct hid_device_id hidpp_devices[] = { { /* Logitech G920 Wheel over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, + + { /* MX5000 keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, {} }; @@ -3665,6 +3773,7 @@ static const struct hid_usage_id hidpp_usages[] = { static struct hid_driver hidpp_driver = { .name = "logitech-hidpp-device", .id_table = hidpp_devices, + .report_fixup = hidpp_report_fixup, .probe = hidpp_probe, .remove = hidpp_remove, .raw_event = hidpp_raw_event, -- cgit