Add helper functions to virpci to provide means of checking for a VPD
file presence and for VPD resource retrieval using the PCI VPD parser.
The added test assesses the basic functionality of VPD retrieval while
the full parser is tested by virpcivpdtest.
Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov(a)canonical.com>
---
src/libvirt_private.syms | 2 ++
src/util/virpci.c | 70 ++++++++++++++++++++++++++++++++++++++++
src/util/virpci.h | 4 +++
tests/virpcimock.c | 32 ++++++++++++++++++
tests/virpcitest.c | 39 ++++++++++++++++++++++
5 files changed, 147 insertions(+)
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..bddfada06c 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)
+{
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported));
+ return NULL;
+}
+
+virPCIVPDResource *
+virPCIDeviceGetVPD(virPCIDevice *dev)
+{
+ 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..0fe65264ee 100644
--- a/tests/virpcimock.c
+++ b/tests/virpcimock.c
@@ -18,6 +18,10 @@
#include <config.h>
+#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,10 @@ 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 +958,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 +1038,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..c5e97c6475 100644
--- a/tests/virpcitest.c
+++ b/tests/virpcitest.c
@@ -17,6 +17,7 @@
*/
#include <config.h>
+#include "internal.h"
#include "testutils.h"
@@ -26,6 +27,7 @@
# include <sys/stat.h>
# include <fcntl.h>
# include <virpci.h>
+# include <virpcivpd.h>
# define VIR_FROM_THIS VIR_FROM_NONE
@@ -328,6 +330,41 @@ testVirPCIDeviceUnbind(const void *opaque)
return ret;
}
+
+static int
+testVirPCIDeviceGetVPD(const void *opaque)
+{
+ const struct testPCIDevData *data = opaque;
+ g_autofree 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 +446,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);
--
2.32.0