Skip to content

usb: xhci: Honour nonblock for interrupt transfers - #34

Open
cb2206 wants to merge 2 commits into
AsahiLinux:asahi-relengfrom
cb2206:usb-ignore-magic-keyboards
Open

cb2206 wants to merge 2 commits into
AsahiLinux:asahi-relengfrom
cb2206:usb-ignore-magic-keyboards

Conversation

@cb2206

@cb2206 cb2206 commented Oct 1, 2026 •

Copy link
Copy Markdown

The USB keyboard driver polls with nonblock set, but xHCI ignores it and
waits up to XHCI_INT_TIMEOUT (1 s) for every poll. Keyboards that honour
SET_IDLE answer within the 40 ms idle period, so this goes unnoticed.
Keyboards with USB_HID_QUIRK_POLL_NO_REPORT_IDLE (Apple, Keychron) send
nothing until a key changes, so every poll waits out the full second.

With such a keyboard on stdin, the UEFI console timer polls it between
EFI block reads, and GRUB takes about 80 s instead of a few seconds to
load the kernel and initramfs. Seen on an M1 Pro MacBook Pro with a
USB-C Magic Keyboard with Touch ID (05ac:029a) on a USB-C hub.

Give up on non-blocking interrupt transfers after 20 ms, like dwc2,
which already returns at once for them. A report a keyboard queues in
the meantime stays in its endpoint and is picked up by the next poll.
Probing a keyboard still uses the full timeout.

Fixes #33
See also #21

@jannau

jannau commented Oct 2, 2026

Copy link
Copy Markdown
Member

I don't think this is the right solution as it will break keyboard input on apple desktop devices with only an apple keyboard connected.
Better solution would be to reduce the timeout if the USB_HID_QUIRK_POLL_NO_REPORT_IDLE quirk is set. See 63f6a44 which made the keyboards usable in the first place.

The USB keyboard driver polls with nonblock set, but xHCI ignores it and
waits up to XHCI_INT_TIMEOUT (1 s) for every poll. Keyboards that honour
SET_IDLE answer within the 40 ms idle period, so this goes unnoticed.
Keyboards with USB_HID_QUIRK_POLL_NO_REPORT_IDLE (Apple, Keychron) send
nothing until a key changes, so every poll waits out the full second.

With such a keyboard on stdin, the UEFI console timer polls it between
EFI block reads, and GRUB takes about 80 s instead of a few seconds to
load the kernel and initramfs. Seen on an M1 Pro MacBook Pro with a
USB-C Magic Keyboard with Touch ID (05ac:029a) on a USB-C hub.

Give up on non-blocking interrupt transfers after 20 ms, like dwc2,
which already returns at once for them. A report a keyboard queues in
the meantime stays in its endpoint and is picked up by the next poll.
Probing a keyboard still uses the full timeout.

Link: AsahiLinux#21
Link: AsahiLinux#33
Signed-off-by: Christian Bartels <christian@thebartels.de>
@cb2206
cb2206 force-pushed the usb-ignore-magic-keyboards branch from e9a53c2 to bf08a2f Compare October 2, 2026 18:21
@cb2206 cb2206 changed the title env: Add USB-C Magic Keyboards to the default usb_ignorelist usb: xhci: Honour nonblock for interrupt transfers Oct 2, 2026
@cb2206
cb2206 changed the base branch from asahi to asahi-releng October 2, 2026 18:21
@cb2206

cb2206 commented Oct 2, 2026

Copy link
Copy Markdown
Author

Thanks, that makes sense. I hadn't thought about desktops with only an Apple keyboard. I've replaced the ignorelist change with a timeout change.

What I found: on xHCI the keyboard poll passes nonblock = true, but _xhci_submit_int_msg() ignores it and waits XHCI_INT_TIMEOUT (1 s on asahi-releng, 5 s XHCI_TIMEOUT on asahi). The keyboards you added USB_HID_QUIRK_POLL_NO_REPORT_IDLE for in 63f6a44 send nothing until a key changes, so every poll from the UEFI console timer waits out the whole second. That's where the ~80 s in #33 goes.

The new commit makes xHCI honour nonblock: non-blocking interrupt transfers give up after 20 ms (XHCI_INT_NONBLOCK_TIMEOUT), and probing still uses the full timeout. dwc2 already returns at once for nonblock, so this brings xHCI in line with it.

I didn't gate it on the quirk itself. The quirk lives in usb_kbd.c, and the only per-call signal the host controller gets is nonblock. Gating on the quirk would need a new parameter through submit_int_msg() for every host driver. Passing nonblock only for quirk keyboards would make dwc2 block on all other keyboards again, which is what 9dcab2c fixed. Keyboards that honour SET_IDLE should see no difference: a report they queue while no transfer is pending stays in the endpoint, and the next poll picks it up. If you'd rather have an explicit quirk gate, I can add the parameter.

