Skip to content

Commit

Permalink
VM usb nic passthrough (from Incus) (#13890)
Browse files Browse the repository at this point in the history
What constitutes adequate testing for this set of changes? I don't have
access (currently) to a USB/Ethernet adapter; while I'm sure it's
_possible_ to simulate one I doubt that would be a
straightforward/worthwhile process. Should I check if someone else on
the team has appropriate hardware to run a quick check on this?
  • Loading branch information
tomponline authored Aug 10, 2024
2 parents d02424d + 3dd1227 commit 15439ec
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 4 deletions.
88 changes: 85 additions & 3 deletions lxd/device/nic_physical.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"fmt"
"net"
"strconv"
"strings"

deviceConfig "github.com/canonical/lxd/lxd/device/config"
pcidev "github.com/canonical/lxd/lxd/device/pci"
"github.com/canonical/lxd/lxd/instance"
"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"
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand All @@ -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": "",
})
}()

Expand Down
5 changes: 5 additions & 0 deletions lxd/device/pci/pci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
}
Expand Down
9 changes: 8 additions & 1 deletion lxd/instance/drivers/driver_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 18 additions & 0 deletions lxd/instance/instancetype/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 15439ec

Please sign in to comment.