Skip to content

Conversation

rightaditya
Copy link
Contributor

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 #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.

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.
@benedekkupper
Copy link
Contributor

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.

@mondalaci
Copy link
Member

@benedekkupper Can you offer a better solution?

@benedekkupper
Copy link
Contributor

@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.

@kareltucek
Copy link
Collaborator

IIUC UHK80 is also affected

Yes.

I wonder if only through the USB link, or also through BLE.

Don't know, can test later.

@benedekkupper
Copy link
Contributor

Can you offer a better solution?

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?

@mondalaci
Copy link
Member

I don't want to use such OS-specific workarounds.

@mondalaci
Copy link
Member

@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?

@benedekkupper
Copy link
Contributor

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?

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.

@mondalaci
Copy link
Member

@benedekkupper Can you implement your suggestion? If so, how much time does it take?

@mondalaci
Copy link
Member

@rightaditya The build for this PR failed. Do you know why?

@rightaditya
Copy link
Contributor Author

@rightaditya The build for this PR failed. Do you know why?

@mondalaci The step it failed on is "Run build-archiver" with error:

Error: Error: Input required and not supplied: S3_ACCESS_KEY

I'm not familiar with the CI setup, but the compilation itself (up to producing a tarball) worked fine.

@rightaditya
Copy link
Contributor Author

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.

Damn. That's a good point.

In practical terms, does scrolling become very slow or fast, or just low-resolution?

@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).

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.

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?

@kareltucek
Copy link
Collaborator

kareltucek commented Sep 19, 2025

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.

@rightaditya
Copy link
Contributor Author

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 s2idle for suspend rather than deep. I'm not sure how distros or the kernel determine which to use by default. IIRC my laptop (running Manjaro, Intel 11th gen platform) defaults to s2idle while my desktop (running CachyOS, AMD AM5 platform (7000 series CPU)).

$ 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 s2idle; likewise for @kareltucek's Ubuntu 25.04 system (Tuxedo laptop, I'm guessing?), but the Ubuntu 24.04 system to be set to deep.

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.

@mondalaci
Copy link
Member

$ cat /sys/power/mem_sleep
s2idle [deep]
$ echo s2idle | sudo tee /sys/power/mem_sleep
s2idle
$ cat /sys/power/mem_sleep
[s2idle] deep

After sleep-wake, scrolling still works fine for me regardless. My CPU is a Ryzen 9 9950X3D.

@rightaditya
Copy link
Contributor Author

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.

@mondalaci
Copy link
Member

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.

@rightaditya
Copy link
Contributor Author

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. s2idle isn't a true suspend-to-RAM. It just turns off select components and the CPU stops executing anything (and goes into a corresponding low-power state). deep actually turns everything off except the RAM. My desktop behaves like you describe as well.

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.

@kareltucek
Copy link
Collaborator

kareltucek commented Sep 22, 2025

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.

@rightaditya
Copy link
Contributor Author

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
UHK80 w/ cable: scrolling issue after sleep-wake
UHK80 w/ dongle: no 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 lsusb -t. This port is a USB 3.2gen2 port, but I tried one of the other1 USB 3.2gen2 ports in the back of the motherboard and the problem returned. Previously I had the keyboard plugged into a USB2 port, where I first encountered the problem.

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:

  • UHK80 w/ dongle isn't affected, regardless of port
  • Choice of port matters for whether the issue will be triggered with a cable for both UHK60(v1) and UHK80.

Footnotes

  1. Other than the "working" port, the two other USB-A 3.2gen2 ports in the back are "Lightning Gaming Ports"2.

  2. I have no clue what the hell that means other than that the ports are red... I guess like the blood of my opponents?? Maybe I'll be better at squashing bugs if I use these ports...???

@rightaditya
Copy link
Contributor Author

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

@pcooke9
Copy link

pcooke9 commented Sep 24, 2025

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.

@rightaditya
Copy link
Contributor Author

rightaditya commented Sep 24, 2025

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...

@pcooke9
Copy link

pcooke9 commented Sep 24, 2025

This also seems to indicate that those silly Lightning Gaming Ports go to the CPU

Actually, I think the point of the "Lightning Gaming Ports" is that they run on separate controllers-
"Aimed for die hard gamers and enthusiasts! Lightning Gaming Ports are sourced from two different controller interfaces that allow gamers to connect the high speed mice/keyboard with the lowest jitter latency and win the best performance!"

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...
🧙‍♂️🪄

@benedekkupper
Copy link
Contributor

I retested this with a Ubuntu 24.04 machine running 6.14.0-24-generic kernel, and some ports do reliably produce the error. The analysis shows this sequence of events:

  1. wake from suspend
  2. USB reset (dt=40ms)
  3. suspend again (dt=5ms)
  4. wake again (dt=600ms)
  5. USB re-enumeration, this time without negotiating high-resolution scrolling

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:

  1. on a suspend, if the device is configured, back up the high-res multiplier report
  2. on a USB reset, reset the report, but also start measuring the time
  3. if a suspend event comes, and the device is not configured, and the last USB reset was within a 20ms or so timeframe, restore the report from the backup

@rightaditya
Copy link
Contributor Author

TLDR: it seems that for my machine, all of the ports that are safe from this issue go straight to the CPU.

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...

@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.

@pcooke9
Copy link

pcooke9 commented Sep 25, 2025

I assumed it was some marketing BS.

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...

@rightaditya
Copy link
Contributor Author

@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.

@benedekkupper
Copy link
Contributor

@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.

@rightaditya
Copy link
Contributor Author

Forgive me if you already went through this, but if not, I found this bit in the guide for reporting kernel issues:

Locate the driver or kernel subsystem that seems to be causing the issue. Find out how and where its developers expect reports. Note: most of the time this won’t be bugzilla.kernel.org, as issues typically need to be sent by mail to a maintainer and a public mailing list.

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.

@benedekkupper
Copy link
Contributor

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

@rightaditya
Copy link
Contributor Author

@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 :)

@mhantsch
Copy link
Contributor

mhantsch commented Oct 2, 2025

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?

@benedekkupper
Copy link
Contributor

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.

@mhantsch
Copy link
Contributor

mhantsch commented Oct 2, 2025

even VBUS is removed from the device during suspend

Interesting, wouldn't you need to renegotiate everything anyway when the device starts up from a power down state?

@rightaditya
Copy link
Contributor Author

@benedekkupper Patch works great. No issues that I could detect!

@benedekkupper
Copy link
Contributor

@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.

@rightaditya
Copy link
Contributor Author

Thanks for putting it together!

@rightaditya
Copy link
Contributor Author

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 :)

@benedekkupper benedekkupper marked this pull request as draft October 3, 2025 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

High-resolution scrolling breaks on Linux when waking from suspend
6 participants