Add support for USB host device passthrough with LXC

This adds support for host device passthrough with the
LXC driver. Since there is only a single kernel image,
it doesn't make sense to pass through PCI devices, but
USB devices are fine. For the latter we merely need to
make the /dev/bus/usb/NNN/MMM character device exist
in the container's /dev

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
Daniel P. Berrange 2012-11-23 14:46:18 +00:00
parent 368e341ac1
commit 95fef5f407
9 changed files with 649 additions and 1 deletions

View File

@ -57,6 +57,7 @@ src/locking/lock_manager.c
src/locking/sanlock_helper.c
src/lxc/lxc_cgroup.c
src/lxc/lxc_fuse.c
src/lxc/lxc_hostdev.c
src/lxc/lxc_container.c
src/lxc/lxc_conf.c
src/lxc/lxc_controller.c

View File

@ -456,6 +456,7 @@ LXC_DRIVER_SOURCES = \
lxc/lxc_container.c lxc/lxc_container.h \
lxc/lxc_cgroup.c lxc/lxc_cgroup.h \
lxc/lxc_domain.c lxc/lxc_domain.h \
lxc/lxc_hostdev.c lxc/lxc_hostdev.h \
lxc/lxc_monitor.c lxc/lxc_monitor.h \
lxc/lxc_process.c lxc/lxc_process.h \
lxc/lxc_fuse.c lxc/lxc_fuse.h \

View File

