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 -W
1,
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 integer
2.
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
73
3.
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
.
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.
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.