-
Notifications
You must be signed in to change notification settings - Fork 71
Fix broken scrolling on Linux wake from suspend #1355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix broken scrolling on Linux wake from suspend #1355
Conversation
When waking from sleep, a Linux host will communicate with the device such that `kUSB_DeviceEventSetConfiguration` is triggered, but won't send `SetReport` like it does when first initializing the device upon connection. This broke scrolling when waking from sleep since the `usbMouseFeatBuffer` got re-initialized to `0` but never got the `SetReport` that would set it back to `1`, resulting in the device thinking it should operate in low-res scrolling mode while the host continues to expect high-resolution scrolling. To fix this, I've removed the initialization of `usbMouseFeatBuffer` from `kUSB_DeviceEventSetConfiguration` and instead initialize it globally, when it's declared. Fixes UltimateHackingKeyboard#1261.
My concern is that with this patch, having a powered hub, and swapping to a mac (or a graphical BIOS) the scrolling will still be high resolution, so incorrect for that OS. Admittedly, this is a rather unlikely scenario, but worthy of mentioning. |
@benedekkupper Can you offer a better solution? |
Not in the current circumstances, as I was unable to reproduce the problem on the Linux machine I have access to. It would be important to understand why the USB configuration is changed after a wake-up (or why a USB bus reset is performed), and if that is something that happened before the high-resolution scrolling was added, or only afterwards. Another thing to note is that this is a UHK60 only fix. IIUC UHK80 is also affected, I wonder if only through the USB link, or also through BLE. |
Yes.
Don't know, can test later. |
How about creating a udev rule / systemd service, that unbinds and re-binds the UHK devices from the USB hub, resetting both the OS and device side state to known good? |
I don't want to use such OS-specific workarounds. |
@rightaditya I also cannot reproduce this issue, so I am unable to test the fix. I'm on Linux Mint 22.2 with kernel 6.8.0-78-generic. In practical terms, does scrolling become very slow or fast, or just low-resolution? |
We could achieve the same on the device side, we can already identify when the OS is Linux, after a wake-up do a soft disconnect-connect sequence. |
@benedekkupper Can you implement your suggestion? If so, how much time does it take? |
@rightaditya The build for this PR failed. Do you know why? |
@mondalaci The step it failed on is "Run build-archiver" with error:
I'm not familiar with the CI setup, but the compilation itself (up to producing a tarball) worked fine. |
Damn. That's a good point.
@mondalaci Very slow. If you're in an application that doesn't do anything with high-res scrolling, it will appear to not do anything at all until it accumulates a full detent's worth of displacement (120× what it should be).
Huh. That's strange. When I was looking through the kernel's HID code, IIRC the relevant portions hadn't been touched in years, so I'm not sure what's accounting for the difference. Maybe some daemon is triggering a USB disconnect/reconnect. I'll see if I can take a look on Mint with Wireshark... I'll have to run off of a USB flash drive and Wireshark couldn't do USB packet capture on Windows when running off a USB flash drive, so it may not let me do it. @kareltucek and @benedekkupper: could you let me know what distros (including version, and ideally kernel version as well) you tested on? |
I can reproduce it with Ubuntu 24.04, with a 6.8.0-71-generic kernel. I have no issues with Ubuntu 25.04 with a 6.11.0 tuxedo kernel. |
I may have figured out what accounts for the difference across our different Linux environments. On my system, there's no issue if I set my system to use $ cat /sys/power/mem_sleep
s2idle [deep]
$ echo s2idle | sudo tee /sys/power/mem_sleep
$ cat /sys/power/mem_sleep
[s2idle] deep
$ systemctl suspend # scroll is fine after waking
$ echo deep | sudo tee /sys/power/mem_sleep
$ cat /sys/power/mem_sleep
s2idle [deep]
$ systemctl suspend # scroll is broken after waking @mondalaci @kareltucek @benedekkupper Could you check if this is in line with your cases and if swapping the suspend mode changes anything? So I'd expect @mondalaci and @benedekkupper systems to be set to use To be safe, maybe make sure to save state on anything you have open... depending on the platform, certain suspend mode might crash the system (but this is unlikely these days). Changing it the way I mentioned above won't persist across reboots, so you shouldn't need to revert anything if your system does crash. |
After sleep-wake, scrolling still works fine for me regardless. My CPU is a Ryzen 9 9950X3D. |
Damn, I was hoping that would simplify testing :(. Ah well, guess I'll have to try the other distros. |
My system may not be representative. After putting it to sleep in s2idle mode, the fans are still going and the power supply is enabled. Something is not right. |
No, I think that's right. I think it was similar for my laptop, but I wouldn't be surprised to learn that laptops have some more sensible behaviour when it comes to the fan(s) so that users don't think their laptop is still on when it goes into suspend mode. |
I have taken the affected laptop with me to the office today and flashed fresh master on a UHK80. The problem has gone away with this setup (different UHK, no hub inbetween, same host pc). Will dig deeper tomorrow. |
I replicated the following behaviour on Fedora 42, Ubuntu 24.04, Ubuntu 25.04, and Mint 22.2, all with live USB environments (via Ventoy), as well as on CachyOS installed to NVMe (for the hell of it, I tried the CachyOS live USB image I had as well, which is from April, and got the same results): UHK60v1: scrolling issue after sleep-wake All UHKs were running firmware 15.2.0. Everything was plugged in without any hubs—straight to USB in the back of the motherboard or via front connector. Something semi-related prompted me to try a different USB port, which led me to confirm that yes, USB is hell: the problem seems to go away with one specific USB port on my motherboard. I don't know what's special about this (USB-A) port other than that it's right next to the one USB-C port in the back. Nothing seems to stand out about it topologically with I didn't test all the distros above with the magic port, but I first discovered it on Ubuntu 25.04 and replicated it on my main CachyOS installation. So it looks like:
Footnotes |
I just tried the UHK80 with the USB-C port next to the magic USB-A port, and this USB-C port is also magic. (The USB-C port at the front of the machine is not.) Can anyone point me to where in the USB spec they specify "magic"? /s |
I'm not currently having any scrolling issues, but I'm curious... Do you know if the working USB port(s) are connected directly to the CPU, or via the chipset? I've noticed that when I flash firmware while plugged into a port on the chipset, Agent logs show reenumeration errors rather consistently. Doesn't happen on ports that go straight to CPU. In fact, most of my UHK's stability issues have cleared up since I switched the USB cables and dongle to ports connected directly to CPU a few months ago. |
Interesting. That's actually what prompted me to try the port. I was having issues flashing (particularly the dongle), like in Agent#2606, that went away on the magic port. I don't know how reliable the manual (p. 11) is, but it seems to indicate that one magic port (the USB-C one) goes to the CPU while the other (USB-A) goes to the chipset. This also seems to indicate that those silly Lightning Gaming Ports go to the CPU, which I guess is what makes them Lightning Gaming Ports. But they don't have the magic that prevents this issue from occurring... |
Actually, I think the point of the "Lightning Gaming Ports" is that they run on separate controllers- The block diagram for that board (where it shows the "Rear USB 3.2 Gen2 2 ports" going to the CPU) is a little misleading. Since you only have two controllers on that MB, one of the lightning gaming ports must be on the CPU, and the other is on the chipset. So If I'm not mistaken, that means the USB-A&C ports under your LAN port are both on the CPU... |
I retested this with a Ubuntu 24.04 machine running
Based on this information, I have put together a simple demo logic, that should be sufficient at identifying this scenario without side-effects. The gist is this:
|
TLDR: it seems that for my machine, all of the ports that are safe from this issue go straight to the CPU.
@pcooke9 Damn, nice catch! I never bothered to look into the Lightning Gaming Ports because I assumed it was some marketing BS. (I mean, it probably still is, except maybe for the real pros, and even then I'm skeptical...). The block diagram is indeed extremely misleading given how the ports are laid out. I had assumed the "Rear USB 3.2 Gen2 2 ports" meant the two LGPs... but I just checked and, indeed, one of the LGPs prevents the wake-scroll issue (I hadn't tried that one before as it was occupied, and assumed the behaviour of the two LGPs would be the same wrt this issue). So it seems the "2 ports" means one LGP plus the USB-A port between the USB-C port and the LAN port. Thanks a lot for looking into that—it really helps make sense of this. |
I was having all kinds of weird USB issues a while back. I have a gaming mouse capable of an 8000Hz polling rate. I usually keep it set at around 2000Hz, but if I have both the UHK dongle and the gaming mouse on the same chipset, the dongle will stop working when I raise the mouse to 8000Hz. After shifting things around to different ports, everything is pretty stable. I assume that's the type of scenario your MB's Lightning Gaming port labels were designed to prevent. It's definitely marketing nonsense to call them "Lightning", because they aren't actually faster, but they do have a legit use-case. My MB isn't as clear about it, but it effectively has the same "feature" if I use different USB controllers... |
@benedekkupper I'm not totally sure, but this already seemed like a Linux bug from the start, and seems even more so now. After the complete non-response to the issue you reported (mentioned in #1216), I don't expect much to come of another bug report... but maybe improper USB initialization is severe enough to warrant some actual attention? We at least have replications across different distros, kernel versions, and (I assume) hardware. |
I completely agree, I just don't know what's the best approach to report it in a way that will result in any action from the kernel developers. The original patches to implement it were done more than 10 years ago, and I doubt that those people are still around to deal with these issues. |
Forgive me if you already went through this, but if not, I found this bit in the guide for reporting kernel issues:
Here they mention a script to make finding the maintainer easier if you know the file. I ran it for the bug mentioned in #1216 and got: $ perl scripts/get_maintainer.pl -f drivers/hid/hid-input.c
Jiri Kosina <[email protected]> (maintainer:HID CORE LAYER)
Benjamin Tissoires <[email protected]> (maintainer:HID CORE LAYER)
[email protected] (open list:HID CORE LAYER)
[email protected] (open list) So perhaps email is the way to go? The instructions say to email the maintainer(s) and CC the given mailing list. I don't know exactly where the USB bug for this issue would go. I guess the USB subsystem? $ perl scripts/get_maintainer.pl -f drivers/usb
Greg Kroah-Hartman <[email protected]> (maintainer:USB SUBSYSTEM)
[email protected] (open list:USB SUBSYSTEM)
[email protected] (open list)
USB SUBSYSTEM status: Supported Great, more email... These people must be superheroes to be able to get anything done with email as the primary method of communicating bugs and patches. |
I did some digging through the kernel source code, and came up with a theoretical patch that could fix the issue, if anybody has the time and courage to try it, would be appreciated: From e7b1f6003e5c3f017a28730765ffe653b8ad7304 Mon Sep 17 00:00:00 2001
From: Benedek Kupper <[email protected]>
Date: Wed, 1 Oct 2025 09:42:21 +0200
Subject: [PATCH] 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.
Signed-off-by: Benedek Kupper <[email protected]>
---
drivers/hid/hid-generic.c | 9 +++++++++
drivers/hid/hid-input.c | 7 +++++++
include/linux/hid.h | 1 +
3 files changed, 17 insertions(+)
diff --git a/drivers/hid/hid-generic.c b/drivers/hid/hid-generic.c
index 9e04c6d0fcc8..c2de916747de 100644
--- a/drivers/hid/hid-generic.c
+++ b/drivers/hid/hid-generic.c
@@ -70,6 +70,14 @@ static int hid_generic_probe(struct hid_device *hdev,
return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
}
+static int hid_generic_reset_resume(struct hid_device *hdev)
+{
+ if (hdev->claimed & HID_CLAIMED_INPUT)
+ hidinput_reset_resume(hdev);
+
+ return 0;
+}
+
static const struct hid_device_id hid_table[] = {
{ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, HID_ANY_ID, HID_ANY_ID) },
{ }
@@ -81,6 +89,7 @@ static struct hid_driver hid_generic = {
.id_table = hid_table,
.match = hid_generic_match,
.probe = hid_generic_probe,
+ .reset_resume = hid_generic_reset_resume,
};
module_hid_driver(hid_generic);
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index f45f856a127f..3dd3822cc549 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -2382,6 +2382,13 @@ void hidinput_disconnect(struct hid_device *hid)
}
EXPORT_SYMBOL_GPL(hidinput_disconnect);
+void hidinput_reset_resume(struct hid_device *hid)
+{
+ /* renegotiate host-device shared state after reset */
+ hidinput_change_resolution_multipliers(hid);
+}
+EXPORT_SYMBOL_GPL(hidinput_reset_resume);
+
#ifdef CONFIG_HID_KUNIT_TEST
#include "hid-input-test.c"
#endif
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 2cc4f1e4ea96..b78fb55ea85e 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -953,6 +953,7 @@ extern void hidinput_hid_event(struct hid_device *, struct hid_field *, struct h
extern void hidinput_report_event(struct hid_device *hid, struct hid_report *report);
extern int hidinput_connect(struct hid_device *hid, unsigned int force);
extern void hidinput_disconnect(struct hid_device *);
+void hidinput_reset_resume(struct hid_device *hid);
struct hid_field *hid_find_field(struct hid_device *hdev, unsigned int report_type,
unsigned int application, unsigned int usage);
--
2.43.0
|
@benedekkupper Just started a compile. If everything goes OK I'll give it a shot tomorrow. This is my first time compiling the kernel so be patient as I figure this out :) |
Can the USB device trigger a renegotiation (disconnect/reconnect?) with the USB host, so that the Linux kernel on the host would not need to be modified? |
As we discovered, in some scenarios even VBUS is removed from the device during suspend, which makes any device-side workaround impossible. |
Interesting, wouldn't you need to renegotiate everything anyway when the device starts up from a power down state? |
@benedekkupper Patch works great. No issues that I could detect! |
Thanks for taking the time to test it, happy to hear it works :) I will submit the patch for merging in the next days, and let you know if I need some support with it. |
Thanks for putting it together! |
Since most of the discussion of this issue has moved here, I'll refrain from closing it myself, but the PR itself seems to be clearly obviated :) |
When waking from sleep, a Linux host will communicate with the device such that
kUSB_DeviceEventSetConfiguration
is triggered, but won't sendSetReport
like it does when first initializing the device upon connection. This broke scrolling when waking from sleep since theusbMouseFeatBuffer
got re-initialized to0
but never got theSetReport
that would set it back to1
, resulting in the device thinking it should operate in low-res scrolling mode while the host continues to expect high-resolution scrolling.To fix this, I've removed the initialization of
usbMouseFeatBuffer
fromkUSB_DeviceEventSetConfiguration
and instead initialize it globally, when it's declared.Fixes #1261.
I've tested this on my Linux desktop (running CachyOS). From my earlier testing, Windows wasn't affected by this issue, and I don't think this fix will affect anything on it, but it should be tested on Windows to make sure that high-res scrolling works as expected, including after waking from suspend. I can't boot into Windows for the moment so it'd be great if someone else could verify.
Ideally, this should also be tested to make sure that nothing breaks on an OS that doesn't support the high-res scrolling (MacOS, maybe? I seem to recall we didn't have a clear consensus on whether it worked there or not), but this is probably just paranoia on my part... I really don't think there's anything in this fix that would affect that scenario.