Skip to content

Commit 2e87efc

Browse files
benedekkupperintel-lab-lkp
authored andcommitted
drivers: hid: renegotiate resolution multipliers with device after reset
The scroll resolution multipliers are set in the context of hidinput_connect(), which is only called at probe time: when the host changes the value on the device with a SET_REPORT(FEATURE), and the device accepts it, these multipliers are stored on the host side, and used to calculate the final scroll event values sent to userspace. After a USB suspend, the resume operation on many hubs and chipsets involve a USB reset signal as well. A reset on the device side clears all previous state information, including the value of the multiplier report. This reset is not handled by the multiplier handling logic, so what ends up happening is the host is still expecting high-resolution scroll events, but the device is reset to default resolution, making the effective, user-perceived scroll speed incredibly slow. The solution is to renegotiate the multiplier selection after each reset. This is not the only bug related to the high-resolution scrolling implementation in the kernel (the other one is https://bugzilla.kernel.org/show_bug.cgi?id=220144), but for this one, there is no device side workaround for, leading to poor user experience with our product: UltimateHackingKeyboard/firmware#1155 UltimateHackingKeyboard/firmware#1261 UltimateHackingKeyboard/firmware#1355 This patch was tested by an affected user and has been reported to fix the issue (see discussion in 1355). Signed-off-by: Benedek Kupper <[email protected]>
1 parent 54ba6d9 commit 2e87efc

File tree

3 files changed

+17
-0
lines changed

3 files changed

+17
-0
lines changed

drivers/hid/hid-generic.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ static int hid_generic_probe(struct hid_device *hdev,
7070
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
7171
}
7272

73+
static int hid_generic_reset_resume(struct hid_device *hdev)
74+
{
75+
if (hdev->claimed & HID_CLAIMED_INPUT)
76+
hidinput_reset_resume(hdev);
77+
78+
return 0;
79+
}
80+
7381
static const struct hid_device_id hid_table[] = {
7482
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
7583
{ }
@@ -81,6 +89,7 @@ static struct hid_driver hid_generic = {
8189
.id_table = hid_table,
8290
.match = hid_generic_match,
8391
.probe = hid_generic_probe,
92+
.reset_resume = hid_generic_reset_resume,
8493
};
8594
module_hid_driver(hid_generic);
8695

drivers/hid/hid-input.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,13 @@ void hidinput_disconnect(struct hid_device *hid)
23962396
}
23972397
EXPORT_SYMBOL_GPL(hidinput_disconnect);
23982398

2399+
void hidinput_reset_resume(struct hid_device *hid)
2400+
{
2401+
/* renegotiate host-device shared state after reset */
2402+
hidinput_change_resolution_multipliers(hid);
2403+
}
2404+
EXPORT_SYMBOL_GPL(hidinput_reset_resume);
2405+
23992406
#ifdef CONFIG_HID_KUNIT_TEST
24002407
#include "hid-input-test.c"
24012408
#endif

include/linux/hid.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,7 @@ extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct h
984984
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report);
985985
extern int hidinput_connect(struct hid_device *hid, unsigned int force);
986986
extern void hidinput_disconnect(struct hid_device *);
987+
void hidinput_reset_resume(struct hid_device *hid);
987988

988989
struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_type,
989990
unsigned int application, unsigned int usage);

0 commit comments

Comments
 (0)