]> xenbits.xensource.com Git - libvirt.git/commitdiff
qemu: automatically bind to a vfio variant driver, if available
authorLaine Stump <laine@redhat.com>
Fri, 5 Jan 2024 01:12:51 +0000 (20:12 -0500)
committerLaine Stump <laine@redhat.com>
Mon, 8 Jan 2024 06:00:11 +0000 (01:00 -0500)
Rather than always binding to the vfio-pci driver, use the new
function virPCIDeviceFindBestVFIOVariant() to see if the running
kernel has a VFIO variant driver available that is a better match for
the device, and if one is found, use that instead.

virPCIDeviceFindBestVFIOVariant() function reads the modalias file for
the given device from sysfs, then looks through
/lib/modules/${kernel_release}/modules.alias for the vfio_pci alias
that matches with the least number of wildcard ('*') fields.

The appropriate "VFIO variant" driver for a device will be the PCI
driver implemented by the discovered module - these drivers are
compatible with (and provide the entire API of) the standard vfio-pci
driver, but have additional device-specific APIs that can be useful
for, e.g., saving/restoring state for migration.

If a specific driver is named (using <driver model='blah'/> in the
device XML), that will still be used rather than searching
modules.alias; this makes it possible to force binding of vfio-pci if
there is an issue with the auto-selected variant driver.

Signed-off-by: Laine Stump <laine@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
src/libvirt_private.syms
src/util/virpci.c
src/util/virpci.h
tests/virpcimock.c

index 79bbcc80fc7fb7d822598a3a51cc17b913a1fee8..fc261090297746ffaa2c6eb058bc13a0338a92c8 100644 (file)
@@ -3077,6 +3077,7 @@ virPCIDeviceCopy;
 virPCIDeviceDetach;
 virPCIDeviceExists;
 virPCIDeviceFileIterate;
+virPCIDeviceFindBestVFIOVariant;
 virPCIDeviceFree;
 virPCIDeviceGetAddress;
 virPCIDeviceGetConfigPath;
index 346be3e206e7b55ed778c3171c8ea5c56bb1208a..6c04e57038f4c4e7312c5a6460319b0846efeb5b 100644 (file)
 #include <sys/stat.h>
 #include <unistd.h>
 
+#ifdef __linux__
+# include <sys/utsname.h>
+#endif
+
 #include "virlog.h"
 #include "virerror.h"
 #include "virfile.h"
@@ -1320,6 +1324,206 @@ virPCIDeviceFindDriver(virPCIDevice *dev)
 }
 
 
