[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