QEMU USB Passthrough on Linux

The intended audience of this post is those who are very familiar with the inner workings of Linux and familiar with usage of the QEMU emulator from the command line.

I recently found myself needing to set up a Windows VM on my Linux laptop such that the guest operating system of the VM had unrestricted access to one very real USB device attached to the host, while other programs of the host had no such access. (This need arose when trying to set up a professional photo printer that had no Linux driver and needed to be connected over USB to set up its network connection.) While QEMU's documentation is relatively straightforward in its explanation of USB emulation in QEMU, there appears to be no full explanation of how to go from a USB device appearing on a host operating system to one appearing in a guest operating system. This is such an explanation.

First, run sudo dmesg -W1, plug your device in, and then run the same command again. Look for a few lines appearing in the end of the second command but not the first that look something like this:

1[  461.858562] usb 1-1: new high-speed USB device number 12 using xhci_hcd
2[  462.001086] usb 1-1: New USB device found, idVendor=04a9, idProduct=10de, bcdDevice= 2.00
3[  462.001092] usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
4[  462.001094] usb 1-1: Product: PRO-100 series
5[  462.001095] usb 1-1: Manufacturer: Canon
6[  462.001096] usb 1-1: SerialNumber: 106A63
7[  462.004692] usblp 1-1:1.0: usblp0: USB Bidirectional printer dev 12 if 0 alt 0 proto 2 vid 0x04A9 pid 0x10DE

Note that the first line says …usb 1-1: new high-speed USB device number 12…. Note the first number in that entry (the first 1 in usb 1-1) as the bus number and the device number. Then, run sudo chown $(whoami) /dev/bus/usb/<PB>/<PD>, replacing <PB> and <PD> with the bus and device numbers, respectively, left-padded with zeros to a length of 3 characters. (In our example, the command would be sudo chown $(whoami) /dev/bus/usb/001/012.)

Then, run QEMU. First, enable the XHCI USB controller device by adding -device qemu-xhci to the list of options passed to qemu-system-x86_64. Then add the USB device by passing -device usb-host,hostbus=<B>,hostaddr=<D>,guest-reset=true,guest-resets-all=true, with <B> and <D> replaced with the non-padded bus and device numbers found earlier. If the bus and device numbers contain leading zeros, QEMU will interpret then as octal and might throw an error like qemu-system-x86_64: -device usb-host,hostbus=001,hostaddr=012,guest-reset=true,guest-resets-all=true: Parameter 'hostbus' expects integer2.

This process must be repeated for each time the device is unplugged and plugged back in, and for each device to be set up for use with QEMU.

Making the process easier with udev

The above instructions are easy enough for one use, but become tedious with repeated uses. To make things easier, one can create a udev rule to ensure that proper permissions are automatically applied. First, locate the vendor and product ID of your device either by running sudo dmesg -W and looking for a line such as usb 1-1: New USB device found, idVendor=04a9, idProduct=10de, bcdDevice= 2.00 in the output, or by running lsusb and looking for a line like Bus 001 Device 012: ID 04a9:10de Canon, Inc. PRO-100 series in its output. In the dmesg case, the vendor and product IDs are the four-character hex strings following idVendor= and idProduct=, respectively. In the lsusb case, on each line after ID are the vendor and product IDs separated by a colon. In both examples, they are 04a9 and 10de, respectively.

With the vendor and product IDs located, create a file named /etc/udev/rules.d/69-vm-access.rules. The actual name is unimportant, but the first two characters must sort less than 733. Put the following in the file:

ACTION!="add|change",    GOTO="vm-access_rules_end"
SUBSYSTEM!="usb",        GOTO="vm-access_rules_end"
SUBSYSTEM=="usb",    ATTRS{idVendor}=="<VID>", ATTRS{idProduct}=="<PID>", MODE="0660", GROUP="plugdev", TAG+="uaccess"
LABEL="vm-access_rules_end"

Replace <VID> and <PID> with the vendor and product IDs found previously. To allow QEMU to access multiple USB devices, repeat the penultimate line for each additional device, filling in the vendor and product IDs of such devices on each line.

Instead of adding the USB device by bus and device number, add it using vendor and product ID by passing -device usb-host,vendorid=0x<VID>,productid=0x<PID>,guest-reset=true,guest-resets-all=true, with <VID> and <PID> replaced with the vendor and product IDs found earlier. Ensure that the IDs are prefixed with 0x as shown above, or QEMU will throw an error like qemu-system-x86_64: -device usb-host,vendorid=0801,productid=0x0003,guest-reset=true,guest-resets-all=true: Parameter 'vendorid' expects integer.


1

The dmesg command outputs kernel log entries made while the system has been running. The -W flag makes dmesg run continuously, printing log entries as they are made.

2

The decision (originating with the C programming language) to treat numbers prefixed with 0 as octal is truly a triumph of unambiguous and intuitive input parsing that could have no possible negative repercussions and cause no confusion. Not.