Skip to content

usb: keep EHCI/OHCI/UHCI DMA below 4 GiB, don't de-power unsupported devices - #113

Open
JiTianYu391 wants to merge 1 commit into
ViudiraTech:developfrom
JiTianYu391:fix/usb-hcd-dma32
Open

JiTianYu391 wants to merge 1 commit into
ViudiraTech:developfrom
JiTianYu391:fix/usb-hcd-dma32

Conversation

@JiTianYu391

Copy link
Copy Markdown
Contributor

Problem

On machines with enough RAM the EHCI controller never works: control transfers time out (`usb-ehci: Port 0 enumeration failed: -110`), and on real hardware the log shows `Control/Transfer DMA allocation failed` and `usb-hub: ... enumerate failed: -22`; the keyboard behind the hub then loses power.

Reproduction (QEMU, bare ISO, baseline = develop)

`-machine q35 -enable-kvm -m 16G -device usb-ehci -device usb-storage`

Instrumented `ehci_dma_alloc()` on the baseline:
```
DEBUG ehci dma size=4096 phys=0x47ff19000 # periodic list, > 4 GiB
DEBUG ehci dma size=96 phys=0x47ff18000 # first QH, > 4 GiB
usb-ehci: Port 0 enumeration failed: -110; waiting for reconnect.
```
With `-m 6G` all addresses stay below 4 GiB and nothing fails, which is why it depends on the machine.

Root cause

  1. EHCI/OHCI/UHCI use 32-bit DMA pointers (PERIODICLISTBASE, ASYNCLISTADDR, QH/qTD/ED/TD links, buffer pointers) but their DMA helpers called `alloc_frames()`. The buddy allocator can return frames above 4 GiB, and the address is silently truncated when written to the register, so the controller walks unrelated memory.
  2. EHCI cannot drive full/low-speed devices behind a high-speed hub (no split transactions) and returned `-EINVAL`; `hub.c` treats any enumeration failure as babble and clears `PORT_POWER`, so the device appears to have no power. Hub power-on errors were also ignored.

Fix

  • `buddy_alloc_below()` + `alloc_frames_dma32()`: the whole block must end below 4 GiB.
  • EHCI/OHCI/UHCI allocate every DMA object (lists, QH/qTD/ED/TD, transfer buffers) through it.
  • EHCI returns `-EOPNOTSUPP` for non-high-speed devices; the hub no longer de-powers the port for that error and logs failed power-on.

Verification

Same 16G setup after the fix: EHCI list/QH addresses written to the controller are all < 4 GiB (e.g. `P-LIST BASE = 0x7e4ca000`), the device enumerates and QEMU's `usb_msd_*` trace shows 36 SCSI commands completing (baseline: none). clang-tidy introduces no new warnings.

Not covered

Split transactions (full/low-speed devices behind a high-speed hub) and companion-controller handoff beyond the existing K-state release are still missing; those devices now fail with `-EOPNOTSUPP` without being powered off. QEMU has no high-speed hub model, so that path was not reproduced, only read from the code.

🤖 Generated with Claude Code

…rted devices

The legacy host controllers use 32-bit DMA pointers, but their DMA helpers
called alloc_frames(), which happily returns frames above 4 GiB on machines
with enough RAM. The address was then silently truncated when written into
PERIODICLISTBASE/ASYNCLISTADDR/QH/qTD links, so the controller walked the
wrong memory and every transfer timed out.

- buddy/frame: add buddy_alloc_below() and alloc_frames_dma32().
- ehci/ohci/uhci: allocate all DMA memory through alloc_frames_dma32().
- ehci: return -EOPNOTSUPP (not -EINVAL) for full/low-speed devices behind
  a high-speed hub; split transactions are not implemented.
- hub: keep the port powered when the HCD merely does not support the
  device, and report failed port power-on instead of ignoring it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

1 participant