Skip to content

usb: fix SuperSpeedPlus passthrough - #7928

Merged
osy merged 1 commit into
utmapp:mainfrom
giovariot:usb-passthrough-7530
Nov 11, 2026
Merged

osy merged 1 commit into
utmapp:mainfrom
giovariot:usb-passthrough-7530

Conversation

@giovariot

@giovariot giovariot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

QEMU USB passthrough fails for USB 3.2 Gen 2 (10 Gbps) devices — the external SSDs and NVMe enclosures that most users hit in #7530 — with the guest rejecting the device as Invalid ep0 maxpacket: 9. This change fixes the speed negotiation that causes it~~, unmounts the host volumes of a mass storage device before it is redirected so macOS never loses a mounted disk, and reconnects a saved device automatically when it appears again (for example after a firmware update restarts it)~~.

Resolves #7530, #2728, #3975, #3995, #5154, #6291, #6721 and #6764.

Root cause

UTM's QEMU passthrough does not hand the device to QEMU directly: it uses SPICE usb-redir with libusb running in the app process (CocoaSpice/usbredirhost). The entitlements are already in place (com.apple.security.device.usb, com.apple.vm.device-access), so macOS can release the device from its kernel drivers; the failure is in the speed negotiation:

  1. libusb reports a USB 3.2 Gen 2 (10 Gbps) device as LIBUSB_SPEED_SUPER_PLUS (kUSBDeviceSpeedSuperPlus).
  2. usbredir 0.14.0 has no case for that value in usbredirhost_send_device_connect(), so it sends usb_redir_speed_unknown.
  3. QEMU maps unknown speed to full speed (hw/usb/redirect.c) while still forwarding the device's SuperSpeed descriptor, whose bMaxPacketSize0 is 9 (the SuperSpeed encoding, 512 bytes).
  4. The guest enumerates the device at a non-SuperSpeed link and rejects the descriptor: Invalid ep0 maxpacket: 9, then unable to enumerate USB device.

This matches the reports: USB 2.0 and USB 3.0 (5 Gbps) devices keep working, 10 Gbps SSDs and NVMe enclosures fail, and a USB 2.0 hub "fixes" it by forcing the host device to enumerate at high speed. The usb-redir protocol has no speed above SuperSpeed, so these devices are presented at 5 Gbps instead of 10 Gbps.

Changes

  • patches/usbredir-0.14.0.patch (new): present LIBUSB_SPEED_SUPER_PLUS devices as SuperSpeed, so the guest gets a link and descriptors that match.
  • patches/qemu-10.0.12-utm.patch: the existing bMaxPacketSize0 fix-up only ran when QEMU had marked the device as SuperSpeed. Run it whenever the device is presented at a non-SuperSpeed link, which also covers speeds libusb cannot report (for example 20 Gbps kUSBDeviceSpeedSuperPlusBy2 devices are reported as unknown speed).
  • Services/UTMUSBDeviceUnmount.swift (new, macOS): before redirecting a device, unmount its mounted volumes with DiskArbitration so the capture does not tear the storage driver away from macOS with volumes still in use. If a volume is busy the connection is refused with a clear message instead of risking data loss. The helper also maps the "device still in use" errors from libusb to a message that tells the user what to do.
  • Reconnect a saved device automatically (VMDisplayQemuDisplayController): a device in the saved auto-connect list is connected again without prompting when it is attached while the virtual machine is running. This covers devices that reset themselves, for example during a firmware update, and come back a few seconds later.

Related issues

These issues are less specific or describe a different problem and are only possibly related: #3322, #3375, #4167, #5664, #7288.

Testing

I confirmed the report with a synthetic test: without access to the com.apple.vm.device-access entitlement a local build cannot capture the physical device (on macOS 27 the device is marked NeedsDeviceAccessEntitlement = Yes, so even root cannot), which makes a hardware test impossible. The tests were run on macOS 27.0, MacBook Air M4 (Apple Silicon).