@ -291,6 +291,49 @@ struct _virLXCCgroupDevicePolicy {
};
int
virLXCSetupHostUsbDeviceCgroup(usbDevice *dev ATTRIBUTE_UNUSED,
const char *path,
void *opaque)
{
virCgroupPtr cgroup = opaque;
int rc;
VIR_DEBUG("Process path '%s' for USB device", path);
rc = virCgroupAllowDevicePath(cgroup, path,
VIR_CGROUP_DEVICE_RW);
if (rc < 0) {
virReportSystemError(-rc,
_("Unable to allow device %s"),
path);
return -1;
}
return 0;
}
int
virLXCTeardownHostUsbDeviceCgroup(usbDevice *dev ATTRIBUTE_UNUSED,
const char *path,
void *opaque)
{
virCgroupPtr cgroup = opaque;
int rc;
VIR_DEBUG("Process path '%s' for USB device", path);
rc = virCgroupDenyDevicePath(cgroup, path,
VIR_CGROUP_DEVICE_RW);
if (rc < 0) {
virReportSystemError(-rc,
_("Unable to deny device %s"),
path);
return -1;
}
return 0;
}
static int virLXCCgroupSetupDeviceACL(virDomainDefPtr def,
virCgroupPtr cgroup)
@ -367,6 +410,27 @@ static int virLXCCgroupSetupDeviceACL(virDomainDefPtr def,
}
}
for (i = 0; i < def->nhostdevs; i++) {
virDomainHostdevDefPtr hostdev = def->hostdevs[i];
usbDevice *usb;
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
continue;
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB)
continue;
if (hostdev->missing)
continue;
if ((usb = usbGetDevice(hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
NULL)) == NULL)
goto cleanup;
if (usbDeviceFileIterate(usb, virLXCSetupHostUsbDeviceCgroup,
cgroup) < 0)
goto cleanup;
}
rc = virCgroupAllowDeviceMajor(cgroup, 'c', LXC_DEV_MAJ_PTY,
VIR_CGROUP_DEVICE_RWM);
if (rc != 0) {

View File

@ -24,7 +24,19 @@
# include "domain_conf.h"
# include "lxc_fuse.h"
# include "hostusb.h"
int virLXCCgroupSetup(virDomainDefPtr def);
int virLXCCgroupGetMeminfo(virLXCMeminfoPtr meminfo);
int
virLXCSetupHostUsbDeviceCgroup(usbDevice *dev,
const char *path,
void *opaque);
int
virLXCTeardownHostUsbDeviceCgroup(usbDevice *dev,
const char *path,
void *opaque);
#endif /* __VIR_LXC_CGROUP_H__ */

View File

@ -35,6 +35,7 @@
# include "cgroup.h"
# include "security/security_manager.h"
# include "configmake.h"
# include "hostusb.h"
# define LXC_DRIVER_NAME "LXC"
@ -65,6 +66,8 @@ struct _virLXCDriver {
int log_libvirtd;
int have_netns;
usbDeviceList *activeUsbHostdevs;
virDomainEventStatePtr domainEventState;
char *securityDriverName;

View File

@ -62,6 +62,7 @@
#include "uuid.h"
#include "virfile.h"
#include "command.h"
#include "hostusb.h"
#include "virnetdev.h"
#include "virprocess.h"
@ -1311,6 +1312,124 @@ static int lxcContainerSetupAllDisks(virDomainDefPtr vmDef,
}
static int lxcContainerSetupHostdevSubsysUSB(virDomainDefPtr vmDef ATTRIBUTE_UNUSED,
virDomainHostdevDefPtr def ATTRIBUTE_UNUSED,
const char *dstprefix ATTRIBUTE_UNUSED,
virSecurityManagerPtr securityDriver ATTRIBUTE_UNUSED)
{
int ret = -1;
char *src = NULL;
char *dstdir = NULL;
char *dstfile = NULL;
struct stat sb;
mode_t mode;
if (virAsprintf(&dstdir, USB_DEVFS "/%03d",
def->source.subsys.u.usb.bus) < 0) {
virReportOOMError();
goto cleanup;
}
if (virAsprintf(&dstfile, "%s/%03d",
dstdir,
def->source.subsys.u.usb.device) < 0) {
virReportOOMError();
goto cleanup;
}
if (virAsprintf(&src, "%s/%s", dstprefix, dstfile) < 0) {
virReportOOMError();
goto cleanup;
}
if (stat(src, &sb) < 0) {
virReportSystemError(errno,
_("Unable to access %s"), src);
goto cleanup;
}
if (!S_ISCHR(sb.st_mode)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("USB source %s was not a character device"),
src);
goto cleanup;
}
mode = 0700 | S_IFCHR;
if (virFileMakePath(dstdir) < 0) {
virReportSystemError(errno,
_("Unable to create %s"), dstdir);
goto cleanup;
}
VIR_DEBUG("Creating dev %s (%d,%d)",
dstfile, major(sb.st_rdev), minor(sb.st_rdev));
if (mknod(dstfile, mode, sb.st_rdev) < 0) {
virReportSystemError(errno,
_("Unable to create device %s"),
dstfile);
goto cleanup;
}
if (virSecurityManagerSetHostdevLabel(securityDriver, vmDef, def, NULL) < 0)
goto cleanup;
ret = 0;
cleanup:
VIR_FREE(src);
VIR_FREE(dstfile);
VIR_FREE(dstdir);
return ret;
}
static int lxcContainerSetupHostdevSubsys(virDomainDefPtr vmDef,
virDomainHostdevDefPtr def,
const char *dstprefix,
virSecurityManagerPtr securityDriver)
{
switch (def->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
return lxcContainerSetupHostdevSubsysUSB(vmDef, def, dstprefix, securityDriver);
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported host device mode %s"),
virDomainHostdevSubsysTypeToString(def->source.subsys.type));
return -1;
}
}
static int lxcContainerSetupAllHostdevs(virDomainDefPtr vmDef,
const char *dstprefix,
virSecurityManagerPtr securityDriver)
{
size_t i;
VIR_DEBUG("Setting up hostdevs %s", dstprefix);
for (i = 0 ; i < vmDef->nhostdevs ; i++) {
virDomainHostdevDefPtr def = vmDef->hostdevs[i];
switch (def->mode) {
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
if (lxcContainerSetupHostdevSubsys(vmDef, def, dstprefix, securityDriver) < 0)
return -1;
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported host device mode %s"),
virDomainHostdevModeTypeToString(def->mode));
return -1;
}
}
VIR_DEBUG("Setup all hostdevs");
return 0;
}
static int lxcContainerGetSubtree(const char *prefix,
char ***mountsret,
size_t *nmountsret)
@ -1710,7 +1829,11 @@ static int lxcContainerSetupPivotRoot(virDomainDefPtr vmDef,
if (lxcContainerSetupAllDisks(vmDef, "/.oldroot", securityDriver) < 0)
goto cleanup;
/* Gets rid of all remaining mounts from host OS, including /.oldroot itself */
/* Sets up any extra host devices from guest config */
if (lxcContainerSetupAllHostdevs(vmDef, "/.oldroot", securityDriver) < 0)
goto cleanup;
/* Gets rid of all remaining mounts from host OS, including /.oldroot itself */
if (lxcContainerUnmountSubtree("/.oldroot", true) < 0)
goto cleanup;

390
src/lxc/lxc_hostdev.c Normal file
View File

@ -0,0 +1,390 @@
/*
* virLXC_hostdev.c: VIRLXC hostdev management
*
* Copyright (C) 2006-2007, 2009-2012 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include "lxc_hostdev.h"
#include "logging.h"
#include "virterror_internal.h"
#include "memory.h"
#define VIR_FROM_THIS VIR_FROM_LXC
int
virLXCUpdateActiveUsbHostdevs(virLXCDriverPtr driver,
virDomainDefPtr def)
{
virDomainHostdevDefPtr hostdev = NULL;
size_t i;
if (!def->nhostdevs)
return 0;
for (i = 0; i < def->nhostdevs; i++) {
usbDevice *usb = NULL;
hostdev = def->hostdevs[i];
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
continue;
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB)
continue;
usb = usbGetDevice(hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
NULL);
if (!usb) {
VIR_WARN("Unable to reattach USB device %03d.%03d on domain %s",
hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
def->name);
continue;
}
usbDeviceSetUsedBy(usb, def->name);
if (usbDeviceListAdd(driver->activeUsbHostdevs, usb) < 0) {
usbFreeDevice(usb);
return -1;
}
}
return 0;
}
int
virLXCPrepareHostdevUSBDevices(virLXCDriverPtr driver,
const char *name,
usbDeviceList *list)
{
size_t i, j;
unsigned int count;
usbDevice *tmp;
count = usbDeviceListCount(list);
for (i = 0; i < count; i++) {
usbDevice *usb = usbDeviceListGet(list, i);
if ((tmp = usbDeviceListFind(driver->activeUsbHostdevs, usb))) {
const char *other_name = usbDeviceGetUsedBy(tmp);
if (other_name)
virReportError(VIR_ERR_OPERATION_INVALID,
_("USB device %s is in use by domain %s"),
usbDeviceGetName(tmp), other_name);
else
virReportError(VIR_ERR_OPERATION_INVALID,
_("USB device %s is already in use"),
usbDeviceGetName(tmp));
goto error;
}
usbDeviceSetUsedBy(usb, name);
VIR_DEBUG("Adding %03d.%03d dom=%s to activeUsbHostdevs",
usbDeviceGetBus(usb), usbDeviceGetDevno(usb), name);
/*
* The caller is responsible to steal these usb devices
* from the usbDeviceList that passed in on success,
* perform rollback on failure.
*/
if (usbDeviceListAdd(driver->activeUsbHostdevs, usb) < 0)
goto error;
}
return 0;
error:
for (j = 0; j < i; j++) {
tmp = usbDeviceListGet(list, i);
usbDeviceListSteal(driver->activeUsbHostdevs, tmp);
}
return -1;
}
int
virLXCFindHostdevUSBDevice(virDomainHostdevDefPtr hostdev,
bool mandatory,
usbDevice **usb)
{
unsigned vendor = hostdev->source.subsys.u.usb.vendor;
unsigned product = hostdev->source.subsys.u.usb.product;
unsigned bus = hostdev->source.subsys.u.usb.bus;
unsigned device = hostdev->source.subsys.u.usb.device;
bool autoAddress = hostdev->source.subsys.u.usb.autoAddress;
int rc;
*usb = NULL;
if (vendor && bus) {
rc = usbFindDevice(vendor, product, bus, device,
NULL,
autoAddress ? false : mandatory,
usb);
if (rc < 0) {
return -1;
} else if (!autoAddress) {
goto out;
} else {
VIR_INFO("USB device %x:%x could not be found at previous"
" address (bus:%u device:%u)",
vendor, product, bus, device);
}
}
/* When vendor is specified, its USB address is either unspecified or the
* device could not be found at the USB device where it had been
* automatically found before.
*/
if (vendor) {
usbDeviceList *devs;
rc = usbFindDeviceByVendor(vendor, product,
NULL,
mandatory, &devs);
if (rc < 0)
return -1;
if (rc == 1) {
*usb = usbDeviceListGet(devs, 0);
usbDeviceListSteal(devs, *usb);
}
usbDeviceListFree(devs);
if (rc == 0) {
goto out;
} else if (rc > 1) {
if (autoAddress) {
virReportError(VIR_ERR_OPERATION_FAILED,
_("Multiple USB devices for %x:%x were found,"
" but none of them is at bus:%u device:%u"),
vendor, product, bus, device);
} else {
virReportError(VIR_ERR_OPERATION_FAILED,
_("Multiple USB devices for %x:%x, "
"use <address> to specify one"),
vendor, product);
}
return -1;
}
hostdev->source.subsys.u.usb.bus = usbDeviceGetBus(*usb);
hostdev->source.subsys.u.usb.device = usbDeviceGetDevno(*usb);
hostdev->source.subsys.u.usb.autoAddress = true;
if (autoAddress) {
VIR_INFO("USB device %x:%x found at bus:%u device:%u (moved"
" from bus:%u device:%u)",
vendor, product,
hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
bus, device);
}
} else if (!vendor && bus) {
if (usbFindDeviceByBus(bus, device,
NULL,
mandatory, usb) < 0)
return -1;
}
out:
if (!*usb)
hostdev->missing = 1;
return 0;
}
static int
virLXCPrepareHostUSBDevices(virLXCDriverPtr driver,
virDomainDefPtr def)
{
size_t i;
int ret = -1;
usbDeviceList *list;
usbDevice *tmp;
virDomainHostdevDefPtr *hostdevs = def->hostdevs;
int nhostdevs = def->nhostdevs;
/* To prevent situation where USB device is assigned to two domains
* we need to keep a list of currently assigned USB devices.
* This is done in several loops which cannot be joined into one big
* loop. See virLXCPrepareHostdevPCIDevices()
*/
if (!(list = usbDeviceListNew()))
goto cleanup;
/* Loop 1: build temporary list
*/
for (i = 0 ; i < nhostdevs ; i++) {
virDomainHostdevDefPtr hostdev = hostdevs[i];
bool required = true;
usbDevice *usb;
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
continue;
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB)
continue;
if (hostdev->startupPolicy == VIR_DOMAIN_STARTUP_POLICY_OPTIONAL)
required = false;
if (virLXCFindHostdevUSBDevice(hostdev, required, &usb) < 0)
goto cleanup;
if (usb && usbDeviceListAdd(list, usb) < 0) {
usbFreeDevice(usb);
goto cleanup;
}
}
/* Mark devices in temporary list as used by @name
* and add them do driver list. However, if something goes
* wrong, perform rollback.
*/
if (virLXCPrepareHostdevUSBDevices(driver, def->name, list) < 0)
goto cleanup;
/* Loop 2: Temporary list was successfully merged with
* driver list, so steal all items to avoid freeing them
* in cleanup label.
*/
while (usbDeviceListCount(list) > 0) {
tmp = usbDeviceListGet(list, 0);
usbDeviceListSteal(list, tmp);
}
ret = 0;
cleanup:
usbDeviceListFree(list);
return ret;
}
int virLXCPrepareHostDevices(virLXCDriverPtr driver,
virDomainDefPtr def)
{
size_t i;
if (!def->nhostdevs)
return 0;
/* Sanity check for supported configurations only */
for (i = 0 ; i < def->nhostdevs ; i++) {
virDomainHostdevDefPtr dev = def->hostdevs[i];
switch (dev->mode) {
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
switch (dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB:
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported hostdev type %s"),
virDomainHostdevSubsysTypeToString(dev->source.subsys.type));
break;
}
break;
default:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("Unsupported hostdev mode %s"),
virDomainHostdevModeTypeToString(dev->mode));
break;
}
}
if (virLXCPrepareHostUSBDevices(driver, def) < 0)
return -1;
return 0;
}
static void
virLXCDomainReAttachHostUsbDevices(virLXCDriverPtr driver,
const char *name,
virDomainHostdevDefPtr *hostdevs,
int nhostdevs)
{
size_t i;
for (i = 0; i < nhostdevs; i++) {
virDomainHostdevDefPtr hostdev = hostdevs[i];
usbDevice *usb, *tmp;
const char *used_by = NULL;
if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
continue;
if (hostdev->source.subsys.type != VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB)
continue;
if (hostdev->missing)
continue;
usb = usbGetDevice(hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
NULL);
if (!usb) {
VIR_WARN("Unable to reattach USB device %03d.%03d on domain %s",
hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
name);
continue;
}
/* Delete only those USB devices which belongs
* to domain @name because virLXCProcessStart() might
* have failed because USB device is already taken.
* Therefore we want to steal only those devices from
* the list which were taken by @name */
tmp = usbDeviceListFind(driver->activeUsbHostdevs, usb);
usbFreeDevice(usb);
if (!tmp) {
VIR_WARN("Unable to find device %03d.%03d "
"in list of active USB devices",
hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device);
continue;
}
used_by = usbDeviceGetUsedBy(tmp);
if (STREQ_NULLABLE(used_by, name)) {
VIR_DEBUG("Removing %03d.%03d dom=%s from activeUsbHostdevs",
hostdev->source.subsys.u.usb.bus,
hostdev->source.subsys.u.usb.device,
name);
usbDeviceListDel(driver->activeUsbHostdevs, tmp);
}
}
}
void virLXCDomainReAttachHostDevices(virLXCDriverPtr driver,
virDomainDefPtr def)
{
if (!def->nhostdevs)
return;
virLXCDomainReAttachHostUsbDevices(driver, def->name, def->hostdevs,
def->nhostdevs);
}

