]> git.ipfire.org Git - thirdparty/libvirt.git/commitdiff
virpcitest: Test virPCIDeviceDetach
authorMichal Privoznik <mprivozn@redhat.com>
Wed, 23 Oct 2013 15:55:02 +0000 (16:55 +0100)
committerMichal Privoznik <mprivozn@redhat.com>
Mon, 4 Nov 2013 16:28:48 +0000 (17:28 +0100)
This commit introduces yet another test under virpcitest:
virPCIDeviceDetach. However, in order to be able to do this, the
virpcimock needs to be extended to model the kernel behavior on PCI
device binding and unbinding (create 'driver' symlinks under the device
tree, check for device ID in driver's ID table, etc.)

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
cfg.mk
tests/Makefile.am
tests/virpcimock.c
tests/virpcitest.c

diff --git a/cfg.mk b/cfg.mk
index d7998c89e9456d66944aa2192f4d6b978c14158e..ed47a9b6c34fe3c1842fd44fc0115377a2dd1179 100644 (file)
--- a/cfg.mk
+++ b/cfg.mk
@@ -968,7 +968,7 @@ exclude_file_name_regexp--sc_prohibit_strdup = \
   ^(docs/|examples/|python/|src/util/virstring\.c|tests/virnetserverclientmock.c$$)
 
 exclude_file_name_regexp--sc_prohibit_close = \
-  (\.p[yl]$$|^docs/|^(src/util/virfile\.c|src/libvirt\.c|tests/vircgroupmock\.c)$$)
+  (\.p[yl]$$|^docs/|^(src/util/virfile\.c|src/libvirt\.c|tests/vir(cgroup|pci)mock\.c)$$)
 
 exclude_file_name_regexp--sc_prohibit_empty_lines_at_EOF = \
   (^tests/(qemuhelp|nodeinfo)data/|\.(gif|ico|png|diff)$$)
index 6d3245b3bba2316860f628df985dc4fe906ad82a..2c2e5a93dacedef95be2230cc9ef03f63193fa7e 100644 (file)
@@ -48,12 +48,15 @@ if WITH_DTRACE_PROBES
 PROBES_O += ../src/libvirt_probes.lo
 endif WITH_DTRACE_PROBES
 
+GNULIB_LIBS = \
+       ../gnulib/lib/libgnu.la
+
 LDADDS = \
         $(WARN_CFLAGS) \
        $(NO_INDIRECT_LDFLAGS) \
        $(PROBES_O) \
-       ../src/libvirt.la \
-       ../gnulib/lib/libgnu.la
+       $(GNULIB_LIBS) \
+       ../src/libvirt.la
 
 EXTRA_DIST =           \
        capabilityschemadata \
@@ -751,7 +754,8 @@ virpcitest_LDADD = $(LDADDS)
 virpcimock_la_SOURCES = \
        virpcimock.c
 virpcimock_la_CFLAGS = $(AM_CFLAGS)
-virpcimock_la_LIBADD = ../src/libvirt.la
+virpcimock_la_LIBADD = $(GNULIB_LIBS) \
+                                          ../src/libvirt.la
 virpcimock_la_LDFLAGS = -module -avoid-version \
         -rpath /evil/libtool/hack/to/force/shared/lib/creation
 
index 9e69c3dda98f31a3f4cdf8ee4e39939efc93f69d..2944780478baa4848c4946f6f77fb6028484dc39 100644 (file)
 # include "viralloc.h"
 # include "virstring.h"
 # include "virfile.h"
+# include "dirname.h"
 
 static int (*realaccess)(const char *path, int mode);
 static int (*reallstat)(const char *path, struct stat *sb);
 static int (*real__lxstat)(int ver, const char *path, struct stat *sb);
+static int (*realstat)(const char *path, struct stat *sb);
+static int (*real__xstat)(int ver, const char *path, struct stat *sb);
+static char *(*realcanonicalize_file_name)(const char *path);
 static int (*realopen)(const char *path, int flags, ...);
+static int (*realclose)(int fd);
 
 /* Don't make static, since it causes problems with clang
  * when passed as an arg to virAsprintf()
@@ -64,7 +69,30 @@ char *fakesysfsdir;
  *
  * Mock some file handling functions. Redirect them into a stub tree passed via
  * LIBVIRT_FAKE_SYSFS_DIR env variable. All files and links within stub tree is
- * created by us.
+ * created by us. There are some actions that we must take if some special
+ * files are written to. Here's the list of files we watch:
+ *
+ * /sys/bus/pci/drivers/<driver>/new_id:
+ *   Learn the driver new vendor and device combination.
+ *   Data in format "VVVV DDDD".
+ *
+ * /sys/bus/pci/drivers/<driver>/remove_id
+ *   Make the driver forget about vendor and device.
+ *   Data in format "VVVV DDDD".
+ *
+ * /sys/bus/pci/drivers/<driver>/bind
+ *   Check if driver supports the device and bind driver to it (create symlink
+ *   called 'driver' pointing to the /sys/but/pci/drivers/<driver>).
+ *   Data in format "DDDD:BB:DD.F" (Domain:Bus:Device.Function).
+ *
+ * /sys/bus/pci/drivers/<driver>/unbind
+ *   Unbind driver from the device.
+ *   Data in format "DDDD:BB:DD.F" (Domain:Bus:Device.Function).
+ *
+ * As a little hack, we are not mocking write to these files, but close()
+ * instead. The advantage is we don't need any self growing array to hold the
+ * partial writes and construct them back. We can let all the writes finish,
+ * and then just read the file content back.
  */
 
 /*
@@ -73,18 +101,51 @@ char *fakesysfsdir;
  *
  */
 
+struct pciDriver {
+    char *name;
+    int *vendor;        /* List of vendor:device IDs the driver can handle */
+    int *device;
+    size_t len;            /* @len is used for both @vendor and @device */
+};
+
 struct pciDevice {
     char *id;
     int vendor;
     int device;
+    struct pciDriver *driver;   /* Driver attached. NULL if attached to no driver */
+};
+
+struct fdCallback {
+    int fd;
+    char *path;
 };
 
 struct pciDevice **pciDevices = NULL;
 size_t nPciDevices = 0;
 
+struct pciDriver **pciDrivers = NULL;
+size_t nPciDrivers = 0;
+
+struct fdCallback *callbacks = NULL;
+size_t nCallbacks = 0;
+
 static void init_env(void);
 
+static int pci_device_autobind(struct pciDevice *dev);
 static void pci_device_new_from_stub(const struct pciDevice *data);
+static struct pciDevice *pci_device_find_by_id(const char *id);
+static struct pciDevice *pci_device_find_by_content(const char *path);
+
+static void pci_driver_new(const char *name, ...);
+static struct pciDriver *pci_driver_find_by_dev(struct pciDevice *dev);
+static struct pciDriver *pci_driver_find_by_path(const char *path);
+static int pci_driver_bind(struct pciDriver *driver, struct pciDevice *dev);
+static int pci_driver_unbind(struct pciDriver *driver, struct pciDevice *dev);
+static int pci_driver_handle_change(int fd, const char *path);
+static int pci_driver_handle_bind(const char *path);
+static int pci_driver_handle_unbind(const char *path);
+static int pci_driver_handle_new_id(const char *path);
+static int pci_driver_handle_remove_id(const char *path);
 
 
 /*
@@ -111,6 +172,41 @@ make_file(const char *path,
     VIR_FREE(filepath);
 }
 
+static int
+pci_read_file(const char *path,
+              char *buf,
+              size_t buf_size)
+{
+    int ret = -1;
+    int fd = -1;
+    char *newpath;
+
+    if (virAsprintfQuiet(&newpath, "%s/%s",
+                         fakesysfsdir,
+                         path + strlen(PCI_SYSFS_PREFIX)) < 0) {
+        errno = ENOMEM;
+        goto cleanup;
+    }
+
+    if ((fd = realopen(newpath, O_RDWR)) < 0)
+        goto cleanup;
+
+    bzero(buf, buf_size);
+    if (saferead(fd, buf, buf_size - 1) < 0) {
+        STDERR("Unable to read from %s", newpath);
+        goto cleanup;
+    }
+
+    if (ftruncate(fd, 0) < 0)
+        goto cleanup;
+
+    ret = 0;
+cleanup:
+    VIR_FREE(newpath);
+    realclose(fd);
+    return ret;
+}
+
 static int
 getrealpath(char **newpath,
             const char *path)
@@ -133,6 +229,70 @@ getrealpath(char **newpath,
     return 0;
 }
 
+static bool
+find_fd(int fd, size_t *indx)
+{
+    size_t i;
+
+    for (i = 0; i < nCallbacks; i++) {
+        if (callbacks[i].fd == fd) {
+            if (indx)
+                *indx = i;
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static int
+add_fd(int fd, const char *path)
+{
+    int ret = -1;
+    size_t i;
+
+    if (find_fd(fd, &i)) {
+        struct fdCallback cb = callbacks[i];
+        ABORT("FD %d %s already present in the array as %d %s",
+              fd, path, cb.fd, cb.path);
+    }
+
+    if (VIR_REALLOC_N_QUIET(callbacks, nCallbacks + 1) < 0 ||
+        VIR_STRDUP_QUIET(callbacks[nCallbacks].path, path) < 0) {
+        errno = ENOMEM;
+        goto cleanup;
+    }
+
+    callbacks[nCallbacks++].fd = fd;
+    ret = 0;
+cleanup:
+    return ret;
+}
+
+static int
+remove_fd(int fd)
+{
+    int ret = -1;
+    size_t i;
+
+    if (find_fd(fd, &i)) {
+        struct fdCallback cb = callbacks[i];
+
+        if (pci_driver_handle_change(cb.fd, cb.path) < 0)
+            goto cleanup;
+
+        VIR_FREE(cb.path);
+        if (VIR_DELETE_ELEMENT(callbacks, i, nCallbacks) < 0) {
+            errno = EINVAL;
+            goto cleanup;
+        }
+    }
+
+    ret = 0;
+cleanup:
+    return ret;
+}
+
 
 /*
  * PCI Device functions
@@ -163,12 +323,369 @@ pci_device_new_from_stub(const struct pciDevice *data)
         ABORT("@tmp overflow");
     make_file(devpath, "device", tmp);
 
+    if (pci_device_autobind(dev) < 0)
+        ABORT("Unable to bind: %s", data->id);
+
     if (VIR_APPEND_ELEMENT_QUIET(pciDevices, nPciDevices, dev) < 0)
         ABORT_OOM();
 
     VIR_FREE(devpath);
 }
 
+static struct pciDevice *
+pci_device_find_by_id(const char *id)
+{
+    size_t i;
+    for (i = 0; i < nPciDevices; i++) {
+        struct pciDevice *dev = pciDevices[i];
+
+        if (STREQ(dev->id, id))
+            return dev;
+    }
+
+    return NULL;
+}
+
+static struct pciDevice *
+pci_device_find_by_content(const char *path)
+{
+    char tmp[32];
+
+    if (pci_read_file(path, tmp, sizeof(tmp)) < 0)
+        return NULL;
+
+    return pci_device_find_by_id(tmp);
+}
+
+static int
+pci_device_autobind(struct pciDevice *dev)
+{
+    struct pciDriver *driver = pci_driver_find_by_dev(dev);
+
+    if (!driver) {
+        /* No driver found. Nothing to do */
+        return 0;
+    }
+
+    return pci_driver_bind(driver, dev);
+}
+
+
+/*
+ * PCI Driver functions
+ */
+static void
+pci_driver_new(const char *name, ...)
+{
+    struct pciDriver *driver;
+    va_list args;
+    int vendor, device;
+    char *driverpath;
+
+    if (VIR_ALLOC_QUIET(driver) < 0 ||
+        VIR_STRDUP_QUIET(driver->name, name) < 0 ||
+        virAsprintfQuiet(&driverpath, "%s/drivers/%s", fakesysfsdir, name) < 0)
+        ABORT_OOM();
+
+    if (virFileMakePath(driverpath) < 0)
+        ABORT("Unable to create: %s", driverpath);
+
+    va_start(args, name);
+
+    while ((vendor = va_arg(args, int)) != -1) {
+        if ((device = va_arg(args, int)) == -1)
+            ABORT("Invalid vendor device pair for driver %s", name);
+
+        if (VIR_REALLOC_N_QUIET(driver->vendor, driver->len + 1) < 0 ||
+            VIR_REALLOC_N_QUIET(driver->device, driver->len + 1) < 0)
+            ABORT_OOM();
+
+        driver->vendor[driver->len] = vendor;
+        driver->device[driver->len] = device;
+        driver->len++;
+    }
+
+    make_file(driverpath, "bind", NULL);
+    make_file(driverpath, "unbind", NULL);
+    make_file(driverpath, "new_id", NULL);
+    make_file(driverpath, "remove_id", NULL);
+
+    if (VIR_APPEND_ELEMENT_QUIET(pciDrivers, nPciDrivers, driver) < 0)
+        ABORT_OOM();
+}
+
+static struct pciDriver *
+pci_driver_find_by_dev(struct pciDevice *dev)
+{
+    size_t i;
+
+    for (i = 0; i < nPciDrivers; i++) {
+        struct pciDriver *driver = pciDrivers[i];
+        size_t j;
+
+        for (j = 0; j < driver->len; j++) {
+            if (driver->vendor[j] == dev->vendor &&
+                driver->device[j] == dev->device)
+                return driver;
+        }
+    }
+
+    return NULL;
+}
+
+static struct pciDriver *
+pci_driver_find_by_path(const char *path)
+{
+    size_t i;
+
+    for (i = 0; i < nPciDrivers; i++) {
+        struct pciDriver *driver = pciDrivers[i];
+
+        if (strstr(path, driver->name))
+            return driver;
+    }
+
+    return NULL;
+}
+
+static int
+pci_driver_bind(struct pciDriver *driver,
+                struct pciDevice *dev)
+{
+    int ret = -1;
+    char *devpath = NULL, *driverpath = NULL;
+
+    if (dev->driver) {
+        /* Device already binded */
+        errno = ENODEV;
+        return ret;
+    }
+
+    /* Make symlink under device tree */
+    if (virAsprintfQuiet(&devpath, "%s/devices/%s/driver",
+                         fakesysfsdir, dev->id) < 0 ||
+        virAsprintfQuiet(&driverpath, "%s/drivers/%s",
+                         fakesysfsdir, driver->name) < 0) {
+        errno = ENOMEM;
+        goto cleanup;
+    }
+
+    if (symlink(driverpath, devpath) < 0)
+        goto cleanup;
+
+    /* Make symlink under driver tree */
+    VIR_FREE(devpath);
+    VIR_FREE(driverpath);
+    if (virAsprintfQuiet(&devpath, "%s/devices/%s",
+                         fakesysfsdir, dev->id) < 0 ||
+        virAsprintfQuiet(&driverpath, "%s/drivers/%s/%s",
+                         fakesysfsdir, driver->name, dev->id) < 0) {
+        errno = ENOMEM;
+        goto cleanup;
+    }
+
+    if (symlink(devpath, driverpath) < 0)
+        goto cleanup;
+
+    dev->driver = driver;
+    ret = 0;
+cleanup:
+    VIR_FREE(devpath);
+    VIR_FREE(driverpath);
+    return ret;
+}
+
+static int
+pci_driver_unbind(struct pciDriver *driver,
+                  struct pciDevice *dev)
+{
+    int ret = -1;
+    char *devpath = NULL, *driverpath = NULL;
+
+    if (dev->driver != driver) {
+        /* Device not binded to the @driver */
+        errno = ENODEV;
+        return ret;
+    }
+
+    /* Make symlink under device tree */
+    if (virAsprintfQuiet(&devpath, "%s/devices/%s/driver",
+                         fakesysfsdir, dev->id) < 0 ||
+        virAsprintfQuiet(&driverpath, "%s/drivers/%s/%s",
+                         fakesysfsdir, driver->name, dev->id) < 0) {
+        errno = ENOMEM;
+        goto cleanup;
+    }
+
+    if (unlink(devpath) < 0 ||
+        unlink(driverpath) < 0)
+        goto cleanup;
+
+    dev->driver = NULL;
+    ret = 0;
+cleanup:
+    VIR_FREE(devpath);
+    VIR_FREE(driverpath);
+    return ret;
+}
+
+static int
+pci_driver_handle_change(int fd ATTRIBUTE_UNUSED, const char *path)
+{
+    int ret;
+    const char *file = last_component(path);
+
+    if (STREQ(file, "bind")) {
+        /* handle write to bind */
+        ret = pci_driver_handle_bind(path);
+    } else if (STREQ(file, "unbind")) {
+        /* handle write to unbind */
+        ret = pci_driver_handle_unbind(path);
+    } else if (STREQ(file, "new_id")) {
+        /* handle write to new_id */
+        ret = pci_driver_handle_new_id(path);
+    } else if (STREQ(file, "remove_id")) {
+        /* handle write to remove_id */
+        ret = pci_driver_handle_remove_id(path);
+    } else {
+        /* yet not handled write */
+        ABORT("Not handled write to: %s", path);
+    }
+    return ret;
+}
+
+static int
+pci_driver_handle_bind(const char *path)
+{
+    int ret = -1;
+    struct pciDevice *dev = pci_device_find_by_content(path);
+    struct pciDriver *driver = pci_driver_find_by_path(path);
+
+    if (!driver || !dev) {
+        /* This should never happen (TM) */
+        errno = ENODEV;
+        goto cleanup;
+    }
+
+    ret = pci_driver_bind(driver, dev);
+cleanup:
+    return ret;
+}
+
+static int
+pci_driver_handle_unbind(const char *path)
+{
+    int ret = -1;
+    struct pciDevice *dev = pci_device_find_by_content(path);
+
+    if (!dev || !dev->driver) {
+        /* This should never happen (TM) */
+        errno = ENODEV;
+        goto cleanup;
+    }
+
+    ret = pci_driver_unbind(dev->driver, dev);
+cleanup:
+    return ret;
+}
+static int
+pci_driver_handle_new_id(const char *path)
+{
+    int ret = -1;
+    struct pciDriver *driver = pci_driver_find_by_path(path);
+    size_t i;
+    int vendor, device;
+    char buf[32];
+
+    if (!driver) {
+        /* This should never happen (TM) */
+        errno = ENODEV;
+        goto cleanup;
+    }
+
+    if (pci_read_file(path, buf, sizeof(buf)) < 0)
+        goto cleanup;
+
+    if (sscanf(buf, "%x %x", &vendor, &device) < 2) {
+        errno = EINVAL;
+        goto cleanup;
+    }
+
+    for (i = 0; i < driver->len; i++) {
+        if (driver->vendor[i] == vendor &&
+            driver->device[i] == device)
+            break;
+    }
+
+    if (i == driver->len) {
+        if (VIR_REALLOC_N_QUIET(driver->vendor, driver->len + 1) < 0 ||
+            VIR_REALLOC_N_QUIET(driver->device, driver->len + 1) < 0) {
+            errno = ENOMEM;
+            goto cleanup;
+        }
+
+        driver->vendor[driver->len] = vendor;
+        driver->device[driver->len] = device;
+        driver->len++;
+    }
+
+    for (i = 0; i < nPciDevices; i++) {
+        struct pciDevice *dev = pciDevices[i];
+
+        if (!dev->driver &&
+            dev->vendor == vendor &&
+            dev->device == device &&
+            pci_driver_bind(driver, dev) < 0)
+                goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    return ret;
+}
+
+static int
+pci_driver_handle_remove_id(const char *path)
+{
+    int ret = -1;
+    struct pciDriver *driver = pci_driver_find_by_path(path);
+    size_t i;
+    int vendor, device;
+    char buf[32];
+
+    if (!driver) {
+        /* This should never happen (TM) */
+        errno = ENODEV;
+        goto cleanup;
+    }
+
+    if (pci_read_file(path, buf, sizeof(buf)) < 0)
+        goto cleanup;
+
+    if (sscanf(buf, "%x %x", &vendor, &device) < 2) {
+        errno = EINVAL;
+        goto cleanup;
+    }
+
+    for (i = 0; i < driver->len; i++) {
+        if (driver->vendor[i] == vendor &&
+            driver->device[i] == device)
+            continue;
+    }
+
+    if (i != driver->len) {
+        if (VIR_DELETE_ELEMENT(driver->vendor, i, driver->len) < 0)
+            goto cleanup;
+        driver->len++;
+        if (VIR_DELETE_ELEMENT(driver->device, i, driver->len) < 0)
+            goto cleanup;
+    }
+
+    ret = 0;
+cleanup:
+    return ret;
+}
+
 
 /*
  * Functions to load the symbols and init the environment
@@ -195,7 +712,10 @@ init_syms(void)
 
     LOAD_SYM(access);
     LOAD_SYM_ALT(lstat, __lxstat);
+    LOAD_SYM_ALT(stat, __xstat);
+    LOAD_SYM(canonicalize_file_name);
     LOAD_SYM(open);
+    LOAD_SYM(close);
 }
 
 static void
@@ -207,6 +727,13 @@ init_env(void)
     if (!(fakesysfsdir = getenv("LIBVIRT_FAKE_SYSFS_DIR")))
         ABORT("Missing LIBVIRT_FAKE_SYSFS_DIR env variable\n");
 
+# define MAKE_PCI_DRIVER(name, ...)                                     \
+    pci_driver_new(name, __VA_ARGS__, -1, -1)
+
+    MAKE_PCI_DRIVER("iwlwifi", 0x8086, 0x0044);
+    MAKE_PCI_DRIVER("i915", 0x8086, 0x0046, 0x8086, 0x0047);
+    MAKE_PCI_DRIVER("pci-stub", -1, -1);
+
 # define MAKE_PCI_DEVICE(Id, Vendor, Device, ...)                       \
     do {                                                                \
         struct pciDevice dev = {.id = (char *)Id, .vendor = Vendor,     \
@@ -215,6 +742,9 @@ init_env(void)
     } while (0)
 
     MAKE_PCI_DEVICE("0000:00:00.0", 0x8086, 0x0044);
+    MAKE_PCI_DEVICE("0000:00:01.0", 0x8086, 0x0044);
+    MAKE_PCI_DEVICE("0000:00:02.0", 0x8086, 0x0046);
+    MAKE_PCI_DEVICE("0000:00:03.0", 0x8086, 0x0048);
 }
 
 
@@ -281,6 +811,63 @@ lstat(const char *path, struct stat *sb)
     return ret;
 }
 
+int
+__xstat(int ver, const char *path, struct stat *sb)
+{
+    int ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return -1;
+        ret = real__xstat(ver, newpath, sb);
+        VIR_FREE(newpath);
+    } else {
+        ret = real__xstat(ver, path, sb);
+    }
+    return ret;
+}
+
+int
+stat(const char *path, struct stat *sb)
+{
+    int ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return -1;
+        ret = realstat(newpath, sb);
+        VIR_FREE(newpath);
+    } else {
+        ret = realstat(path, sb);
+    }
+    return ret;
+}
+
+char *
+canonicalize_file_name(const char *path)
+{
+    char *ret;
+
+    init_syms();
+
+    if (STRPREFIX(path, PCI_SYSFS_PREFIX)) {
+        char *newpath;
+        if (getrealpath(&newpath, path) < 0)
+            return NULL;
+        ret = realcanonicalize_file_name(newpath);
+        VIR_FREE(newpath);
+    } else {
+        ret = realcanonicalize_file_name(path);
+    }
+    return ret;
+}
+
 int
 open(const char *path, int flags, ...)
 {
@@ -303,10 +890,26 @@ open(const char *path, int flags, ...)
     } else {
         ret = realopen(newpath ? newpath : path, flags);
     }
+
+    /* Catch both: /sys/bus/pci/drivers/... and
+     * /sys/bus/pci/device/.../driver/... */
+    if (ret >= 0 && STRPREFIX(path, PCI_SYSFS_PREFIX) &&
+        strstr(path, "driver") && add_fd(ret, path) < 0) {
+        realclose(ret);
+        ret = -1;
+    }
+
     VIR_FREE(newpath);
     return ret;
 }
 
