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.
---
src/libvirt_private.syms | 2 ++
src/util/virpci.c | 62 ++++++++++++++++++++++++++++++++++++++
src/util/virpci.h | 3 ++
tests/virpcimock.c | 30 +++++++++++++++++++
tests/virpcitest.c | 64 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 161 insertions(+)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 1a16d97e02..3035adebe1 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2985,7 +2985,9 @@ virPCIDeviceGetReprobe;
virPCIDeviceGetStubDriver;
virPCIDeviceGetUnbindFromStub;
virPCIDeviceGetUsedBy;
+virPCIDeviceGetVPDResources;
virPCIDeviceHasPCIExpressLink;
+virPCIDeviceHasVPD;
virPCIDeviceIsAssignable;
virPCIDeviceIsPCIExpress;
virPCIDeviceListAdd;
diff --git a/src/util/virpci.c b/src/util/virpci.c
index f307580a53..480d91c17f 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,53 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path,
return 0;
}
+
+gboolean
+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;
+}
+
+/**
+ * virPCIDeviceGetVPDResources:
+ * @dev: a PCI device to get a list of VPD resources for.
+ *
+ * Obtain resources stored in the 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 a list of VPDResource types which needs to be freed by the
caller or
+ * NULL if getting it failed for some reason.
+ */
+GList *
+virPCIDeviceGetVPDResources(virPCIDevice *dev)
+{
+ g_autofree char *vpdPath = NULL;
+ int fd;
+
+ 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;
+ }
+ return virPCIVPDParse(fd);
+}
+
#else
static const char *unsupported = N_("not supported on non-linux platforms");
@@ -2713,6 +2761,20 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path
G_GNUC_UNUSED,
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported));
return -1;
}
+
+gboolean
+virPCIDeviceHasVPD(virPCIDevice *dev)
+{
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _(unsupported));
+ return NULL;
+}
+
+GList *
+virPCIDeviceGetVPDResources(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..a89561496b 100644
--- a/src/util/virpci.h
+++ b/src/util/virpci.h
@@ -269,6 +269,9 @@ int virPCIGetVirtualFunctionInfo(const char *vf_sysfs_device_path,
char **pfname,
int *vf_index);
+gboolean virPCIDeviceHasVPD(virPCIDevice *dev);
+GList * virPCIDeviceGetVPDResources(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..e10ebce76f 100644
--- a/tests/virpcimock.c
+++ b/tests/virpcimock.c
@@ -18,6 +18,8 @@
#include <config.h>
+#include "virpcivpd.h"
+
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
# define VIR_MOCK_LOOKUP_MAIN
# include "virmock.h"
@@ -123,6 +125,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 +140,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 +547,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 +956,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 +1036,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..c6bf12ba0b 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,66 @@ testVirPCIDeviceUnbind(const void *opaque)
return ret;
}
+
+static gboolean
+testVirPCIGetOneResourceField(gpointer *key, gpointer *val, gpointer userData)
+{
+ const char **fieldValue = (const char **)userData;
+ if (STREQ((const char*)key, "PN")) {
+ *fieldValue = (const char*)val;
+ return true;
+ }
+ return false;
+}
+
+static int
+testVirPCIDeviceGetVPDResources(const void *opaque)
+{
+ const struct testPCIDevData *data = opaque;
+ int ret = -1;
+ g_autofree virPCIDevice *dev = NULL;
+ virPCIDeviceAddress devAddr = {.domain = data->domain, .bus = data->bus,
+ .slot = data->slot, .function =
data->function};
+ g_autofree GList *resources = NULL;
+ const char* resValue = NULL;
+ const char* fieldValue = NULL;
+ virPCIVPDStringResource *strRes;
+ virPCIVPDKeywordResource *kwRes;
+
+ dev = virPCIDeviceNew(&devAddr);
+ if (!dev) {
+ return ret;
+ }
+
+ resources = virPCIDeviceGetVPDResources(dev);
+
+ /* Here we simply check that 2 resources are present and their basic use.
+ * Full parser validation is done elsewhere.
+ */
+ if (g_list_length(resources) != 2) {
+ return ret;
+ }
+
+ strRes = g_list_nth_data(resources, 0);
+ resValue = virPCIVPDStringResourceGetValue(strRes);
+ if (STRNEQ(resValue, "testname")) {
+ VIR_TEST_DEBUG("Unexpected string resource value: %s", resValue);
+ return ret;
+ }
+ kwRes = g_list_nth_data(resources, 1);
+ virPCIVPDKeywordResourceForEach(kwRes,
+ (GTraverseFunc)testVirPCIGetOneResourceField, &fieldValue);
+
+ if (!fieldValue || STRNEQ(fieldValue, "42")) {
+ VIR_TEST_DEBUG("Could not retrieve an expected value from the Keyword
resource: %s",
+ resValue);
+ return ret;
+ }
+
+ ret = 0;
+ return ret;
+}
+
# define FAKEROOTDIRTEMPLATE abs_builddir "/fakerootdir-XXXXXX"
static int
@@ -409,6 +471,8 @@ mymain(void)
DO_TEST_PCI(testVirPCIDeviceReattachSingle, 0, 0x0a, 3, 0);
DO_TEST_PCI_DRIVER(0, 0x0a, 3, 0, NULL);
+ DO_TEST_PCI(testVirPCIDeviceGetVPDResources, 0, 0x03, 0, 0);
+
if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL)
virFileDeleteTree(fakerootdir);
--
2.30.2