The nusb host path panicked on every non-empty bulk/interrupt OUT transfer:
endpoint.allocate() returns a Buffer whose len is 0, so copy_from_slice()
wrote into an empty slice. Use extend_from_slice() instead.
nusb also rejects IN transfers whose length is not a multiple of the endpoint
max packet size; a 36-byte SCSI INQUIRY on a 512-byte bulk endpoint made
usb-storage repeat resets. Round the request up and truncate the completion
back to the requested length.
While here, fix descriptor passthrough for USB 3 devices:
- encode bcdUSB/bcdDevice and the device qualifier as real BCD, keeping the
sub-minor digit
- pass through endpoint extra descriptors (SuperSpeed endpoint companion, UAS
pipe usage) so the client sees the real endpoint capabilities
- pass through the BOS descriptor so the SuperSpeed capability and its LPM
latencies are not lost
- claim interfaces in the rusb backend; usbfs logged "did not claim interface"
Extend the QEMU passthrough test with a SuperSpeed bulk-only device
(usb-bot) for the data path, in addition to the SuperSpeed UAS device (which
can only be enumerated because vhci-hcd lacks streams) and the high-speed
usb-storage device. The test now writes and reads back a block through both
bulk-only devices with the nusb and rusb backends, and fails if the client
reports missing endpoint companions or LPM information.
Fixes#66
On Ubuntu's 6.8 generic kernel xhci-pci.ko depends on xhci-pci-renesas.ko
(it calls renesas_xhci_check_request_fw), so insmod'ing xhci-pci alone failed
with 'unknown symbol in module' and the SuperSpeed device never appeared.
Verified against the exact CI kernel (linux-image/-modules/-modules-extra
6.8.0-149-generic) locally.
Also dump dmesg when the QEMU devices do not show up, so a failure like this
is diagnosable from the serial log alone.
On Debian/Ubuntu kernels ehci-hcd/ehci-pci and xhci-hcd/xhci-pci are modules,
so without loading them the emulated USB devices QEMU provides never show up
and the test complained about a single exported device (none at all on a
Debian kernel). Bundle and insmod them, and wait for both devices to appear
before starting the server.
The host backends number their speed enums differently from Linux'
enum usb_device_speed, but both passthrough paths cast them with `as u32`:
through nusb a high-speed device was announced as full-speed and a
SuperSpeedPlus one as wireless; through rusb every SuperSpeed device was
announced as wireless. A client cannot enumerate a SuperSpeed device that
is announced with the wrong speed.
Map nusb::Speed and rusb::Speed to UsbSpeed explicitly instead, falling
back to Unknown for the future variants of those non_exhaustive enums.
The nusb path also hardcoded ep0's max packet size to 16 for every device.
bMaxPacketSize0 is the exponent 9 (= 512 bytes) at SuperSpeed and must be
64 at high speed, so take it from the device's own descriptor, like the
rusb path already did. It also left usb_version at 0, and Linux rejects a
device that claims SuperSpeed with bcdUSB < 0x0300 ("got a wrong device
descriptor, warm reset device"), so propagate bcdUSB as well.
While here:
- report 64 instead of the exponent in the device qualifier descriptor,
which describes high-speed operation (USB 2.0 5.5.3)
- add UsbDevice::set_speed(), which keeps the speed and ep0 in sync, so
simulated SuperSpeed devices can be built correctly (#40)
Verified with a new QEMU test (scripts/qemu/run-qemu-passthrough-test.sh):
QEMU provides a SuperSpeed device (usb-uas on xHCI) and a high-speed one
(usb-storage on EHCI); the guest exports them with both backends and
attaches them back through vhci-hcd. Before these changes it fails with
"ATTACH_TEST: FAIL (46f4:0003 speed=480/5000 bMaxPacketSize0=16/9
bcdUSB=0.00/3.00)" and a rusb SuperSpeed device never enumerates; after
them it passes for both backends (speed, bMaxPacketSize0 and bcdUSB of the
virtual copy match the real device). The example is extended with a
--nusb/--rusb switch and VID:PID filters to drive that test.
linux-modules-extra-generic is not a package name; the packages are versioned
as linux-modules-extra-<ver>-generic. Install it after detecting the generic
kernel version.
The GitHub runner's Azure kernel has CONFIG_USB_HID disabled, so it cannot
enumerate the simulated HID keyboard over USB/IP. Install and boot the Ubuntu
generic kernel instead (which ships usbhid/hid-generic as modules and
usbip/cdc-acm in linux-modules-extra), bundle + load the full module chain
(hid, hid-generic, usbhid, usbip-core, vhci-hcd, cdc-acm) in the guest, and
restore the keyboard check as a gating assertion. Now both the CDC ACM serial
device and the HID keyboard are verified in CI.
- Allow overriding KERNEL / VMLINUZ / MODTREE so the test can run against a
downloaded kernel (e.g. to reproduce a CI kernel locally).
- Place decompressed kernel modules under /lib/modules/$KERNEL regardless of
where they are found.
- Make the HID-keyboard check best-effort: it is reported but does not gate the
result, because some kernels (Ubuntu azure/generic) do not complete HID
enumeration over USB/IP. The CDC ACM serial device remains the always-verified
device.
- Share the initramfs builder's MODTREE setting with run-qemu-test.sh.
The simulated HID keyboard handler called unimplemented!() on HID control
requests it did not recognise (e.g. SET_PROTOCOL or a GET_DESCRIPTOR for the
HID descriptor), which panicked the connection task and stalled enumeration on
kernels whose usbhid core issues those requests (e.g. the Ubuntu Azure kernel).
Answer them gracefully (empty response / the HID descriptor) and add a
regression test.
On Ubuntu, linux-tools-common installs /usr/bin/usbip as a tiny shell script
that execs a versioned binary, which is why the guest reported 'usbip: not
found'. Locate a real ELF usbip binary (e.g. /usr/sbin/usbip from the usbip
package, or /usr/lib/linux-tools/*/usbip) and bundle that instead.
The Azure kernel does not load zstd/xz/gzip-compressed modules via insmod, so
decompress them to plain .ko when bundling the initramfs. Also copy the usbip
client to both /usr/sbin and /usr/bin (and its real path), and log its presence
in the guest for diagnostics.
On GitHub runners /boot/vmlinuz-* is mode 0600 (root-only), so a plain cp by
the non-root runner user fails. Fall back to sudo cp + chmod so QEMU can read
the kernel.
On GitHub runners the kernel image (/boot/vmlinuz-*) is root-only and
/dev/kvm is not writable by the runner user. Copy the kernel to a readable
temp path, and when /dev/kvm needs root, run QEMU under passwordless sudo to
get KVM acceleration (falling back to TCG). Ensure the serial log stays
readable for the artifact upload.
ldd can return non-zero / empty for statically-linked or symlinked binaries
(e.g. the distro usbip tool), which under set -o pipefail aborted the build.
Copy the dynamic loader plus a comprehensive set of libc/usbip libraries
explicitly, and drive the ldd-based copy via process substitution so it
never aborts. Also follow the usbip symlink and preserve its real path.
Under set -o pipefail, 'find ... | head -1' aborts with SIGPIPE once head
reads one line, which happened after installing linux-modules-extra (a large
tree). Use find -print -quit instead of piping to head, and add an ERR trap
for clearer diagnostics.
The GitHub runner uses an Azure kernel that does not ship the usbip /
cdc-acm modules by default; on Ubuntu those live in the separate
linux-modules-extra package. Install it and make the initramfs builder /
guest loader accept compressed kernel modules (.ko.zst, .ko.xz, .ko.gz).
Boot a real Linux inside QEMU and confirm that simulated USB/IP devices
work against a real kernel (vhci-hcd + cdc_acm):
- Add examples/demo.rs: a single server exporting a HID keyboard and a
CDC ACM serial device on port 3240.
- Add scripts/qemu/: build a minimal initramfs (host kernel + busybox +
usbip tool + demo server), boot it in QEMU, attach the simulated devices
over 127.0.0.1, and verify:
* the serial port emits 'a' (0x61) -> SERIAL_TEST
* the keyboard generates a KEY_1 input event -> KEYBOARD_TEST
- Add .github/workflows/qemu.yml to run this in CI (KVM when available,
TCG fallback otherwise), uploading the serial console log as an artefact.
1. src/device.rs: control transfer dispatch only forwarded requests to
device_handler when the recipient bit was Device (0), even though
device_handler implementations (RusbUsbHostDeviceHandler,
NusbUsbHostDeviceHandler) reconstruct the full recipient from
setup_packet.request_type themselves and pass it straight through to
the real transfer - they're correct for any recipient already.
Endpoint/Other-recipient control transfers fell through to
unimplemented!("control in"/"control out") instead, panicking the
worker task handling that transfer. Some USB-serial chips (observed:
WCH's CH34x/CH9102 family, widely used in hobbyist robotics/serial
adapters) use vendor-specific, non-Device-recipient control transfers
to configure line state, and hit this on every connection attempt.
2. src/lib.rs: with_rusb_device_handles() read each device's manufacturer/
product/serial-number string descriptors with .unwrap(), which is not
guaranteed to succeed (some devices NAK a string descriptor read, or
don't support the requested language ID). A single device failing this
panicked the whole synchronous enumeration pass in new_from_host(),
taking down every other device on the bus along with it. Now logs a
warning and leaves that one string unset instead.
Both were found while passing a physical CH9102-based USB-serial device
through to a Docker container via the `host` example. The recipient
restriction in (1) was confirmed as the cause by inspecting
NusbUsbHostDeviceHandler::handle_urb, which already reconstructs
Device/Interface/Endpoint/Other recipients generically from the raw
setup packet - nothing in the handler itself required narrowing the
caller's guard to Device only.
For OUT transfers, actual_length should reflect bytes written to device,
not response buffer length (which is empty by design). This matches the
Linux kernel USB/IP implementation where actual_length = urb->actual_length.
Fixes#61
Replace &[device.clone()] with std::slice::from_ref(&device) in
test to address clippy::cloned_ref_to_slice_refs warning.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>