Automatically reattach USB devices to KVM VM using serial number (e.g. for Zigbee/Thread sticks)
Problem
When passing USB devices like Zigbee or Thread dongles (e.g. Silicon Labs CP210x) through to a KVM virtual machine (e.g. Home Assistant OS), you typically use the vendor and product ID for passthrough via virsh
.
However, after a server reboot, the bus/device address can change, and sometimes the device is no longer correctly attached to the VM β especially if multiple devices share the same vendor/product ID, like two identical Silicon Labs sticks (one for Zigbee, one for Thread).
This guide shows how to reliably re-attach USB devices to a VM based on their unique serial number β automatically at boot.
Solution Overview
- Use a Bash script to identify the correct USB device via serial number.
- Detach any previously attached device with the same vendor/product ID.
- Attach the correct one using its current bus/device address.
- Run this script via systemd after boot, once
libvirtd
is ready.
Step 1: The USB Attach Script
Path:
/home/user/scripts/hassos_vm_usb_attach.sh
#!/bin/bash
exec >> /var/log/hassos_vm_usb_attach.log 2>&1
echo "[INFO] Starting script $(date)"
VM_NAME="hassos"
SUCCESS=0
handle_usb_device() {
SERIAL="$1"
VENDOR="$2"
PRODUCT="$3"
echo "[INFO] Handling USB device with serial $SERIAL"
virsh dumpxml "$VM_NAME" > /tmp/vm_config.xml
xmlstarlet sel -t -c "//hostdev[source/vendor[@id='0x$VENDOR'] and source/product[@id='0x$PRODUCT']]" /tmp/vm_config.xml > /tmp/old_usb.xml
if [[ -s /tmp/old_usb.xml ]]; then
echo "[INFO] Removing old USB device entries..."
virsh detach-device "$VM_NAME" /tmp/old_usb.xml --config
virsh detach-device "$VM_NAME" /tmp/old_usb.xml --live
fi
for dev in /sys/bus/usb/devices/*; do
if [[ -f "$dev/serial" ]] && [[ $(cat "$dev/serial") == "$SERIAL" ]]; then
BUSNUM=$(printf "%03d" "$(cat "$dev/busnum")")
DEVNUM=$(printf "%03d" "$(cat "$dev/devnum")")
BUSNUM=$(echo $BUSNUM | sed 's/^0*//')
DEVNUM=$(echo $DEVNUM | sed 's/^0*//')
cat > /tmp/usb-device.xml <<EOF
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x$VENDOR'/>
<product id='0x$PRODUCT'/>
<address bus='$BUSNUM' device='$DEVNUM'/>
</source>
</hostdev>
EOF
virsh attach-device "$VM_NAME" /tmp/usb-device.xml --config
virsh attach-device "$VM_NAME" /tmp/usb-device.xml --live
SUCCESS=1
return
fi
done
echo "[WARN] Device with serial $SERIAL not found"
}
# === Add your USB devices here ===
handle_usb_device "0ec5a3f1de49ef1185b9d58cff00cc63" "10c4" "ea60" # Example: Zigbee
# handle_usb_device "SERIAL2" "VENDOR2" "PRODUCT2" # Example: Thread
if [[ $SUCCESS -eq 1 ]]; then
echo "[INFO] Script finished successfully"
exit 0
else
echo "[ERROR] No USB device was attached"
exit 1
fi
Donβt forget to make it executable:
chmod +x /home/user/scripts/hassos_vm_usb_attach.sh
Step 2: Create a systemd service
Path:
/etc/systemd/system/usb-vm-attach.service
[Unit]
Description=Attach USB device to KVM VM
Requires=libvirtd.service
After=libvirtd.service
[Service]
Type=oneshot
ExecStart=/home/user/scripts/hassos_vm_usb_attach.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target
Enable and test the service:
sudo systemctl daemon-reload
sudo systemctl enable usb-vm-attach.service
sudo systemctl start usb-vm-attach.service
journalctl -u usb-vm-attach.service -b
How to get your USB device info (Vendor, Product, Serial)
- First list USB devices:
lsusb
Example:
Bus 001 Device 005: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
- Then get detailed info:
udevadm info --name=/dev/bus/usb/001/005 --query=all
Look for:
ID_VENDOR_ID
ID_MODEL_ID
ID_SERIAL
Notes
- The script uses
xmlstarlet
, which you may need to install:
sudo apt install xmlstarlet
- Works with any Linux distribution using KVM + libvirt (
virsh
). - Tested with Home Assistant OS VM and Silicon Labs CP210x sticks for Zigbee and Thread.
Happy automating!