This 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. This can be used to find an appropriate "VFIO variant" driver for a device (it 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. Signed-off-by: Laine Stump <laine@xxxxxxxxxx> --- src/util/virpci.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++ src/util/virpci.h | 2 + 2 files changed, 226 insertions(+) diff --git a/src/util/virpci.c b/src/util/virpci.c index 513ae948c0..70fcedc4a5 100644 --- a/src/util/virpci.c +++ b/src/util/virpci.c @@ -30,6 +30,10 @@ #include <sys/stat.h> #include <unistd.h> +#ifndef WIN32 +# include <sys/utsname.h> +#endif + #include "virlog.h" #include "virerror.h" #include "virfile.h" @@ -1539,6 +1543,226 @@ virPCIDeviceReattach(virPCIDevice *dev, return 0; } + +#ifndef WIN32 +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 6 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); + + if (!str) + return g_steal_pointer(&ret); + + /* 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; + virSkipToSpace(&next); + } + + ret->fields[i] = g_strndup(field, next - field); + field = next; + } + + return g_steal_pointer(&ret); +} + + +static void +virPCIDeviceAliasInfoPrint(virPCIDeviceAliasInfo *info) +{ + size_t i; + + for (i = 0; i < G_N_ELEMENTS(info->fields); i++) + VIR_DEBUG("%s: '%s'", fieldnames[i], info->fields[i]); +} + + +static bool +virPCIDeviceAliasInfoMatch(virPCIDeviceAliasInfo *orig, + virPCIDeviceAliasInfo *match, + 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; + int currentBestWildcardCt = INT_MAX; + + *moduleName = NULL; + + /* get the modalias values for the device from sysfs */ + devModAliasPath = virPCIFile(dev->name, "modalias"); + if (virFileReadAll(devModAliasPath, 100, &devModAliasContent) < 0) + return 0; + + 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; + } + + virPCIDeviceAliasInfoPrint(devModAliasInfo); + + uname(&unameInfo); + modulesAliasPath = g_strdup_printf("/lib/modules/%s/modules.alias", + unameInfo.release); + if (virFileReadAll(modulesAliasPath, + 4 * 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; + int wildCardCt; + + /* "alias vfio_pci:vNNNNNNNNdNNNNNNNNsvNNNNNNNNsdNNNNNNNNbcNNscNNiNN XXXX\n" */ + line += strlen("\nalias vfio_pci:"); + if (!(fileModAliasInfo = virPCIDeviceAliasInfoNew(line))) + continue; + + virPCIDeviceAliasInfoPrint(fileModAliasInfo); + + 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, %d wildcards", + aliasName, wildCardCt); + + if (wildCardCt < currentBestWildcardCt) { + + /* this is a better match than previous */ + currentBestWildcardCt = wildCardCt; + g_free(*moduleName); + *moduleName = g_steal_pointer(&aliasName); + } + } + } + return 0; +} + + +#else /* WIN32 */ + + +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 /* WIN32 */ + + static char * virPCIDeviceReadID(virPCIDevice *dev, const char *id_name) { diff --git a/src/util/virpci.h b/src/util/virpci.h index faca6cf6f9..ca94145207 100644 --- a/src/util/virpci.h +++ b/src/util/virpci.h @@ -286,6 +286,8 @@ int virPCIDeviceGetCurrentDriverPathAndName(virPCIDevice *dev, int virPCIDeviceGetCurrentDriverNameAndType(virPCIDevice *dev, char **drvName, virPCIStubDriver *drvType); +int virPCIDeviceFindBestVFIOVariant(virPCIDevice *dev, + char **driverName); int virPCIDeviceIsPCIExpress(virPCIDevice *dev); int virPCIDeviceHasPCIExpressLink(virPCIDevice *dev); -- 2.41.0