[lxc-devel] [lxd/master] USB passthrough for VMs

monstermunchkin on Github lxc-bot at linuxcontainers.org
Fri Oct 9 14:43:07 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20201009/3a75c12a/attachment.bin>
-------------- next part --------------
From e1e86a1d38e28c6c8d95aae6b5feb6cd2bf74dae Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 8 Oct 2020 10:57:55 +0200
Subject: [PATCH 1/7] lxd/device/usb: Allow USB devices for VMs

This removes the container-only restriction, and allows USB devices for
VMs.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/device/usb.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index 6e5c09aab9..519da70edb 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -45,7 +45,7 @@ func (d *usb) isRequired() bool {
 
 // validateConfig checks the supplied config for correctness.
 func (d *usb) validateConfig(instConf instance.ConfigReader) error {
-	if !instanceSupported(instConf.Type(), instancetype.Container) {
+	if !instanceSupported(instConf.Type(), instancetype.Container, instancetype.VM) {
 		return ErrUnsupportedDevType
 	}
 

From eadab2d3d214010edeaf8c4c04df59caf94858fb Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 8 Oct 2020 16:30:22 +0200
Subject: [PATCH 2/7] lxd/device: Add bus and dev number to USBEvent

This adds two new public fields BusNum and DevNum to the USBEvent
struct.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/device/device_utils_usb_events.go | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lxd/device/device_utils_usb_events.go b/lxd/device/device_utils_usb_events.go
index a0a2f01e11..18840eafb2 100644
--- a/lxd/device/device_utils_usb_events.go
+++ b/lxd/device/device_utils_usb_events.go
@@ -26,6 +26,9 @@ type USBEvent struct {
 	Minor       uint32
 	UeventParts []string
 	UeventLen   int
+
+	BusNum int
+	DevNum int
 }
 
 // usbHandlers stores the event handler callbacks for USB events.
@@ -106,14 +109,16 @@ func USBNewEvent(action string, vendor string, product string, major string, min
 		return USBEvent{}, err
 	}
 
+	busnumInt := 0
+	devnumInt := 0
 	path := devname
 	if devname == "" {
-		busnumInt, err := strconv.Atoi(busnum)
+		busnumInt, err = strconv.Atoi(busnum)
 		if err != nil {
 			return USBEvent{}, err
 		}
 
-		devnumInt, err := strconv.Atoi(devnum)
+		devnumInt, err = strconv.Atoi(devnum)
 		if err != nil {
 			return USBEvent{}, err
 		}
@@ -133,5 +138,7 @@ func USBNewEvent(action string, vendor string, product string, major string, min
 		uint32(minorInt),
 		ueventParts,
 		ueventLen,
+		busnumInt,
+		devnumInt,
 	}, nil
 }

From bf3c02d11b68ee13da5eca32e61a926adf216eff Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Oct 2020 16:36:25 +0200
Subject: [PATCH 3/7] lxd/apparmor: Allow USB specific paths

This adds some paths to the AppArmor profile which need to be accessible
when using USB pass through.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/apparmor/instance_qemu.go | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/lxd/apparmor/instance_qemu.go b/lxd/apparmor/instance_qemu.go
index c529e76ffc..a1183e27e4 100644
--- a/lxd/apparmor/instance_qemu.go
+++ b/lxd/apparmor/instance_qemu.go
@@ -27,9 +27,13 @@ profile "{{ .name }}" flags=(attach_disconnected,mediate_deleted) {
   /dev/vhost-net                            rw,
   /dev/vhost-vsock                          rw,
   /etc/ceph/**                              r,
+  /run/udev/data/*                          r,
+  /sys/bus/                                 r,
   /sys/bus/nd/devices/                      r,
-  /sys/devices/system/node/                 r,
-  /sys/devices/system/node/**               r,
+  /sys/bus/usb/devices/                     r,
+  /sys/bus/usb/devices/**                   r,
+  /sys/class/                               r,
+  /sys/devices/**                           r,
   /sys/module/vhost/**                      r,
   /{,usr/}bin/qemu*                         mrix,
   /usr/share/OVMF/OVMF_CODE.fd              kr,

From 1a811e7e7ca3f82d3b507d7b5462c1ccd3de579a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Oct 2020 16:37:07 +0200
Subject: [PATCH 4/7] lxd/device/config: Add USBDevice to RunConfig

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/device/config/device_runconfig.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/device/config/device_runconfig.go b/lxd/device/config/device_runconfig.go
index eba388047a..f68d77b27e 100644
--- a/lxd/device/config/device_runconfig.go
+++ b/lxd/device/config/device_runconfig.go
@@ -42,4 +42,5 @@ type RunConfig struct {
 	Uevents          [][]string       // Uevents to inject.
 	PostHooks        []func() error   // Functions to be run after device attach/detach.
 	GPUDevice        []RunConfigItem  // GPU device configuration settings.
+	USBDevice        []RunConfigItem  // USB device configuration settings.
 }

From e1cd9f34e268837d932c8842dfbe39cfe85962ac Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Oct 2020 16:38:01 +0200
Subject: [PATCH 5/7] lxd/device: Handle USB devices for VMs

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/device/usb.go | 57 ++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 51 insertions(+), 6 deletions(-)

diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index 519da70edb..be646839fd 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -118,6 +118,14 @@ func (d *usb) Register() error {
 
 // Start is run when the device is added to the instance.
 func (d *usb) Start() (*deviceConfig.RunConfig, error) {
+	if d.inst.Type() == instancetype.VM {
+		return d.startVM()
+	}
+
+	return d.startContainer()
+}
+
+func (d *usb) startContainer() (*deviceConfig.RunConfig, error) {
 	usbs, err := d.loadUsb()
 	if err != nil {
 		return nil, err
@@ -144,18 +152,48 @@ func (d *usb) Start() (*deviceConfig.RunConfig, error) {
 	return &runConf, nil
 }
 
+func (d *usb) startVM() (*deviceConfig.RunConfig, error) {
+	usbs, err := d.loadUsb()
+	if err != nil {
+		return nil, err
+	}
+
+	runConf := deviceConfig.RunConfig{}
+	runConf.PostHooks = []func() error{d.Register}
+
+	for _, usb := range usbs {
+		if !usbIsOurDevice(d.config, &usb) {
+			continue
+		}
+
+		runConf.USBDevice = append(runConf.USBDevice, []deviceConfig.RunConfigItem{
+			{Key: "devName", Value: d.name},
+			{Key: "hostBus", Value: fmt.Sprintf("%d", usb.BusNum)},
+			{Key: "hostAddr", Value: fmt.Sprintf("%d", usb.DevNum)},
+		}...)
+	}
+
+	if d.isRequired() && len(runConf.Mounts) <= 0 {
+		return nil, fmt.Errorf("Required USB device not found")
+	}
+
+	return &runConf, nil
+}
+
 // Stop is run when the device is removed from the instance.
 func (d *usb) Stop() (*deviceConfig.RunConfig, error) {
-	// Unregister any USB event handlers for this device.
-	usbUnregisterHandler(d.inst, d.name)
-
 	runConf := deviceConfig.RunConfig{
 		PostHooks: []func() error{d.postStop},
 	}
 
-	err := unixDeviceRemove(d.inst.DevicesPath(), "unix", d.name, "", &runConf)
-	if err != nil {
-		return nil, err
+	if d.inst.Type() == instancetype.Container {
+		// Unregister any USB event handlers for this device.
+		usbUnregisterHandler(d.inst, d.name)
+
+		err := unixDeviceRemove(d.inst.DevicesPath(), "unix", d.name, "", &runConf)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	return &runConf, nil
@@ -246,3 +284,10 @@ func (d *usb) loadRawValues(p string) (map[string]string, error) {
 
 	return values, nil
 }
+
+// CanHotPlug returns true if attached to a container, otherwise false. It also
+// returns a list of fields that can be updated without triggering a device remove & add (in this
+// case an empty list).
+// func (d *usb) CanHotPlug() (bool, []string) {
+// 	return d.inst.Type() == instancetype.Container, []string{}
+// }

From c1a959117631fd0177c66c28fd1ad7f6cf1dd562 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Oct 2020 16:38:45 +0200
Subject: [PATCH 6/7] lxd/instance/drivers: Add qemuUSBDev template

This adds a device template for USB devices.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/instance/drivers/driver_qemu_templates.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lxd/instance/drivers/driver_qemu_templates.go b/lxd/instance/drivers/driver_qemu_templates.go
index c1b383bc6b..f1f77eb231 100644
--- a/lxd/instance/drivers/driver_qemu_templates.go
+++ b/lxd/instance/drivers/driver_qemu_templates.go
@@ -508,3 +508,12 @@ addr = "{{.devAddr}}"
 multifunction = "on"
 {{- end }}
 `))
+
+var qemuUSBDev = template.Must(template.New("qemuUSBDev").Parse(`
+# USB host device ("{{.devName}}" device)
+[device "dev-lxd_{{.devName}}"]
+driver = "usb-host"
+bus = "qemu_usb.0"
+hostaddr = "{{.hostAddr}}"
+hostbus = "{{.hostBus}}"
+`))

From b45f0198c757029860340522eef3443e3a71f376 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Fri, 9 Oct 2020 16:39:41 +0200
Subject: [PATCH 7/7] lxd/instance/drivers: Add USB devices to qemu config

This adds configured USB devices to the qemu config file.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/instance/drivers/driver_qemu.go | 49 +++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go
index 0b697cbf58..0d17257419 100644
--- a/lxd/instance/drivers/driver_qemu.go
+++ b/lxd/instance/drivers/driver_qemu.go
@@ -1842,6 +1842,14 @@ func (vm *qemu) generateQemuConfigFile(busName string, devConfs []*deviceConfig.
 				return "", err
 			}
 		}
+
+		// Add USB device.
+		if len(runConf.USBDevice) > 0 {
+			err = vm.addUSBDeviceConfig(sb, bus, runConf.USBDevice)
+			if err != nil {
+				return "", err
+			}
+		}
 	}
 
 	// Write the agent mount config.
@@ -2256,6 +2264,47 @@ func (vm *qemu) addGPUDevConfig(sb *strings.Builder, bus *qemuBus, gpuConfig []d
 	return nil
 }
 
+func (vm *qemu) addUSBDeviceConfig(sb *strings.Builder, bus *qemuBus, usbConfig []deviceConfig.RunConfigItem) error {
+	var devName, hostBus, hostAddr string
+
+	for _, usbItem := range usbConfig {
+		if usbItem.Key == "devName" {
+			devName = usbItem.Value
+		} else if usbItem.Key == "hostBus" {
+			hostBus = usbItem.Value
+		} else if usbItem.Key == "hostAddr" {
+			hostAddr = usbItem.Value
+		}
+	}
+
+	tplFields := map[string]interface{}{
+		"hostBus":  hostBus,
+		"hostAddr": hostAddr,
+		"devName":  devName,
+	}
+
+	// Add main GPU device in VGA mode to qemu config.
+	err := qemuUSBDev.Execute(sb, tplFields)
+	if err != nil {
+		return err
+	}
+
+	hostBusInt, err := strconv.Atoi(hostBus)
+	if err != nil {
+		return err
+	}
+
+	hostAddrInt, err := strconv.Atoi(hostAddr)
+	if err != nil {
+		return err
+	}
+
+	// Add path to devPaths. This way, the path will be included in the apparmor profile.
+	vm.devPaths = append(vm.devPaths, fmt.Sprintf("/dev/bus/usb/%03d/%03d", hostBusInt, hostAddrInt))
+
+	return nil
+}
+
 // pidFilePath returns the path where the qemu process should write its PID.
 func (vm *qemu) pidFilePath() string {
 	return filepath.Join(vm.LogPath(), "qemu.pid")


More information about the lxc-devel mailing list