80 lines
2.8 KiB
YAML
80 lines
2.8 KiB
YAML
date: 2021-01-08
|
|
tags:
|
|
- linux
|
|
- kvm
|
|
title: KVM USB Auto-Passthrough
|
|
---
|
|
I do all of my PC gaming on a Windows VM which I run on a Linux host running
|
|
KVM. With a new gaming monitor on the way, I got the idea of using a
|
|
DisplayPort/USB switcher to switch my keyboard, mouse, and monitor between my
|
|
laptop and Windows VM (via KVM's USB passthrough). That way I can connect
|
|
the monitor directly to the Windows VM and use its 144 Hz refresh rate instead
|
|
of being stuck at what Linux is displaying along with the 30 or 60 fps provided
|
|
by [Parsec](https://parsec.app/).
|
|
|
|
Before I buy that USB switcher, I need to make sure that I can have my USB
|
|
devices to auto-connect to the VM when they get connected to my Linux host.
|
|
|
|
Through my cursory searching I couldn't find a way native to libvirt/qemu to do
|
|
this, so udev to the rescue! Through udev rules, I'm able to detect when a
|
|
device matching some parameters is connected, and then run a command. This
|
|
command will get a bunch of environment variables set from udev. The
|
|
interesting ones to us are:
|
|
|
|
* `ACTION` - add, remove, etc.
|
|
* `ID_VENDOR_ID` - the USB vendor ID
|
|
* `ID_MODEL_ID` - the USB model ID.
|
|
|
|
To test this idea out, I set up a simple rule that matches against my keyboard
|
|
and saved this as `/etc/udev/rules.d/90-libvirt-usb.rules`:
|
|
```
|
|
ACTION=="bind", \
|
|
SUBSYSTEM=="usb", \
|
|
ENV{ID_VENDOR_ID}=="6b62", \
|
|
ENV{ID_MODEL_ID}=="6869", \
|
|
RUN+="/usr/local/bin/kvm-udev attach steam"
|
|
ACTION=="remove", \
|
|
SUBSYSTEM=="usb", \
|
|
ENV{ID_VENDOR_ID}=="6b62", \
|
|
ENV{ID_MODEL_ID}=="6869", \
|
|
RUN+="/usr/local/bin/kvm-udev detach steam"
|
|
```
|
|
|
|
The VENDOR_ID and MODEL_ID can be found by running the `lsusb` command, which
|
|
shows this as "ID vendor_id:model_id", for example "ID 6b62:6869".
|
|
|
|
And of course, here's the script `/usr/local/bin/kvm-udev` which takes care of
|
|
the auto-connecting. It takes two parameters, first either being "attach" or
|
|
"detach" and the second being the name of the KVM domain (aka VM).
|
|
```
|
|
#!/bin/bash
|
|
|
|
# Usage: ./kvm-udev.sh attach|detach <domain>
|
|
|
|
set -e
|
|
|
|
ACTION=$1
|
|
DOMAIN=$2
|
|
|
|
CONF_FILE=$(mktemp --suffix=.kvm-udev)
|
|
cat << EOF >$CONF_FILE
|
|
<hostdev mode='subsystem' type='usb'>
|
|
<source>
|
|
<vendor id='0x${ID_VENDOR_ID}' />
|
|
<product id='0x${ID_MODEL_ID}' />
|
|
</source>
|
|
</hostdev>
|
|
EOF
|
|
|
|
virsh "${ACTION}-device" "$DOMAIN" "$conf_file"
|
|
rm "$CONF_FILE"
|
|
```
|
|
|
|
This writes out a temporary file which contains some libvirt XML config for doing
|
|
device passthrough, picking details from the environment variables that udev
|
|
sets. It then calls `virsh` to attach/detach the device to the given domain
|
|
using that temporary config file.
|
|
|
|
And with that, every time I plug my keyboard into my KVM host, it automatically
|
|
routes it to my Windows VM! Now that this works, I can add an additional rule
|
|
for my mouse, so that switching these devices between computers works smoothly.
|