+#ifdef __linux__
+typedef struct {
+    /* this is the decomposed version of a string like:
+     *
+     *   vNNNNNNNNdNNNNNNNNsvNNNNNNNNsdNNNNNNNNbcNNscNNiNN
+     *
+     * (followed by a space or newline). The "NNNN" are always of the
+     * length in the example unless replaced with a wildcard ("*"),
+     * but we make no assumptions about length.
+     *
+     * Rather than name each field, we just put them
+     * all in an array of 7 elements, so that we
+     * can write a simple loop to compare them
+     */
+    char *fields[7]; /* v, d, sv, sd, bc, sc, i */
+} virPCIDeviceAliasInfo;
+
+
+/* NULL in last position makes parsing loop simpler */
+static const char *fieldnames[] = { "v", "d", "sv", "sd", "bc", "sc", "i", NULL };
+
+
+static void
+virPCIDeviceAliasInfoFree(virPCIDeviceAliasInfo *info)
+{
+    if (info) {
+        size_t i;
+
+        for (i = 0; i < G_N_ELEMENTS(info->fields); i++)
+            g_free(info->fields[i]);
+
+        g_free(info);
+    }
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIDeviceAliasInfo, virPCIDeviceAliasInfoFree);
+
+
+static virPCIDeviceAliasInfo *
+virPCIDeviceAliasInfoNew(const char *str)
+{
+    const char *field = str;
+
+    size_t i;
+    g_autoptr(virPCIDeviceAliasInfo) ret = g_new0(virPCIDeviceAliasInfo, 1);
+
+    /* initialize from str */
+    for (i = 0; i < G_N_ELEMENTS(ret->fields); i++) {
+        int len = strlen(fieldnames[i]);
+        const char *next;
+
+        if (strncmp(field, fieldnames[i], len))
+            return NULL;
+
+        field += len;
+        if (fieldnames[i + 1]) {
+            if (!(next = strstr(field, fieldnames[i + 1])))
+                return NULL;
+        } else {
+            next = field;
+            while (*next && !g_ascii_isspace(*next))
+                next++;
+        }
+
+        ret->fields[i] = g_strndup(field, next - field);
+        field = next;
+    }
+
+    return g_steal_pointer(&ret);
+}
+
+
+static bool
+virPCIDeviceAliasInfoMatch(virPCIDeviceAliasInfo *orig,
+                           virPCIDeviceAliasInfo *match,
+                           unsigned int *wildCardCt)
+{
+    size_t i;
+
+    *wildCardCt = 0;
+
+    for (i = 0; i < G_N_ELEMENTS(orig->fields); i++) {
+        if (STREQ(match->fields[i], "*"))
+            (*wildCardCt)++;
+        else if (STRNEQ(orig->fields[i], match->fields[i]))
+            return false;
+    }
+    return true;
+}
+
+
+/* virPCIDeviceFindBestVFIOVariant:
+ *
+ * Find the "best" match of all vfio_pci aliases for @dev in the host
+ * modules.alias file. This uses the algorithm of finding every
+ * modules.alias line that begins with "vfio_pci:", then picking the
+ * one that matches the device's own modalias value (from the file of
+ * that name in the device's sysfs directory) with the fewest
+ * "wildcards" (* character, meaning "match any value for this
+ * attribute").
+ */
+int
+virPCIDeviceFindBestVFIOVariant(virPCIDevice *dev,
+                                char **moduleName)
+{
+    g_autofree char *devModAliasPath = NULL;
+    g_autofree char *devModAliasContent = NULL;
+    const char *devModAlias;
+    g_autoptr(virPCIDeviceAliasInfo) devModAliasInfo = NULL;
+    struct utsname unameInfo;
+    g_autofree char *modulesAliasPath = NULL;
+    g_autofree char *modulesAliasContent = NULL;
+    const char *line;
+    unsigned int currentBestWildcardCt = UINT_MAX;
+
+    *moduleName = NULL;
+
+    /* get the modalias values for the device from sysfs */
+    devModAliasPath = virPCIFile(dev->name, "modalias");
+    if (virFileReadAll(devModAliasPath, 100, &devModAliasContent) < 0)
+        return -1;
+
+    VIR_DEBUG("modalias path: '%s' contents: '%s'",
+              devModAliasPath, devModAliasContent);
+
+    /* "pci:vNNNNNNNNdNNNNNNNNsvNNNNNNNNsdNNNNNNNNbcNNscNNiNN\n" */
+    if  ((devModAlias = STRSKIP(devModAliasContent, "pci:")) == NULL ||
+         !(devModAliasInfo = virPCIDeviceAliasInfoNew(devModAlias))) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       _("device modalias file %1$s content has improper format"),
+                       devModAliasPath);
+        return -1;
+    }
+
+    uname(&unameInfo);
+    modulesAliasPath = g_strdup_printf("/lib/modules/%s/modules.alias", unameInfo.release);
+    if (virFileReadAll(modulesAliasPath, 8 * 1024 * 1024, &modulesAliasContent) < 0)
+        return -1;
+
+    /* Look for all lines that are aliases for vfio_pci drivers.
+     * (The first line is always a comment, so we can be sure "alias"
+     * is preceded by a newline)
+     */
+    line = modulesAliasContent;
+
+    while ((line = strstr(line, "\nalias vfio_pci:"))) {
+        g_autoptr(virPCIDeviceAliasInfo) fileModAliasInfo = NULL;
+        unsigned int wildCardCt;
+
+        /* "alias vfio_pci:vNNNNNNNNdNNNNNNNNsvNNNNNNNNsdNNNNNNNNbcNNscNNiNN XXXX\n" */
+        line += strlen("\nalias vfio_pci:");
+        if (!(fileModAliasInfo = virPCIDeviceAliasInfoNew(line)))
+            continue;
+
+        if (virPCIDeviceAliasInfoMatch(devModAliasInfo,
+                                       fileModAliasInfo, &wildCardCt)) {
+
+            const char *aliasStart = strchr(line, ' ');
+            const char *aliasEnd = NULL;
+            g_autofree char *aliasName = NULL;
+
+            if (!aliasStart) {
+                VIR_WARN("malformed modules.alias vfio_pci: line");
+                continue;
+            }
+
+            aliasStart++;
+            line = aliasEnd = strchrnul(aliasStart, '\n');
+            aliasName = g_strndup(aliasStart, aliasEnd - aliasStart);
+
+            VIR_DEBUG("matching alias '%s' found, %u wildcards, best previously was %u",
+                      aliasName, wildCardCt, currentBestWildcardCt);
+
+            if (wildCardCt < currentBestWildcardCt) {
+
+                /* this is a better match than previous */
+                currentBestWildcardCt = wildCardCt;
+                g_free(*moduleName);
+                *moduleName = g_steal_pointer(&aliasName);
+            }
+        }
+    }
+    return 0;
+}
+
+
+#else /* __linux__ */
+
+
+int
+virPCIDeviceFindBestVFIOVariant(virPCIDevice *dev G_GNUC_UNUSED,
+                                char **moduleName G_GNUC_UNUSED)
+{
+    virReportSystemError(ENOSYS, "%s",
+                         _("VFIO device assignment is not available on this platform"));
+    return -1;
+}
+#endif /* __linux__ */
+
+
 int
 virPCIDeviceUnbind(virPCIDevice *dev)
 {
@@ -1430,6 +1634,21 @@ virPCIDeviceBindToStub(virPCIDevice *dev)
         return -1;
     }
 
