[libvirt PATCH v5 1/7] Add a PCI/PCIe device VPD Parser

Daniel P. Berrangé berrange at redhat.com
Fri Oct 1 09:57:58 UTC 2021


On Mon, Sep 27, 2021 at 10:30:47PM +0300, Dmitrii Shcherbakov wrote:
> Add support for deserializing the binary PCI/PCIe VPD format and storing
> results in memory.
> 
> The VPD format is specified in "I.3. VPD Definitions" in PCI specs
> (2.2+) and "6.28.1 VPD Format" PCIe 4.0. As section 6.28 in PCIe 4.0
> notes, the PCI Local Bus and PCIe VPD formats are binary compatible
> and PCIe 4.0 merely started incorporating what was already present in
> PCI specs.
> 
> Linux kernel exposes a binary blob in the VPD format via sysfs since
> v2.6.26 (commit 94e6108803469a37ee1e3c92dafdd1d59298602f) which requires
> a parser to interpret.
> 
> A GTree is used as a data structure in order to maintain key ordering
> which will be important in XML to XML tests later.
> 
> Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov at canonical.com>
> ---
>  build-aux/syntax-check.mk |   4 +-
>  po/POTFILES.in            |   1 +
>  src/libvirt_private.syms  |  15 +
>  src/util/meson.build      |   1 +
>  src/util/virpcivpd.c      | 755 ++++++++++++++++++++++++++++++++++++++
>  src/util/virpcivpd.h      | 117 ++++++
>  src/util/virpcivpdpriv.h  |  45 +++
>  tests/meson.build         |   1 +
>  tests/testutils.c         |  40 ++
>  tests/testutils.h         |   4 +
>  tests/virpcivpdtest.c     | 705 +++++++++++++++++++++++++++++++++++
>  11 files changed, 1686 insertions(+), 2 deletions(-)
>  create mode 100644 src/util/virpcivpd.c
>  create mode 100644 src/util/virpcivpd.h
>  create mode 100644 src/util/virpcivpdpriv.h
>  create mode 100644 tests/virpcivpdtest.c
> 
> diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk
> index cb54c8ba36..a428831380 100644
> --- a/build-aux/syntax-check.mk
> +++ b/build-aux/syntax-check.mk
> @@ -775,9 +775,9 @@ sc_prohibit_windows_special_chars_in_filename:
>  	{ echo '$(ME): Windows special chars in filename not allowed' 1>&2; echo exit 1; } || :
>  
>  sc_prohibit_mixed_case_abbreviations:
> -	@prohibit='Pci|Usb|Scsi' \
> +	@prohibit='Pci|Usb|Scsi|Vpd' \
>  	in_vc_files='\.[ch]$$' \
> -	halt='Use PCI, USB, SCSI, not Pci, Usb, Scsi' \
> +	halt='Use PCI, USB, SCSI, VPD, not Pci, Usb, Scsi, Vpd' \
>  	  $(_sc_search_regexp)
>  
>  # Require #include <locale.h> in all files that call setlocale()
> diff --git a/po/POTFILES.in b/po/POTFILES.in
> index c200d7452a..4be4139529 100644
> --- a/po/POTFILES.in
> +++ b/po/POTFILES.in
> @@ -302,6 +302,7 @@
>  @SRCDIR at src/util/virnvme.c
>  @SRCDIR at src/util/virobject.c
>  @SRCDIR at src/util/virpci.c
> + at SRCDIR@src/util/virpcivpd.c
>  @SRCDIR at src/util/virperf.c
>  @SRCDIR at src/util/virpidfile.c
>  @SRCDIR at src/util/virpolkit.c
> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
> index 6de9d9aef1..bb9b380599 100644
> --- a/src/libvirt_private.syms
> +++ b/src/libvirt_private.syms
> @@ -3571,6 +3571,21 @@ virVHBAManageVport;
>  virVHBAPathExists;
>  
>  
> +# util/virpcivpd.h
> +
> +virPCIVPDKeywordResourceForEach;
> +virPCIVPDKeywordResourceNew;
> +virPCIVPDParse;
> +virPCIVPDParseVPDLargeResourceFields;
> +virPCIVPDParseVPDLargeResourceString;
> +virPCIVPDReadVPDBytes;
> +virPCIVPDResourceGetFieldValueFormat;
> +virPCIVPDResourceGetResourceType;
> +virPCIVPDResourceIsExpectedKeyword;
> +virPCIVPDResourceIsValidTextValue;
> +virPCIVPDStringResourceGetValue;
> +virPCIVPDStringResourceNew;
> +
>  # util/virvsock.h
>  virVsockAcquireGuestCid;
>  virVsockSetGuestCid;
> diff --git a/src/util/meson.build b/src/util/meson.build
> index 05934f6841..24350a3e67 100644
> --- a/src/util/meson.build
> +++ b/src/util/meson.build
> @@ -105,6 +105,7 @@ util_sources = [
>    'virutil.c',
>    'viruuid.c',
>    'virvhba.c',
> +  'virpcivpd.c',
>    'virvsock.c',
>    'virxml.c',
>  ]
> diff --git a/src/util/virpcivpd.c b/src/util/virpcivpd.c
> new file mode 100644
> index 0000000000..849ea0570c
> --- /dev/null
> +++ b/src/util/virpcivpd.c
> @@ -0,0 +1,755 @@
> +/*
> + * virpcivpd.c: helper APIs for working with the PCI/PCIe VPD capability
> + *
> + * Copyright (C) 2021 Canonical Ltd.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library.  If not, see
> + * <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <config.h>
> +
> +#ifdef __linux__
> +# include <unistd.h>
> +#endif
> +
> +#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
> +
> +#include "virpcivpdpriv.h"
> +#include "virlog.h"
> +#include "virerror.h"
> +#include "virfile.h"
> +
> +#define VIR_FROM_THIS VIR_FROM_NONE
> +
> +VIR_LOG_INIT("util.pcivpd");
> +
> +GType
> +vir_pci_vpd_resource_type_get_type(void)
> +{
> +    static GType resourceType;
> +
> +    static const GEnumValue resourceTypes[] = {
> +        {VIR_PCI_VPD_RESOURCE_TYPE_STRING, "String resource", "string"},
> +        {VIR_PCI_VPD_RESOURCE_TYPE_VPD_R, "Read-only resource", "vpd-r"},
> +        {VIR_PCI_VPD_RESOURCE_TYPE_VPD_W, "Read-write resource", "vpd-w"},
> +        {VIR_PCI_VPD_RESOURCE_TYPE_LAST, "last", "last"},
> +        {0, NULL, NULL},
> +    };
> +
> +    if (!resourceType) {
> +        resourceType = g_enum_register_static("virPCIVPDResourceType", resourceTypes);
> +    }
> +    return resourceType;
> +}
> +
> +static gboolean
> +virPCIVPDResourceIsVendorKeyword(const gchar *keyword)
> +{
> +    return g_str_has_prefix(keyword, "V");
> +}
> +
> +static gboolean
> +virPCIVPDResourceIsSystemKeyword(const gchar *keyword)
> +{
> +    /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
> +    return g_str_has_prefix(keyword, "Y") && STRNEQ(keyword, "YA");
> +}
> +
> +static gchar *
> +virPCIVPDResourceGetKeywordPrefix(const gchar *keyword)
> +{
> +    g_autofree gchar *key = NULL;
> +
> +    /* Keywords must have a length of 2 bytes. */
> +    if (strlen(keyword) != 2) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, _("The keyword length is not 2 bytes: %s"), keyword);
> +        return NULL;
> +    } else if (!(g_ascii_isalnum(keyword[0]) && g_ascii_isalnum(keyword[1]))) {
> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> +                       _("The keyword contains non-alphanumeric ASCII characters"));
> +        return NULL;
> +    }
> +    /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
> +    if (virPCIVPDResourceIsSystemKeyword(keyword) || virPCIVPDResourceIsVendorKeyword(keyword)) {
> +        key = g_strndup(keyword, 1);
> +    } else {
> +        key = g_strndup(keyword, 2);
> +    }
> +    return g_steal_pointer(&key);
> +}
> +
> +/**
> + * virPCIVPDResourceGetFieldValueFormat:
> + * @keyword: A keyword for which to get a value type
> + *
> + * Returns: a virPCIVPDResourceFieldValueFormat value which specifies the field value type for
> + * a provided keyword based on the static information from PCI(e) specs.
> + */
> +virPCIVPDResourceFieldValueFormat
> +virPCIVPDResourceGetFieldValueFormat(const gchar *keyword)
> +{
> +    static GHashTable *fieldValueFormats;
> +    g_autofree gchar *key = NULL;
> +    gpointer keyVal = NULL;
> +    virPCIVPDResourceFieldValueFormat format = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
> +
> +    /* Keywords are expected to be 2 bytes in length which is defined in the specs. */
> +    if (strlen(keyword) != 2) {
> +        return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
> +    }
> +
> +    if (!fieldValueFormats) {

I don't  know what the thread concurrency rules are of the callers of
this code, but regardless we generally aim to make any one-time
initializers thread safe by default.

Recommend using VIR_ONCE_GLOBAL_INIT to do one-time init.

> +        /* Initialize a hash table once with static format metadata coming from the PCI(e) specs.
> +         * The VPD format does not embed format metadata into the resource records so it is not
> +         * possible to do format discovery without static information. Legacy PICMIG keywords
> +         * are not included. */
> +        fieldValueFormats = g_hash_table_new(g_str_hash, g_str_equal);
> +        /* Extended capability. Contains binary data per PCI(e) specs. */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("CP"),

IIUC, this hash table is created once and never changed. I'm
not seeing a clear need for g_strdup here. Can't we just
directly use the constant string as the key ?

> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY));
> +        /* Engineering Change Level of an Add-in Card. */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("EC"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* Manufacture ID */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("MN"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* Add-in Card Part Number */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("PN"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* Checksum and Reserved */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("RV"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD));
> +        /* Remaining Read/Write Area */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("RW"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR));
> +        /* Serial Number */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("SN"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* Asset Tag Identifier */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("YA"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* This is a vendor specific item and the characters are alphanumeric. The second
> +         * character (x) of the keyword can be 0 through Z so only the first one is stored. */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("V"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +        /* This is a system specific item and the characters are alphanumeric.
> +         * The second character (x) of the keyword can be 0 through 9 and B through Z. */
> +        g_hash_table_insert(fieldValueFormats, g_strdup("Y"),
> +                            GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
> +    }
> +
> +    /* The system and vendor-specific keywords have a variable part - lookup
> +     * the prefix significant for determining the value format. */
> +    key = virPCIVPDResourceGetKeywordPrefix(keyword);
> +    if (key) {
> +        keyVal = g_hash_table_lookup(fieldValueFormats, key);
> +        if (keyVal) {
> +            format = GPOINTER_TO_INT(keyVal);
> +        }
> +    }
> +    return format;
> +}

> +/**
> + * virPCIVPDResourceIsValidTextValue:
> + * @value: A NULL-terminated string to assess.
> + *
> + * Returns: a boolean indicating whether this value is a valid string resource
> + * value or text field value. The expectations are based on the keywords specified
> + * in relevant sections of PCI(e) specifications
> + * ("I.3. VPD Definitions" in PCI specs, "6.28.1 VPD Format" PCIe 4.0).
> + */
> +gboolean
> +virPCIVPDResourceIsValidTextValue(const gchar *value)
> +{
> +    /*
> +     * The PCI(e) specs mention alphanumeric characters when talking about text fields
> +     * and the string resource but also include spaces and dashes in the provided example.
> +     * Dots, commas, equal signs have also been observed in values used by major device vendors.
> +     * The specs do not specify a full set of allowed code points and for Libvirt it is important
> +     * to keep values in the ranges allowed within XML elements (mainly excluding less-than,
> +     * greater-than and ampersand).
> +     */
> +    if (!g_regex_match_simple("^[a-zA-Z0-9\\-_\\s,.:=]*$", value, 0, 0)) {

Since you're only trying to validate a set of permitted characters,
it is sufficient to use strspn() for this task.

eg what we do in vsh.c is

  /* Characters permitted in $EDITOR environment variable and temp filename. */
  #define ACCEPTED_CHARS \
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/_.:@"

    if (strspn(editor, ACCEPTED_CHARS) != strlen(editor)) {
       ....error...




> +/*
> + * PCI Local bus (2.2+, Appendix I) and PCIe 4.0+ (7.9.19 VPD Capability) define
> + * the VPD capability structure (8 bytes in total) and VPD registers that can be used to access
> + * VPD data including:
> + * bit 31 of the first 32-bit DWORD: data transfer completion flag (between the VPD data register
> + * and the VPD data storage hardware);
> + * bits 30:16 of the first 32-bit DWORD: VPD address of the first VPD data byte to be accessed;
> + * bits 31:0 of the second 32-bit DWORD: VPD data bytes with LSB being pointed to by the VPD address.
> + *
> + * Given that only 15 bits (30:16) are allocated for VPD address its mask is 0x7fff.
> +*/
> +#define PCI_VPD_ADDR_MASK 0x7FFF
> +
> +/*
> + * VPD data consists of small and large resource data types. Information within a resource type
> + * consists of a 2-byte keyword, 1-byte length and data bytes (up to 255).
> +*/
> +#define PCI_VPD_MAX_FIELD_SIZE 255
> +#define PCI_VPD_LARGE_RESOURCE_FLAG 0x80
> +#define PCI_VPD_STRING_RESOURCE_FLAG 0x02
> +#define PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG 0x10
> +#define PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG 0x11
> +#define PCI_VPD_RESOURCE_END_TAG 0x0F
> +#define PCI_VPD_RESOURCE_END_VAL PCI_VPD_RESOURCE_END_TAG << 3
> +#define VIR_TYPE_PCI_VPD_RESOURCE_TYPE vir_pci_vpd_resource_type_get_type()
> +
> +G_BEGIN_DECLS;

This is not required in libvirt internal code, since we never use C++
internally.



> +#ifdef __linux__
> +/**
> + * virCreateAnonymousFile:
> + * @data: a pointer to data to be written into a new file.
> + * @len: the length of data to be written (in bytes).
> + *
> + * Create a fake fd, write initial data to it.
> + *
> + */
> +int
> +virCreateAnonymousFile(const uint8_t *data, size_t len)
> +{
> +    int fd = -1;
> +    char path[] = abs_builddir "testutils-memfd-XXXXXX";
> +    /* A temp file is used since not all supported distributions support memfd. */
> +    if ((fd = g_mkstemp_full(path, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) < 0) {
> +        return fd;
> +    }
> +    g_unlink(path);
> +
> +    if (ftruncate(fd, len) != 0) {
> +        VIR_TEST_DEBUG("%s: %s", "failed to ftruncate an anonymous file",
> +                g_strerror(errno));
> +        goto cleanup;
> +    }

Since it is a new temporary file it is guaranteed zero length, so
this should be redundant AFAICT.

> +    if (safewrite(fd, data, len) != len) {
> +        VIR_TEST_DEBUG("%s: %s", "failed to write to an anonymous file",
> +                g_strerror(errno));
> +        goto cleanup;
> +    }
> +    return fd;
> + cleanup:
> +    if (VIR_CLOSE(fd) < 0) {
> +        VIR_TEST_DEBUG("%s: %s", "failed to close an anonymous file",
> +                g_strerror(errno));
> +    }
> +    return -1;
> +}
> +#endif

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|




More information about the libvir-list mailing list