Skip to content

Instantly share code, notes, and snippets.

@gpoole
Last active April 10, 2024 02:41
Show Gist options
  • Save gpoole/c89807e9de9cb5c87bbcc2da27e0c3e4 to your computer and use it in GitHub Desktop.
Save gpoole/c89807e9de9cb5c87bbcc2da27e0c3e4 to your computer and use it in GitHub Desktop.
Xen/XCP-ng script to attach physical USB devices via passthrough to a VM

XCP-ng USB passthrough tools

A set of command line tools and a service to make setting up passthrough USB devices easier:

  • add-custom-usb-policies - script to store and apply custom USB policies required for specific devices
  • attach-usb-devices - script and service to automatically connect USB devices to VMS when XCP-ng boots up
  • plug-usb - attach a physical USB device to a VM in one command
  • unplug-usb - remove a physical USB device from a VM in one command

To set up, copy the files onto your server and run install.sh to copy everything and set up the service. You can add any plug-usb commands you want to run on boot by modifying /usr/bin/attach-usb-devices. You can add any custom USB policies you need applied on install or after updates by modifying add-custom-usb-policies before running install.sh.

You can also use plug-usb directly if you want to attach a new device on demand:

# You can use xe vusb-list and xe vm-list to figure out the IDs you need
plug-usb <vusb-uuid> <vm-uuid>

If you want to unplug a device later, you can use unplug-usb:

unplug-usb <vusb-uuid>

You can list all physical USB devices available and get their UUIDs with xe pusb-list.

If you customise add-custom-usb-policies, you can run it again after upgrades to ensure your custom policies are applied again.

#!/bin/bash
# find information about which device you want to add a policy for with:
# lsusb
# Bus 001 Device 049: ID 1cf1:0030 Dresden Elektronik
# =>
# ALLOW:vid=1cf1 pid=0030 # Dresden Elektronik
# add the following policies to usb-policy.conf if they don't already exist
USB_POLICY_DRESDEN="ALLOW:vid=1cf1 pid=0030 # Dresden Elektronik"
USB_POLICY_BLUETOOTH="ALLOW:vid=0b05 pid=190e # Asus USB-BT500"
POLICY_FILE="/etc/xensource/usb-policy.conf"
for POLICY in "${USB_POLICY_DRESDEN}" "${USB_POLICY_BLUETOOTH}"; do
# -q be quiet
# -x match the whole line
# -F pattern is a plain string
grep -xqF -- "$POLICY" "$POLICY_FILE" || sed -i "1s/^/${POLICY}\n/" "$POLICY_FILE"
done
# NOTE: assumes you only have one host...
xe pusb-scan host-uuid=$(xe host-list --minimal)
#!/bin/bash
function get-vm-uuid-by-name() {
vm_name="$1"
xe vm-list name-label="$vm_name" --minimal
}
function get-usb-uuid-by() {
key="$1"
value="$2"
xe pusb-list $key="$value" --minimal
}
# Below list all the connections you want made automatically on boot
# The name here ("Example Disk Name") is the pusb product-desc,
# which you can find with: xe pusb-list params=uuid,product-desc
plug-usb "`get-usb-uuid-by product-desc "Example Disk Name"`" "`get-vm-uuid-by-name "Some VM Name"`"
[Unit]
Description=Automatically attach USB devices on startup
Wants=xapi-init-complete.target
# Runs BEFORE xapi-domains.service which is when VMS are autostarted
Before=xapi-domains.service
After=remote-fs.target xapi-init-complete.target xapi.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/attach-usb-devices
[Install]
WantedBy=multi-user.target
#/bin/bash
cp attach-usb-devices plug-usb unplug-usb add-custom-usb-policies /usr/bin
chmod +x /usr/bin/attach-usb-devices /usr/bin/plug-usb /usr/bin/unplug-usb /usr/bin/add-custom-usb-policies
cp attach-usb-devices.service /etc/systemd/system
systemctl enable attach-usb-devices
add-custom-usb-policies
# You can try running systemctl start attach-usb-devices but it'll fail to attach any VMs that are already running.
#!/bin/bash
pusb_uuid="$1"
vm_uuid="$2"
if [ -z "$pusb_uuid" ] || [ -z "$vm_uuid" ]; then
echo "Usage: plug-usb <pusb-device-uuid> <vm-uuid>"
exit 1
fi
vm_name=`xe vm-list uuid="$vm_uuid" | grep name-label | sed -E 's/^.*: //g'`
pusb_name=`xe pusb-list uuid="$pusb_uuid" | grep product-desc | sed -E 's/^.*: //g'`
if ! [ -z "`xe vm-list uuid="$vm_uuid" power-state=running`" ]; then
echo "VM $vm_name must be stopped first."
exit 1
fi
echo -n "Attaching $pusb_name ($pusb_uuid) to $vm_name ($vm_uuid)..."
group_uuid=`xe usb-group-list PUSB-uuids="$pusb_uuid" --minimal`
xe pusb-param-set uuid="$pusb_uuid" passthrough-enabled=true
if [ -z "`xe vusb-list vm-uuid="$vm_uuid" usb-group-uuid="$group_uuid"`" ]; then
xe vusb-create usb-group-uuid="$group_uuid" vm-uuid="$vm_uuid" > /dev/null
if [ $? -eq 0 ]; then
echo "Attached"
else
echo "Failed."
fi
else
echo "Already attached."
fi
#!/bin/bash
pusb_uuid="$1"
if [ -z "$pusb_uuid" ]; then
echo "Usage: usb-unplug <pusb-uuid>"
exit 1
fi
group_uuid=`xe usb-group-list PUSB-uuids="$pusb_uuid" --minimal`
if [ -z "$group_uuid" ]; then
echo "Could not find USB group."
exit 1
fi
vusb_uuid=`xe vusb-list usb-group-uuid="$group_uuid" --minimal`
if [ -z "$vusb_uuid" ]; then
echo "Failed to find virtual USB."
exit 1
fi
# Unplug from running VM
vm_uuid=`xe vusb-list usb-group-uuid="$group_uuid" | grep vm-uuid | sed -E 's/^.*: //g'`
if ! [ -z "`xe vm-list uuid="$vm_uuid" power-state=running`" ]; then
xe vusb-unplug uuid="$vusb_uuid"
fi
xe vusb-destroy uuid="$vusb_uuid"
xe pusb-param-set uuid="$pusb_uuid" passthrough-enabled=false
@gpoole
Copy link
Author