Tested on a 14" M1 Pro (j314s) with the USB-C Magic Keyboard with Touch ID (05ac:029a) on a USB-C hub, usb_ignorelist back to the default 0x1050:*:

  • cold start: the kernel's Linux version line at 16.2 s. That's ~91 s without either change and 16.9 s with the ignorelist.
  • warm reboot: 9.9 s.
  • The Magic Keyboard works in the GRUB menu (Enter selects the entry). With the ignorelist, it didn't work there at all.
  • A keyboard without the quirk: I tried a generic low-speed keyboard (1a2c:0e24) on the same hub. It doesn't work in the GRUB menu on this machine with or without this commit (same result with the stock-xHCI build), so it couldn't tell me anything about the non-quirk path. It works fine in Linux and macOS.

I also moved the PR to asahi-releng, since that's what asahi-v2026.07-2 was tagged from and its xHCI code (XHCI_INT_TIMEOUT) differs from asahi's. Let me know if it should target asahi instead.

@chriskearney

Copy link
Copy Markdown

Thanks for separating blocking probe from ordinary polling. I checked bf08a2f and found two existing timeout/cancellation paths that become more frequent with the shorter wait:

  • xhci_bulk_tx returns after abort_td without the DMA unmap used by the successful tail. Eight controlled idle polls leave eight mappings outstanding in translated Apple DART mode. Bypass mode does not allocate those IOVAs.
  • abort_td acknowledges a late successful transfer but does not return its report. The caller then reports timeout/NAK with zero length, so the keyboard report is lost.

Both controls reproduce on ec49c9d too; these are base defects, not cleanup code introduced by this PR. Normal reports and blocking probes pass. Also, 20 ms is the initial wait: cancellation can add three 200 ms waits. No hardware qualification claimed.

Before we package the shorter timeout, could cancellation retain a matching completed report and balance DMA mappings after proven completion/stop? Failed STOP/SET_DEQ acknowledgements need to preserve uncertain DMA ownership rather than blindly unmapping. That would make the improvement much safer for idle and bursty keyboards.

abort_td() acknowledged every transfer event that arrived after the
Stop Endpoint command, including one whose transfer had completed
between the caller's timeout and the stop. The caller then reported a
timeout with no data, so an interrupt report that did arrive (a key
press during idle keyboard polling) was lost. Its completion-code check
never fired either: the misplaced parenthesis made it test
`transfer_len != COMP_STOP` instead of the completion code.

On timeout, xhci_bulk_tx() and xhci_ctrl_tx() also returned without
unmapping the transfer buffer. With the IOMMU in translated mode every
cancelled poll left an IOVA mapping behind.

Have abort_td() report how the transfer ended: stopped, completed (with
a copy of its event), or unknown when the Stop Endpoint or Set TR
Dequeue command was not acknowledged. xhci_bulk_tx() now returns the
result of a transfer that completed late (when the event is for the
TD's last TRB), and unmaps the buffer once the controller is known to
be done with it. When a command was not acknowledged the buffer stays
mapped, as the controller may still write to it.

Signed-off-by: Christian Bartels <christian@thebartels.de>
@cb2206

cb2206 commented Oct 12, 2026

Copy link
Copy Markdown
Author

Thanks for checking this so carefully. I've pushed a follow-up commit, 54ae558503 ("usb: xhci: Keep a late transfer result and balance DMA on abort"), that addresses both points:

  • Late completion: abort_td() now reports how the cancelled transfer ended: stopped, completed (with a copy of its transfer event), or unknown when the Stop Endpoint or Set TR Dequeue command was not acknowledged. If the transfer completed between the timeout and the stop, xhci_bulk_tx() returns its real result instead of a timeout, as long as the event is for the TD's last TRB, so the key report isn't lost. Along the way: the existing completion-code check there never fired, because the parenthesis made it test transfer_len != COMP_STOP instead of the completion code.
  • DMA balance: on a timeout, the buffer is now unmapped once the controller has confirmed the stop (bulk and control paths). When a command isn't acknowledged, the buffer stays mapped, since the controller may still own it.

Tested on a 14" MacBook Pro M1 Pro (j314s) as a local uboot-asahi package (2026.07-asahi2 with both commits): it boots with a Magic Keyboard (05ac:029a) on a USB-C hub at power-on, and the keyboard works in the GRUB menu. I couldn't reproduce your eight-poll DART mapping count myself, and there's no hardware qualification beyond that.

Disclosure: both commits in this PR were written with the help of an AI coding assistant (Claude). I reviewed and tested them on my own hardware, but I know this project doesn't accept LLM-generated contributions, so please close the PR if that rules it out. I'm offering the same patches to the Aurora side, which packages U-Boot for these Macs (omacom/linux-aurora#83).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Boot hangs before GRUB with a Genesys USB-C hub attached (M1 Pro j314s), unplugging the hub resumes it

3 participants