The reporter's ASM2362 enclosure (174c:2362) was used to confirm the reported values:

  • libusb: speed=5 (LIBUSB_SPEED_SUPER_PLUS), bcdUSB=0x0320, bMaxPacketSize0=9
  • IORegistry: USBSpeed=5, UsbLinkSpeed=10000000000
  • binary comparison of the pinned usbredirhost: the current one handles only speeds below 5 (so SUPER_PLUS falls through to "unknown"); the patched one handles speeds below 6 and maps SUPER_PLUS to super.

End-to-end in QEMU, with a synthetic usb-redir device that mirrors the SSD descriptors (bcdUSB 0x0320, bMaxPacketSize0 = 9, mass storage interface) and an Alpine Linux guest on the serial console:

usbredir QEMU Guest result
current (unknown speed) current fails: new high-speed USB device, Invalid ep0 maxpacket: 9, unable to enumerate USB device
patched (super speed) current enumerates: new SuperSpeed USB device number 2, New USB device found, idVendor=174c, idProduct=2362
current (unknown speed) patched enumerates: new high-speed USB device, descriptor accepted (the fix-up rewrites bMaxPacketSize0 to 64)

The physical device itself could not be captured on this machine (see above), and the automatic reconnection path also needs a device that resets while a virtual machine is running, which was not exercised here.

Testing: Tested by the author on macOS 27.0, MacBook Air M4 (Apple Silicon), assisted by the AI agent (DeepSeek V4.1 Flash via OpenCode) which set up and ran the diagnostics and the QEMU tests under the author's supervision. The author acknowledges that this change has been tested and/or reviewed by a human in accordance with UTM's AI contribution guidelines.