gpoole commented Oct 28, 2023

Nice, thanks @martijnhazebroek 👍

@stoneobscurity
Copy link

i have a related issue and i was hoping you could address it in the attach-usb-devices. i have a usb keyboard i need to pass thru to a VM. in order to do that, i have to edit the /etc/xensource/usb-policy.conf to ALLOW keyboards. however, every time i run a xcp-ng patch update, that file gets overwritten, and i lose my edits. would it be possible for your script to copy over a user-usb-policy.conf and then rescan the devices before doing the usb attach to VM?

@brumwald
Copy link

Thank you gpoole for this, appreciated!

@stoneobscurity I had the same problem, this is my proposal, a new file called add-custom-usb-policies with the following contents:

#!/bin/bash

# find information about which device you want to add a policy for with:
# lsusb
#     Bus 001 Device 049: ID 1cf1:0030 Dresden Elektronik
# =>
# ALLOW:vid=1cf1 pid=0030 # Dresden Elektronik

# add the following policies to usb-policy.conf if they don't already exist
POLICY_FILE="/etc/xensource/usb-policy.conf"
USB_POLICY_DRESDEN="ALLOW:vid=1cf1 pid=0030 # Dresden Elektronik"
USB_POLICY_BLUETOOTH="ALLOW:vid=0b05 pid=190e # Asus USB-BT500"

for POLICY in "${USB_POLICY_DRESDEN}" "${USB_POLICY_BLUETOOTH}"; do
  # -q be quiet
  # -x match the whole line
  # -F pattern is a plain string
  grep -xqF -- "$POLICY" "$POLICY_FILE" || sed -i "1s/^/${POLICY}\n/" "$POLICY_FILE"
done

# NOTE: assumes you only have one host...
xe pusb-scan host-uuid=$(xe host-list --minimal)

# device should now show up in xe pusb-list

Kept my two devices in there just for illustration, make sure to read through and edit to your needs.

Then, in attach-usb-devices, just add add-custom-usb-policies before the plug-usb calls.
And make sure to add the add-custom-usb-policies file to install.sh

@gpoole
Copy link
Author

gpoole commented Jan 16, 2024

Thanks for that @brumwald, I have updated to include your script with a few formatting changes and notes on usage. This set of scripts has grown a lot beyond the original one-script gist I had so I really should move it to a dedicated repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment