[libvirt] [PATCH 2/3] virpcitest: Test virPCIDeviceDetach
Martin Kletzander
mkletzan at redhat.com
Fri Nov 1 09:24:56 UTC 2013
On Fri, Oct 25, 2013 at 02:03:42PM +0100, Michal Privoznik wrote:
> 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 at redhat.com>
> ---
> cfg.mk | 2 +-
> tests/Makefile.am | 10 +-
> tests/virpcimock.c | 562 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> tests/virpcitest.c | 44 +++++
> 4 files changed, 613 insertions(+), 5 deletions(-)
>
> diff --git a/cfg.mk b/cfg.mk
> index 15114a9..306b052 100644
> --- a/cfg.mk
> +++ b/cfg.mk
> @@ -964,7 +964,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)$$)
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index 6d3245b..2c2e5a9 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -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
>
> diff --git a/tests/virpcimock.c b/tests/virpcimock.c
> index d545361..16a5ff3 100644
> --- a/tests/virpcimock.c
> +++ b/tests/virpcimock.c
> @@ -32,11 +32,14 @@
> # 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 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 +67,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 +99,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 */
> +};
> +
Don't you want to use few typedefs to save a bunch of 'struct'
keywords?
> 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 pciDevices_size = 0;
>
> +struct pciDriver **pciDrivers = NULL;
> +size_t pciDrivers_size = 0;
> +
> +struct fdCallback *callbacks = NULL;
> +size_t callbacks_size = 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);
>
>
> /*
> @@ -112,6 +171,41 @@ make_file(const char *path,
> }
>
> 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 +227,70 @@ getrealpath(char **newpath,
> return 0;
> }
>
> +static bool
> +find_fd(int fd, size_t *indx)
> +{
> + size_t i;
> +
> + for (i = 0; i < callbacks_size; 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, callbacks_size + 1) < 0 ||
> + VIR_STRDUP_QUIET(callbacks[callbacks_size].path, path) < 0) {
> + errno = ENOMEM;
> + goto cleanup;
> + }
> +
> + callbacks[callbacks_size++].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, callbacks_size) < 0) {
> + errno = EINVAL;
> + goto cleanup;
> + }
> + }
> +
> + ret = 0;
> +cleanup:
> + return ret;
> +}
> +
>
> /*
> * PCI Device functions
> @@ -163,12 +321,367 @@ 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, pciDevices_size, 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 < pciDevices_size; 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, pciDrivers_size, driver) < 0)
> + ABORT_OOM();
> +}
> +
> +static struct pciDriver *
> +pci_driver_find_by_dev(struct pciDevice *dev)
> +{
> + size_t i;
> +
> + for (i = 0; i < pciDrivers_size; 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 < pciDrivers_size; 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 */
> + 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) {
> + /* Device not 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/%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 < pciDevices_size; 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 +708,9 @@ init_syms(void)
>
> LOAD_SYM(access);
> LOAD_SYM_ALT(lstat, __lxstat);
> + LOAD_SYM(canonicalize_file_name);
> LOAD_SYM(open);
> + LOAD_SYM(close);
> }
>
> static void
> @@ -207,6 +722,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 +737,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 +806,25 @@ lstat(const char *path, struct stat *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 +847,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;
> + }
> +
Wouldn't it be safer to canonicalize the path (which should be done in
other functions anyway, since there could be a call to
'open("/sys//bus/pci/...", ...)', which this code would miss? Not
that we're doing this somewhere right now, but there's no performance
impact when it is done in tests and it's a bit future-safer.
[...]
Other than that, this patch looks OK, so ACK (conditional; when [1/3]
gets in).
Martin
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20131101/bcf5881a/attachment-0001.sig>
More information about the libvir-list
mailing list