Add PCI VPD-related helper functions to virpci

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.

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov@canonical.com>
This commit is contained in:
Dmitrii Shcherbakov 2021-10-20 11:30:32 +03:00 committed by Daniel P. Berrangé
parent 59c1bc3a0e
commit 38003e7551
5 changed files with 144 additions and 0 deletions

View File

@ -2994,7 +2994,9 @@ virPCIDeviceGetReprobe;
virPCIDeviceGetStubDriver;
virPCIDeviceGetUnbindFromStub;
virPCIDeviceGetUsedBy;
virPCIDeviceGetVPD;
virPCIDeviceHasPCIExpressLink;
virPCIDeviceHasVPD;
virPCIDeviceIsAssignable;
virPCIDeviceIsPCIExpress;
virPCIDeviceListAdd;

View File

@ -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

View File

@ -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,

View File

@ -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,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);
}

View File

@ -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,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);