Update (by a maintainer's AI agent)

This PR now carries one commit, still authored by @giovariot, and only the QEMU part: usb: fix SuperSpeedPlus passthrough. The rest was moved out so that each PR is one change (the parts struck out above):

QEMU (patches/qemu-10.0.12-utm.patch): e37b2e515d and 8ab298bc52 from the UTM QEMU fork (utmapp/qemu#30), a rework of the QEMU patch first proposed here.

  • The existing bMaxPacketSize0 fix-up did not "only run when QEMU had marked the device as SuperSpeed" (struck out above): since usb_pick_speed() (2014) it never ran at all, because the speed of an attached device is the one it runs at on its port. A SuperSpeed device on a USB 2.0 controller failed the same way as a SuperSpeedPlus device. The fix-up now runs whenever the device is not attached at SuperSpeed.
  • New: a device announced with an unknown speed is attached at SuperSpeed when it has bulk endpoints with 1024 byte packets, which only a SuperSpeed link has. With the fix-up alone such a device enumerates at high speed, but a USB Attached SCSI drive then does not work: it uses streams on its SuperSpeed link and the guest uses none below SuperSpeed (uas_eh_abort_handler, Device offlined - not ready after error recovery). The row "current usbredir, patched QEMU" of the table above showed enumeration only.

Tests of the versions that are now in the branch, by an AI agent on a MacBook Pro M2 Max, macOS 27.2, with a Seagate Backup Plus 500 GB drive (0bc2:ab24, USB Attached SCSI) on a 5 Gb/s link and an Ubuntu 26.04 arm64 guest. Only reads were done. The UTM builds used carry the QEMU patches of this PR and the libusb patch of #8131:

Case Without the QEMU patches With them
VM with a USB 2.0 controller Invalid ep0 maxpacket: 9, unable to enumerate USB device enumerates (Driver=uas, 480M); the disk does not appear, for the reason above
USB 3.0 controller, the drive announced with an unknown speed as a SuperSpeedPlus drive is (a usbredirhost changed for the test) high speed, Driver=uas, 480M, no disk new SuperSpeed USB device, Driver=uas, 5000M, the disk appears, 1000 MiB read at 105 MB/s
USB 3.0 controller, announced as SuperSpeed works works, 105 MB/s

With the libusb patch, disconnecting the drive returns within a second, also during a read, and the drive comes back to macOS when the VM stops. Without it (UTM 5.0.8), disconnecting freezes UTM.

Not tested: a real SuperSpeedPlus (10 Gbps) device; the unknown speed was forced on a 5 Gb/s drive. The tests in the Testing section and in the comments were made with the first versions of the patches.

@osy

osy commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

If you don't have the entitlement you can still test with sudo (QEMU command line only) or with SIP off (get entitlement without needing a profile). Either way we do not accept untested PR as per CONTRIBUTING.md.

@giovariot

Copy link
Copy Markdown
Contributor Author

As requested, I ran the tests physically with the hardware from the report.

Setup: MacBook Air M4, macOS 27.0.1, the ASM2362 USB 3.2 Gen 2 enclosure (174c:2362, 4 TB NVMe), guest Fedora 38 aarch64 (QEMU, HVF). The test build carries the standard project signing (no restricted entitlements), so UTM was started as root (sudo) to capture the device.

  • Current main reproduces the report: usb 3-3: new high-speed USB device, Invalid ep0 maxpacket: 9, then unable to enumerate USB device.
  • With this branch the device enumerates at SuperSpeed: usb 4-3: new SuperSpeed USB device number 2 using xhci_hcd; lsusb -t reports Driver=uas, 5000M; the 4 TB disk attaches (sd 1:0:0:0: [sda] 7813971617 512-byte logical blocks: (4.00 TB/3.64 TiB)) and reads at ~199 MB/s (dd if=/dev/sda of=/dev/null bs=1M count=2000), with no uas_eh_abort_handler and no device resets while probing.
  • Stopping the virtual machine returns the device to macOS and the disk reappears in diskutil/Finder, instead of staying captured until a physical replug.
  • The only remaining messages are the ses enclosure LUN errors (Failed to get diagnostic page 0x1, Failed to bind enclosure -19), which are harmless: the ASM2362 exposes an enclosure LUN that does not respond.

Both attaching and detaching were confirmed on the physical device. The detach behaviour depends on the libusb fix (darwin_cancel_transfer() handling LIBUSB_TRANSFER_TYPE_BULK_STREAM), now included in this PR.

@giovariot

Copy link
Copy Markdown
Contributor Author

Hello @osy, is this proposal alright? Should I fix anything about it?

@giovariot
giovariot force-pushed the usb-passthrough-7530 branch from a768d7a to 6ee9f03 Compare October 27, 2026 17:14
@giovariot

Copy link
Copy Markdown
Contributor Author

@osy the hardware test is done (details in the comment above): with the ASM2362 enclosure the guest enumerates it at SuperSpeed, dd reads at ~200 MB/s, and stopping the virtual machine returns the device to macOS; current main still fails with Invalid ep0 maxpacket: 9. I also rebased the branch onto current main so CI is running again, and added the libusb fix for UAS bulk-stream cancellation that the detach needs. Could you take a look when you have a moment?

@Vlad1mir-D

Vlad1mir-D commented Nov 6, 2026 •

Copy link
Copy Markdown

@giovariot
I successfully test this patchset with SanDisk Extreme Pro 4TB and Adata SD600 256GB. Thank you for your efforts!

UTMapp-bot pushed a commit to UTMapp-bot/UTM that referenced this pull request Nov 11, 2026
Disconnecting a USB drive that the guest uses through USB Attached SCSI
(most USB 3 drives and SSD enclosures) froze UTM: the USB menu and every
other part of the app stopped answering, and after UTM was ended the
drive did not come back to macOS until it was replugged.

When a device is released, usbredirhost cancels its transfers and waits
for them to complete. libusb on macOS refused to cancel bulk stream
transfers (LIBUSB_ERROR_INVALID_PARAM), and USB Attached SCSI always
has one pending on its status pipe, so the wait never ended while it
held the lock the main thread needs.

Pick up c1c01e6593 from the UTM libusb fork (utmapp/libusb#1), which
cancels bulk stream transfers the way plain bulk transfers are. The
change is by giovariot, who included it in utmapp#7928.

Assisted-by: Claude:claude-fable-5-2
USB 3.2 Gen 2 (10 Gbps) devices were reported by libusb at a speed the
usb-redir protocol cannot express, so QEMU attached them at full speed
while forwarding their SuperSpeed descriptors. Guests rejected them with
"Invalid ep0 maxpacket: 9" and "unable to enumerate USB device".

- attach a device announced with an unknown speed at SuperSpeed when its
  endpoints show it is one (8ab298bc52 from the UTM QEMU fork)
- apply QEMU's bMaxPacketSize0 fix-up whenever the device is not attached
  at SuperSpeed (e37b2e515d from the UTM QEMU fork)

The two QEMU commits (utmapp/qemu#30) are a rework of the QEMU change
first proposed here. They take the place of the usbredir patch that
presented SuperSpeedPlus devices as SuperSpeed: QEMU now works that out
itself, so usbredir stays unpatched.

Resolves utmapp#7530

Assisted-by: OpenCode:deepseek-v4.1-flash
Assisted-by: Claude:claude-fable-5-2
@UTMapp-bot UTMapp-bot changed the title usb: fix SuperSpeedPlus passthrough and reconnect saved devices usb: fix SuperSpeedPlus passthrough Nov 11, 2026
@UTMapp-bot

Copy link
Copy Markdown

@giovariot this PR was split up, so here is where each part went. The description above has the details, with the outdated parts struck out.

Part of this PR Where it is now
QEMU change for USB 3 devices that reach the guest below SuperSpeed Still here, as the only commit. It is now two commits from the UTM QEMU fork (utmapp/qemu#30), a rework of your patch.
Unmounting a drive's volumes before it is connected #8134
Connecting a saved device when it is plugged in again #8133, stacked on #8134
Cancelling bulk stream transfers in libusb #8131, as the commit merged in the UTM libusb fork (utmapp/libusb#1)
usbredir patch Dropped. The second QEMU commit attaches a device of unknown speed at SuperSpeed when its endpoints show it is one, so usbredir does not need to change.

Why it was split: each PR is meant to be one change, and dependency patches go through UTM's forks of those projects first. You are the author of the commit here, of the commits in #8134 and #8133, and of the libusb commit.

What changed in your code, in short:

  • QEMU: the existing bMaxPacketSize0 fix-up had not run at all since 2014, so a SuperSpeed device on a USB 2.0 controller failed the same way. With the fix-up alone a 10 Gbps drive enumerates at high speed, but a UAS drive then times out on its first command, which is why the speed detection was added.
  • Unmount (usb(macOS): unmount a drive's volumes before connecting it to a VM #8134): rewritten. It finds the device by vendor, product, bus, port and serial, names the volume in the message, and has no timeout or error-text matching. UTM's sandbox may unmount but not mount, so it checks that the connect can work before unmounting anything.
  • Reconnect (usb(macOS): connect a saved USB device when it is plugged in again #8133): the saved-device lookup runs off the main thread, a secondary window hands the connect to the primary one, and a device the VM already has is skipped.

Tested with a Seagate 500 GB and a Seagate 4 TB drive on a 5 Gb/s link; the results are in each PR. A real 10 Gbps device was not available, so the unknown-speed case was forced on a 5 Gb/s drive. If you can run this PR's branch with your ASM2362 enclosure, that would close the gap.

@osy
osy merged commit 1995e1e into utmapp:main Nov 11, 2026
@giovariot
giovariot deleted the usb-passthrough-7530 branch November 11, 2026 23:30
osy pushed a commit that referenced this pull request Nov 12, 2026
A USB device set to "Auto connect on start" was only connected when the
virtual machine started. When it was unplugged and plugged in again while
the virtual machine ran, or restarted itself as a device does after a
firmware update, UTM asked whether to connect it, or did nothing when
the prompt is turned off or the window is not in front.

Connect such a device when it appears, without asking. A device that
another virtual machine has, or that this one has no room for, is left
alone without an alert.

The idea and a first version are from giovariot in #7928.

Assisted-by: OpenCode:deepseek-v4.1-flash
Assisted-by: Claude:claude-fable-5-2
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.

USBDriverKit extension to fix USB Passthrough for SCSI/UAS devices

4 participants