QEMU USB host device pass-through

Speeding up UEFI driver development

While working on the ChaosKey UEFI driver, I once again found myself in a situation of tedium: how to simplify driver development when the target for my device driver is firmware. Moving around a USB key with a FAT filesystem may be a completely valid thing to do in order to install a pre-existing driver. But when you are trying to learn how to write drivers for a new platform (and write blog posts about it), the modify -> compile -> copy -> reboot -> load -> fail -> try again cycle gets quite tedious. As usual, QEMU has/is the answer, letting you pass a USB device controlled by the host over to the guest.

Make sure your QEMU is built with libusb support

Unsurprisingly, in order to support passing through USB device access to guests, QEMU needs to know how to deal with USB - and it does this using libusb. Most Linux distributions package a version of QEMU with libusb support enabled, but if you are building your own version from source, make sure the output from configure contains libusb yes If it does not, under Debian, $ sudo apt-get build-dep qemu should do the trick - anywhere else make sure you manually install libusb v1.0 or later (Debian's libusb-dev package is v0.1, which will not work), with development headers. Without libusb support, you'll get the error message 'usb-host' is not a valid device model name when following the below instructions.

Identifying your device

Once your QEMU is ready, in order to pass a device through to a guest, you must first locate it on the host.

$ lsusb
...
Bus 008 Device 002: ID 1d50:60c6 OpenMoko, Inc
Bus 008 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
....

The vendor ID (1d50) of ChaosKey is taken from the OpenMoko project, which has repurposed their otherwise unused device registry space for various community/homebrew devices.

If you want to be able to access the device as non-root, ensure the device node under /dev/bus/usb/bus/device (in this case /dev/bus/usb/008/002) has suitable read-write access for your user.

Launching QEMU

Unlike the x86 default "pc" machine, QEMU has no default machine type for ARM. The "generic virtualized platform" type virt has no USB attached by default, so we need to manually add one. ich-usb-uhci1 supports high-speed, and so is needed for some devices to work under emulation. Using the "virtual FAT" support in QEMU is the easiest way to get a driver into UEFI without recompiling. The example below uses a subdirectory called fatdir which QEMU generates an emulated FAT filesystem from.

USB_HOST_BUS=8
USB_HOST_PORT=2
AARCH64_OPTS="-pflash QEMU_EFI.fd -cpu cortex-a57 -M virt -usb -device ich9-usb-uhci1"
X86_OPTS="-pflash OVMF.fd -usb"
COMMON_OPTS="-device usb-host,bus=usb-bus.0,hostbus=$USB_HOST_BUS,hostport=$USB_HOST_PORT -nographic -net none -hda fat:fatdir/"

This can then be executed as $ qemu-system-x86_64 $X86_OPTS $COMMON_OPTS or $ qemu-system-aarch64 $AARCH64_OPTS $COMMON_OPTS respectively.

Note: Ordering of some of the above options is important.

Note2: That AARCH64 QEMU_EFI.fd needs to be extended to 64MB in order for QEMU to be happy with it. See this older post for an example.

My usage then looks like the following, after having been dropped into the UEFI shell, with a bunch of overly verbose debug messages in my last compiled version of the driver:

Shell> load fs0:\ChaosKeyDxe.efi
*** Installed ChaosKey driver! ***
Image 'FS0:\ChaosKeyDxe.efi' loaded at 47539000 - Success
Shell> y (0x1D50:0x60C6) is my homeboy!
1 supported languages
0: 0409
Manufacturer: altusmetrum.org
Product: ChaosKey-hw-1.0-sw-1.6.6
Serial: 001c00325346430b20333632

And, yes, those last three lines are actually read from the USB device connected to host QEMU is running on.

Final comments

One thing that is unsurprising, but very cool and useful, is that this works well cross-architecture. So you can test that your drivers are truly portable by building (and testing) them for AARCH64, EBC and X64 without having to move around between physical machines.

Oh, and another useful command in this cycle is reset -s in the UEFI shell, which "powers down" the machine and exits QEMU.