diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go index be5db45d14db..98804bf4c607 100644 --- a/lxd/device/nic_physical.go +++ b/lxd/device/nic_physical.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "strconv" + "strings" deviceConfig "github.com/canonical/lxd/lxd/device/config" pcidev "github.com/canonical/lxd/lxd/device/pci" @@ -11,6 +12,7 @@ import ( "github.com/canonical/lxd/lxd/instance/instancetype" "github.com/canonical/lxd/lxd/ip" "github.com/canonical/lxd/lxd/network" + "github.com/canonical/lxd/lxd/resources" "github.com/canonical/lxd/lxd/util" "github.com/canonical/lxd/shared" "github.com/canonical/lxd/shared/api" @@ -198,10 +200,15 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) { } } } else if d.inst.Type() == instancetype.VM { - // Get PCI information about the network interface. + // Try to get PCI information about the network interface. ueventPath := fmt.Sprintf("/sys/class/net/%s/device/uevent", saveData["host_name"]) pciDev, err := pcidev.ParseUeventFile(ueventPath) if err != nil { + if err == pcidev.ErrDeviceIsUSB { + // Device is USB rather than PCI. + return d.startVMUSB(saveData["host_name"]) + } + return nil, fmt.Errorf("Failed to get PCI device info for %q: %w", saveData["host_name"], err) } @@ -245,14 +252,87 @@ func (d *nicPhysical) Start() (*deviceConfig.RunConfig, error) { return &runConf, nil } +func (d *nicPhysical) startVMUSB(name string) (*deviceConfig.RunConfig, error) { + // Get the list of network interfaces. + interfaces, err := resources.GetNetwork() + if err != nil { + return nil, err + } + + // Look for our USB device. + var addr string + for _, card := range interfaces.Cards { + for _, port := range card.Ports { + if port.ID == name { + addr = card.USBAddress + break + } + } + + if addr != "" { + break + } + } + + if addr == "" { + return nil, fmt.Errorf("Failed to get USB device info for %q", name) + } + + // Parse the USB address. + fields := strings.Split(addr, ":") + if len(fields) != 2 { + return nil, fmt.Errorf("Bad USB device info for %q", name) + } + + usbBus, err := strconv.Atoi(fields[0]) + if err != nil { + return nil, fmt.Errorf("Bad USB device info for %q: %w", name, err) + } + + usbDev, err := strconv.Atoi(fields[1]) + if err != nil { + return nil, fmt.Errorf("Bad USB device info for %q: %w", name, err) + } + + // Record the addresses. + saveData := map[string]string{} + saveData["last_state.usb.bus"] = fmt.Sprintf("%03d", usbBus) + saveData["last_state.usb.device"] = fmt.Sprintf("%03d", usbDev) + + err = d.volatileSet(saveData) + if err != nil { + return nil, err + } + + // Generate a config. + runConf := deviceConfig.RunConfig{} + runConf.USBDevice = append(runConf.USBDevice, deviceConfig.USBDeviceItem{ + DeviceName: fmt.Sprintf("%s-%03d-%03d", d.name, usbBus, usbDev), + HostDevicePath: fmt.Sprintf("/dev/bus/usb/%03d/%03d", usbBus, usbDev), + }) + + return &runConf, nil +} + // Stop is run when the device is removed from the instance. func (d *nicPhysical) Stop() (*deviceConfig.RunConfig, error) { v := d.volatileGet() + runConf := deviceConfig.RunConfig{ PostHooks: []func() error{d.postStop}, - NetworkInterface: []deviceConfig.RunConfigItem{ + } + + if v["last_state.usb.bus"] != "" && v["last_state.usb.device"] != "" { + // Handle USB NICs. + runConf.USBDevice = append(runConf.USBDevice, deviceConfig.USBDeviceItem{ + DeviceName: fmt.Sprintf("%s-%s-%s", d.name, v["last_state.usb.bus"], v["last_state.usb.device"]), + HostDevicePath: fmt.Sprintf("/dev/bus/usb/%s/%s", v["last_state.usb.bus"], v["last_state.usb.device"]), + }) + } else { + // Handle all other NICs. + runConf.NetworkInterface = []deviceConfig.RunConfigItem{ {Key: "link", Value: v["host_name"]}, - }, + } } return &runConf, nil @@ -268,6 +348,8 @@ func (d *nicPhysical) postStop() error { "last_state.created": "", "last_state.pci.slot.name": "", "last_state.pci.driver": "", + "last_state.usb.bus": "", + "last_state.usb.device": "", }) }() diff --git a/lxd/device/pci/pci.go b/lxd/device/pci/pci.go index cdf419d4fcc7..e02edadc2c0f 100644 --- a/lxd/device/pci/pci.go +++ b/lxd/device/pci/pci.go @@ -13,6 +13,9 @@ import ( "github.com/canonical/lxd/shared/revert" ) +// ErrDeviceIsUSB is returned when dealing with a USB device. +var ErrDeviceIsUSB = fmt.Errorf("Device is USB instead of PCI") + // Device represents info about a PCI uevent device. type Device struct { ID string @@ -40,6 +43,8 @@ func ParseUeventFile(ueventFilePath string) (Device, error) { dev.SlotName = fields[1] } else if fields[0] == "PCI_ID" { dev.ID = fields[1] + } else if fields[0] == "DEVTYPE" && fields[1] == "usb_interface" { + return dev, ErrDeviceIsUSB } else if fields[0] == "DRIVER" { dev.Driver = fields[1] } diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index ae3192a9c33a..efe8bf0adc72 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -2461,13 +2461,20 @@ func (d *qemu) deviceStop(dev device.Device, instanceRunning bool, _ string) err if instanceRunning { // Detach NIC from running instance. if configCopy["type"] == "nic" { + for _, usbDev := range runConf.USBDevice { + err = d.deviceDetachUSB(usbDev) + if err != nil { + return err + } + } + err = d.deviceDetachNIC(dev.Name()) if err != nil { return err } } - // Detach USB drom running instance. + // Detach USB from running instance. if configCopy["type"] == "usb" && runConf != nil { for _, usbDev := range runConf.USBDevice { err = d.deviceDetachUSB(usbDev) diff --git a/lxd/instance/instancetype/instance.go b/lxd/instance/instancetype/instance.go index c57075f5a398..cdcfbd52990d 100644 --- a/lxd/instance/instancetype/instance.go +++ b/lxd/instance/instancetype/instance.go @@ -1210,6 +1210,24 @@ func ConfigKeyChecker(key string, instanceType Type) (func(value string) error, return validate.IsAny, nil } + // lxdmeta:generate(entities=network-physical; group=volatile; key=volatile.last_state.usb.bus) + // + // -- + // type: string + // shortdesc: USB Bus Number + if strings.HasSuffix(key, ".bus") { + return validate.IsAny, nil + } + + // lxdmeta:generate(entities=network-physical; group=volatile; key=volatile.last_state.usb.device) + // + // -- + // type: string + // shortdesc: USB Device Number + if strings.HasSuffix(key, ".device") { + return validate.IsAny, nil + } + if strings.HasSuffix(key, ".uuid") { return validate.IsAny, nil }