diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 444b51c880..55ae7d5b6f 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2994,7 +2994,9 @@ virPCIDeviceGetReprobe; virPCIDeviceGetStubDriver; virPCIDeviceGetUnbindFromStub; virPCIDeviceGetUsedBy; +virPCIDeviceGetVPD; virPCIDeviceHasPCIExpressLink; +virPCIDeviceHasVPD; virPCIDeviceIsAssignable; virPCIDeviceIsPCIExpress; virPCIDeviceListAdd; diff --git a/src/util/virpci.c b/src/util/virpci.c index f307580a53..e746a2b25b 100644 --- a/src/util/virpci.c +++ b/src/util/virpci.c @@ -37,6 +37,7 @@ #include "virkmod.h" #include "virstring.h" #include "viralloc.h" +#include "virpcivpd.h" VIR_LOG_INIT("util.pci"); @@ -2640,6 +2641,61 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path, return 0; } + +bool +virPCIDeviceHasVPD(virPCIDevice *dev) +{ + g_autofree char *vpdPath = NULL; + + vpdPath = virPCIFile(dev->name, "vpd"); + if (!virFileExists(vpdPath)) { + VIR_INFO("Device VPD file does not exist %s", vpdPath); + return false; + } else if (!virFileIsRegular(vpdPath)) { + VIR_WARN("VPD path does not point to a regular file %s", vpdPath); + return false; + } + return true; +} + +/** + * virPCIDeviceGetVPD: + * @dev: a PCI device to get a PCI VPD for. + * + * Obtain a PCI device's Vital Product Data (VPD). VPD is optional in + * both PCI Local Bus and PCIe specifications so there is no guarantee it + * will be there for a particular device. + * + * Returns: a pointer to virPCIVPDResource which needs to be freed by the caller + * or NULL if getting it failed for some reason (e.g. invalid format, I/O error). + */ +virPCIVPDResource * +virPCIDeviceGetVPD(virPCIDevice *dev) +{ + g_autofree char *vpdPath = NULL; + int fd; + g_autoptr(virPCIVPDResource) res = NULL; + + vpdPath = virPCIFile(dev->name, "vpd"); + if (!virPCIDeviceHasVPD(dev)) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("Device %s does not have a VPD"), + virPCIDeviceGetName(dev)); + return NULL; + } + if ((fd = open(vpdPath, O_RDONLY)) < 0) { + virReportSystemError(-fd, _("Failed to open a VPD file '%s'"), vpdPath); + return NULL; + } + res = virPCIVPDParse(fd); + + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, _("Unable to close the VPD file, fd: %d"), fd); + return NULL; + } + + return g_steal_pointer(&res); +} + #else static const char *unsupported = N_("not supported on non-linux platforms"); @@ -2713,6 +2769,20 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path G_GNUC_UNUSED, virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); return -1; } + +bool +virPCIDeviceHasVPD(virPCIDevice *dev G_GNUC_UNUSED) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); + return NULL; +} + +virPCIVPDResource * +virPCIDeviceGetVPD(virPCIDevice *dev G_GNUC_UNUSED) +{ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported)); + return NULL; +} #endif /* __linux__ */ int diff --git a/src/util/virpci.h b/src/util/virpci.h index 9a3db6c6d8..3346321ec9 100644 --- a/src/util/virpci.h +++ b/src/util/virpci.h @@ -24,6 +24,7 @@ #include "virmdev.h" #include "virobject.h" #include "virenum.h" +#include "virpcivpd.h" typedef struct _virPCIDevice virPCIDevice; typedef struct _virPCIDeviceAddress virPCIDeviceAddress; @@ -269,6 +270,9 @@ int virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path, char **pfname, int *vf_index); +bool virPCIDeviceHasVPD(virPCIDevice *dev); +virPCIVPDResource * virPCIDeviceGetVPD(virPCIDevice *dev); + int virPCIDeviceUnbind(virPCIDevice *dev); int virPCIDeviceRebind(virPCIDevice *dev); int virPCIDeviceGetDriverPathAndName(virPCIDevice *dev, diff --git a/tests/virpcimock.c b/tests/virpcimock.c index d4d43aac51..0f8d5ad54f 100644 --- a/tests/virpcimock.c +++ b/tests/virpcimock.c @@ -18,6 +18,10 @@ #include +#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW + +#include "virpcivpdpriv.h" + #if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) # define VIR_MOCK_LOOKUP_MAIN # include "virmock.h" @@ -123,6 +127,13 @@ struct pciDeviceAddress { }; # define ADDR_STR_FMT "%04x:%02x:%02x.%u" +struct pciVPD { + /* PCI VPD contents (binary, may contain NULLs), NULL if not present. */ + const char *data; + /* VPD length in bytes. */ + size_t vpd_len; +}; + struct pciDevice { struct pciDeviceAddress addr; int vendor; @@ -131,6 +142,7 @@ struct pciDevice { int iommuGroup; const char *physfn; struct pciDriver *driver; /* Driver attached. NULL if attached to no driver */ + struct pciVPD vpd; }; struct fdCallback { @@ -537,6 +549,9 @@ pci_device_new_from_stub(const struct pciDevice *data) make_symlink(devpath, "physfn", tmp); } + if (dev->vpd.data && dev->vpd.vpd_len) + make_file(devpath, "vpd", dev->vpd.data, dev->vpd.vpd_len); + if (pci_device_autobind(dev) < 0) ABORT("Unable to bind: %s", devid); @@ -942,6 +957,20 @@ static void init_env(void) { g_autofree char *tmp = NULL; + const char fullVPDExampleData[] = { + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x08, 0x00, + 't', 'e', 's', 't', 'n', 'a', 'm', 'e', + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x16, 0x00, + 'P', 'N', 0x02, '4', '2', + 'E', 'C', 0x04, '4', '2', '4', '2', + 'V', 'A', 0x02, 'E', 'X', + 'R', 'V', 0x02, 0x31, 0x00, + PCI_VPD_RESOURCE_END_VAL + }; + struct pciVPD exampleVPD = { + .data = fullVPDExampleData, + .vpd_len = sizeof(fullVPDExampleData) / sizeof(fullVPDExampleData[0]), + }; if (!(fakerootdir = getenv("LIBVIRT_FAKE_ROOT_DIR"))) ABORT("Missing LIBVIRT_FAKE_ROOT_DIR env variable\n"); @@ -1008,6 +1037,8 @@ init_env(void) MAKE_PCI_DEVICE("0000:01:00.0", 0x1cc1, 0x8201, 14, .klass = 0x010802); MAKE_PCI_DEVICE("0000:02:00.0", 0x1cc1, 0x8201, 15, .klass = 0x010802); + + MAKE_PCI_DEVICE("0000:03:00.0", 0x15b3, 0xa2d6, 16, .vpd = exampleVPD); } diff --git a/tests/virpcitest.c b/tests/virpcitest.c index 6fe9b7d13a..4c0f0b91c3 100644 --- a/tests/virpcitest.c +++ b/tests/virpcitest.c @@ -17,6 +17,7 @@ */ #include +#include "internal.h" #include "testutils.h" @@ -26,6 +27,7 @@ # include # include # include +# include # define VIR_FROM_THIS VIR_FROM_NONE @@ -328,6 +330,39 @@ testVirPCIDeviceUnbind(const void *opaque) return ret; } + +static int +testVirPCIDeviceGetVPD(const void *opaque) +{ + const struct testPCIDevData *data = opaque; + g_autoptr(virPCIDevice) dev = NULL; + virPCIDeviceAddress devAddr = {.domain = data->domain, .bus = data->bus, + .slot = data->slot, .function = data->function}; + g_autoptr(virPCIVPDResource) res = NULL; + + dev = virPCIDeviceNew(&devAddr); + if (!dev) + return -1; + + res = virPCIDeviceGetVPD(dev); + + /* Only basic checks - full parser validation is done elsewhere. */ + if (res->ro == NULL) + return -1; + + if (STRNEQ(res->name, "testname")) { + VIR_TEST_DEBUG("Unexpected name present in VPD: %s", res->name); + return -1; + } + + if (STRNEQ(res->ro->part_number, "42")) { + VIR_TEST_DEBUG("Unexpected part number value present in VPD: %s", res->ro->part_number); + return -1; + } + + return 0; +} + # define FAKEROOTDIRTEMPLATE abs_builddir "/fakerootdir-XXXXXX" static int @@ -409,6 +444,8 @@ mymain(void) DO_TEST_PCI(testVirPCIDeviceReattachSingle, 0, 0x0a, 3, 0); DO_TEST_PCI_DRIVER(0, 0x0a, 3, 0, NULL); + DO_TEST_PCI(testVirPCIDeviceGetVPD, 0, 0x03, 0, 0); + if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL) virFileDeleteTree(fakerootdir);