43
src/lxc/lxc_hostdev.h Normal file
View File

@ -0,0 +1,43 @@
/*
* virLXC_hostdev.h: VIRLXC hostdev management
*
* Copyright (C) 2006-2007, 2009-2010 Red Hat, Inc.
* Copyright (C) 2006 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Author: Daniel P. Berrange <berrange@redhat.com>
*/
#ifndef __LXC_HOSTDEV_H__
# define __LXC_HOSTDEV_H__
# include "lxc_conf.h"
# include "domain_conf.h"
int virLXCUpdateActiveUsbHostdevs(virLXCDriverPtr driver,
virDomainDefPtr def);
int virLXCFindHostdevUSBDevice(virDomainHostdevDefPtr hostdev,
bool mandatory,
usbDevice **usb);
int virLXCPrepareHostdevUSBDevices(virLXCDriverPtr driver,
const char *name,
usbDeviceList *list);
int virLXCPrepareHostDevices(virLXCDriverPtr driver,
virDomainDefPtr def);
void virLXCDomainReAttachHostDevices(virLXCDriverPtr driver,
virDomainDefPtr def);
#endif /* __LXC_HOSTDEV_H__ */

View File

@ -44,6 +44,7 @@
#include "logging.h"
#include "command.h"
#include "hooks.h"
#include "lxc_hostdev.h"
#define VIR_FROM_THIS VIR_FROM_LXC
@ -256,6 +257,8 @@ static void virLXCProcessCleanup(virLXCDriverPtr driver,
if (!driver->nactive && driver->inhibitCallback)
driver->inhibitCallback(false, driver->inhibitOpaque);
virLXCDomainReAttachHostDevices(driver, vm->def);
for (i = 0 ; i < vm->def->nnets ; i++) {
virDomainNetDefPtr iface = vm->def->nets[i];
vport = virDomainNetGetActualVirtPortProfile(iface);
@ -978,6 +981,11 @@ int virLXCProcessStart(virConnectPtr conn,
goto cleanup;
}
/* Must be run before security labelling */
VIR_DEBUG("Preparing host devices");
if (virLXCPrepareHostDevices(driver, vm->def) < 0)
goto cleanup;
/* Here we open all the PTYs we need on the host OS side.
* The LXC controller will open the guest OS side PTYs
* and forward I/O between them.
@ -1311,6 +1319,9 @@ virLXCProcessReconnectDomain(void *payload, const void *name ATTRIBUTE_UNUSED, v
if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm)))
goto error;
if (virLXCUpdateActiveUsbHostdevs(driver, vm->def) < 0)
goto error;
if (virSecurityManagerReserveLabel(driver->securityManager,
vm->def, vm->pid) < 0)
goto error;