+    if (dev->stubDriverType == VIR_PCI_STUB_DRIVER_VFIO && !dev->stubDriverName) {
+        g_autofree char *autodetectModuleName = NULL;
+
+        /*  automatically use a VFIO variant driver if available for
+         *  this device.
+         */
+
+        if (virPCIDeviceFindBestVFIOVariant(dev, &autodetectModuleName) < 0)
+            return -1;
+
+        g_free(dev->stubDriverName);
+        dev->stubDriverName = g_steal_pointer(&autodetectModuleName);
+    }
+
+    /* if a driver name hasn't been decided by now, use default for this type */
     if (!dev->stubDriverName) {
 
         const char *stubDriverName = NULL;
index bc7cb2329f805688fe525988db168cb5359ed4df..a5bfe9c35da1ca3728c9ba59265bec4266d28040 100644 (file)
@@ -124,6 +124,8 @@ int virPCIDeviceReset(virPCIDevice *dev,
                       virPCIDeviceList *activeDevs,
                       virPCIDeviceList *inactiveDevs);
 
+int virPCIDeviceFindBestVFIOVariant(virPCIDevice *dev, char **moduleName) G_NO_INLINE;
+
 void virPCIDeviceSetManaged(virPCIDevice *dev,
                             bool managed);
 bool virPCIDeviceGetManaged(virPCIDevice *dev);
index b2111794e601564048a2b33a3e57c7ca789d76ef..13b37bb23d46adc3abcbccd0e482300930c42705 100644 (file)
@@ -25,6 +25,7 @@
 #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
 # define VIR_MOCK_LOOKUP_MAIN
 # include "virmock.h"
+# include "virpci.h"
 # include <unistd.h>
 # include <fcntl.h>
 # include <sys/stat.h>
@@ -925,6 +926,14 @@ pci_driver_handle_unbind(const char *path)
 }
 
 
+
+int
+virPCIDeviceFindBestVFIOVariant(virPCIDevice *dev G_GNUC_UNUSED,
+                                char **moduleName G_GNUC_UNUSED)
+{
+    return 0;
+}
+
 /*
  * Functions to load the symbols and init the environment
  */