+int
+close(int fd)
+{
+    if (remove_fd(fd) < 0)
+        return -1;
+    return realclose(fd);
+}
 #else
 /* Nothing to override on non-__linux__ platforms */
 #endif
index 295dd9b427c1eece930bd4098f04ca16cc1289c8..4f2191100609966dd7aa0cc375c229d951aafedf 100644 (file)
@@ -57,6 +57,47 @@ cleanup:
     return ret;
 }
 
+# define CHECK_LIST_COUNT(list, cnt)                                    \
+    if ((count = virPCIDeviceListCount(list)) != cnt) {                 \
+        virReportError(VIR_ERR_INTERNAL_ERROR,                          \
+                       "Unexpected count of items in " #list ": %d, "   \
+                       "expecting " #cnt, count);                       \
+        goto cleanup;                                                   \
+    }
+
+static int
+testVirPCIDeviceDetach(const void *oaque ATTRIBUTE_UNUSED)
+{
+    int ret = -1;
+    virPCIDevicePtr dev;
+    virPCIDeviceListPtr activeDevs = NULL, inactiveDevs = NULL;
+    int count;
+
+    if (!(dev = virPCIDeviceNew(0, 0, 1, 0)) ||
+        !(activeDevs = virPCIDeviceListNew()) ||
+        !(inactiveDevs = virPCIDeviceListNew()))
+        goto cleanup;
+
+    CHECK_LIST_COUNT(activeDevs, 0);
+    CHECK_LIST_COUNT(inactiveDevs, 0);
+
+    if (virPCIDeviceSetStubDriver(dev, "pci-stub") < 0)
+        goto cleanup;
+
+    if (virPCIDeviceDetach(dev, activeDevs, inactiveDevs) < 0)
+        goto cleanup;
+
+    CHECK_LIST_COUNT(activeDevs, 0);
+    CHECK_LIST_COUNT(inactiveDevs, 1);
+
+    ret = 0;
+cleanup:
+    virPCIDeviceFree(dev);
+    virObjectUnref(activeDevs);
+    virObjectUnref(inactiveDevs);
+    return ret;
+}
+
 # define FAKESYSFSDIRTEMPLATE abs_builddir "/fakesysfsdir-XXXXXX"
 
 static int
@@ -84,6 +125,7 @@ mymain(void)
     } while (0)
 
     DO_TEST(testVirPCIDeviceNew);
+    DO_TEST(testVirPCIDeviceDetach);
 
     if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL)
         virFileDeleteTree(fakesysfsdir);