From nobody Sun Apr 28 21:58:50 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1633961171089784.2833705836223; Mon, 11 Oct 2021 07:06:11 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-318-XgCfM2tCO8W-k8gT3b7Pkw-1; Mon, 11 Oct 2021 10:06:07 -0400 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 268D910247AF; Mon, 11 Oct 2021 14:05:36 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id B585D4180; Mon, 11 Oct 2021 14:05:35 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 4CA674E590; Mon, 11 Oct 2021 14:05:35 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com [10.11.54.1]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 19BE5CvY030337 for ; Mon, 11 Oct 2021 10:05:12 -0400 Received: by smtp.corp.redhat.com (Postfix) id F21D640CFD0F; Mon, 11 Oct 2021 14:05:11 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast01.extmail.prod.ext.rdu2.redhat.com [10.11.55.17]) by smtp.corp.redhat.com (Postfix) with ESMTPS id EB00940CFD10 for ; Mon, 11 Oct 2021 14:05:10 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id C31D5899ECB for ; Mon, 11 Oct 2021 14:05:10 +0000 (UTC) Received: from smtp-relay-internal-1.canonical.com (smtp-relay-internal-1.canonical.com [185.125.188.123]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-565-JU4AW3OjPWyFeQSzSAbaKg-1; Mon, 11 Oct 2021 10:05:08 -0400 Received: from mail-lf1-f71.google.com (mail-lf1-f71.google.com [209.85.167.71]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-1.canonical.com (Postfix) with ESMTPS id E0BB13FFDE for ; Mon, 11 Oct 2021 14:05:03 +0000 (UTC) Received: by mail-lf1-f71.google.com with SMTP id v2-20020ac25582000000b003fd1c161a31so12755278lfg.15 for ; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) Received: from ws.lan.d-node.is ([95.165.29.203]) by smtp.gmail.com with ESMTPSA id o12sm741144lft.254.2021.10.11.07.05.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 07:05:00 -0700 (PDT) X-MC-Unique: XgCfM2tCO8W-k8gT3b7Pkw-1 X-MC-Unique: JU4AW3OjPWyFeQSzSAbaKg-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=YafRxknlJhmCIAly3EY5RfO5oHvJkkpvXKKmBDlGXx0=; b=esRm/YFF0h9bILKPSNwtfBS0abnXBPFQrMVW2a0WOUJJJRkM4eL3IlnSjkjLdudsm+ q7+8eX8DVjPsyB1WdSCnDGtgk4X1WLLq1MwEevi+hpH9ERjYi7OEwNRqdRD++jisxVXC womuvpgLsFAbwGp3RrQciuA0WRLgELF8a96UhZIbZXUWl/wXJ4NAG1/ebDOLsqPPqd8x jRBnOCyyoZRHr3e4+U5wV65NL26y7fZCZc7f7LYHziHFc0qa6tsqOQdZtvKHiM/fkfVW a2RgF+DUOvyTsY+hQQp82YKjaBcW4pO6VLyQw8WEhkH1KPnMh94L7qGtsZ7GXVF2AOaM XW+g== X-Gm-Message-State: AOAM5316TQ75iATK0yPztK2tB0uS1ELnDoPcYC/KNUZsmw+2Vy0Qiryp LKPNEmGInmxJeIyc+hT8dONpd61ZEme9FwYEtSyJsUzQqIh5h4+p4JJsRkTYxkIKHal+q+eYY1X kcje2DZLcCp4JBd4Z/tPuX2GItDbsVSr2ZA== X-Received: by 2002:a19:7003:: with SMTP id h3mr28362104lfc.267.1633961102054; Mon, 11 Oct 2021 07:05:02 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzzCqBDSzRcWCKe5cJZCWIDpvKcLGRmtsq+V/6LOGyDWd0/9QSu0lXpZc7oLWPdnhTBcIxo4Q== X-Received: by 2002:a19:7003:: with SMTP id h3mr28362003lfc.267.1633961101097; Mon, 11 Oct 2021 07:05:01 -0700 (PDT) From: Dmitrii Shcherbakov To: dmitrii.shcherbakov@canonical.com, libvir-list@redhat.com Subject: [libvirt PATCH v6 1/5] Add a PCI/PCIe device VPD Parser Date: Mon, 11 Oct 2021 17:04:42 +0300 Message-Id: <20211011140446.220390-2-dmitrii.shcherbakov@canonical.com> In-Reply-To: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> References: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.84 on 10.11.54.1 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 19BE5CvY030337 X-loop: libvir-list@redhat.com Cc: laine@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1633961236703100003 Content-Type: text/plain; charset="utf-8" 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. Signed-off-by: Dmitrii Shcherbakov --- build-aux/syntax-check.mk | 4 +- po/POTFILES.in | 1 + src/libvirt_private.syms | 18 + src/util/meson.build | 1 + src/util/virpcivpd.c | 743 ++++++++++++++++++++++++++++++++++++ src/util/virpcivpd.h | 111 ++++++ src/util/virpcivpdpriv.h | 48 +++ tests/meson.build | 1 + tests/testutils.c | 35 ++ tests/testutils.h | 4 + tests/virpcivpdtest.c | 768 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 1732 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 cb12b64532..2a6e2f86a1 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; } || : =20 sc_prohibit_mixed_case_abbreviations: - @prohibit=3D'Pci|Usb|Scsi' \ + @prohibit=3D'Pci|Usb|Scsi|Vpd' \ in_vc_files=3D'\.[ch]$$' \ - halt=3D'Use PCI, USB, SCSI, not Pci, Usb, Scsi' \ + halt=3D'Use PCI, USB, SCSI, VPD, not Pci, Usb, Scsi, Vpd' \ $(_sc_search_regexp) =20 # Require #include 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@src/util/virnvme.c @SRCDIR@src/util/virobject.c @SRCDIR@src/util/virpci.c +@SRCDIR@src/util/virpcivpd.c @SRCDIR@src/util/virperf.c @SRCDIR@src/util/virpidfile.c @SRCDIR@src/util/virpolkit.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index fd0eea0777..8499281864 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -3576,6 +3576,24 @@ virVHBAManageVport; virVHBAPathExists; =20 =20 +# util/virpcivpd.h + +virPCIVPDParse; +virPCIVPDParseVPDLargeResourceFields; +virPCIVPDParseVPDLargeResourceString; +virPCIVPDReadVPDBytes; +virPCIVPDResourceCustomCompareIndex; +virPCIVPDResourceCustomFree; +virPCIVPDResourceCustomUpsertValue; +virPCIVPDResourceFree; +virPCIVPDResourceGetFieldValueFormat; +virPCIVPDResourceIsValidTextValue; +virPCIVPDResourceROFree; +virPCIVPDResourceRONew; +virPCIVPDResourceRWFree; +virPCIVPDResourceRWNew; +virPCIVPDResourceUpdateKeyword; + # 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 =3D [ '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..5caede815f --- /dev/null +++ b/src/util/virpcivpd.c @@ -0,0 +1,743 @@ +/* + * 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 + * . + */ + +#include + +#ifdef __linux__ +# include +#endif + +#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW + +#include "virthread.h" +#include "virpcivpdpriv.h" +#include "virlog.h" +#include "virerror.h" +#include "virfile.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("util.pcivpd"); + +static bool +virPCIVPDResourceIsUpperOrNumber(const char c) +{ + return g_ascii_isupper(c) || g_ascii_isdigit(c); +} + +static bool +virPCIVPDResourceIsVendorKeyword(const char *keyword) +{ + return g_str_has_prefix(keyword, "V") && virPCIVPDResourceIsUpperOrNum= ber(keyword[1]); +} + +static bool +virPCIVPDResourceIsSystemKeyword(const char *keyword) +{ + /* Special-case the system-specific keywords since they share the "Y" = prefix with "YA". */ + return (g_str_has_prefix(keyword, "Y") && virPCIVPDResourceIsUpperOrNu= mber(keyword[1]) && + STRNEQ(keyword, "YA")); +} + +static char * +virPCIVPDResourceGetKeywordPrefix(const char *keyword) +{ + g_autofree char *key =3D NULL; + + /* Keywords must have a length of 2 bytes. */ + if (strlen(keyword) !=3D 2) { + virReportError(VIR_ERR_INTERNAL_ERROR, _("The keyword length is no= t 2 bytes: %s"), keyword); + return NULL; + } else if (!(virPCIVPDResourceIsUpperOrNumber(keyword[0]) && + virPCIVPDResourceIsUpperOrNumber(keyword[1]))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("The keyword is not comprised only of uppercase A= SCII letters or digits")); + return NULL; + } + /* Special-case the system-specific keywords since they share the "Y" = prefix with "YA". */ + if (virPCIVPDResourceIsSystemKeyword(keyword) || virPCIVPDResourceIsVe= ndorKeyword(keyword)) { + key =3D g_strndup(keyword, 1); + } else { + key =3D g_strndup(keyword, 2); + } + return g_steal_pointer(&key); +} + +static GHashTable *fieldValueFormats; + +static int +virPCIVPDResourceOnceInit(void) +{ + if (!fieldValueFormats) { + /* 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. Leg= acy PICMIG keywords + * are not included. NOTE: string literals are copied as g_hash_ta= ble_insert + * requires pointers to non-const data. */ + fieldValueFormats =3D 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"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_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_VAL= UE_FORMAT_TEXT)); + /* Manufacture ID */ + g_hash_table_insert(fieldValueFormats, g_strdup("MN"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_TEXT)); + /* Add-in Card Part Number */ + g_hash_table_insert(fieldValueFormats, g_strdup("PN"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_TEXT)); + /* Checksum and Reserved */ + g_hash_table_insert(fieldValueFormats, g_strdup("RV"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_RESVD)); + /* Remaining Read/Write Area */ + g_hash_table_insert(fieldValueFormats, g_strdup("RW"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_RDWR)); + /* Serial Number */ + g_hash_table_insert(fieldValueFormats, g_strdup("SN"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_TEXT)); + /* Asset Tag Identifier */ + g_hash_table_insert(fieldValueFormats, g_strdup("YA"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_TEXT)); + /* This is a vendor specific item and the characters are alphanume= ric. The second + * character (x) of the keyword can be 0 through Z so only the fir= st one is stored. */ + g_hash_table_insert(fieldValueFormats, g_strdup("V"), + GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_TEXT)); + /* This is a system specific item and the characters are alphanume= ric. + * 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_VAL= UE_FORMAT_TEXT)); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Field value formats must only be initialized onc= e.")); + return -1; + } + return 0; +} + +VIR_ONCE_GLOBAL_INIT(virPCIVPDResource); + +/** + * 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 char *keyword) +{ + g_autofree char *key =3D NULL; + gpointer keyVal =3D NULL; + virPCIVPDResourceFieldValueFormat format =3D VIR_PCI_VPD_RESOURCE_FIEL= D_VALUE_FORMAT_LAST; + + /* Keywords are expected to be 2 bytes in length which is defined in t= he specs. */ + if (strlen(keyword) !=3D 2) { + return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST; + } + + if (virPCIVPDResourceInitialize() < 0) + return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST; + + /* The system and vendor-specific keywords have a variable part - look= up + * the prefix significant for determining the value format. */ + key =3D virPCIVPDResourceGetKeywordPrefix(keyword); + if (key) { + keyVal =3D g_hash_table_lookup(fieldValueFormats, key); + if (keyVal) { + format =3D GPOINTER_TO_INT(keyVal); + } + } + return format; +} + +#define ACCEPTED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX= YZ0123456789 -_,.:;=3D" + +/** + * virPCIVPDResourceIsValidTextValue: + * @value: A NULL-terminated string to assess. + * + * Returns: a boolean indicating whether this value is a valid string reso= urce + * value or text field value. The expectations are based on the keywords s= pecified + * in relevant sections of PCI(e) specifications + * ("I.3. VPD Definitions" in PCI specs, "6.28.1 VPD Format" PCIe 4.0). + */ +bool +virPCIVPDResourceIsValidTextValue(const char *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 p= rovided 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 ex= cluding less-than, + * greater-than and ampersand). + */ + + if (value =3D=3D NULL) { + return false; + } + /* An empty string is a valid value. */ + if (STREQ(value, "")) { + return true; + } + + if (strspn(value, ACCEPTED_CHARS) !=3D strlen(value)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("The provided value contains invalid characters: = %s"), value); + return false; + } + return true; +} + +void +virPCIVPDResourceFree(virPCIVPDResource *res) +{ + if (!res) { + return; + } + virPCIVPDResourceROFree(res->ro); + virPCIVPDResourceRWFree(res->rw); +} + +virPCIVPDResourceRO * +virPCIVPDResourceRONew(void) +{ + g_autoptr(virPCIVPDResourceRO) ro =3D g_new0(virPCIVPDResourceRO, 1); + ro->vendor_specific =3D g_ptr_array_new_full(0, (GDestroyNotify)virPCI= VPDResourceCustomFree); + return g_steal_pointer(&ro); +} + +void +virPCIVPDResourceROFree(virPCIVPDResourceRO *ro) +{ + if (!ro) { + return; + } + g_free(ro->change_level); + g_free(ro->manufacture_id); + g_free(ro->part_number); + g_free(ro->serial_number); + g_ptr_array_unref(ro->vendor_specific); +} + +virPCIVPDResourceRW * +virPCIVPDResourceRWNew(void) +{ + g_autoptr(virPCIVPDResourceRW) rw =3D g_new0(virPCIVPDResourceRW, 1); + rw->vendor_specific =3D g_ptr_array_new_full(0, (GDestroyNotify)virPCI= VPDResourceCustomFree); + rw->system_specific =3D g_ptr_array_new_full(0, (GDestroyNotify)virPCI= VPDResourceCustomFree); + return g_steal_pointer(&rw); +} + +void +virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw) +{ + if (!rw) { + return; + } + g_free(rw->asset_tag); + g_ptr_array_unref(rw->vendor_specific); + g_ptr_array_unref(rw->system_specific); +} + +void +virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom) +{ + g_free(custom->value); + g_free(custom); +} + +bool +virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDR= esourceCustom *b) +{ + if (a =3D=3D b) { + return true; + } else if (a =3D=3D NULL || b =3D=3D NULL) { + return false; + } else { + return a->idx =3D=3D b->idx; + } + return true; +} + +/** + * virPCIVPDResourceCustomUpsertValue: + * @arr: A GPtrArray with virPCIVPDResourceCustom entries to update. + * @index: An index character for the keyword. + * @value: A pointer to the value to be inserted at a given index. + * + * Returns: true if a value has been updated successfully, false otherwise. + */ +bool +virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char = *const value) +{ + g_autoptr(virPCIVPDResourceCustom) custom =3D NULL; + virPCIVPDResourceCustom *existing =3D NULL; + guint pos =3D 0; + bool found =3D false; + + if (arr =3D=3D NULL || value =3D=3D NULL) { + return false; + } + + custom =3D g_new0(virPCIVPDResourceCustom, 1); + custom->idx =3D index; + custom->value =3D g_strdup(value); + found =3D g_ptr_array_find_with_equal_func(arr, custom, + (GEqualFunc)virPCIVPDResource= CustomCompareIndex, + &pos); + if (found) { + existing =3D g_ptr_array_index(arr, pos); + g_free(existing->value); + existing->value =3D g_steal_pointer(&custom->value); + } else { + g_ptr_array_add(arr, g_steal_pointer(&custom)); + } + return true; +} + +/** + * virPCIVPDResourceUpdateKeyword: + * @res: A non-NULL pointer to a virPCIVPDResource where a keyword will be= updated. + * @readOnly: A bool specifying which section to update (in-memory): read-= only or read-write. + * @keyword: A non-NULL pointer to a name of the keyword that will be upda= ted. + * @value: A pointer to the keyword value or NULL. The value is copied on = successful update. + * + * The caller is responsible for initializing the relevant RO or RW sectio= ns of the resource, + * otherwise, false will be returned. + * + * Keyword names are either 2-byte keywords from the spec or their human-r= eadable alternatives + * used in XML elements. For vendor-specific and system-specific keywords = only V%s and Y%s + * (except "YA" which is an asset tag) formatted values are accepted. + * + * Returns: true if a keyword has been updated successfully, false otherwi= se. + */ +bool +virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly, + const char *const keyword, const char *cons= t value) +{ + if (!res) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot update the resource: a NULL resource poin= ter has been provided.")); + return false; + } else if (!keyword) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot update the resource: a NULL keyword point= er has been provided.")); + return false; + } + + if (readOnly) { + if (!res->ro) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Cannot update the read-only keyword: RO sect= ion not initialized.")); + return false; + } + + if (STREQ("EC", keyword) || STREQ("change_level", keyword)) { + res->ro->change_level =3D g_strdup(value); + return true; + } else if (STREQ("MN", keyword) || STREQ("manufacture_id", keyword= )) { + res->ro->manufacture_id =3D g_strdup(value); + return true; + } else if (STREQ("PN", keyword) || STREQ("part_number", keyword)) { + res->ro->part_number =3D g_strdup(value); + return true; + } else if (STREQ("SN", keyword) || STREQ("serial_number", keyword)= ) { + res->ro->serial_number =3D g_strdup(value); + return true; + } else if (virPCIVPDResourceIsVendorKeyword(keyword)) { + if (!virPCIVPDResourceCustomUpsertValue(res->ro->vendor_specif= ic, keyword[1], value)) { + return false; + } + return true; + } else if (STREQ("FG", keyword) || STREQ("LC", keyword) || STREQ("= PG", keyword)) { + /* Legacy PICMIG keywords are skipped on purpose. */ + return true; + } else if (STREQ("CP", keyword)) { + /* The CP keyword is currently not supported and is skipped. */ + return true; + } + + } else { + if (!res->rw) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _ + ("Cannot update the read-write keyword: read-wr= ite section not initialized.")); + return false; + } + + if (STREQ("YA", keyword) || STREQ("asset_tag", keyword)) { + res->rw->asset_tag =3D g_strdup(value); + return true; + } else if (virPCIVPDResourceIsVendorKeyword(keyword)) { + if (!virPCIVPDResourceCustomUpsertValue(res->rw->vendor_specif= ic, keyword[1], value)) { + return false; + } + return true; + } else if (virPCIVPDResourceIsSystemKeyword(keyword)) { + if (!virPCIVPDResourceCustomUpsertValue(res->rw->system_specif= ic, keyword[1], value)) { + return false; + } + return true; + } + } + /* Unsupported keyword. */ + return false; +} + +#ifdef __linux__ + +/** + * virPCIVPDReadVPDBytes: + * @vpdFileFd: A file descriptor associated with a file containing PCI dev= ice VPD. + * @buf: An allocated buffer to use for storing VPD bytes read. + * @count: The number of bytes to read from the VPD file descriptor. + * @offset: The offset at which bytes need to be read. + * @csum: A pointer to a byte containing the current checksum value. Mutat= ed by this function. + * + * Returns: the number of VPD bytes read from the specified file descripto= r. The csum value is + * also modified as bytes are read. If an error occurs while reading data = from the VPD file + * descriptor, it is reported and -1 is returned to the caller. If EOF is = occurred, 0 is returned + * to the caller. + */ +size_t +virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t off= set, uint8_t *csum) +{ + ssize_t numRead =3D pread(vpdFileFd, buf, count, offset); + + if (numRead =3D=3D -1) { + VIR_DEBUG("Unable to read %zu bytes at offset %ld from fd: %d", co= unt, offset, vpdFileFd); + } else if (numRead) { + /* + * Update the checksum for every byte read. Per the PCI(e) specs + * the checksum is correct if the sum of all bytes in VPD from + * VPD address 0 up to and including the VPD-R RV field's first + * data byte is zero. + */ + while (count--) { + *csum +=3D *buf; + buf++; + } + } + return numRead; +} + +/** + * virPCIVPDParseVPDLargeResourceFields: + * @vpdFileFd: A file descriptor associated with a file containing PCI dev= ice VPD. + * @resPos: A position where the resource data bytes begin in a file descr= iptor. + * @resDataLen: A length of the data portion of a resource. + * @readOnly: A boolean showing whether the resource type is VPD-R or VPD-= W. + * @csum: A pointer to a 1-byte checksum. + * @res: A pointer to virPCIVPDResource. + * + * Returns: a pointer to a VPDResource which needs to be freed by the call= er or + * NULL if getting it failed for some reason. + */ +bool +virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, uint1= 6_t resDataLen, + bool readOnly, uint8_t *csum, virPCIV= PDResource *res) +{ + g_autofree char *fieldKeyword =3D NULL; + g_autofree char *fieldValue =3D NULL; + virPCIVPDResourceFieldValueFormat fieldFormat =3D VIR_PCI_VPD_RESOURCE= _FIELD_VALUE_FORMAT_LAST; + + /* A buffer of up to one resource record field size (plus a zero byte)= is needed. */ + g_autofree uint8_t *buf =3D g_malloc0(PCI_VPD_MAX_FIELD_SIZE + 1); + uint16_t fieldDataLen =3D 0, bytesToRead =3D 0; + uint16_t fieldPos =3D resPos; + + bool hasChecksum =3D false; + bool hasRW =3D false; + + while (fieldPos + 3 < resPos + resDataLen) { + /* Keyword resources consist of keywords (2 ASCII bytes per the sp= ec) and 1-byte length. */ + if (virPCIVPDReadVPDBytes(vpdFileFd, buf, 3, fieldPos, csum) !=3D = 3) { + /* Invalid field encountered which means the resource itself i= s invalid too. Report + * That VPD has invalid format and bail. */ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read a resource field header - VPD= has invalid format")); + return false; + } + fieldDataLen =3D buf[2]; + /* Change the position to the field's data portion skipping the ke= yword and length bytes. */ + fieldPos +=3D 3; + fieldKeyword =3D g_strndup((char *)buf, 2); + fieldFormat =3D virPCIVPDResourceGetFieldValueFormat(fieldKeyword); + + /* Handle special cases first */ + if (!readOnly && fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIELD_VAL= UE_FORMAT_RESVD) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unexpected RV keyword in the read-write sect= ion.")); + return false; + } else if (readOnly && fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIE= LD_VALUE_FORMAT_RDWR) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unexpected RW keyword in the read-only secti= on.")); + return false; + } + + /* Determine how many bytes to read per field value type. */ + switch (fieldFormat) { + case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT: + case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR: + case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY: + bytesToRead =3D fieldDataLen; + break; + case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD: + /* Only need one byte to be read and accounted towards + * the checksum calculation. */ + bytesToRead =3D 1; + break; + case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST: + /* The VPD format could be extended in future versions wit= h new + * keywords - attempt to skip them by reading past them si= nce + * their data length would still be specified. */ + VIR_DEBUG("Could not determine a field value format for ke= yword: %s", fieldKeyword); + bytesToRead =3D fieldDataLen; + break; + default: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Unexpected field value format encountere= d.")); + return false; + } + + if (virPCIVPDReadVPDBytes(vpdFileFd, buf, bytesToRead, fieldPos, c= sum) !=3D bytesToRead) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not parse a resource field data - VPD = has invalid format")); + return false; + } + /* Advance the position to the first byte of the next field. */ + fieldPos +=3D fieldDataLen; + + if (fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEX= T) { + /* Trim whitespace around a retrieved value and set it to be a= field's value. Cases + * where unnecessary whitespace was present around a field val= ue have been encountered + * in the wild. + */ + fieldValue =3D g_strstrip(g_strndup((char *)buf, fieldDataLen)= ); + if (!virPCIVPDResourceIsValidTextValue(fieldValue)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Field value contains invalid characters"= )); + return false; + } + } else if (fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FOR= MAT_RESVD) { + if (*csum) { + /* All bytes up to and including the checksum byte should = add up to 0. */ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Checksum v= alidation has failed")); + return false; + } + hasChecksum =3D true; + g_free(g_steal_pointer(&fieldKeyword)); + g_free(g_steal_pointer(&fieldValue)); + continue; + } else if (fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FOR= MAT_RDWR) { + /* Skip the read-write space since it is used for indication o= nly. */ + hasRW =3D true; + g_free(g_steal_pointer(&fieldKeyword)); + g_free(g_steal_pointer(&fieldValue)); + } else if (fieldFormat =3D=3D VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FOR= MAT_LAST) { + /* Skip unknown fields */ + g_free(g_steal_pointer(&fieldKeyword)); + g_free(g_steal_pointer(&fieldValue)); + continue; + } else { + fieldValue =3D g_malloc(fieldDataLen); + memcpy(fieldValue, buf, fieldDataLen); + } + + if (readOnly) { + if (!res->ro) { + res->ro =3D virPCIVPDResourceRONew(); + } + } else { + if (!res->rw) { + res->rw =3D virPCIVPDResourceRWNew(); + } + } + /* The field format, keyword and value are determined. Attempt to = update the resource. */ + if (!virPCIVPDResourceUpdateKeyword(res, readOnly, fieldKeyword, f= ieldValue)) { + /* Write an error but attempt to handle it gracefully by conti= nuing. + * Unexpected keywords may be a result of a spec extension in = the future. */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Could not update the VPD resource keyword: %= s"), fieldKeyword); + } + } + if (readOnly && !hasChecksum) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("VPD-R does not contain the mandatory RV field")); + return false; + } else if (!readOnly && !hasRW) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("VPD-W does not contain the mandatory RW field")); + return false; + } + + return true; +} + +/** + * virPCIVPDParseVPDLargeResourceString: + * @vpdFileFd: A file descriptor associated with a file containing PCI dev= ice VPD. + * @resPos: A position where the resource data bytes begin in a file descr= iptor. + * @resDataLen: A length of the data portion of a resource. + * @csum: A pointer to a 1-byte checksum. + * + * Returns: a pointer to a VPDResource which needs to be freed by the call= er or + * NULL if getting it failed for some reason. + */ +bool +virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos, + uint16_t resDataLen, uint8_t *csum, v= irPCIVPDResource *res) +{ + g_autofree char *resValue =3D NULL; + + /* The resource value is not NULL-terminated so add one more byte. */ + g_autofree char *buf =3D g_malloc0(resDataLen + 1); + + if (virPCIVPDReadVPDBytes(vpdFileFd, (uint8_t *)buf, resDataLen, resPo= s, csum) !=3D resDataLen) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not read a part of a resource - VPD has in= valid format")); + return false; + } + resValue =3D g_strdup(g_strstrip(buf)); + if (!virPCIVPDResourceIsValidTextValue(resValue)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("The string resource has invalid characters in it= s value")); + return false; + } + res->name =3D g_steal_pointer(&resValue); + return true; +} + +/** + * virPCIVPDParse: + * @vpdFileFd: a file descriptor associated with a file containing PCI dev= ice VPD. + * + * Parse a PCI device's Vital Product Data (VPD) contained in a file descr= iptor. + * + * Returns: a pointer to a GList of VPDResource types which needs to be fr= eed by the caller or + * NULL if getting it failed for some reason. + */ +virPCIVPDResource * +virPCIVPDParse(int vpdFileFd) +{ + /* A checksum which is calculated as a sum of all bytes from VPD byte = 0 up to + * the checksum byte in the RV field's value. The RV field is only pre= sent in the + * VPD-R resource and the checksum byte there is the first byte of the= field's value. + * The checksum byte in RV field is actually a two's complement of the= sum of all bytes + * of VPD that come before it so adding the two together must produce = 0 if data + * was not corrupted and VPD storage is intact. + */ + uint8_t csum =3D 0; + uint8_t headerBuf[2]; + + bool isWellFormed =3D false; + uint16_t resPos =3D 0, resDataLen; + uint8_t tag =3D 0; + bool endResReached =3D false, hasReadOnly =3D false; + + g_autoptr(virPCIVPDResource) res =3D g_new0(virPCIVPDResource, 1); + + while (resPos <=3D PCI_VPD_ADDR_MASK) { + /* Read the resource data type tag. */ + if (virPCIVPDReadVPDBytes(vpdFileFd, &tag, 1, resPos, &csum) !=3D = 1) { + break; + } + /* 0x80 =3D=3D 0b10000000 - the large resource data type flag. */ + if (tag & PCI_VPD_LARGE_RESOURCE_FLAG) { + if (resPos > PCI_VPD_ADDR_MASK + 1 - 3) { + /* Bail if the large resource starts at the position + * where the end tag should be. */ + break; + } + /* Read the two length bytes of the large resource record. */ + if (virPCIVPDReadVPDBytes(vpdFileFd, headerBuf, 2, resPos + 1,= &csum) !=3D 2) { + break; + } + resDataLen =3D headerBuf[0] + (headerBuf[1] << 8); + /* Change the position to the byte following the tag and lengt= h bytes. */ + resPos +=3D 3; + } else { + /* Handle a small resource record. + * 0xxxxyyy & 00000111, where xxxx - resource data type bits, = yyy - length bits. */ + resDataLen =3D tag & 7; + /* 0xxxxyyy >> 3 =3D=3D 0000xxxx */ + tag >>=3D 3; + /* Change the position to the byte past the byte containing ta= g and length bits. */ + resPos +=3D 1; + } + if (tag =3D=3D PCI_VPD_RESOURCE_END_TAG) { + /* Stop VPD traversal since the end tag was encountered. */ + endResReached =3D true; + break; + } + if (resDataLen > PCI_VPD_ADDR_MASK + 1 - resPos) { + /* Bail if the resource is too long to fit into the VPD addres= s space. */ + break; + } + + switch (tag) { + /* Large resource type which is also a string: 0x80 | 0x02= =3D 0x82 */ + case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLA= G: + isWellFormed =3D virPCIVPDParseVPDLargeResourceString(vpdF= ileFd, resPos, resDataLen, + &csum,= res); + break; + /* Large resource type which is also a VPD-R: 0x80 | 0x10 = =3D=3D 0x90 */ + case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RES= OURCE_FLAG: + isWellFormed =3D virPCIVPDParseVPDLargeResourceFields(vpdF= ileFd, resPos, + resDat= aLen, true, &csum, res); + /* Encountered the VPD-R tag. The resource record parsing = also validates + * the presence of the required checksum in the RV field. = */ + hasReadOnly =3D true; + break; + /* Large resource type which is also a VPD-W: 0x80 | 0x11 = =3D=3D 0x91 */ + case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RE= SOURCE_FLAG: + isWellFormed =3D virPCIVPDParseVPDLargeResourceFields(vpdF= ileFd, resPos, resDataLen, + false,= &csum, res); + break; + default: + /* While we cannot parse unknown resource types, they can = still be skipped + * based on the header and data length. */ + VIR_DEBUG("Encountered an unexpected VPD resource tag: %#x= ", tag); + resPos +=3D resDataLen; + continue; + } + + if (!isWellFormed) { + VIR_DEBUG("Encountered an invalid VPD"); + return NULL; + } + + /* Continue processing other resource records. */ + resPos +=3D resDataLen; + } + if (!hasReadOnly) { + VIR_DEBUG("Encountered an invalid VPD: does not have a VPD-R recor= d"); + return NULL; + } else if (!endResReached) { + /* Does not have an end tag. */ + VIR_DEBUG("Encountered an invalid VPD"); + return NULL; + } + return g_steal_pointer(&res); +} + +#endif /* __linux__ */ diff --git a/src/util/virpcivpd.h b/src/util/virpcivpd.h new file mode 100644 index 0000000000..25aee5dffe --- /dev/null +++ b/src/util/virpcivpd.h @@ -0,0 +1,111 @@ +/* + * virpcivpd.h: 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 + * . + */ + +#pragma once + +#include "internal.h" + +/* + * 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 (betwee= n 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 poi= nted 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 w= ithin 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 + +typedef enum { + VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT =3D 1, + VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY, + VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD, + VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR, + VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST +} virPCIVPDResourceFieldValueFormat; + +virPCIVPDResourceFieldValueFormat virPCIVPDResourceGetFieldValueFormat(con= st char *value); + +typedef struct virPCIVPDResourceCustom virPCIVPDResourceCustom; +struct virPCIVPDResourceCustom { + char idx; + char *value; +}; + +typedef struct virPCIVPDResourceRO virPCIVPDResourceRO; +struct virPCIVPDResourceRO { + char *part_number; + char *change_level; + char *manufacture_id; + char *serial_number; + GPtrArray *vendor_specific; +}; + +typedef struct virPCIVPDResourceRW virPCIVPDResourceRW; +struct virPCIVPDResourceRW { + char *asset_tag; + GPtrArray *vendor_specific; + GPtrArray *system_specific; +}; + +typedef struct virPCIVPDResource virPCIVPDResource; +struct virPCIVPDResource { + char *name; + virPCIVPDResourceRO *ro; + virPCIVPDResourceRW *rw; +}; + + +virPCIVPDResource *virPCIVPDParse(int vpdFileFd); +void virPCIVPDResourceFree(virPCIVPDResource *res); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResource, virPCIVPDResourceFree); + +virPCIVPDResourceRO *virPCIVPDResourceRONew(void); +void virPCIVPDResourceROFree(virPCIVPDResourceRO *ro); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceRO, virPCIVPDResourceROFree= ); + +virPCIVPDResourceRW *virPCIVPDResourceRWNew(void); +void virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceRW, virPCIVPDResourceRWFree= ); + +bool +virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly, + const char *const keyword, const char *cons= t value); + +void virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceCustom, virPCIVPDResourceCu= stomFree); diff --git a/src/util/virpcivpdpriv.h b/src/util/virpcivpdpriv.h new file mode 100644 index 0000000000..b0347ecef1 --- /dev/null +++ b/src/util/virpcivpdpriv.h @@ -0,0 +1,48 @@ +/* + * virpcivpdpriv.h: helper APIs for working with the PCI/PCIe VPD capabili= ty + * + * 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 + * . + */ + +#ifndef LIBVIRT_VIRPCIVPDPRIV_H_ALLOW +# error "virpcivpdpriv.h may only be included by virpcivpd.c or test suite= s" +#endif /* LIBVIRT_VIRPCIVPDPRIV_H_ALLOW */ + +#pragma once + +#include "virpcivpd.h" + +bool virPCIVPDResourceIsValidTextValue(const char *value); + +bool +virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDR= esourceCustom *b); + +bool +virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char = *const value); + +#ifdef __linux__ + +size_t +virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t off= set, uint8_t *csum); + +bool virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, = uint16_t resDataLen, + bool readOnly, uint8_t *csum, vi= rPCIVPDResource *res); + +bool virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos, = uint16_t resDataLen, + uint8_t *csum, virPCIVPDResource= *res); + +#endif /* __linux__ */ diff --git a/tests/meson.build b/tests/meson.build index dfbc2c01e2..1948c07ae3 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -336,6 +336,7 @@ tests +=3D [ { 'name': 'virtimetest' }, { 'name': 'virtypedparamtest' }, { 'name': 'viruritest' }, + { 'name': 'virpcivpdtest' }, { 'name': 'vshtabletest', 'link_with': [ libvirt_shell_lib ] }, { 'name': 'virmigtest' }, ] diff --git a/tests/testutils.c b/tests/testutils.c index d071abd6d7..3bc4274e97 100644 --- a/tests/testutils.c +++ b/tests/testutils.c @@ -1143,3 +1143,38 @@ virTestStablePath(const char *path) =20 return g_strdup(path); } + +#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 =3D -1; + char path[] =3D abs_builddir "testutils-memfd-XXXXXX"; + /* A temp file is used since not all supported distributions support m= emfd. */ + if ((fd =3D g_mkstemp_full(path, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR= )) < 0) { + return fd; + } + g_unlink(path); + + if (safewrite(fd, data, len) !=3D 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 diff --git a/tests/testutils.h b/tests/testutils.h index 27d135fc02..13a154a5af 100644 --- a/tests/testutils.h +++ b/tests/testutils.h @@ -173,3 +173,7 @@ int testCompareDomXML2XMLFiles(virCaps *caps, =20 char * virTestStablePath(const char *path); + +#ifdef __linux__ +int virCreateAnonymousFile(const uint8_t *data, size_t len); +#endif diff --git a/tests/virpcivpdtest.c b/tests/virpcivpdtest.c new file mode 100644 index 0000000000..fcfb646b96 --- /dev/null +++ b/tests/virpcivpdtest.c @@ -0,0 +1,768 @@ +/* + * 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 + * . + */ + +#include + +#include "internal.h" +#include "testutils.h" + +#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW + +#include "virpcivpdpriv.h" +#include "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +#ifdef __linux__ + +VIR_LOG_INIT("tests.vpdtest"); + + +typedef struct _TestPCIVPDKeywordValue { + const char *keyword; + const char *value; + char **actual; +} TestPCIVPDKeywordValue; + +static int +testPCIVPDResourceBasic(const void *data G_GNUC_UNUSED) +{ + size_t i =3D 0; + g_autoptr(virPCIVPDResourceRO) ro =3D virPCIVPDResourceRONew(); + g_autoptr(virPCIVPDResourceRW) rw =3D virPCIVPDResourceRWNew(); + const TestPCIVPDKeywordValue readOnlyCases[] =3D { + {.keyword =3D "EC", .value =3D "level1", .actual =3D &ro->change_l= evel}, + {.keyword =3D "EC", .value =3D "level2", .actual =3D &ro->change_l= evel}, + {.keyword =3D "change_level", .value =3D "level3", .actual =3D &ro= ->change_level}, + {.keyword =3D "PN", .value =3D "number1", .actual =3D &ro->part_nu= mber}, + {.keyword =3D "PN", .value =3D "number2", .actual =3D &ro->part_nu= mber}, + {.keyword =3D "part_number", .value =3D "number3", .actual =3D &ro= ->part_number}, + {.keyword =3D "MN", .value =3D "id1", .actual =3D &ro->manufacture= _id}, + {.keyword =3D "MN", .value =3D "id2", .actual =3D &ro->manufacture= _id}, + {.keyword =3D "manufacture_id", .value =3D "id3", &ro->manufacture= _id}, + {.keyword =3D "SN", .value =3D "serial1", .actual =3D &ro->serial_= number}, + {.keyword =3D "SN", .value =3D "serial2", .actual =3D &ro->serial_= number}, + {.keyword =3D "serial_number", .value =3D "serial3", .actual =3D &= ro->serial_number}, + }; + const TestPCIVPDKeywordValue readWriteCases[] =3D { + {.keyword =3D "YA", .value =3D "tag1", .actual =3D &ro->change_lev= el}, + {.keyword =3D "YA", .value =3D "tag2", .actual =3D &ro->change_lev= el}, + {.keyword =3D "asset_tag", .value =3D "tag3", .actual =3D &ro->cha= nge_level}, + }; + size_t numROCases =3D sizeof(readOnlyCases) / sizeof(TestPCIVPDKeyword= Value); + size_t numRWCases =3D sizeof(readWriteCases) / sizeof(TestPCIVPDKeywor= dValue); + g_autoptr(virPCIVPDResource) res =3D g_new0(virPCIVPDResource, 1); + virPCIVPDResourceCustom *custom =3D NULL; + + g_autofree char *val =3D g_strdup("testval"); + res->name =3D g_steal_pointer(&val); + + /* RO has not been initialized - make sure updates fail. */ + for (i =3D 0; i < numROCases; ++i) { + if (virPCIVPDResourceUpdateKeyword(res, true, + readOnlyCases[i].keyword, + readOnlyCases[i].value)) { + return -1; + } + } + /* RW has not been initialized - make sure updates fail. */ + for (i =3D 0; i < numRWCases; ++i) { + if (virPCIVPDResourceUpdateKeyword(res, false, + readWriteCases[i].keyword, + readWriteCases[i].value)) { + return -1; + } + } + /* Initialize RO */ + res->ro =3D g_steal_pointer(&ro); + + /* Update keywords one by one and compare actual values with the expec= ted ones. */ + for (i =3D 0; i < numROCases; ++i) { + if (!virPCIVPDResourceUpdateKeyword(res, true, + readOnlyCases[i].keyword, + readOnlyCases[i].value)) { + return -1; + } + if (STRNEQ(readOnlyCases[i].value, *readOnlyCases[i].actual)) { + return -1; + } + } + + /* Do a basic vendor field check. */ + if (!virPCIVPDResourceUpdateKeyword(res, true, "V0", "vendor0")) { + return -1; + } + if (res->ro->vendor_specific->len !=3D 1) { + return -1; + } + custom =3D g_ptr_array_index(res->ro->vendor_specific, 0); + if (custom->idx !=3D '0' || STRNEQ(custom->value, "vendor0")) { + return -1; + } + + /* Check that RW updates fail if RW has not been initialized. */ + if (virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1")) { + return -1; + } + if (virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag1")) { + return -1; + } + + /* Initialize RW */ + res->rw =3D g_steal_pointer(&rw); + if (!virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1") + || STRNEQ(res->rw->asset_tag, "tag1")) { + return -1; + } + if (!virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag2") + || STRNEQ(res->rw->asset_tag, "tag2")) { + return -1; + } + + /* Do a basic system field check. */ + if (!virPCIVPDResourceUpdateKeyword(res, false, "Y0", "system0")) { + return -1; + } + if (res->rw->system_specific->len !=3D 1) { + return -1; + } + custom =3D g_ptr_array_index(res->rw->system_specific, 0); + if (custom->idx !=3D '0' || STRNEQ(custom->value, "system0")) { + return -1; + } + + /* Just make sure the name has not been changed during keyword updates= . */ + if (!STREQ_NULLABLE(res->name, "testval")) { + return -1; + } + return 0; +} + +static int +testPCIVPDResourceCustomCompareIndex(const void *data G_GNUC_UNUSED) +{ + g_autoptr(virPCIVPDResourceCustom) a =3D NULL, b =3D NULL; + + /* Both are NULL */ + if (!virPCIVPDResourceCustomCompareIndex(a, b)) { + return -1; + } + + /* a is not NULL */ + a =3D g_new0(virPCIVPDResourceCustom, 1); + if (virPCIVPDResourceCustomCompareIndex(a, b)) { + return -1; + } + + /* Reverse */ + if (virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + + /* Same index, different strings */ + b =3D g_new0(virPCIVPDResourceCustom, 1); + a->idx =3D 'z'; + a->value =3D g_strdup("42"); + b->idx =3D 'z'; + b->value =3D g_strdup("24"); + if (!virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + /* Different index, different strings */ + a->idx =3D 'a'; + if (virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + + /* Same index, same strings */ + a->idx =3D 'z'; + a->value =3D g_strdup("42"); + b->idx =3D 'z'; + b->value =3D g_strdup("42"); + if (!virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + /* Different index, same strings */ + a->idx =3D 'a'; + if (virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + /* Different index, same value pointers */ + g_free(b->value); + b->value =3D a->value; + if (virPCIVPDResourceCustomCompareIndex(b, a)) { + return -1; + } + b->value =3D NULL; + + return 0; +} + +static int +testPCIVPDResourceCustomUpsertValue(const void *data G_GNUC_UNUSED) +{ + g_autoptr(GPtrArray) arr =3D g_ptr_array_new_full(0, (GDestroyNotify)v= irPCIVPDResourceCustomFree); + virPCIVPDResourceCustom *custom =3D NULL; + if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval")) { + return -1; + } + if (arr->len !=3D 1) { + return -1; + } + custom =3D g_ptr_array_index(arr, 0); + if (custom =3D=3D NULL || custom->idx !=3D 'A' || STRNEQ_NULLABLE(cust= om->value, "testval")) { + return -1; + } + + /* Idempotency */ + if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval")) { + return -1; + } + if (arr->len !=3D 1) { + return -1; + } + custom =3D g_ptr_array_index(arr, 0); + if (custom =3D=3D NULL || custom->idx !=3D 'A' || STRNEQ_NULLABLE(cust= om->value, "testval")) { + return -1; + } + + /* Existing value updates. */ + if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testvalnew")) { + return -1; + } + if (arr->len !=3D 1) { + return -1; + } + custom =3D g_ptr_array_index(arr, 0); + if (custom =3D=3D NULL || custom->idx !=3D 'A' || STRNEQ_NULLABLE(cust= om->value, "testvalnew")) { + return -1; + } + + /* Inserting multiple values */ + if (!virPCIVPDResourceCustomUpsertValue(arr, '1', "42")) { + return -1; + } + if (arr->len !=3D 2) { + return -1; + } + custom =3D g_ptr_array_index(arr, 1); + if (custom =3D=3D NULL || custom->idx !=3D '1' || STRNEQ_NULLABLE(cust= om->value, "42")) { + return -1; + } + + return 0; +} + + +typedef struct _TestPCIVPDExpectedString { + const char *keyword; + bool expected; +} TestPCIVPDExpectedString; + +/* + * testPCIVPDIsValidTextValue: + * + * Test expected text value validation. Static metadata about possible val= ues is taken + * from the PCI(e) standards and based on some real-world hardware example= s. + * */ +static int +testPCIVPDIsValidTextValue(const void *data G_GNUC_UNUSED) +{ + size_t i =3D 0; + + const TestPCIVPDExpectedString textValueCases[] =3D { + /* Numbers */ + {"42", true}, + /* Alphanumeric */ + {"DCM1001008FC52101008FC53201008FC54301008FC5", true}, + /* Dots */ + {"DSV1028VPDR.VER1.0", true}, + /* Whitespace presence */ + {"NMVIntel Corp", true}, + /* Comma and spaces */ + {"BlueField-2 DPU 25GbE Dual-Port SFP56, Tall Bracket", true}, + /* Equal signs and colons. */ + {"MLX:MN=3DMLNX:CSKU=3DV2:UUID=3DV3:PCI=3DV0:MODL=3DBF2H332A", tru= e}, + /* Dashes */ + {"MBF2H332A-AEEOT", true}, + {"under_score_example", true}, + {"", true}, + {";", true}, + {"\\42", false}, + {"/42", false}, + }; + for (i =3D 0; i < sizeof(textValueCases) / sizeof(textValueCases[0]); = ++i) { + if (virPCIVPDResourceIsValidTextValue(textValueCases[i].keyword) != =3D + textValueCases[i].expected) { + return -1; + } + } + return 0; +} + +/* + * testPCIVPDGetFieldValueFormat: + * + * A simple test to assess the functionality of the + * virPCIVPDResourceGetFieldValueFormat function. + * */ +static int +testPCIVPDGetFieldValueFormat(const void *data G_GNUC_UNUSED) +{ + typedef struct _TestPCIVPDExpectedFieldValueFormat { + const char *keyword; + virPCIVPDResourceFieldValueFormat expected; + } TestPCIVPDExpectedFieldValueFormat; + + size_t i =3D 0; + + const TestPCIVPDExpectedFieldValueFormat valueFormatCases[] =3D { + {"SN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"EC", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"MN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"PN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"RV", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD}, + {"RW", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR}, + {"VA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"YA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"YZ", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT}, + {"CP", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY}, + /* Invalid keywords. */ + {"", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"sn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"ec", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"mn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"pn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"4", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"42", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"Y", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"V", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"v", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"vA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"va", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + {"Ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + /* 2 bytes but not present in the spec. */ + {"EX", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + /* Many numeric bytes. */ + {"4242", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + /* Many letters. */ + {"EXAMPLE", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST}, + }; + for (i =3D 0; i < sizeof(valueFormatCases) / sizeof(valueFormatCases[0= ]); ++i) { + if (virPCIVPDResourceGetFieldValueFormat(valueFormatCases[i].keywo= rd) !=3D + valueFormatCases[i].expected) { + return -1; + } + } + return 0; +} + +# define VPD_STRING_RESOURCE_EXAMPLE_HEADER \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x08, 0x00 + +# define VPD_STRING_RESOURCE_EXAMPLE_DATA \ + 't', 'e', 's', 't', 'n', 'a', 'm', 'e' + +# define VPD_R_FIELDS_EXAMPLE_HEADER \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x16, 0x00 + +# define VPD_R_EXAMPLE_VALID_RV_FIELD \ + 'R', 'V', 0x02, 0x31, 0x00 + +# define VPD_R_EXAMPLE_INVALID_RV_FIELD \ + 'R', 'V', 0x02, 0xFF, 0x00 + +# define VPD_R_EXAMPLE_FIELDS \ + 'P', 'N', 0x02, '4', '2', \ + 'E', 'C', 0x04, '4', '2', '4', '2', \ + 'V', 'A', 0x02, 'E', 'X' + +# define VPD_R_FIELDS_EXAMPLE_DATA \ + VPD_R_EXAMPLE_FIELDS, \ + VPD_R_EXAMPLE_VALID_RV_FIELD + +# define VPD_W_FIELDS_EXAMPLE_HEADER \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, = 0x19, 0x00 + +# define VPD_W_EXAMPLE_FIELDS \ + 'V', 'Z', 0x02, '4', '2', \ + 'Y', 'A', 0x04, 'I', 'D', '4', '2', \ + 'Y', 'F', 0x02, 'E', 'X', \ + 'Y', 'E', 0x00, \ + 'R', 'W', 0x02, 0x00, 0x00 + +static int +testVirPCIVPDReadVPDBytes(const void *opaque G_GNUC_UNUSED) +{ + int fd =3D -1; + g_autofree uint8_t *buf =3D NULL; + uint8_t csum =3D 0; + size_t readBytes =3D 0; + size_t dataLen =3D 0; + + /* An example of a valid VPD record with one VPD-R resource and 2 fiel= ds. */ + uint8_t fullVPDExample[] =3D { + VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DA= TA, + VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, + PCI_VPD_RESOURCE_END_VAL + }; + dataLen =3D sizeof(fullVPDExample) / sizeof(uint8_t) - 2; + buf =3D g_malloc0(dataLen); + + fd =3D virCreateAnonymousFile(fullVPDExample, dataLen); + + readBytes =3D virPCIVPDReadVPDBytes(fd, buf, dataLen, 0, &csum); + + if (readBytes !=3D dataLen) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "The number of bytes read %zu is lower than expecte= d %zu ", + readBytes, dataLen); + return -1; + } + + if (csum) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "The sum of all VPD bytes up to and including the c= hecksum byte" + "is equal to zero: 0x%02x", csum); + return -1; + } + return 0; +} + +static int +testVirPCIVPDParseVPDStringResource(const void *opaque G_GNUC_UNUSED) +{ + int fd =3D -1; + uint8_t csum =3D 0; + size_t dataLen =3D 0; + bool result =3D false; + + g_autoptr(virPCIVPDResource) res =3D g_new0(virPCIVPDResource, 1); + const char *expectedValue =3D "testname"; + + const uint8_t stringResExample[] =3D { + VPD_STRING_RESOURCE_EXAMPLE_DATA + }; + + dataLen =3D sizeof(stringResExample) / sizeof(uint8_t); + fd =3D virCreateAnonymousFile(stringResExample, dataLen); + result =3D virPCIVPDParseVPDLargeResourceString(fd, 0, dataLen, &csum,= res); + VIR_FORCE_CLOSE(fd); + + if (!result) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Could not parse the example resource."); + return -1; + } + + if (STRNEQ(expectedValue, res->name)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Unexpected string resource value: %s, expected: %s= ", + res->name, expectedValue); + return -1; + } + return 0; +} + +static int +testVirPCIVPDValidateExampleReadOnlyFields(virPCIVPDResource *res) +{ + const char *expectedName =3D "testname"; + virPCIVPDResourceCustom *custom =3D NULL; + if (STRNEQ(res->name, expectedName)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "Unexpected string resource value: %s, expected: %s", + res->name, expectedName); + return -1; + } + + if (!res->ro) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Read-only keywords are missing from the VPD resource."); + return -1; + } + + if (STRNEQ_NULLABLE(res->ro->part_number, "42")) { + return -1; + } else if (STRNEQ_NULLABLE(res->ro->change_level, "4242")) { + return -1; + } + if (!res->ro->vendor_specific) { + return -1; + } + + custom =3D g_ptr_array_index(res->ro->vendor_specific, 0); + if (custom->idx !=3D 'A' || STRNEQ_NULLABLE(custom->value, "EX")) { + return -1; + } + return 0; +} + +static int +testVirPCIVPDParseFullVPD(const void *opaque G_GNUC_UNUSED) +{ + int fd =3D -1; + size_t dataLen =3D 0; + int ret =3D 0; + + virPCIVPDResource *res =3D NULL; + virPCIVPDResourceCustom *custom =3D NULL; + + const uint8_t fullVPDExample[] =3D { + VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DA= TA, + VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA, + VPD_W_FIELDS_EXAMPLE_HEADER, VPD_W_EXAMPLE_FIELDS, + PCI_VPD_RESOURCE_END_VAL + }; + + dataLen =3D sizeof(fullVPDExample) / sizeof(uint8_t); + fd =3D virCreateAnonymousFile(fullVPDExample, dataLen); + res =3D virPCIVPDParse(fd); + VIR_FORCE_CLOSE(fd); + + if (!res) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "The resource pointer is NULL after parsing which i= s unexpected"); + return ret; + } + + if (!res->ro) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Read-only keywords are missing from the VPD resource."); + return -1; + } else if (!res->rw) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "Read-write keywords are missing from the VPD resource."); + return -1; + } + + if (testVirPCIVPDValidateExampleReadOnlyFields(res)) { + return -1; + } + + if (STRNEQ_NULLABLE(res->rw->asset_tag, "ID42")) { + return -1; + } + + if (!res->rw->vendor_specific) { + return -1; + } + custom =3D g_ptr_array_index(res->rw->vendor_specific, 0); + if (custom->idx !=3D 'Z' || STRNEQ_NULLABLE(custom->value, "42")) { + return -1; + } + + if (!res->rw->system_specific) { + return -1; + } + + custom =3D g_ptr_array_index(res->rw->system_specific, 0); + if (custom->idx !=3D 'F' || STRNEQ_NULLABLE(custom->value, "EX")) { + return -1; + } + + custom =3D g_ptr_array_index(res->rw->system_specific, 1); + if (custom->idx !=3D 'E' || STRNEQ_NULLABLE(custom->value, "")) { + return -1; + } + return ret; +} + +static int +testVirPCIVPDParseFullVPDSkipInvalidKeywords(const void *opaque G_GNUC_UNU= SED) +{ + int fd =3D -1; + size_t dataLen =3D 0; + + virPCIVPDResource *res =3D NULL; + + const uint8_t fullVPDExample[] =3D { + VPD_STRING_RESOURCE_EXAMPLE_HEADER, + VPD_STRING_RESOURCE_EXAMPLE_DATA, + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLA= G, 0x25, 0x00, + VPD_R_EXAMPLE_FIELDS, + /* The keywords below (except for "RV") are invalid but will be sk= ipped by the parser */ + 0x07, 'A', 0x02, 0x00, 0x00, + 'V', 0x07, 0x02, 0x00, 0x00, + 'e', 'x', 0x02, 0x00, 0x00, + 'R', 'V', 0x02, 0x9A, 0x00, + PCI_VPD_RESOURCE_END_VAL + }; + + dataLen =3D sizeof(fullVPDExample) / sizeof(uint8_t); + fd =3D virCreateAnonymousFile(fullVPDExample, dataLen); + res =3D virPCIVPDParse(fd); + VIR_FORCE_CLOSE(fd); + + if (!res) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "The resource pointer is NULL after parsing which i= s unexpected."); + return -1; + } + if (!res->ro) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + "The RO portion of the VPD resource is NULL."); + return -1; + } + + if (testVirPCIVPDValidateExampleReadOnlyFields(res)) { + return -1; + } + return 0; +} + +static int +testVirPCIVPDParseFullVPDInvalid(const void *opaque G_GNUC_UNUSED) +{ + int fd =3D -1; + size_t dataLen =3D 0; + +# define VPD_INVALID_ZERO_BYTE \ + 0x00 + +# define VPD_INVALID_STRING_HEADER_DATA_LONG \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x04, 0x00= , \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x05, 0x00, \ + 'R', 'V', 0x02, 0xDA, 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_INVALID_STRING_HEADER_DATA_SHORT \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x0A, 0x00= , \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x05, 0x00, \ + 'R', 'V', 0x02, 0xD4, 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_NO_VPD_R \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_R_NO_RV \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + VPD_R_FIELDS_EXAMPLE_HEADER, \ + VPD_R_EXAMPLE_FIELDS, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_R_INVALID_RV \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + VPD_R_FIELDS_EXAMPLE_HEADER, \ + VPD_R_EXAMPLE_FIELDS, \ + VPD_R_EXAMPLE_INVALID_RV_FIELD, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_R_INVALID_RV_ZERO_LENGTH \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x14, 0x00, \ + VPD_R_EXAMPLE_FIELDS, \ + 'R', 'V', 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +/* The RW key is not expected in a VPD-R record. */ +# define VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x1B, 0x00, \ + VPD_R_EXAMPLE_FIELDS, \ + 'R', 'W', 0x02, 0x00, 0x00, \ + 'R', 'V', 0x02, 0x81, 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_R_INVALID_FIELD_VALUE \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + VPD_STRING_RESOURCE_EXAMPLE_DATA, \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x0A, 0x00, \ + 'S', 'N', 0x02, 0x04, 0x02, \ + 'R', 'V', 0x02, 0x28, 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +# define VPD_INVALID_STRING_RESOURCE_VALUE \ + VPD_STRING_RESOURCE_EXAMPLE_HEADER, \ + 't', 0x03, 's', 't', 'n', 'a', 'm', 'e', \ + PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0= x0A, 0x00, \ + 'S', 'N', 0x02, 0x04, 0x02, \ + 'R', 'V', 0x02, 0x8A, 0x00, \ + PCI_VPD_RESOURCE_END_VAL + +# define TEST_INVALID_VPD(invalidVPD) \ + do { \ + const uint8_t testCase[] =3D { invalidVPD }; \ + dataLen =3D sizeof(testCase) / sizeof(uint8_t); \ + fd =3D virCreateAnonymousFile(testCase, dataLen); \ + if (virPCIVPDParse(fd)) { \ + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", \ + "Successfully parsed an invalid VPD - this is not expe= cted"); \ + return -1; \ + } \ + VIR_FORCE_CLOSE(fd); \ + } while (0); + + TEST_INVALID_VPD(VPD_INVALID_ZERO_BYTE); + TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_SHORT); + TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_LONG); + TEST_INVALID_VPD(VPD_NO_VPD_R); + TEST_INVALID_VPD(VPD_R_NO_RV); + TEST_INVALID_VPD(VPD_R_INVALID_RV); + TEST_INVALID_VPD(VPD_R_INVALID_RV_ZERO_LENGTH); + TEST_INVALID_VPD(VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY); + TEST_INVALID_VPD(VPD_R_INVALID_FIELD_VALUE); + TEST_INVALID_VPD(VPD_INVALID_STRING_RESOURCE_VALUE); + + return 0; +} + +static int +mymain(void) +{ + int ret =3D 0; + + if (virTestRun("Basic functionality of virPCIVPDResource ", testPCIVPD= ResourceBasic, NULL) < 0) + ret =3D -1; + if (virTestRun("Custom field index comparison", + testPCIVPDResourceCustomCompareIndex, NULL) < 0) + ret =3D -1; + if (virTestRun("Custom field value insertion and updates ", + testPCIVPDResourceCustomUpsertValue, NULL) < 0) + ret =3D -1; + if (virTestRun("Valid text values ", testPCIVPDIsValidTextValue, NULL)= < 0) + ret =3D -1; + if (virTestRun("Determining a field value format by a key ", + testPCIVPDGetFieldValueFormat, NULL) < 0) + ret =3D -1; + if (virTestRun("Reading VPD bytes ", testVirPCIVPDReadVPDBytes, NULL) = < 0) + ret =3D -1; + if (virTestRun("Parsing VPD string resources ", testVirPCIVPDParseVPDS= tringResource, NULL) < 0) + ret =3D -1; + if (virTestRun("Parsing a VPD resource with an invalid keyword ", + testVirPCIVPDParseFullVPDSkipInvalidKeywords, NULL) < 0) + ret =3D -1; + if (virTestRun("Parsing VPD resources from a full VPD ", testVirPCIVPD= ParseFullVPD, NULL) < 0) + ret =3D -1; + if (virTestRun("Parsing invalid VPD records ", testVirPCIVPDParseFullV= PDInvalid, NULL) < 0) + ret =3D -1; + + return ret =3D=3D 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +VIR_TEST_MAIN(mymain) +#endif --=20 2.30.2 From nobody Sun Apr 28 21:58:50 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 216.205.24.124 as permitted sender) client-ip=216.205.24.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 216.205.24.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by mx.zohomail.com with SMTPS id 1633961200299797.2306545933054; Mon, 11 Oct 2021 07:06:40 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-555-3DwWWs8XMriiEbVybPQguA-1; Mon, 11 Oct 2021 10:06:35 -0400 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 7F6D4824FA6; Mon, 11 Oct 2021 14:06:30 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 57E033AEB; Mon, 11 Oct 2021 14:06:30 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 151304EA29; Mon, 11 Oct 2021 14:06:30 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.rdu2.redhat.com [10.11.54.5]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 19BE58kp030305 for ; Mon, 11 Oct 2021 10:05:08 -0400 Received: by smtp.corp.redhat.com (Postfix) id 6A55C7C58; Mon, 11 Oct 2021 14:05:08 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast04.extmail.prod.ext.rdu2.redhat.com [10.11.55.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 6528E7C4F for ; Mon, 11 Oct 2021 14:05:05 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [207.211.31.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 723CC10AF954 for ; Mon, 11 Oct 2021 14:05:05 +0000 (UTC) Received: from smtp-relay-internal-0.canonical.com (smtp-relay-internal-0.canonical.com [185.125.188.122]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-587-xLwHmutLNIyIZmBt0vZlew-1; Mon, 11 Oct 2021 10:05:03 -0400 Received: from mail-lf1-f69.google.com (mail-lf1-f69.google.com [209.85.167.69]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-0.canonical.com (Postfix) with ESMTPS id BF9483F31E for ; Mon, 11 Oct 2021 14:05:02 +0000 (UTC) Received: by mail-lf1-f69.google.com with SMTP id s8-20020ac25c48000000b003faf62e104eso12809131lfp.22 for ; Mon, 11 Oct 2021 07:05:02 -0700 (PDT) Received: from ws.lan.d-node.is ([95.165.29.203]) by smtp.gmail.com with ESMTPSA id o12sm741144lft.254.2021.10.11.07.05.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 07:05:01 -0700 (PDT) X-MC-Unique: 3DwWWs8XMriiEbVybPQguA-1 X-MC-Unique: xLwHmutLNIyIZmBt0vZlew-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Jjz2pTd+CX8ev1ebwx28oIGTdTpejNgnyHrGs2xnbV4=; b=pwkOJhC9qqLPgljDxjGyKY6QDe1e8H6M+Pugh3LnlMhsXruv8GJDBw/lNiBygBhAil tvDpYCoLrwX4JGfi/jv3pg0b9SYPENxlZ20ltzew161a6x7/s9DvFfOl3PBNsgF5KQUV vZ4AAXi+OTQ58axpfAYwc1d0AMg4qqh3H4w1MuD6jdG7kHA7I/C5qS1byAiHDSlBANie rsxRO5l3JnTV7MmMlbBc5d/BouG2ICjfX6fRiM2KpfA9twKON4nX1oHA9FAu7SLkWsWB D5i1jg9P1T+N/vFxjOtUQaFBhxHjk/01DqZ7qy1e1kCiRvxvF1WF9ASPu73nUaZGQa6m Zuiw== X-Gm-Message-State: AOAM5312NcEDe7ONV41xLq+X7lKKljfDrlOeuSnOcfd86G3cAUI5MMUR seIJ5a1eNboKbNEHW8O4LFqxxwyWH7LAHbjij76IrY7ZsSTzI2E6ulp3a+jnGwUYctUMcFH/Z8/ dsy6pjtUkEgFvhU9U8xEvw4Y2Rz0bzO9SOw== X-Received: by 2002:ac2:544d:: with SMTP id d13mr29771452lfn.594.1633961101836; Mon, 11 Oct 2021 07:05:01 -0700 (PDT) X-Google-Smtp-Source: ABdhPJy9xOwiFi6+3tRZTwycQhe5rLk65geKyCG29wv3Bnt/AgQDVKpRL2AbsmMrggOshyrGEGCe+A== X-Received: by 2002:ac2:544d:: with SMTP id d13mr29771424lfn.594.1633961101602; Mon, 11 Oct 2021 07:05:01 -0700 (PDT) From: Dmitrii Shcherbakov To: dmitrii.shcherbakov@canonical.com, libvir-list@redhat.com Subject: [libvirt PATCH v6 2/5] Add PCI VPD-related helper functions to virpci Date: Mon, 11 Oct 2021 17:04:43 +0300 Message-Id: <20211011140446.220390-3-dmitrii.shcherbakov@canonical.com> In-Reply-To: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> References: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.79 on 10.11.54.5 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 19BE58kp030305 X-loop: libvir-list@redhat.com Cc: laine@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1633961258272100001 Content-Type: text/plain; charset="utf-8" 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 --- src/libvirt_private.syms | 2 ++ src/util/virpci.c | 70 ++++++++++++++++++++++++++++++++++++++++ src/util/virpci.h | 4 +++ tests/virpcimock.c | 30 +++++++++++++++++ tests/virpcitest.c | 39 ++++++++++++++++++++++ 5 files changed, 145 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 8499281864..7d825e6f11 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2993,7 +2993,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" =20 VIR_LOG_INIT("util.pci"); =20 @@ -2640,6 +2641,61 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_de= vice_path, return 0; } =20 + +bool +virPCIDeviceHasVPD(virPCIDevice *dev) +{ + g_autofree char *vpdPath =3D NULL; + + vpdPath =3D 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 =3D NULL; + int fd; + g_autoptr(virPCIVPDResource) res =3D NULL; + + vpdPath =3D 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 =3D open(vpdPath, O_RDONLY)) < 0) { + virReportSystemError(-fd, _("Failed to open a VPD file '%s'"), vpd= Path); + return NULL; + } + res =3D 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 =3D N_("not supported on non-linux platform= s"); =20 @@ -2713,6 +2769,20 @@ virPCIGetVirtualFunctionInfo(const char *vf_sysfs_de= vice_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__ */ =20 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" =20 typedef struct _virPCIDevice virPCIDevice; typedef struct _virPCIDeviceAddress virPCIDeviceAddress; @@ -269,6 +270,9 @@ int virPCIGetVirtualFunctionInfo(const char *vf_sysfs_d= evice_path, char **pfname, int *vf_index); =20 +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..e10ebce76f 100644 --- a/tests/virpcimock.c +++ b/tests/virpcimock.c @@ -18,6 +18,8 @@ =20 #include =20 +#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" =20 +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; }; =20 struct fdCallback { @@ -537,6 +547,10 @@ pci_device_new_from_stub(const struct pciDevice *data) make_symlink(devpath, "physfn", tmp); } =20 + 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); =20 @@ -942,6 +956,20 @@ static void init_env(void) { g_autofree char *tmp =3D NULL; + const char fullVPDExampleData[] =3D { + 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_FLA= G, 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 =3D { + .data =3D fullVPDExampleData, + .vpd_len =3D sizeof(fullVPDExampleData) / sizeof(fullVPDExampleDat= a[0]), + }; =20 if (!(fakerootdir =3D getenv("LIBVIRT_FAKE_ROOT_DIR"))) ABORT("Missing LIBVIRT_FAKE_ROOT_DIR env variable\n"); @@ -1008,6 +1036,8 @@ init_env(void) =20 MAKE_PCI_DEVICE("0000:01:00.0", 0x1cc1, 0x8201, 14, .klass =3D 0x01080= 2); MAKE_PCI_DEVICE("0000:02:00.0", 0x1cc1, 0x8201, 15, .klass =3D 0x01080= 2); + + MAKE_PCI_DEVICE("0000:03:00.0", 0x15b3, 0xa2d6, 16, .vpd =3D exampleVP= D); } =20 =20 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 @@ */ =20 #include +#include "internal.h" =20 #include "testutils.h" =20 @@ -26,6 +27,7 @@ # include # include # include +# include =20 # define VIR_FROM_THIS VIR_FROM_NONE =20 @@ -328,6 +330,41 @@ testVirPCIDeviceUnbind(const void *opaque) return ret; } =20 + +static int +testVirPCIDeviceGetVPD(const void *opaque) +{ + const struct testPCIDevData *data =3D opaque; + g_autofree virPCIDevice *dev =3D NULL; + virPCIDeviceAddress devAddr =3D {.domain =3D data->domain, .bus =3D da= ta->bus, + .slot =3D data->slot, .function =3D dat= a->function}; + g_autoptr(virPCIVPDResource) res =3D NULL; + + dev =3D virPCIDeviceNew(&devAddr); + if (!dev) { + return -1; + } + + res =3D virPCIDeviceGetVPD(dev); + + /* Only basic checks - full parser validation is done elsewhere. */ + if (res->ro =3D=3D 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" =20 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); =20 + DO_TEST_PCI(testVirPCIDeviceGetVPD, 0, 0x03, 0, 0); + if (getenv("LIBVIRT_SKIP_CLEANUP") =3D=3D NULL) virFileDeleteTree(fakerootdir); =20 --=20 2.30.2 From nobody Sun Apr 28 21:58:50 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1633961194835486.9194277327148; Mon, 11 Oct 2021 07:06:34 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-160-ENrqbgzbOXyoCy_snHC6Cg-1; Mon, 11 Oct 2021 10:06:31 -0400 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 752FA344A1; Mon, 11 Oct 2021 14:06:26 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 5307C1B4B8; Mon, 11 Oct 2021 14:06:26 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 018EF1801241; Mon, 11 Oct 2021 14:06:26 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.rdu2.redhat.com [10.11.54.2]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 19BE566G030295 for ; Mon, 11 Oct 2021 10:05:07 -0400 Received: by smtp.corp.redhat.com (Postfix) id BF7B24047272; Mon, 11 Oct 2021 14:05:06 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast05.extmail.prod.ext.rdu2.redhat.com [10.11.55.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id BB465404727C for ; Mon, 11 Oct 2021 14:05:06 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-2.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 6C136802A6A for ; Mon, 11 Oct 2021 14:05:06 +0000 (UTC) Received: from smtp-relay-internal-0.canonical.com (smtp-relay-internal-0.canonical.com [185.125.188.122]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-415-me4PVhlaMcWr8en2kxBLEQ-1; Mon, 11 Oct 2021 10:05:04 -0400 Received: from mail-lf1-f70.google.com (mail-lf1-f70.google.com [209.85.167.70]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-0.canonical.com (Postfix) with ESMTPS id 2193E40000 for ; Mon, 11 Oct 2021 14:05:03 +0000 (UTC) Received: by mail-lf1-f70.google.com with SMTP id k18-20020a05651210d200b003fd86616d39so3184595lfg.2 for ; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) Received: from ws.lan.d-node.is ([95.165.29.203]) by smtp.gmail.com with ESMTPSA id o12sm741144lft.254.2021.10.11.07.05.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 07:05:01 -0700 (PDT) X-MC-Unique: ENrqbgzbOXyoCy_snHC6Cg-1 X-MC-Unique: me4PVhlaMcWr8en2kxBLEQ-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=mntqhZ9wH1XAwgMDsjQiY0+yU2Zji07BxlRRPUmViGY=; b=j1hV6yvhQoAyDwcDtBbNGIqECFKb3pVJUB/kPdv8FZIAu1HgeACUQed7YpsYZj4lVK BfwQ6uiZuXlN55t6Jwj+F++g5YUSxxDaNiO0lD00Mb//bxTVTmKkL+FtuOOWjSRHL/YM nNC2K+qpC3kMtRdMvdOl8q0lNFKLVN8Ir2JDqKaa8K6GcCoOtZEmzuk7jUWGasK/A3qo s+xZaScIC5xny38A+etGkwAix9jZNDtvhpc8p5x1o0GhFW0P9+lZjq+v47V+/jjAm1g1 n0/luP6mcDkN0N2dtyuPHeG76CijmPwRwiA0cLHcHZa0jTCLH7pG3iLcAwJ1z/Gr35yo 8prw== X-Gm-Message-State: AOAM533xUG3hDkla81KF5IMuM/MllGuIs2OJMnM+abtIyoJk6O6ZGtCP kgLCHgK8wsTwO51PovfLtTytxnySI9iNv3UtZ468UBWZpGdTxYwAzA4DQbuIkpzxTXgCioTnZDg ClPUVamLDD3ssCO6J1qpr9Bxq424EyG32SA== X-Received: by 2002:a05:651c:1025:: with SMTP id w5mr23705813ljm.282.1633961102379; Mon, 11 Oct 2021 07:05:02 -0700 (PDT) X-Google-Smtp-Source: ABdhPJwL8bUf7fsRla10SbiGqzWe49jZul7izzSWi8IEphgooDg/inVExsLH0bkfhJE7bAw96G0+Tg== X-Received: by 2002:a05:651c:1025:: with SMTP id w5mr23705767ljm.282.1633961102076; Mon, 11 Oct 2021 07:05:02 -0700 (PDT) From: Dmitrii Shcherbakov To: dmitrii.shcherbakov@canonical.com, libvir-list@redhat.com Subject: [libvirt PATCH v6 3/5] Add PCI VPD Capability Support Date: Mon, 11 Oct 2021 17:04:44 +0300 Message-Id: <20211011140446.220390-4-dmitrii.shcherbakov@canonical.com> In-Reply-To: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> References: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.84 on 10.11.54.2 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 19BE566G030295 X-loop: libvir-list@redhat.com Cc: laine@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1633961254026100001 Content-Type: text/plain; charset="utf-8" * XML serialization and deserialization of PCI VPD; * PCI VPD capability flags added and used in relevant places; * XML to XML tests for the added capability. Signed-off-by: Dmitrii Shcherbakov --- docs/schemas/nodedev.rng | 96 ++++++ include/libvirt/libvirt-nodedev.h | 1 + src/conf/node_device_conf.c | 293 ++++++++++++++++++ src/conf/node_device_conf.h | 7 +- src/conf/virnodedeviceobj.c | 7 +- src/node_device/node_device_driver.c | 2 + src/node_device/node_device_udev.c | 2 + .../pci_0000_42_00_0_vpd.xml | 42 +++ .../pci_0000_42_00_0_vpd.xml | 1 + tests/nodedevxml2xmltest.c | 1 + tools/virsh-nodedev.c | 3 + 11 files changed, 453 insertions(+), 2 deletions(-) create mode 100644 tests/nodedevschemadata/pci_0000_42_00_0_vpd.xml create mode 120000 tests/nodedevxml2xmlout/pci_0000_42_00_0_vpd.xml diff --git a/docs/schemas/nodedev.rng b/docs/schemas/nodedev.rng index e089e66858..e4733f0804 100644 --- a/docs/schemas/nodedev.rng +++ b/docs/schemas/nodedev.rng @@ -223,6 +223,10 @@ =20 + + + + @@ -770,6 +774,80 @@ =20 + + + + vpd + + + + + + + + readonly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + readwrite + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -782,4 +860,22 @@ =20 + + + [0-9a-zA-F -_,.:;=3D]{0,255} + + + + + + [0-9A-Z]{1} + + + + + + [0-9B-Z]{1} + + + diff --git a/include/libvirt/libvirt-nodedev.h b/include/libvirt/libvirt-no= dedev.h index e492634217..245365b07f 100644 --- a/include/libvirt/libvirt-nodedev.h +++ b/include/libvirt/libvirt-nodedev.h @@ -84,6 +84,7 @@ typedef enum { VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_CARD =3D 1 << 18, /* s390 A= P Card device */ VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_QUEUE =3D 1 << 19, /* s390 A= P Queue */ VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX =3D 1 << 20, /* s390 A= P Matrix */ + VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD =3D 1 << 21, /* Device= with VPD */ =20 /* filter the devices by active state */ VIR_CONNECT_LIST_NODE_DEVICES_INACTIVE =3D 1 << 30, /* Inacti= ve devices */ diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c index 9bbff97ffd..8ff756b666 100644 --- a/src/conf/node_device_conf.c +++ b/src/conf/node_device_conf.c @@ -36,6 +36,7 @@ #include "virrandom.h" #include "virlog.h" #include "virfcp.h" +#include "virpcivpd.h" =20 #define VIR_FROM_THIS VIR_FROM_NODEDEV =20 @@ -70,6 +71,7 @@ VIR_ENUM_IMPL(virNodeDevCap, "ap_card", "ap_queue", "ap_matrix", + "vpd", ); =20 VIR_ENUM_IMPL(virNodeDevNetCap, @@ -240,6 +242,80 @@ virNodeDeviceCapMdevTypesFormat(virBuffer *buf, } } =20 +static void +virNodeDeviceCapVPDFormatCustomVendorField(virPCIVPDResourceCustom *field,= virBuffer *buf) +{ + if (field =3D=3D NULL || field->value =3D=3D NULL) { + return; + } + virBufferAsprintf(buf, "%s\n= ", field->idx, + field->value); +} + +static void +virNodeDeviceCapVPDFormatCustomSystemField(virPCIVPDResourceCustom *field,= virBuffer *buf) +{ + if (field =3D=3D NULL || field->value =3D=3D NULL) { + return; + } + virBufferAsprintf(buf, "%s\n= ", field->idx, + field->value); +} + +static inline void +virNodeDeviceCapVPDFormatRegularField(virBuffer *buf, const char *keyword,= const char *value) +{ + if (keyword =3D=3D NULL || value =3D=3D NULL) { + return; + } + virBufferAsprintf(buf, "<%s>%s\n", keyword, value, keyword); +} + +static void +virNodeDeviceCapVPDFormat(virBuffer *buf, virPCIVPDResource *res) +{ + if (res =3D=3D NULL) { + return; + } + + virBufferAddLit(buf, "\n"); + virBufferAdjustIndent(buf, 2); + if (res->name !=3D NULL) { + virBufferEscapeString(buf, "%s\n", res->name); + } + + if (res->ro !=3D NULL) { + virBufferEscapeString(buf, "\n", "readonly"); + + virBufferAdjustIndent(buf, 2); + virNodeDeviceCapVPDFormatRegularField(buf, "change_level", res->ro= ->change_level); + virNodeDeviceCapVPDFormatRegularField(buf, "manufacture_id", res->= ro->manufacture_id); + virNodeDeviceCapVPDFormatRegularField(buf, "part_number", res->ro-= >part_number); + virNodeDeviceCapVPDFormatRegularField(buf, "serial_number", res->r= o->serial_number); + g_ptr_array_foreach(res->ro->vendor_specific, + (GFunc)virNodeDeviceCapVPDFormatCustomVendorFi= eld, buf); + virBufferAdjustIndent(buf, -2); + + virBufferAddLit(buf, "\n"); + } + + if (res->rw !=3D NULL) { + virBufferEscapeString(buf, "\n", "readwrite"= ); + + virBufferAdjustIndent(buf, 2); + virNodeDeviceCapVPDFormatRegularField(buf, "asset_tag", res->rw->a= sset_tag); + g_ptr_array_foreach(res->rw->vendor_specific, + (GFunc)virNodeDeviceCapVPDFormatCustomVendorFi= eld, buf); + g_ptr_array_foreach(res->rw->system_specific, + (GFunc)virNodeDeviceCapVPDFormatCustomSystemFi= eld, buf); + virBufferAdjustIndent(buf, -2); + + virBufferAddLit(buf, "\n"); + } + + virBufferAdjustIndent(buf, -2); + virBufferAddLit(buf, "\n"); +} =20 static void virNodeDeviceCapPCIDefFormat(virBuffer *buf, @@ -315,6 +391,9 @@ virNodeDeviceCapPCIDefFormat(virBuffer *buf, data->pci_dev.mdev_types, data->pci_dev.nmdev_types); } + if (data->pci_dev.flags & VIR_NODE_DEV_CAP_FLAG_PCI_VPD) { + virNodeDeviceCapVPDFormat(buf, data->pci_dev.vpd); + } if (data->pci_dev.nIommuGroupDevices) { virBufferAsprintf(buf, "\n", data->pci_dev.iommuGroupNumber); @@ -673,6 +752,7 @@ virNodeDeviceDefFormat(const virNodeDeviceDef *def) case VIR_NODE_DEV_CAP_MDEV_TYPES: case VIR_NODE_DEV_CAP_FC_HOST: case VIR_NODE_DEV_CAP_VPORTS: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: break; } @@ -859,6 +939,165 @@ virNodeDevCapMdevTypesParseXML(xmlXPathContextPtr ctx= t, return ret; } =20 +static int +virNodeDeviceCapVPDParseCustomFields(xmlXPathContextPtr ctxt, virPCIVPDRes= ource *res, bool readOnly) +{ + int nfields =3D -1; + g_autofree char *index =3D NULL; + g_autofree char* value =3D NULL; + g_autofree xmlNodePtr *nodes =3D NULL; + xmlNodePtr orignode =3D NULL; + size_t i =3D 0; + + orignode =3D ctxt->node; + if ((nfields =3D virXPathNodeSet("./vendor_field[@index]", ctxt, &node= s)) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("failed to evaluate elements")); + ctxt->node =3D orignode; + return -1; + } + for (i =3D 0; i < nfields; i++) { + ctxt->node =3D nodes[i]; + if (!(index =3D virXPathStringLimit("string(./@index[1])", 2, ctxt= ))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _(" evaluation has failed")); + continue; + } + if (!(value =3D virXPathString("string(./text())", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _(" value evaluation has failed")); + continue; + } + virPCIVPDResourceUpdateKeyword(res, readOnly, g_strdup_printf("V%c= ", index[0]), value); + } + ctxt->node =3D orignode; + + if (!readOnly) { + if ((nfields =3D virXPathNodeSet("./system_field[@index]", ctxt, &= nodes)) < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("failed to evaluate elements")); + ctxt->node =3D orignode; + return -1; + } + for (i =3D 0; i < nfields; i++) { + ctxt->node =3D nodes[i]; + if (!(index =3D virXPathStringLimit("string(./@index[1])", 2, = ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _(" evaluation has failed")); + continue; + } + if (!(value =3D virXPathString("string(./text())", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _(" value evaluation has failed")); + continue; + } + virPCIVPDResourceUpdateKeyword(res, readOnly, g_strdup_printf(= "Y%c", index[0]), value); + } + ctxt->node =3D orignode; + } + + return 0; +} + +static int +virNodeDeviceCapVPDParseReadOnlyFields(xmlXPathContextPtr ctxt, virPCIVPDR= esource *res) +{ + const char *keywords[] =3D {"change_level", "manufacture_id", + "serial_number", "part_number", NULL}; + g_autofree char *expression =3D NULL; + size_t i =3D 0; + + if (res =3D=3D NULL) { + return -1; + } + res->ro =3D virPCIVPDResourceRONew(); + + while (keywords[i]) { + expression =3D g_strdup_printf("string(./%s)", keywords[i]); + virPCIVPDResourceUpdateKeyword(res, true, keywords[i], virXPathStr= ing(expression, ctxt)); + g_free(g_steal_pointer(&expression)); + ++i; + } + if (virNodeDeviceCapVPDParseCustomFields(ctxt, res, true) < 0) { + return -1; + } + return 0; +} + +static int +virNodeDeviceCapVPDParseReadWriteFields(xmlXPathContextPtr ctxt, virPCIVPD= Resource *res) +{ + res->rw =3D virPCIVPDResourceRWNew(); + virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", virXPathString= ("string(./asset_tag)", ctxt)); + if (virNodeDeviceCapVPDParseCustomFields(ctxt, res, false) < 0) { + return -1; + } + return 0; +} + +static int +virNodeDeviceCapVPDParseXML(xmlXPathContextPtr ctxt, virPCIVPDResource **r= es) +{ + xmlNodePtr orignode =3D NULL; + g_autofree xmlNodePtr *nodes =3D NULL; + int nfields =3D -1; + g_autofree char *access =3D NULL; + size_t i =3D 0; + g_autoptr(virPCIVPDResource) newres =3D g_new0(virPCIVPDResource, 1); + + if (res =3D=3D NULL) { + return -1; + } + + orignode =3D ctxt->node; + + if (!(newres->name =3D virXPathString("string(./name)", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Could not read a device name from the element")); + ctxt->node =3D orignode; + return -1; + } + + if ((nfields =3D virXPathNodeSet("./fields[@access]", ctxt, &nodes)) <= 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no VPD elements with an access type attribute = found")); + ctxt->node =3D orignode; + return -1; + } + + for (i =3D 0; i < nfields; i++) { + ctxt->node =3D nodes[i]; + if (!(access =3D virXPathString("string(./@access[1])", ctxt))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("VPD fields access type parsing has failed")); + ctxt->node =3D orignode; + return -1; + } + + if (STREQ(access, "readonly")) { + if (virNodeDeviceCapVPDParseReadOnlyFields(ctxt, newres) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Could not parse %s VPD resource fields"), acces= s); + } + } else if (STREQ(access, "readwrite")) { + if (virNodeDeviceCapVPDParseReadWriteFields(ctxt, newres) < 0)= { + virReportError(VIR_ERR_XML_ERROR, + _("Could not parse %s VPD resource fields"), acces= s); + } + } else { + virReportError(VIR_ERR_XML_ERROR, _("Unsupported VPD field acc= ess type specified %s"), + access); + } + } + ctxt->node =3D orignode; + + /* Replace the existing VPD representation if there is one already. */ + if (*res !=3D NULL) { + virPCIVPDResourceFree(*res); + } + *res =3D g_steal_pointer(&newres); + return 0; +} =20 static int virNodeDevAPMatrixCapabilityParseXML(xmlXPathContextPtr ctxt, @@ -1718,6 +1957,11 @@ virNodeDevPCICapabilityParseXML(xmlXPathContextPtr c= txt, &pci_dev->nmdev_types) < 0) return -1; pci_dev->flags |=3D VIR_NODE_DEV_CAP_FLAG_PCI_MDEV; + } else if (STREQ(type, "vpd")) { + if (virNodeDeviceCapVPDParseXML(ctxt, &pci_dev->vpd) < 0) { + return -1; + } + pci_dev->flags |=3D VIR_NODE_DEV_CAP_FLAG_PCI_VPD; } else { int hdrType =3D virPCIHeaderTypeFromString(type); =20 @@ -2024,6 +2268,7 @@ virNodeDevCapsDefParseXML(xmlXPathContextPtr ctxt, case VIR_NODE_DEV_CAP_VPORTS: case VIR_NODE_DEV_CAP_SCSI_GENERIC: case VIR_NODE_DEV_CAP_VDPA: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("unknown capability type '%d' for '%s'"), @@ -2287,6 +2532,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps) for (i =3D 0; i < data->pci_dev.nmdev_types; i++) virMediatedDeviceTypeFree(data->pci_dev.mdev_types[i]); g_free(data->pci_dev.mdev_types); + virPCIVPDResourceFree(g_steal_pointer(&data->pci_dev.vpd)); break; case VIR_NODE_DEV_CAP_USB_DEV: g_free(data->usb_dev.product_name); @@ -2352,6 +2598,7 @@ virNodeDevCapsDefFree(virNodeDevCapsDef *caps) case VIR_NODE_DEV_CAP_VDPA: case VIR_NODE_DEV_CAP_AP_CARD: case VIR_NODE_DEV_CAP_AP_QUEUE: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: /* This case is here to shutup the compiler */ break; @@ -2418,6 +2665,7 @@ virNodeDeviceUpdateCaps(virNodeDeviceDef *def) case VIR_NODE_DEV_CAP_VDPA: case VIR_NODE_DEV_CAP_AP_CARD: case VIR_NODE_DEV_CAP_AP_QUEUE: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: break; } @@ -2489,6 +2737,10 @@ virNodeDeviceCapsListExport(virNodeDeviceDef *def, MAYBE_ADD_CAP(VIR_NODE_DEV_CAP_MDEV_TYPES); ncaps++; } + if (flags & VIR_NODE_DEV_CAP_FLAG_PCI_VPD) { + MAYBE_ADD_CAP(VIR_NODE_DEV_CAP_VPD); + ncaps++; + } } =20 if (caps->data.type =3D=3D VIR_NODE_DEV_CAP_CSS_DEV) { @@ -2749,6 +3001,44 @@ virNodeDeviceGetMdevTypesCaps(const char *sysfspath, } =20 =20 +/** + * virNodeDeviceGetPCIVPDDynamicCap: + * @devCapPCIDev: a virNodeDevCapPCIDev for which to add VPD resources. + * + * While VPD has a read-only portion, there may be a read-write portion per + * the specs which may change dynamically. + * + * Returns: 0 if the operation was successful (even if VPD is not present = for + * that device since it is optional in the specs, -1 otherwise. + */ +static int +virNodeDeviceGetPCIVPDDynamicCap(virNodeDevCapPCIDev *devCapPCIDev) +{ + g_autoptr(virPCIDevice) pciDev =3D NULL; + virPCIDeviceAddress devAddr; + g_autoptr(virPCIVPDResource) res =3D NULL; + + devAddr.domain =3D devCapPCIDev->domain; + devAddr.bus =3D devCapPCIDev->bus; + devAddr.slot =3D devCapPCIDev->slot; + devAddr.function =3D devCapPCIDev->function; + + if (!(pciDev =3D virPCIDeviceNew(&devAddr))) + return -1; + + if (virPCIDeviceHasVPD(pciDev)) { + /* VPD is optional in PCI(e) specs. If it is there, attempt to add= it. */ + if ((res =3D virPCIDeviceGetVPD(pciDev))) { + devCapPCIDev->flags |=3D VIR_NODE_DEV_CAP_FLAG_PCI_VPD; + devCapPCIDev->vpd =3D g_steal_pointer(&res); + } else { + virPCIVPDResourceFree(g_steal_pointer(&devCapPCIDev->vpd)); + } + } + return 0; +} + + /* virNodeDeviceGetPCIDynamicCaps() get info that is stored in sysfs * about devices related to this device, i.e. things that can change * without this device itself changing. These must be refreshed @@ -2771,6 +3061,9 @@ virNodeDeviceGetPCIDynamicCaps(const char *sysfsPath, if (pci_dev->nmdev_types > 0) pci_dev->flags |=3D VIR_NODE_DEV_CAP_FLAG_PCI_MDEV; =20 + if (virNodeDeviceGetPCIVPDDynamicCap(pci_dev) < 0) + return -1; + return 0; } =20 diff --git a/src/conf/node_device_conf.h b/src/conf/node_device_conf.h index 5a4d9c7a55..e4d1f67d53 100644 --- a/src/conf/node_device_conf.h +++ b/src/conf/node_device_conf.h @@ -24,6 +24,7 @@ =20 #include "internal.h" #include "virbitmap.h" +#include "virpcivpd.h" #include "virscsihost.h" #include "virpci.h" #include "virvhba.h" @@ -69,6 +70,7 @@ typedef enum { VIR_NODE_DEV_CAP_AP_CARD, /* s390 AP Card device */ VIR_NODE_DEV_CAP_AP_QUEUE, /* s390 AP Queue */ VIR_NODE_DEV_CAP_AP_MATRIX, /* s390 AP Matrix device */ + VIR_NODE_DEV_CAP_VPD, /* Device provides VPD */ =20 VIR_NODE_DEV_CAP_LAST } virNodeDevCapType; @@ -103,6 +105,7 @@ typedef enum { VIR_NODE_DEV_CAP_FLAG_PCI_VIRTUAL_FUNCTION =3D (1 << 1), VIR_NODE_DEV_CAP_FLAG_PCIE =3D (1 << 2), VIR_NODE_DEV_CAP_FLAG_PCI_MDEV =3D (1 << 3), + VIR_NODE_DEV_CAP_FLAG_PCI_VPD =3D (1 << 4), } virNodeDevPCICapFlags; =20 typedef enum { @@ -181,6 +184,7 @@ struct _virNodeDevCapPCIDev { int hdrType; /* enum virPCIHeaderType or -1 */ virMediatedDeviceType **mdev_types; size_t nmdev_types; + virPCIVPDResource *vpd; }; =20 typedef struct _virNodeDevCapUSBDev virNodeDevCapUSBDev; @@ -418,7 +422,8 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNodeDevCapsDef, virNod= eDevCapsDefFree); VIR_CONNECT_LIST_NODE_DEVICES_CAP_VDPA | \ VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_CARD | \ VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_QUEUE | \ - VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX) + VIR_CONNECT_LIST_NODE_DEVICES_CAP_AP_MATRIX | \ + VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD) =20 #define VIR_CONNECT_LIST_NODE_DEVICES_FILTERS_ACTIVE \ VIR_CONNECT_LIST_NODE_DEVICES_ACTIVE | \ diff --git a/src/conf/virnodedeviceobj.c b/src/conf/virnodedeviceobj.c index 9a9841576a..165ec1f1dd 100644 --- a/src/conf/virnodedeviceobj.c +++ b/src/conf/virnodedeviceobj.c @@ -701,6 +701,9 @@ virNodeDeviceObjHasCap(const virNodeDeviceObj *obj, if (type =3D=3D VIR_NODE_DEV_CAP_MDEV_TYPES && (cap->data.pci_dev.flags & VIR_NODE_DEV_CAP_FLAG_PCI_MDEV)) return true; + if (type =3D=3D VIR_NODE_DEV_CAP_VPD && + (cap->data.pci_dev.flags & VIR_NODE_DEV_CAP_FLAG_PCI_VPD)) + return true; break; =20 case VIR_NODE_DEV_CAP_SCSI_HOST: @@ -742,6 +745,7 @@ virNodeDeviceObjHasCap(const virNodeDeviceObj *obj, case VIR_NODE_DEV_CAP_VDPA: case VIR_NODE_DEV_CAP_AP_CARD: case VIR_NODE_DEV_CAP_AP_QUEUE: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: break; } @@ -899,7 +903,8 @@ virNodeDeviceObjMatch(virNodeDeviceObj *obj, MATCH_CAP(VDPA) || MATCH_CAP(AP_CARD) || MATCH_CAP(AP_QUEUE) || - MATCH_CAP(AP_MATRIX))) + MATCH_CAP(AP_MATRIX) || + MATCH_CAP(VPD))) return false; } =20 diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_de= vice_driver.c index 3bc6eb1c11..d19ed7d948 100644 --- a/src/node_device/node_device_driver.c +++ b/src/node_device/node_device_driver.c @@ -708,6 +708,7 @@ nodeDeviceObjFormatAddress(virNodeDeviceObj *obj) case VIR_NODE_DEV_CAP_VDPA: case VIR_NODE_DEV_CAP_AP_CARD: case VIR_NODE_DEV_CAP_AP_QUEUE: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: break; } @@ -1983,6 +1984,7 @@ int nodeDeviceDefValidate(virNodeDeviceDef *def, case VIR_NODE_DEV_CAP_AP_CARD: case VIR_NODE_DEV_CAP_AP_QUEUE: case VIR_NODE_DEV_CAP_AP_MATRIX: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_LAST: break; } diff --git a/src/node_device/node_device_udev.c b/src/node_device/node_devi= ce_udev.c index 71f0bef827..7c3bb762b3 100644 --- a/src/node_device/node_device_udev.c +++ b/src/node_device/node_device_udev.c @@ -42,6 +42,7 @@ #include "virnetdev.h" #include "virmdev.h" #include "virutil.h" +#include "virpcivpd.h" =20 #include "configmake.h" =20 @@ -1397,6 +1398,7 @@ udevGetDeviceDetails(struct udev_device *device, case VIR_NODE_DEV_CAP_AP_MATRIX: return udevProcessAPMatrix(device, def); case VIR_NODE_DEV_CAP_MDEV_TYPES: + case VIR_NODE_DEV_CAP_VPD: case VIR_NODE_DEV_CAP_SYSTEM: case VIR_NODE_DEV_CAP_FC_HOST: case VIR_NODE_DEV_CAP_VPORTS: diff --git a/tests/nodedevschemadata/pci_0000_42_00_0_vpd.xml b/tests/noded= evschemadata/pci_0000_42_00_0_vpd.xml new file mode 100644 index 0000000000..8b56e4f6b4 --- /dev/null +++ b/tests/nodedevschemadata/pci_0000_42_00_0_vpd.xml @@ -0,0 +1,42 @@ + + pci_0000_42_00_0 + + 0x020000 + 0 + 66 + 0 + 0 + MT42822 BlueField-2 integrated ConnectX-6 Dx ne= twork controller + Mellanox Technologies + + + BlueField-2 DPU 25GbE Dual-Port SFP56, Crypto Enabled, 16GB on= -board DDR, 1GbE OOB management, Tall Bracket + + B1 + foobar + MBF2H332A-AEEOT + MT2113X00000 + PCIeGen4 x8 + MBF2H332A-AEEOT + 3c53d07eec484d8aab34dabd24fe575aa + MLX:MN=3DMLNX:CSKU=3DV2:UUID=3DV3:PCI=3D= V0:MODL=3DBF2H332A + + + fooasset + vendorfield0 + vendorfield2 + vendorfieldA + systemfieldB + systemfield0 + + + +
+ + + + + + + + diff --git a/tests/nodedevxml2xmlout/pci_0000_42_00_0_vpd.xml b/tests/noded= evxml2xmlout/pci_0000_42_00_0_vpd.xml new file mode 120000 index 0000000000..a0b5372ca0 --- /dev/null +++ b/tests/nodedevxml2xmlout/pci_0000_42_00_0_vpd.xml @@ -0,0 +1 @@ +../nodedevschemadata/pci_0000_42_00_0_vpd.xml \ No newline at end of file diff --git a/tests/nodedevxml2xmltest.c b/tests/nodedevxml2xmltest.c index 9e32e7d553..557347fb07 100644 --- a/tests/nodedevxml2xmltest.c +++ b/tests/nodedevxml2xmltest.c @@ -121,6 +121,7 @@ mymain(void) DO_TEST("pci_0000_02_10_7_sriov_pf_vfs_all_header_type"); DO_TEST("drm_renderD129"); DO_TEST("pci_0000_02_10_7_mdev_types"); + DO_TEST("pci_0000_42_00_0_vpd"); DO_TEST("mdev_3627463d_b7f0_4fea_b468_f1da537d301b"); DO_TEST("ccw_0_0_ffff"); DO_TEST("css_0_0_ffff"); diff --git a/tools/virsh-nodedev.c b/tools/virsh-nodedev.c index c989a77ad2..1ad8db7a3f 100644 --- a/tools/virsh-nodedev.c +++ b/tools/virsh-nodedev.c @@ -472,6 +472,9 @@ cmdNodeListDevices(vshControl *ctl, const vshCmd *cmd G= _GNUC_UNUSED) case VIR_NODE_DEV_CAP_MDEV: flags |=3D VIR_CONNECT_LIST_NODE_DEVICES_CAP_MDEV; break; + case VIR_NODE_DEV_CAP_VPD: + flags |=3D VIR_CONNECT_LIST_NODE_DEVICES_CAP_VPD; + break; case VIR_NODE_DEV_CAP_CCW_DEV: flags |=3D VIR_CONNECT_LIST_NODE_DEVICES_CAP_CCW_DEV; break; --=20 2.30.2 From nobody Sun Apr 28 21:58:50 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) client-ip=170.10.129.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 170.10.129.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by mx.zohomail.com with SMTPS id 1633961170466570.6945689789812; Mon, 11 Oct 2021 07:06:10 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-27-qP42KtwvPomFc45CmmVsDA-1; Mon, 11 Oct 2021 10:06:05 -0400 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 5EB241019627; Mon, 11 Oct 2021 14:05:20 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id D01DD60871; Mon, 11 Oct 2021 14:05:18 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id C4CCE4E58F; Mon, 11 Oct 2021 14:05:15 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 19BE5Bu6030331 for ; Mon, 11 Oct 2021 10:05:11 -0400 Received: by smtp.corp.redhat.com (Postfix) id 6DBE32026D46; Mon, 11 Oct 2021 14:05:11 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast04.extmail.prod.ext.rdu2.redhat.com [10.11.55.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 67B782026D67 for ; Mon, 11 Oct 2021 14:05:08 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-delivery-1.mimecast.com [205.139.110.120]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 4C9DD10AF953 for ; Mon, 11 Oct 2021 14:05:08 +0000 (UTC) Received: from smtp-relay-internal-0.canonical.com (smtp-relay-internal-0.canonical.com [185.125.188.122]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-276-aUki6p7KM-28GPsjtzHzbA-1; Mon, 11 Oct 2021 10:05:05 -0400 Received: from mail-lf1-f71.google.com (mail-lf1-f71.google.com [209.85.167.71]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-0.canonical.com (Postfix) with ESMTPS id DBFF24001A for ; Mon, 11 Oct 2021 14:05:03 +0000 (UTC) Received: by mail-lf1-f71.google.com with SMTP id i6-20020a056512318600b003fd8c8c2677so2617038lfe.1 for ; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) Received: from ws.lan.d-node.is ([95.165.29.203]) by smtp.gmail.com with ESMTPSA id o12sm741144lft.254.2021.10.11.07.05.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 07:05:02 -0700 (PDT) X-MC-Unique: qP42KtwvPomFc45CmmVsDA-1 X-MC-Unique: aUki6p7KM-28GPsjtzHzbA-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=h3q00/snmdSwiLfIh20jzFRfEuKTqtcMhFldueLHKgA=; b=swJUA1bop3EpuhmNtc2+uPrfYZ2+na6NTjE3nQAz6qqF2n0KxSrXIFIun8E7YSm5Kq 7bt/phZDRJrxCEYwPOuIUyuYvKvkx8HkWdZkdb+szbTHdUqH/rzXkGsuCDYcXQEzbM7u OIrxci0B6Uj7nxmB+17w7pTKU0lLlqhviZHCVKVb+ZNHd8wYOOo9nrmm44Qivuvby52e kxkhZbSmnh2oex51/9n8rvZmWQxirBmsByQvWRqJRebitFL3YmxeHXtAvmztwbtYExG4 qz+eGb03WeEzzj11xO3fOq5ifuxn1Jgz0Qm8FJfygE5gXdoZLjEZr1zy+OpWJZoEU5e8 1yCA== X-Gm-Message-State: AOAM533/gnXvg5V49Sk/ju5LoM2ln3x7JrpgfzmiPwAi5QUAwoo2HkFc dvN8dA0ZIkh+KfXOpb1t3MxNccqCfWXRyvu7/Ft4vTo2bpsIMzPK8oJWL/29eSyHIShgaLm0jMu 1UjoknfZjur82LAL7unkg1T9L2P2sIyoytw== X-Received: by 2002:ac2:5fee:: with SMTP id s14mr26897638lfg.537.1633961103272; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyDaiQa9e1wGpq7a/hoQ/3oIDHtov5XzOQiTbDRBzF7SFYdBkqroUOEH7gGxf+4ASxqsIHUWQ== X-Received: by 2002:ac2:5fee:: with SMTP id s14mr26897580lfg.537.1633961102686; Mon, 11 Oct 2021 07:05:02 -0700 (PDT) From: Dmitrii Shcherbakov To: dmitrii.shcherbakov@canonical.com, libvir-list@redhat.com Subject: [libvirt PATCH v6 4/5] Add PCI VPD Capability Documentation Date: Mon, 11 Oct 2021 17:04:45 +0300 Message-Id: <20211011140446.220390-5-dmitrii.shcherbakov@canonical.com> In-Reply-To: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> References: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.78 on 10.11.54.4 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 19BE5Bu6030331 X-loop: libvir-list@redhat.com Cc: laine@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1633961235956100001 Content-Type: text/plain; charset="utf-8" Describes the format of the newly added VPD capability and gives and example for a real-world device. Signed-off-by: Dmitrii Shcherbakov --- docs/drvnodedev.html.in | 69 +++++++++++++++++++++++++++++++++++++++++ docs/formatnode.html.in | 63 ++++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/docs/drvnodedev.html.in b/docs/drvnodedev.html.in index 70f7e6717d..ee75eeb25c 100644 --- a/docs/drvnodedev.html.in +++ b/docs/drvnodedev.html.in @@ -185,6 +185,75 @@ </capability> </device> =20 +

VPD capability

+

+ A device that exposes a PCI/PCIe VPD capability will include a nested + capability vpd which presents data stored in the Vital = Product + Data (VPD). VPD provides a device name and a number of other standar= d-defined + read-only fields (change level, manufacture id, part number, serial = number) and + vendor-specific read-only fields. Additionally, if a device supports= it, + read-write fields (asset tag, vendor-specific fields or system field= s) may + also be present. The VPD capability is optional for PCI/PCIe devices= and the + set of exposed fields may vary depending on a device. The XML format= follows + the binary format described in "I.3. VPD Definitions" in PCI Local B= us (2.2+) + and the identical format in PCIe 4.0+. At the time of writing, the s= upport for + exposing this capability is only present on Linux-based systems (ker= nel version + v2.6.26 is the first one to expose VPD via sysfs which Libvirt relie= s on). + Reading the VPD contents requires root privileges, therefore, + virsh nodedev-dumpxml must be executed accordingly. + A description of the XML format for the vpd capability = can + be found here. +

+

+ The following example shows a VPD representation for a device that e= xposes the + VPD capability with read-only and read-write fields. Among other thi= ngs, + the VPD of this particular device includes a unique board serial num= ber. +

+
+<device>
+  <name>pci_0000_42_00_0</name>
+  <capability type=3D'pci'>
+    <class>0x020000</class>
+    <domain>0</domain>
+    <bus>66</bus>
+    <slot>0</slot>
+    <function>0</function>
+    <product id=3D'0xa2d6'>MT42822 BlueField-2 integrated =
ConnectX-6 Dx network controller</product>
+    <vendor id=3D'0x15b3'>Mellanox Technologies</vendor=
>
+    <capability type=3D'virt_functions' maxCount=3D'16&a=
pos;/>
+    <capability type=3D'vpd'>
+      <name>BlueField-2 DPU 25GbE Dual-Port SFP56, Crypto Enabled, 1=
6GB on-board DDR, 1GbE OOB management, Tall Bracket</name>
+      <fields access=3D'readonly'>
+        <change_level>B1</change_level>
+        <manufacture_id>foobar</manufacture_id>
+        <part_number>MBF2H332A-AEEOT</part_number>
+        <serial_number>MT2113X00000</serial_number>
+        <vendor_field index=3D'0'>PCIeGen4 x8</vendor_f=
ield>
+        <vendor_field index=3D'2'>MBF2H332A-AEEOT</vend=
or_field>
+        <vendor_field index=3D'3'>3c53d07eec484d8aab34dabd=
24fe575aa</vendor_field>
+        <vendor_field index=3D'A'>MLX:MN=3DMLNX:CSKU=3DV2:=
UUID=3DV3:PCI=3DV0:MODL=3DBF2H332A</vendor_field>
+      </fields>
+      <fields access=3D'readwrite'>
+        <asset_tag>fooasset</asset_tag>
+        <vendor_field index=3D'0'>vendorfield0</vendor_=
field>
+        <vendor_field index=3D'2'>vendorfield2</vendor_=
field>
+        <vendor_field index=3D'A'>vendorfieldA</vendor_=
field>
+        <system_field index=3D'B'>systemfieldB</system_=
field>
+        <system_field index=3D'0'>systemfield0</system_=
field>
+      </fields>
+    </capability>
+    <iommuGroup number=3D'65'>
+      <address domain=3D'0x0000' bus=3D'0x42' slot=
=3D'0x00' function=3D'0x0'/>
+    </iommuGroup>
+    <numa node=3D'0'/>
+    <pci-express>
+      <link validity=3D'cap' port=3D'0' speed=3D&ap=
os;16' width=3D'8'/>
+      <link validity=3D'sta' speed=3D'8' width=3D&a=
pos;8'/>
+    </pci-express>
+  </capability>
+</device>
+
+

Mediated devices (MDEVs)

Mediated devices (Since 3.2.0) are soft= ware diff --git a/docs/formatnode.html.in b/docs/formatnode.html.in index 3b3c3105d4..fb2f356396 100644 --- a/docs/formatnode.html.in +++ b/docs/formatnode.html.in @@ -162,7 +162,13 @@ This device is capable of creating mediated devices. The sub-elements are summarized in mdev_types capability. - + +

vpd
+
+ This device exposes a VPD PCI/PCIe capability. + The sub-elements are summarized in + vpd capability. +
=20 @@ -592,5 +598,60 @@ </device> =20 +

vpd capability

+ +

+ PCI devices can expose a VPD capability w= hich + is optional per PCI Local Bus 2.2+ and PCIe 4.0+ specifications. If + the VPD capability is present, then the parent capability + element with the vpd type will contain a name + element (containing a manufacturer-provided device name) and optiona= lly + one or two fields elements with an access + attribute set to readonly or readwrite. +

+

+ The read-only fields element may contain the following = elements: +

+
change_level
+
An engineering change level for this add-in card.
+
manufacture_id
+
An extension to the Vendor ID (or Subsystem Vendor ID) in the + Configuration Space header which allows vendors the flexibility = to identify + an additional level of detail pertaining to the sourcing of a PC= I device.
+
part_number
+
An extension to the Device ID (or Subsystem ID) in the Configu= ration + Space header specifying a part number of an add-in card.
+
serial_number
+
A unique add-in card Serial Number.
+
vendor_field
+
Zero or many of those elements with an index attr= ibute + (since-character upper-case ASCII alphanumeric indexes). Content= s will vary + depending on a vendor.
+
+ All fields are optional and are not guaranteed to be present for a g= eneric PCI device. +

+

+ The read-write fields element may contain the following= elements: +

+
asset_tag
+
A system asset identifier provided by the system owner.
+
vendor_field
+
Zero or many of those elements with an index attr= ibute + (since-character upper-case ASCII alphanumeric indexes). Content= s will vary depending + on a vendor.
+
system_field
+
Zero or many of those elements with an index attr= ibute (since-character + upper-case ASCII alphanumeric indexes, except for letter 'A'). M= ay store system-specific + data related to a PCI device.
+
+ All fields are optional and are not guaranteed to be present for a g= eneric PCI device. + Read-write fields are not possible to alter via Libvirt at the time = of writing but their + content is refreshed on each invocation in case this is done by mean= s external to Libvirt. +

+

+ The device name and all fields may contain only the following charac= ters: + [0-9a-zA-F -_,.:;=3D]. + The device name may be as large as 65535 bytes while fields are limi= ted with 255 bytes. +

--=20 2.30.2 From nobody Sun Apr 28 21:58:50 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=fail(p=none dis=none) header.from=canonical.com Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1633961201729481.67859038419647; Mon, 11 Oct 2021 07:06:41 -0700 (PDT) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-502-G1gzB6YsOSeAxTd-CCk4sA-1; Mon, 11 Oct 2021 10:06:38 -0400 Received: from smtp.corp.redhat.com (int-mx08.intmail.prod.int.phx2.redhat.com [10.5.11.23]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 6D548100C618; Mon, 11 Oct 2021 14:06:33 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 4751526E4B; Mon, 11 Oct 2021 14:06:33 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 11A391806D00; Mon, 11 Oct 2021 14:06:33 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.rdu2.redhat.com [10.11.54.2]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 19BE5Bu7030326 for ; Mon, 11 Oct 2021 10:05:11 -0400 Received: by smtp.corp.redhat.com (Postfix) id 0256E4047279; Mon, 11 Oct 2021 14:05:11 +0000 (UTC) Received: from mimecast-mx02.redhat.com (mimecast03.extmail.prod.ext.rdu2.redhat.com [10.11.55.19]) by smtp.corp.redhat.com (Postfix) with ESMTPS id F27484047272 for ; Mon, 11 Oct 2021 14:05:10 +0000 (UTC) Received: from us-smtp-1.mimecast.com (us-smtp-1.mimecast.com [207.211.31.81]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id DB58E811E8F for ; Mon, 11 Oct 2021 14:05:10 +0000 (UTC) Received: from smtp-relay-internal-1.canonical.com (smtp-relay-internal-1.canonical.com [185.125.188.123]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-437-4C0z-YsuMLu0s2JBXL_qug-1; Mon, 11 Oct 2021 10:05:09 -0400 Received: from mail-lf1-f69.google.com (mail-lf1-f69.google.com [209.85.167.69]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-1.canonical.com (Postfix) with ESMTPS id 93C883FFF4 for ; Mon, 11 Oct 2021 14:05:04 +0000 (UTC) Received: by mail-lf1-f69.google.com with SMTP id g9-20020a0565123b8900b003f33a027130so12836544lfv.18 for ; Mon, 11 Oct 2021 07:05:04 -0700 (PDT) Received: from ws.lan.d-node.is ([95.165.29.203]) by smtp.gmail.com with ESMTPSA id o12sm741144lft.254.2021.10.11.07.05.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Oct 2021 07:05:03 -0700 (PDT) X-MC-Unique: G1gzB6YsOSeAxTd-CCk4sA-1 X-MC-Unique: 4C0z-YsuMLu0s2JBXL_qug-1 X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Wacmv74573+0xYqPHKiB0JzPFW48TDtjf4+hlspzeTA=; b=Z+0dQR6GsI1hQKFVbPUKA0WJF3DGDqdAWrpo/vEmuATC7c2dNDCOIB40UVLlkowopH iPTcL2bQh8NBuZcke2f1TQPMC7u2M98RiDIV7VG6woDMyx8+vKV5wcw//fJrHUTvcP7Y ewrCJbSMITnrKj05gZZBC4SKrC4XqF6UPSHNOCVGIGLa7Q6ZxqdRyr9ukRPCtuSoUyXX qoSXnkoFGwaB8/R5Mkn46nM3qJ62NRw0osDTel3fmt8D1iTwVlrXeajAmooApJChq4ex CxbkTyIsLSx9jotUvMSOFoGqHlhg0a5Ugupf8LEBQfo1ugkoo8DoGaYFusbD2suJ2Gft RpOw== X-Gm-Message-State: AOAM530iKuKbnqtUfeGeWt4NJE0gcjcB8pCE1jdlMQzJ+9ARkjznAD9V VJf9plhFpPn/eLMRkffZz8nKuM092VyZOjGEfKpjlfuIFZT1cPta1jBbljtuK8Pj1huiPKqrZEa KHQqpZVZQjSVSWDnKY1L5BGH2R4/pw63zjw== X-Received: by 2002:a19:4805:: with SMTP id v5mr27798230lfa.219.1633961103924; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyikNiMy9cHcgpuyR2QqbOR6rr0NtdUG2+RFYI1ydiXKkP7sXsLcCitK+QwappcUWbkvMWc7Q== X-Received: by 2002:a19:4805:: with SMTP id v5mr27798167lfa.219.1633961103357; Mon, 11 Oct 2021 07:05:03 -0700 (PDT) From: Dmitrii Shcherbakov To: dmitrii.shcherbakov@canonical.com, libvir-list@redhat.com Subject: [libvirt PATCH v6 5/5] news: Add PCI VPD parser & capability notes Date: Mon, 11 Oct 2021 17:04:46 +0300 Message-Id: <20211011140446.220390-6-dmitrii.shcherbakov@canonical.com> In-Reply-To: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> References: <20211011140446.220390-1-dmitrii.shcherbakov@canonical.com> MIME-Version: 1.0 X-Mimecast-Impersonation-Protect: Policy=CLT - Impersonation Protection Definition; Similar Internal Domain=false; Similar Monitored External Domain=false; Custom External Domain=false; Mimecast External Domain=false; Newly Observed Domain=false; Internal User Name=false; Custom Display Name List=false; Reply-to Address Mismatch=false; Targeted Threat Dictionary=false; Mimecast Threat Dictionary=false; Custom Threat Dictionary=false X-Scanned-By: MIMEDefang 2.84 on 10.11.54.2 X-MIME-Autoconverted: from quoted-printable to 8bit by lists01.pubmisc.prod.ext.phx2.redhat.com id 19BE5Bu7030326 X-loop: libvir-list@redhat.com Cc: laine@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.23 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZM-MESSAGEID: 1633961259460100003 Content-Type: text/plain; charset="utf-8" Signed-off-by: Dmitrii Shcherbakov --- NEWS.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/NEWS.rst b/NEWS.rst index ae678bffc4..7d4244e938 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -40,6 +40,28 @@ v7.9.0 (unreleased) domain definition when the guest was first started).This setting is on= ly applicable for x86 guests using qemu driver. =20 + * virpcivpd: Add a PCI VPD parser + + A parser for the standard PCI/PCIe VPD ("I.3. VPD Definitions" in PCI = 2.2+ + and an equivalent definition in "6.28.1 VPD Format" PCIe 4.0) was added + along with relevant types to represent PCI VPD in memory. This + functionality got added for Linux only at this point (kernels above + v2.6.26 have support for exposing VPD via sysfs). + + * virpci: Add PCI VPD-related helper functions to virpci + + In order to utilize the PCI VPD parser, a couple of helper functions g= ot + introduced to check for the presence of a VPD file in the sysfs tree a= nd + to invoke the PCI VPD parser to get a list of resources representing P= CI + VPD contents in memory. + + * nodedev: Add PCI VPD capability support + + Support for serializing and deserializing PCI VPD data structures is a= dded + following the addition of the PCI VPD parser. A new PCI device capabil= ity + called "vpd" is introduced holding string resources and keyword resour= ces + found in PCI VPD. + * **Improvements** =20 * **Bug fixes** --=20 2.30.2