Skip to content

Instantly share code, notes, and snippets.

@TobleMiner
Last active January 31, 2024 11:02
Show Gist options
  • Save TobleMiner/9a06eaba59e37ed1c7d16edd8b10d41e to your computer and use it in GitHub Desktop.
Save TobleMiner/9a06eaba59e37ed1c7d16edd8b10d41e to your computer and use it in GitHub Desktop.
ACTION=="bind", SUBSYSTEMS=="usb", RUN="/usr/local/sbin/libvirt-hotplug.py usb"
#!/usr/bin/env python3
import libvirt
import xml.etree.ElementTree as ET
import sys
import os
import re
RE_HEX_SUFFIX = re.compile('([0-9a-fA-F]+)$')
def hex_suffix(s):
match = RE_HEX_SUFFIX.search(s)
if match == None:
return ''
return match.groups()[0]
def usage(bin):
print("Usage: {} <bus>".format(bin))
if len(sys.argv) < 2:
print("Too few arguments")
usage(sys.argv[0])
sys.exit(1)
bus = sys.argv[1]
if bus != 'usb':
print('Sorry, atm only usb devices are supported')
sys.exit(1)
if not 'ID_VENDOR_ID' in os.environ or \
not 'ID_MODEL_ID' in os.environ:
sys.exit()
vid = os.environ['ID_VENDOR_ID'].lower()
pid = os.environ['ID_MODEL_ID'].lower()
conn = libvirt.open(None)
if not conn:
raise Exception("Failed to connect to libvirt")
def handle_dev_usb(hostdev):
vendor = hostdev.find("./source/vendor")
product = hostdev.find("./source/product")
if vendor == None or product == None:
print("Invalid XML, needs 'vendor' and 'product'")
return False
if not 'id' in vendor.attrib or not 'id' in product.attrib:
return False
candidate_vid = hex_suffix(vendor.attrib['id']).lower()
candidate_pid = hex_suffix(product.attrib['id']).lower()
return candidate_vid == vid and candidate_pid == pid
def hostdev_purge_address(dev):
source = dev.find('./source')
if source != None:
address = source.find('./address')
if address != None:
source.remove(address)
# Iterate through active domains
for domid in conn.listDomainsID():
dom = conn.lookupByID(domid)
xml = ET.fromstring(dom.XMLDesc())
# Retrieve all host devices on the bus
hostdevs = xml.findall(".//hostdev[@type='{}']".format(bus))
for dev in hostdevs:
if handle_dev_usb(dev):
print("Attaching device [{}:{}] to {}".format(vid, pid, dom.name()))
devxml = ET.tostring(dev, encoding='unicode')
dom.detachDevice(devxml)
# Remove address node, libvirt will figure that one out by itself
hostdev_purge_address(dev)
devxml = ET.tostring(dev, encoding='unicode')
dom.attachDevice(devxml)
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment