From d3132792285859253c466354fd8d54d1fe0ba786 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Tue, 9 Jun 2020 21:38:24 -0700 Subject: [PATCH 01/16] HID: usbhid: do not sleep when opening device usbhid tries to give the device 50 milliseconds to drain its queues when opening the device, but does it naively by simply sleeping in open handler, which slows down device probing (and thus may affect overall boot time). However we do not need to sleep as we can instead mark a point of time in the future when we should start processing the events. Reported-by: Nicolas Boichat Signed-off-by: Dmitry Torokhov Reviewed-by: Guenter Roeck Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 53 +++++++++++++++++++---------------- drivers/hid/usbhid/usbhid.h | 2 ++ 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 17a638f15082..1235288b65bf 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include @@ -95,6 +96,18 @@ static int hid_start_in(struct hid_device *hid) set_bit(HID_NO_BANDWIDTH, &usbhid->iofl); } else { clear_bit(HID_NO_BANDWIDTH, &usbhid->iofl); + + if (test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) { + /* + * In case events are generated while nobody was + * listening, some are released when the device + * is re-opened. Wait 50 msec for the queue to + * empty before allowing events to go through + * hid. + */ + usbhid->input_start_time = + ktime_add_ms(ktime_get_coarse(), 50); + } } } spin_unlock_irqrestore(&usbhid->lock, flags); @@ -280,20 +293,23 @@ static void hid_irq_in(struct urb *urb) if (!test_bit(HID_OPENED, &usbhid->iofl)) break; usbhid_mark_busy(usbhid); - if (!test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) { - hid_input_report(urb->context, HID_INPUT_REPORT, - urb->transfer_buffer, - urb->actual_length, 1); - /* - * autosuspend refused while keys are pressed - * because most keyboards don't wake up when - * a key is released - */ - if (hid_check_keys_pressed(hid)) - set_bit(HID_KEYS_PRESSED, &usbhid->iofl); - else - clear_bit(HID_KEYS_PRESSED, &usbhid->iofl); + if (test_bit(HID_RESUME_RUNNING, &usbhid->iofl)) { + if (ktime_before(ktime_get_coarse(), + usbhid->input_start_time)) + break; + clear_bit(HID_RESUME_RUNNING, &usbhid->iofl); } + hid_input_report(urb->context, HID_INPUT_REPORT, + urb->transfer_buffer, urb->actual_length, 1); + /* + * autosuspend refused while keys are pressed + * because most keyboards don't wake up when + * a key is released + */ + if (hid_check_keys_pressed(hid)) + set_bit(HID_KEYS_PRESSED, &usbhid->iofl); + else + clear_bit(HID_KEYS_PRESSED, &usbhid->iofl); break; case -EPIPE: /* stall */ usbhid_mark_busy(usbhid); @@ -720,17 +736,6 @@ static int usbhid_open(struct hid_device *hid) usb_autopm_put_interface(usbhid->intf); - /* - * In case events are generated while nobody was listening, - * some are released when the device is re-opened. - * Wait 50 msec for the queue to empty before allowing events - * to go through hid. - */ - if (res == 0) - msleep(50); - - clear_bit(HID_RESUME_RUNNING, &usbhid->iofl); - Done: mutex_unlock(&usbhid->mutex); return res; diff --git a/drivers/hid/usbhid/usbhid.h b/drivers/hid/usbhid/usbhid.h index 75fe85d3d27a..c6ad684d099a 100644 --- a/drivers/hid/usbhid/usbhid.h +++ b/drivers/hid/usbhid/usbhid.h @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -83,6 +84,7 @@ struct usbhid_device { struct mutex mutex; /* start/stop/open/close */ spinlock_t lock; /* fifo spinlock */ unsigned long iofl; /* I/O flags (CTRL_RUNNING, OUT_RUNNING) */ + ktime_t input_start_time; /* When to start handling input */ struct timer_list io_retry; /* Retry timer */ unsigned long stop_retry; /* Time to give up, in jiffies */ unsigned int retry_delay; /* Delay length in ms */ From 8e9ddbde9ddbba74c86b0c5f8eefb26192b3242e Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 10 Jun 2020 13:31:01 +0100 Subject: [PATCH 02/16] HID: usbhid: remove redundant assignment to variable retval The variable retval is being initialized with a value that is never read and it is being updated later with a new value. The initialization is redundant and can be removed. Addresses-Coverity: ("Unused value") Signed-off-by: Colin Ian King Signed-off-by: Jiri Kosina --- drivers/hid/usbhid/hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 1235288b65bf..492dd641a25d 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -1672,7 +1672,7 @@ struct usb_interface *usbhid_find_interface(int minor) static int __init hid_init(void) { - int retval = -ENOMEM; + int retval; retval = hid_quirks_init(quirks_param, BUS_USB, MAX_USBHID_BOOT_QUIRKS); if (retval) From 1ad273d6ff2d3af6ce3a4c8bd81a2ba10151ae0e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Tue, 9 Jun 2020 16:03:19 +1000 Subject: [PATCH 03/16] HID: input: do not run GET_REPORT unless there's a Resolution Multiplier hid-multitouch currently runs GET_REPORT for Contact Max and again to retrieve the Win8 blob. If both are within the same report, the Resolution Multiplier code calls GET_FEATURE again and this time, possibly due to timing, it causes the ILITEK-TP device interpret the GET_FEATURE as an instruction to change the mode and effectively stop the device from functioning as expected. Notably: the device doesn't even have a Resolution Multiplier so it shouldn't be affected by any of this at all. Fix this by making sure we only execute GET_REPORT if there is a Resolution Multiplier in the respective report. Where the HID_QUIRK_NO_INIT_REPORTS field is set we just bail out immediately. This shouldn't be triggered by any real device anyway. Signed-off-by: Peter Hutterer Tested-by: Wen He Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index dea9cc65bf80..c8633beae260 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1560,21 +1560,12 @@ static bool __hidinput_change_resolution_multipliers(struct hid_device *hid, { struct hid_usage *usage; bool update_needed = false; + bool get_report_completed = false; int i, j; if (report->maxfield == 0) return false; - /* - * If we have more than one feature within this report we - * need to fill in the bits from the others before we can - * overwrite the ones for the Resolution Multiplier. - */ - if (report->maxfield > 1) { - hid_hw_request(hid, report, HID_REQ_GET_REPORT); - hid_hw_wait(hid); - } - for (i = 0; i < report->maxfield; i++) { __s32 value = use_logical_max ? report->field[i]->logical_maximum : @@ -1593,6 +1584,25 @@ static bool __hidinput_change_resolution_multipliers(struct hid_device *hid, if (usage->hid != HID_GD_RESOLUTION_MULTIPLIER) continue; + /* + * If we have more than one feature within this + * report we need to fill in the bits from the + * others before we can overwrite the ones for the + * Resolution Multiplier. + * + * But if we're not allowed to read from the device, + * we just bail. Such a device should not exist + * anyway. + */ + if (!get_report_completed && report->maxfield > 1) { + if (hid->quirks & HID_QUIRK_NO_INIT_REPORTS) + return update_needed; + + hid_hw_request(hid, report, HID_REQ_GET_REPORT); + hid_hw_wait(hid); + get_report_completed = true; + } + report->field[i]->value[j] = value; update_needed = true; } From a8cbf80e9fb175feb40cb56af93b3504f3c68551 Mon Sep 17 00:00:00 2001 From: "Daniel G. Morse" Date: Tue, 26 May 2020 02:55:50 +0100 Subject: [PATCH 04/16] HID: Wiimote: Treat the d-pad as an analogue stick The controllers from the Super Nintendo Classic Edition (AKA the SNES Mini) appear as a Classic Controller Pro when connected to a Wii Remote. All the buttons work as the same, with the d-pad being mapped the same as the d-pad on the Classic Controller Pro. This differs from the behaviour of most controllers with d-pads and no analogue sticks, where the d-pad maps to ABS_HAT1X for left and right, and ABS_HAT1Y for up and down. This patch adds an option to the hid-wiimote module to make the Super Nintendo Classic Controller behave this way. The patch has been tested with a Super Nintendo Classic Controller plugged into a Wii Remote in both with the option both enabled and disabled. When enabled the d-pad acts as the analogue control, and when disabled it acts as it did before the patch was applied. This patch has not been tested with e Wii Classic Controller (either the original or the pro version) as I do not have one of these controllers. Although I have not tested it with these controllers, I think it is likely this patch will also work with the NES Classic Edition Controllers. Signed-off-by: Daniel G. Morse Reviewed-by: David Rheinsberg Signed-off-by: Jiri Kosina --- drivers/hid/hid-wiimote-core.c | 5 +++ drivers/hid/hid-wiimote-modules.c | 67 ++++++++++++++++++++----------- drivers/hid/hid-wiimote.h | 2 + 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/drivers/hid/hid-wiimote-core.c b/drivers/hid/hid-wiimote-core.c index 92874dbe4d4a..679e142fc850 100644 --- a/drivers/hid/hid-wiimote-core.c +++ b/drivers/hid/hid-wiimote-core.c @@ -1870,6 +1870,11 @@ static const struct hid_device_id wiimote_hid_devices[] = { USB_DEVICE_ID_NINTENDO_WIIMOTE2) }, { } }; + +bool wiimote_dpad_as_analog = false; +module_param_named(dpad_as_analog, wiimote_dpad_as_analog, bool, 0644); +MODULE_PARM_DESC(dpad_as_analog, "Use D-Pad as main analog input"); + MODULE_DEVICE_TABLE(hid, wiimote_hid_devices); static struct hid_driver wiimote_hid_driver = { diff --git a/drivers/hid/hid-wiimote-modules.c b/drivers/hid/hid-wiimote-modules.c index 2c3925357857..213c58bf2495 100644 --- a/drivers/hid/hid-wiimote-modules.c +++ b/drivers/hid/hid-wiimote-modules.c @@ -1088,12 +1088,28 @@ static void wiimod_classic_in_ext(struct wiimote_data *wdata, const __u8 *ext) * is the same as before. */ + static const s8 digital_to_analog[3] = {0x20, 0, -0x20}; + if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) { - lx = ext[0] & 0x3e; - ly = ext[1] & 0x3e; + if (wiimote_dpad_as_analog) { + lx = digital_to_analog[1 - !(ext[4] & 0x80) + + !(ext[1] & 0x01)]; + ly = digital_to_analog[1 - !(ext[4] & 0x40) + + !(ext[0] & 0x01)]; + } else { + lx = (ext[0] & 0x3e) - 0x20; + ly = (ext[1] & 0x3e) - 0x20; + } } else { - lx = ext[0] & 0x3f; - ly = ext[1] & 0x3f; + if (wiimote_dpad_as_analog) { + lx = digital_to_analog[1 - !(ext[4] & 0x80) + + !(ext[5] & 0x02)]; + ly = digital_to_analog[1 - !(ext[4] & 0x40) + + !(ext[5] & 0x01)]; + } else { + lx = (ext[0] & 0x3f) - 0x20; + ly = (ext[1] & 0x3f) - 0x20; + } } rx = (ext[0] >> 3) & 0x18; @@ -1110,19 +1126,13 @@ static void wiimod_classic_in_ext(struct wiimote_data *wdata, const __u8 *ext) rt <<= 1; lt <<= 1; - input_report_abs(wdata->extension.input, ABS_HAT1X, lx - 0x20); - input_report_abs(wdata->extension.input, ABS_HAT1Y, ly - 0x20); + input_report_abs(wdata->extension.input, ABS_HAT1X, lx); + input_report_abs(wdata->extension.input, ABS_HAT1Y, ly); input_report_abs(wdata->extension.input, ABS_HAT2X, rx - 0x20); input_report_abs(wdata->extension.input, ABS_HAT2Y, ry - 0x20); input_report_abs(wdata->extension.input, ABS_HAT3X, rt); input_report_abs(wdata->extension.input, ABS_HAT3Y, lt); - input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_RIGHT], - !(ext[4] & 0x80)); - input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_DOWN], - !(ext[4] & 0x40)); input_report_key(wdata->extension.input, wiimod_classic_map[WIIMOD_CLASSIC_KEY_LT], !(ext[4] & 0x20)); @@ -1157,20 +1167,29 @@ static void wiimod_classic_in_ext(struct wiimote_data *wdata, const __u8 *ext) wiimod_classic_map[WIIMOD_CLASSIC_KEY_ZR], !(ext[5] & 0x04)); - if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) { + if (!wiimote_dpad_as_analog) { input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT], - !(ext[1] & 0x01)); + wiimod_classic_map[WIIMOD_CLASSIC_KEY_RIGHT], + !(ext[4] & 0x80)); input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP], - !(ext[0] & 0x01)); - } else { - input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT], - !(ext[5] & 0x02)); - input_report_key(wdata->extension.input, - wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP], - !(ext[5] & 0x01)); + wiimod_classic_map[WIIMOD_CLASSIC_KEY_DOWN], + !(ext[4] & 0x40)); + + if (wdata->state.flags & WIIPROTO_FLAG_MP_ACTIVE) { + input_report_key(wdata->extension.input, + wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT], + !(ext[1] & 0x01)); + input_report_key(wdata->extension.input, + wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP], + !(ext[0] & 0x01)); + } else { + input_report_key(wdata->extension.input, + wiimod_classic_map[WIIMOD_CLASSIC_KEY_LEFT], + !(ext[5] & 0x02)); + input_report_key(wdata->extension.input, + wiimod_classic_map[WIIMOD_CLASSIC_KEY_UP], + !(ext[5] & 0x01)); + } } input_sync(wdata->extension.input); diff --git a/drivers/hid/hid-wiimote.h b/drivers/hid/hid-wiimote.h index b2a26a0a8f12..ad4ff837f43e 100644 --- a/drivers/hid/hid-wiimote.h +++ b/drivers/hid/hid-wiimote.h @@ -162,6 +162,8 @@ struct wiimote_data { struct work_struct init_worker; }; +extern bool wiimote_dpad_as_analog; + /* wiimote modules */ enum wiimod_module { From 7e341061ddd9239f7214fc3abb794f0ed977e259 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Mon, 29 Jun 2020 12:16:46 +0800 Subject: [PATCH 05/16] HID: intel-ish-hid: Replace PCI_DEV_FLAGS_NO_D3 with pci_save_state PCI_DEV_FLAGS_NO_D3 should not be used outside of PCI core. Instead, we can use pci_save_state() to hint PCI core that the device should stay at D0 during suspend. By doing so, PCI core will let the upstream bridges also stays at D0 so the entire hierarchy is in the correct power state as PCI spec mandates. Signed-off-by: Kai-Heng Feng Acked-by: Srinivas Pandruvada Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ipc/pci-ish.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ipc/pci-ish.c b/drivers/hid/intel-ish-hid/ipc/pci-ish.c index f491d8b4e24c..c6d48a8648b7 100644 --- a/drivers/hid/intel-ish-hid/ipc/pci-ish.c +++ b/drivers/hid/intel-ish-hid/ipc/pci-ish.c @@ -106,6 +106,11 @@ static inline bool ish_should_enter_d0i3(struct pci_dev *pdev) return !pm_suspend_via_firmware() || pdev->device == CHV_DEVICE_ID; } +static inline bool ish_should_leave_d0i3(struct pci_dev *pdev) +{ + return !pm_resume_via_firmware() || pdev->device == CHV_DEVICE_ID; +} + /** * ish_probe() - PCI driver probe callback * @pdev: pci device @@ -215,9 +220,7 @@ static void __maybe_unused ish_resume_handler(struct work_struct *work) struct ishtp_device *dev = pci_get_drvdata(pdev); int ret; - /* Check the NO_D3 flag to distinguish the resume paths */ - if (pdev->dev_flags & PCI_DEV_FLAGS_NO_D3) { - pdev->dev_flags &= ~PCI_DEV_FLAGS_NO_D3; + if (ish_should_leave_d0i3(pdev) && !dev->suspend_flag) { disable_irq_wake(pdev->irq); ishtp_send_resume(dev); @@ -281,8 +284,11 @@ static int __maybe_unused ish_suspend(struct device *device) */ ish_disable_dma(dev); } else { - /* Set the NO_D3 flag, the ISH would enter D0i3 */ - pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + /* + * Save state so PCI core will keep the device at D0, + * the ISH would enter D0i3 + */ + pci_save_state(pdev); enable_irq_wake(pdev->irq); } From b72cdfa824243f8da7b7c4503844cd0d2f9fae09 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 15:27:38 +0200 Subject: [PATCH 06/16] HID: lenovo: Merge tpkbd and cptkbd data structures Merge the tpkbd and cptkbd data structures, into a single unified structure instead of having a separate data structure per keyboard type. This is a preparation patch for making the tpkbd LED functions more generic, so that they can be re-used for supporting the LEDs on the Thinkpad 10 ultrabook keyboard. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 56 +++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 96fa2a2c2cd3..e58d270c909d 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -32,7 +32,7 @@ #include "hid-ids.h" -struct lenovo_drvdata_tpkbd { +struct lenovo_drvdata { int led_state; struct led_classdev led_mute; struct led_classdev led_micmute; @@ -42,12 +42,8 @@ struct lenovo_drvdata_tpkbd { int select_right; int sensitivity; int press_speed; -}; - -struct lenovo_drvdata_cptkbd { u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */ bool fn_lock; - int sensitivity; }; #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) @@ -242,7 +238,7 @@ static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, static void lenovo_features_set_cptkbd(struct hid_device *hdev) { int ret; - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); if (ret) @@ -258,7 +254,7 @@ static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); } @@ -269,7 +265,7 @@ static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -288,7 +284,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->sensitivity); @@ -300,7 +296,7 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) @@ -357,7 +353,7 @@ static int lenovo_raw_event(struct hid_device *hdev, static int lenovo_event_cptkbd(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); /* "wheel" scroll events */ if (usage->type == EV_REL && (usage->code == REL_WHEEL || @@ -404,7 +400,7 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, static int lenovo_features_set_tpkbd(struct hid_device *hdev) { struct hid_report *report; - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; @@ -425,7 +421,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); } @@ -436,7 +432,7 @@ static ssize_t attr_press_to_select_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -455,7 +451,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); } @@ -466,7 +462,7 @@ static ssize_t attr_dragging_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -485,7 +481,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); } @@ -496,7 +492,7 @@ static ssize_t attr_release_to_select_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -515,7 +511,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); } @@ -526,7 +522,7 @@ static ssize_t attr_select_right_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -545,7 +541,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->sensitivity); @@ -557,7 +553,7 @@ static ssize_t attr_sensitivity_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) @@ -574,7 +570,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_speed); @@ -586,7 +582,7 @@ static ssize_t attr_press_speed_store_tpkbd(struct device *dev, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) @@ -647,7 +643,7 @@ static enum led_brightness lenovo_led_brightness_get_tpkbd( { struct device *dev = led_cdev->dev->parent; struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); int led_nr = 0; if (led_cdev == &data_pointer->led_micmute) @@ -663,7 +659,7 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, { struct device *dev = led_cdev->dev->parent; struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); struct hid_report *report; int led_nr = 0; @@ -684,7 +680,7 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, static int lenovo_probe_tpkbd(struct hid_device *hdev) { struct device *dev = &hdev->dev; - struct lenovo_drvdata_tpkbd *data_pointer; + struct lenovo_drvdata *data_pointer; size_t name_sz = strlen(dev_name(dev)) + 16; char *name_mute, *name_micmute; int i; @@ -712,7 +708,7 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev) hid_warn(hdev, "Could not create sysfs group: %d\n", ret); data_pointer = devm_kzalloc(&hdev->dev, - sizeof(struct lenovo_drvdata_tpkbd), + sizeof(struct lenovo_drvdata), GFP_KERNEL); if (data_pointer == NULL) { hid_err(hdev, "Could not allocate memory for driver data\n"); @@ -767,7 +763,7 @@ err: static int lenovo_probe_cptkbd(struct hid_device *hdev) { int ret; - struct lenovo_drvdata_cptkbd *cptkbd_data; + struct lenovo_drvdata *cptkbd_data; /* All the custom action happens on the USBMOUSE device for USB */ if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD @@ -852,7 +848,7 @@ err: static void lenovo_remove_tpkbd(struct hid_device *hdev) { - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); /* * Only the trackpoint half of the keyboard has drvdata and stuff that From 484921f571b1924a49421a999897387f8de33fbf Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 15:27:39 +0200 Subject: [PATCH 07/16] HID: lenovo: Factor out generic parts of the LED code Factor out the generic parts of the tpkbd LED code, so that they can be re-used for supporting the LEDs on the Thinkpad 10 ultrabook kbd. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 98 ++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index e58d270c909d..feb0cbc6742e 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -638,7 +638,18 @@ static const struct attribute_group lenovo_attr_group_tpkbd = { .attrs = lenovo_attributes_tpkbd, }; -static enum led_brightness lenovo_led_brightness_get_tpkbd( +static void lenovo_led_set_tpkbd(struct hid_device *hdev) +{ + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); + struct hid_report *report; + + report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; + report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; + report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; + hid_hw_request(hdev, report, HID_REQ_SET_REPORT); +} + +static enum led_brightness lenovo_led_brightness_get( struct led_classdev *led_cdev) { struct device *dev = led_cdev->dev->parent; @@ -654,13 +665,12 @@ static enum led_brightness lenovo_led_brightness_get_tpkbd( : LED_OFF; } -static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, +static void lenovo_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness value) { struct device *dev = led_cdev->dev->parent; struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - struct hid_report *report; int led_nr = 0; if (led_cdev == &data_pointer->led_micmute) @@ -671,20 +681,54 @@ static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, else data_pointer->led_state |= 1 << led_nr; - report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; - report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; - report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; - hid_hw_request(hdev, report, HID_REQ_SET_REPORT); + switch (hdev->product) { + case USB_DEVICE_ID_LENOVO_TPKBD: + lenovo_led_set_tpkbd(hdev); + break; + } +} + +static int lenovo_register_leds(struct hid_device *hdev) +{ + struct lenovo_drvdata *data = hid_get_drvdata(hdev); + size_t name_sz = strlen(dev_name(&hdev->dev)) + 16; + char *name_mute, *name_micm; + int ret; + + name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); + name_micm = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); + if (name_mute == NULL || name_micm == NULL) { + hid_err(hdev, "Could not allocate memory for led data\n"); + return -ENOMEM; + } + snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(&hdev->dev)); + snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev)); + + data->led_mute.name = name_mute; + data->led_mute.brightness_get = lenovo_led_brightness_get; + data->led_mute.brightness_set = lenovo_led_brightness_set; + data->led_mute.dev = &hdev->dev; + ret = led_classdev_register(&hdev->dev, &data->led_mute); + if (ret < 0) + return ret; + + data->led_micmute.name = name_micm; + data->led_micmute.brightness_get = lenovo_led_brightness_get; + data->led_micmute.brightness_set = lenovo_led_brightness_set; + data->led_micmute.dev = &hdev->dev; + ret = led_classdev_register(&hdev->dev, &data->led_micmute); + if (ret < 0) { + led_classdev_unregister(&data->led_mute); + return ret; + } + + return 0; } static int lenovo_probe_tpkbd(struct hid_device *hdev) { - struct device *dev = &hdev->dev; struct lenovo_drvdata *data_pointer; - size_t name_sz = strlen(dev_name(dev)) + 16; - char *name_mute, *name_micmute; - int i; - int ret; + int i, ret; /* * Only register extra settings against subdevice where input_mapping @@ -720,38 +764,12 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev) data_pointer->sensitivity = 0xa0; data_pointer->press_speed = 0x38; - name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); - name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); - if (name_mute == NULL || name_micmute == NULL) { - hid_err(hdev, "Could not allocate memory for led data\n"); - ret = -ENOMEM; - goto err; - } - snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); - snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); - hid_set_drvdata(hdev, data_pointer); - data_pointer->led_mute.name = name_mute; - data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd; - data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd; - data_pointer->led_mute.dev = dev; - ret = led_classdev_register(dev, &data_pointer->led_mute); - if (ret < 0) + ret = lenovo_register_leds(hdev); + if (ret) goto err; - data_pointer->led_micmute.name = name_micmute; - data_pointer->led_micmute.brightness_get = - lenovo_led_brightness_get_tpkbd; - data_pointer->led_micmute.brightness_set = - lenovo_led_brightness_set_tpkbd; - data_pointer->led_micmute.dev = dev; - ret = led_classdev_register(dev, &data_pointer->led_micmute); - if (ret < 0) { - led_classdev_unregister(&data_pointer->led_mute); - goto err; - } - lenovo_features_set_tpkbd(hdev); return 0; From ef550c5d0a8e32b9ac080ee83331f23acfe157e3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 15:27:40 +0200 Subject: [PATCH 08/16] HID: lenovo: Rename fn_lock sysfs attr handlers to make them generic Except for a single call, there is nothing keyboard-model specific about the cptkbd fn_lock sysfs attr handlers, rename them dropping the cptkbd post-/pre-fix, so that they can be re-used for supporting Fn-lock on the Thinkpad 10 ultrabook kbd. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index feb0cbc6742e..f2eb91704e9c 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -249,23 +249,23 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev) hid_err(hdev, "Sensitivity setting failed: %d\n", ret); } -static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, +static ssize_t attr_fn_lock_show(struct device *dev, struct device_attribute *attr, char *buf) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *data = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); + return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock); } -static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, +static ssize_t attr_fn_lock_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct hid_device *hdev = to_hid_device(dev); - struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); + struct lenovo_drvdata *data = hid_get_drvdata(hdev); int value; if (kstrtoint(buf, 10, &value)) @@ -273,8 +273,14 @@ static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, if (value < 0 || value > 1) return -EINVAL; - cptkbd_data->fn_lock = !!value; - lenovo_features_set_cptkbd(hdev); + data->fn_lock = !!value; + + switch (hdev->product) { + case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_CBTKBD: + lenovo_features_set_cptkbd(hdev); + break; + } return count; } @@ -309,10 +315,10 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev, } -static struct device_attribute dev_attr_fn_lock_cptkbd = +static struct device_attribute dev_attr_fn_lock = __ATTR(fn_lock, S_IWUSR | S_IRUGO, - attr_fn_lock_show_cptkbd, - attr_fn_lock_store_cptkbd); + attr_fn_lock_show, + attr_fn_lock_store); static struct device_attribute dev_attr_sensitivity_cptkbd = __ATTR(sensitivity, S_IWUSR | S_IRUGO, @@ -321,7 +327,7 @@ static struct device_attribute dev_attr_sensitivity_cptkbd = static struct attribute *lenovo_attributes_cptkbd[] = { - &dev_attr_fn_lock_cptkbd.attr, + &dev_attr_fn_lock.attr, &dev_attr_sensitivity_cptkbd.attr, NULL }; From bc04b37ea0ec1b8ec1306529f3f6dd0d6a5098f1 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 15:27:41 +0200 Subject: [PATCH 09/16] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard support Some of the function keys special functions all use the same 0x000c0001 usage code, add a mapping for these based on the usage_index; And add support for the Speaker and Mic mute LEDs integrated into the F1 and F4 keys. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-lenovo.c | 105 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 874fc3791f3b..dc1a3781fe62 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -723,6 +723,7 @@ #define USB_DEVICE_ID_LENOVO_CUSBKBD 0x6047 #define USB_DEVICE_ID_LENOVO_CBTKBD 0x6048 #define USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL 0x6049 +#define USB_DEVICE_ID_LENOVO_TP10UBKBD 0x6062 #define USB_DEVICE_ID_LENOVO_TPPRODOCK 0x6067 #define USB_DEVICE_ID_LENOVO_X1_COVER 0x6085 #define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_608D 0x608d diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index f2eb91704e9c..ccf41adcfa2b 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -33,7 +33,9 @@ #include "hid-ids.h" struct lenovo_drvdata { + u8 led_report[3]; /* Must be first for proper alignment */ int led_state; + struct mutex led_report_mutex; struct led_classdev led_mute; struct led_classdev led_micmute; int press_to_select; @@ -48,6 +50,34 @@ struct lenovo_drvdata { #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) +#define TP10UBKBD_LED_OUTPUT_REPORT 9 + +#define TP10UBKBD_FN_LOCK_LED 0x54 +#define TP10UBKBD_MUTE_LED 0x64 +#define TP10UBKBD_MICMUTE_LED 0x74 + +#define TP10UBKBD_LED_OFF 1 +#define TP10UBKBD_LED_ON 2 + +static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code, + enum led_brightness value) +{ + struct lenovo_drvdata *data = hid_get_drvdata(hdev); + int ret; + + mutex_lock(&data->led_report_mutex); + + data->led_report[0] = TP10UBKBD_LED_OUTPUT_REPORT; + data->led_report[1] = led_code; + data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF; + ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + if (ret) + hid_err(hdev, "Set LED output report error: %d\n", ret); + + mutex_unlock(&data->led_report_mutex); +} + static const __u8 lenovo_pro_dock_need_fixup_collection[] = { 0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */ 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ @@ -175,6 +205,37 @@ static int lenovo_input_mapping_scrollpoint(struct hid_device *hdev, return 0; } +static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + /* + * The ThinkPad 10 Ultrabook Keyboard uses 0x000c0001 usage for + * a bunch of keys which have no standard consumer page code. + */ + if (usage->hid == 0x000c0001) { + switch (usage->usage_index) { + case 8: /* Fn-Esc: Fn-lock toggle */ + map_key_clear(KEY_FN_ESC); + return 1; + case 9: /* Fn-F4: Mic mute */ + map_key_clear(KEY_MICMUTE); + return 1; + case 10: /* Fn-F7: Control panel */ + map_key_clear(KEY_CONFIG); + return 1; + case 11: /* Fn-F8: Search (magnifier glass) */ + map_key_clear(KEY_SEARCH); + return 1; + case 12: /* Fn-F10: Open My computer (6 boxes) */ + map_key_clear(KEY_FILE); + return 1; + } + } + + return 0; +} + static int lenovo_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -195,6 +256,9 @@ static int lenovo_input_mapping(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL: return lenovo_input_mapping_scrollpoint(hdev, hi, field, usage, bit, max); + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field, + usage, bit, max); default: return 0; } @@ -677,6 +741,7 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev, struct device *dev = led_cdev->dev->parent; struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); + u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED }; int led_nr = 0; if (led_cdev == &data_pointer->led_micmute) @@ -691,6 +756,9 @@ static void lenovo_led_brightness_set(struct led_classdev *led_cdev, case USB_DEVICE_ID_LENOVO_TPKBD: lenovo_led_set_tpkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); + break; } } @@ -831,6 +899,25 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev) return 0; } +static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) +{ + struct lenovo_drvdata *data; + + /* All the custom action happens on the USBMOUSE device for USB */ + if (hdev->type != HID_TYPE_USBMOUSE) + return 0; + + data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_init(&data->led_report_mutex); + + hid_set_drvdata(hdev, data); + + return lenovo_register_leds(hdev); +} + static int lenovo_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -856,6 +943,9 @@ static int lenovo_probe(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_CBTKBD: ret = lenovo_probe_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + ret = lenovo_probe_tp10ubkbd(hdev); + break; default: ret = 0; break; @@ -894,6 +984,17 @@ static void lenovo_remove_cptkbd(struct hid_device *hdev) &lenovo_attr_group_cptkbd); } +static void lenovo_remove_tp10ubkbd(struct hid_device *hdev) +{ + struct lenovo_drvdata *data = hid_get_drvdata(hdev); + + if (data == NULL) + return; + + led_classdev_unregister(&data->led_micmute); + led_classdev_unregister(&data->led_mute); +} + static void lenovo_remove(struct hid_device *hdev) { switch (hdev->product) { @@ -904,6 +1005,9 @@ static void lenovo_remove(struct hid_device *hdev) case USB_DEVICE_ID_LENOVO_CBTKBD: lenovo_remove_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + lenovo_remove_tp10ubkbd(hdev); + break; } hid_hw_stop(hdev); @@ -940,6 +1044,7 @@ static const struct hid_device_id lenovo_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) }, { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) }, { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) }, { } }; From c87de33ed43a89ae8f32ccd40bd0b540244012d6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 15:27:42 +0200 Subject: [PATCH 10/16] HID: lenovo: Add ThinkPad 10 Ultrabook Keyboard fn_lock support Add support for setting the Fn lock value of the ThinkPad 10 Ultrabook Keyboard through sysfs, re-using the fn_lock sysfs attribute read/write helpers from the existing ThinkPad Compact Keyboard with TrackPoint support. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 72 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index ccf41adcfa2b..b5122ee46801 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "hid-ids.h" @@ -38,6 +39,8 @@ struct lenovo_drvdata { struct mutex led_report_mutex; struct led_classdev led_mute; struct led_classdev led_micmute; + struct work_struct fn_lock_sync_work; + struct hid_device *hdev; int press_to_select; int dragging; int release_to_select; @@ -78,6 +81,15 @@ static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code, mutex_unlock(&data->led_report_mutex); } +static void lenovo_tp10ubkbd_sync_fn_lock(struct work_struct *work) +{ + struct lenovo_drvdata *data = + container_of(work, struct lenovo_drvdata, fn_lock_sync_work); + + lenovo_led_set_tp10ubkbd(data->hdev, TP10UBKBD_FN_LOCK_LED, + data->fn_lock); +} + static const __u8 lenovo_pro_dock_need_fixup_collection[] = { 0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */ 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ @@ -344,6 +356,9 @@ static ssize_t attr_fn_lock_store(struct device *dev, case USB_DEVICE_ID_LENOVO_CBTKBD: lenovo_features_set_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value); + break; } return count; @@ -420,6 +435,24 @@ static int lenovo_raw_event(struct hid_device *hdev, return 0; } +static int lenovo_event_tp10ubkbd(struct hid_device *hdev, + struct hid_field *field, struct hid_usage *usage, __s32 value) +{ + struct lenovo_drvdata *data = hid_get_drvdata(hdev); + + if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { + /* + * The user has toggled the Fn-lock state. Toggle our own + * cached value of it and sync our value to the keyboard to + * ensure things are in sync (the sycning should be a no-op). + */ + data->fn_lock = !data->fn_lock; + schedule_work(&data->fn_lock_sync_work); + } + + return 0; +} + static int lenovo_event_cptkbd(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage, __s32 value) { @@ -462,6 +495,8 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, case USB_DEVICE_ID_LENOVO_CUSBKBD: case USB_DEVICE_ID_LENOVO_CBTKBD: return lenovo_event_cptkbd(hdev, field, usage, value); + case USB_DEVICE_ID_LENOVO_TP10UBKBD: + return lenovo_event_tp10ubkbd(hdev, field, usage, value); default: return 0; } @@ -899,9 +934,19 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev) return 0; } +static struct attribute *lenovo_attributes_tp10ubkbd[] = { + &dev_attr_fn_lock.attr, + NULL +}; + +static const struct attribute_group lenovo_attr_group_tp10ubkbd = { + .attrs = lenovo_attributes_tp10ubkbd, +}; + static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) { struct lenovo_drvdata *data; + int ret; /* All the custom action happens on the USBMOUSE device for USB */ if (hdev->type != HID_TYPE_USBMOUSE) @@ -912,10 +957,32 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) return -ENOMEM; mutex_init(&data->led_report_mutex); + INIT_WORK(&data->fn_lock_sync_work, lenovo_tp10ubkbd_sync_fn_lock); + data->hdev = hdev; hid_set_drvdata(hdev, data); - return lenovo_register_leds(hdev); + /* + * The Thinkpad 10 ultrabook USB kbd dock's Fn-lock defaults to on. + * We cannot read the state, only set it, so we force it to on here + * (which should be a no-op) to make sure that our state matches the + * keyboard's FN-lock state. This is the same as what Windows does. + */ + data->fn_lock = true; + lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock); + + ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); + if (ret) + return ret; + + ret = lenovo_register_leds(hdev); + if (ret) + goto err; + + return 0; +err: + sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); + return ret; } static int lenovo_probe(struct hid_device *hdev, @@ -993,6 +1060,9 @@ static void lenovo_remove_tp10ubkbd(struct hid_device *hdev) led_classdev_unregister(&data->led_micmute); led_classdev_unregister(&data->led_mute); + + sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); + cancel_work_sync(&data->fn_lock_sync_work); } static void lenovo_remove(struct hid_device *hdev) From 49429428381b1f52c0b17abbbb4e4e8617fca96e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 4 Jul 2020 22:10:59 +0200 Subject: [PATCH 11/16] HID: lenovo: Fix spurious F23 key press report during resume from suspend The Ultrabook Keyboard sends a spurious F23 key-press when resuming from suspend. GNOME uses F23 to toggle the touchpad on/off so this causes the OSD graphics for the touchpad toggle to show on resume. The keyboard does not actually have a F23 key, se we can simple fix it by marking the 0x00070072 HID usage, which normally is F23, to be ignored. Signed-off-by: Hans de Goede Signed-off-by: Jiri Kosina --- drivers/hid/hid-lenovo.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index b5122ee46801..c6c8e20f3e8d 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -245,6 +245,13 @@ static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev, } } + /* + * The Ultrabook Keyboard sends a spurious F23 key-press when resuming + * from suspend and it does not actually have a F23 key, ignore it. + */ + if (usage->hid == 0x00070072) + return -1; + return 0; } From 4f57cace81438cc873a96f9f13f08298815c9b51 Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Fri, 10 Jul 2020 16:19:39 +0100 Subject: [PATCH 12/16] HID: input: Fix devices that return multiple bytes in battery report Some devices, particularly the 3DConnexion Spacemouse wireless 3D controllers, return more than just the battery capacity in the battery report. The Spacemouse devices return an additional byte with a device specific field. However, hidinput_query_battery_capacity() only requests a 2 byte transfer. When a spacemouse is connected via USB (direct wire, no wireless dongle) and it returns a 3 byte report instead of the assumed 2 byte battery report the larger transfer confuses and frightens the USB subsystem which chooses to ignore the transfer. Then after 2 seconds assume the device has stopped responding and reset it. This can be reproduced easily by using a wired connection with a wireless spacemouse. The Spacemouse will enter a loop of resetting every 2 seconds which can be observed in dmesg. This patch solves the problem by increasing the transfer request to 4 bytes instead of 2. The fix isn't particularly elegant, but it is simple and safe to backport to stable kernels. A further patch will follow to more elegantly handle battery reports that contain additional data. Signed-off-by: Grant Likely Cc: Darren Hart Cc: Jiri Kosina Cc: Benjamin Tissoires Cc: stable@vger.kernel.org Tested-by: Darren Hart Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index c8633beae260..b8eabf206e74 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -350,13 +350,13 @@ static int hidinput_query_battery_capacity(struct hid_device *dev) u8 *buf; int ret; - buf = kmalloc(2, GFP_KERNEL); + buf = kmalloc(4, GFP_KERNEL); if (!buf) return -ENOMEM; - ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 2, + ret = hid_hw_raw_request(dev, dev->battery_report_id, buf, 4, dev->battery_report_type, HID_REQ_GET_REPORT); - if (ret != 2) { + if (ret < 2) { kfree(buf); return -ENODATA; } From b266eacf5f02b24de8a5a4de53de9acdd7ae8afd Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 Jul 2020 10:21:35 +0200 Subject: [PATCH 13/16] HID: mcp2221: Replace HTTP links with HTTPS ones Rationale: Reduces attack surface on kernel devs opening the links for MITM as HTTPS traffic is much harder to manipulate. Deterministic algorithm: For each file: If not .svg: For each line: If doesn't contain `\bxmlns\b`: For each link, `\bhttp://[^# \t\r\n]*(?:\w|/)`: If neither `\bgnu\.org/license`, nor `\bmozilla\.org/MPL\b`: If both the HTTP and HTTPS versions return 200 OK and serve the same content: Replace HTTP with HTTPS. Signed-off-by: Alexander A. Klimov Signed-off-by: Jiri Kosina --- drivers/hid/hid-mcp2221.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-mcp2221.c b/drivers/hid/hid-mcp2221.c index e1b93ce32e01..0d27ccb55dd9 100644 --- a/drivers/hid/hid-mcp2221.c +++ b/drivers/hid/hid-mcp2221.c @@ -4,7 +4,7 @@ * * Copyright (c) 2020, Rishi Gupta * - * Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20005565B.pdf + * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/20005565B.pdf */ #include From 238b5bdefdf9767f5de8574ed79ead397eccf05e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Sat, 18 Jul 2020 12:33:44 +0200 Subject: [PATCH 14/16] HID: udraw-ps3: Replace HTTP links with HTTPS ones Rationale: Reduces attack surface on kernel devs opening the links for MITM as HTTPS traffic is much harder to manipulate. Deterministic algorithm: For each file: If not .svg: For each line: If doesn't contain `\bxmlns\b`: For each link, `\bhttp://[^# \t\r\n]*(?:\w|/)`: If neither `\bgnu\.org/license`, nor `\bmozilla\.org/MPL\b`: If both the HTTP and HTTPS versions return 200 OK and serve the same content: Replace HTTP with HTTPS. Signed-off-by: Alexander A. Klimov Acked-by: Bastien Nocera Signed-off-by: Jiri Kosina --- drivers/hid/hid-udraw-ps3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-udraw-ps3.c b/drivers/hid/hid-udraw-ps3.c index b0fbd11aa0fc..b2e17ef2ea27 100644 --- a/drivers/hid/hid-udraw-ps3.c +++ b/drivers/hid/hid-udraw-ps3.c @@ -16,7 +16,7 @@ MODULE_LICENSE("GPL"); /* * Protocol information from: - * http://brandonw.net/udraw/ + * https://brandonw.net/udraw/ * and the source code of: * https://vvvv.org/contribution/udraw-hid */ From b1631b84fcdba8d11cb33291bacab751ed4cf649 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Sat, 18 Jul 2020 13:47:49 +0200 Subject: [PATCH 15/16] HID: Replace HTTP links with HTTPS ones Rationale: Reduces attack surface on kernel devs opening the links for MITM as HTTPS traffic is much harder to manipulate. Deterministic algorithm: For each file: If not .svg: For each line: If doesn't contain `\bxmlns\b`: For each link, `\bhttp://[^# \t\r\n]*(?:\w|/)`: If neither `\bgnu\.org/license`, nor `\bmozilla\.org/MPL\b`: If both the HTTP and HTTPS versions return 200 OK and serve the same content: Replace HTTP with HTTPS. Signed-off-by: Alexander A. Klimov Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 +- drivers/hid/hid-cp2112.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 443c5cbbde04..3ddb9cbe7dee 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -20,7 +20,7 @@ config HID removed from the HID bus by the transport-layer drivers, such as usbhid (USB_HID) and hidp (BT_HIDP). - For docs and specs, see http://www.usb.org/developers/hidpage/ + For docs and specs, see https://www.usb.org/developers/hidpage/ If unsure, say Y. diff --git a/drivers/hid/hid-cp2112.c b/drivers/hid/hid-cp2112.c index db1b55df0d13..f64517bc33e2 100644 --- a/drivers/hid/hid-cp2112.c +++ b/drivers/hid/hid-cp2112.c @@ -11,7 +11,7 @@ * host communicates with the CP2112 via raw HID reports. * * Data Sheet: - * http://www.silabs.com/Support%20Documents/TechnicalDocs/CP2112.pdf + * https://www.silabs.com/Support%20Documents/TechnicalDocs/CP2112.pdf * Programming Interface Specification: * https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf */ From 68f775ddd2a6f513e225f9a565b054ab48fef142 Mon Sep 17 00:00:00 2001 From: Ikjoon Jang Date: Tue, 21 Jul 2020 14:54:09 +0800 Subject: [PATCH 16/16] HID: quirks: add NOGET quirk for Logitech GROUP Add HID_QUIRK_NOGET for Logitech GROUP device. Logitech GROUP is a compound with camera and audio. When the HID interface in an audio device is requested to get specific report id, all following control transfers are stalled and never be restored back. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=203419 Signed-off-by: Ikjoon Jang Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 6f370e020feb..7cfa9785bfbb 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -773,6 +773,7 @@ #define USB_DEVICE_ID_LOGITECH_G27_WHEEL 0xc29b #define USB_DEVICE_ID_LOGITECH_WII_WHEEL 0xc29c #define USB_DEVICE_ID_LOGITECH_ELITE_KBD 0xc30a +#define USB_DEVICE_ID_LOGITECH_GROUP_AUDIO 0x0882 #define USB_DEVICE_ID_S510_RECEIVER 0xc50c #define USB_DEVICE_ID_S510_RECEIVER_2 0xc517 #define USB_DEVICE_ID_LOGITECH_CORDLESS_DESKTOP_LX500 0xc512 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 934fc0a798d4..c242150d35a3 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -179,6 +179,7 @@ static const struct hid_device_id hid_quirks[] = { { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD2, USB_DEVICE_ID_SMARTJOY_DUAL_PLUS), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT }, { HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE), HID_QUIRK_MULTI_INPUT }, + { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_GROUP_AUDIO), HID_QUIRK_NOGET }, { 0 } };