On Mon, Jul 22, 2024 at 2:32 PM Michal Privoznik <mprivozn@redhat.com> wrote:
The aim of pstore device is to provide a bit of NVRAM storage for
guest kernel to record oops/panic logs just before the it
crashes. Typical usage includes usage in combination with a
watchdog so that the logs can be inspected after the watchdog
rebooted the machine. While Linux kernel (and possibly Windows
too) support many backends, in QEMU there's just 'acpi-erst'
device so stick with that for now. The device must be attached to
a PCI bus and needs two additional values (well, corresponding
memory-backend-file needs them): size and path. Despite using
memory-backeng-file this does NOT add any additional RAM to the

*memory-backend-file this does....

guest and thus I've decided to expose it as another device type
instead of memory model.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
---
 docs/formatdomain.rst                         |  32 ++++
 src/ch/ch_domain.c                            |   1 +
 src/conf/domain_conf.c                        | 153 ++++++++++++++++++
 src/conf/domain_conf.h                        |  19 +++
 src/conf/domain_postparse.c                   |   1 +
 src/conf/domain_validate.c                    |  30 ++++
 src/conf/schemas/domaincommon.rng             |  25 +++
 src/conf/virconftypes.h                       |   2 +
 src/hyperv/hyperv_driver.c                    |   1 +
 src/libvirt_private.syms                      |   2 +
 src/libxl/libxl_driver.c                      |   6 +
 src/lxc/lxc_driver.c                          |   6 +
 src/qemu/qemu_command.c                       |   1 +
 src/qemu/qemu_domain.c                        |   3 +
 src/qemu/qemu_domain_address.c                |  11 ++
 src/qemu/qemu_driver.c                        |   3 +
 src/qemu/qemu_hotplug.c                       |   5 +
 src/qemu/qemu_validate.c                      |  26 +++
 src/test/test_driver.c                        |   1 +
 .../pstore-acpi-erst.x86_64-latest.args       |  36 +++++
 .../pstore-acpi-erst.x86_64-latest.xml        |   1 +
 tests/qemuxmlconfdata/pstore-acpi-erst.xml    |  53 ++++++
 tests/qemuxmlconftest.c                       |   1 +
 23 files changed, 419 insertions(+)
 create mode 100644 tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.args
 create mode 120000 tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.xml
 create mode 100644 tests/qemuxmlconfdata/pstore-acpi-erst.xml

diff --git a/docs/formatdomain.rst b/docs/formatdomain.rst
index 10584dfe83..8b35005c9e 100644
--- a/docs/formatdomain.rst
+++ b/docs/formatdomain.rst
@@ -8659,6 +8659,38 @@ The optional attribute ``backend`` is required if the ``type`` is ``qemu``, the
    ...


+Pstore
+~~~~~~~~~
+
+Pstore is an oops/panic logger that writes its logs to a block device and
+non-block device before the system crashes. Currently only ACPI Error Record
+Serialization Table, ERST, is supported. This feature is designed for storing
+error records in persistent storage for future reference and/or debugging.
+:since:`Since v10.6.0`
+
+::
+
+  ...
+  <pstore backend='acpi-erst'>
+    <path>/tmp/guest_acpi_esrt</path>
+    <size unit='KiB'>8</size>
+    <address type='pci' domain='0x0000' bus='0x02' slot='0x01' function='0x0'/>
+  </pstore>
+  ...
+
+The ``pstore`` element has one mandatory attribute ``backend`` which selects
+desired backend (only ``acpi-erst`` is accepted for now). Then it has the
+following child elements:
+
+``path``
+  Represents a path in the host that backs the pstore device in the guest. It
+  is mandatory.
+
+``size``
+  Configures the size of the persistent storage available to the guest. It is
+  mandatory.
+
+
 Security label
 --------------

diff --git a/src/ch/ch_domain.c b/src/ch/ch_domain.c
index 8e3e205c8c..e1e14554a8 100644
--- a/src/ch/ch_domain.c
+++ b/src/ch/ch_domain.c
@@ -180,6 +180,7 @@ chValidateDomainDeviceDef(const virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                        _("Cloud-Hypervisor doesn't support '%1$s' device"),
                        virDomainDeviceTypeToString(dev->type));
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 6733857a3a..12ae8a9526 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -337,6 +337,7 @@ VIR_ENUM_IMPL(virDomainDevice,
               "vsock",
               "audio",
               "crypto",
+              "pstore",
 );

 VIR_ENUM_IMPL(virDomainDiskDevice,
@@ -1513,6 +1514,11 @@ VIR_ENUM_IMPL(virDomainLaunchSecurity,
               "s390-pv",
 );

+VIR_ENUM_IMPL(virDomainPstoreBackend,
+              VIR_DOMAIN_PSTORE_BACKEND_LAST,
+              "acpi-erst",
+);
+
 typedef enum {
     VIR_DOMAIN_NET_VHOSTUSER_MODE_NONE,
     VIR_DOMAIN_NET_VHOSTUSER_MODE_CLIENT,
@@ -3548,6 +3554,16 @@ void virDomainMemoryDefFree(virDomainMemoryDef *def)
     g_free(def);
 }

+void virDomainPstoreDefFree(virDomainPstoreDef *def)
+{
+    if (!def)
+        return;
+
+    g_free(def->path);
+    virDomainDeviceInfoClear(&def->info);
+    g_free(def);
+}
+
 void virDomainDeviceDefFree(virDomainDeviceDef *def)
 {
     if (!def)
@@ -3632,6 +3648,9 @@ void virDomainDeviceDefFree(virDomainDeviceDef *def)
     case VIR_DOMAIN_DEVICE_CRYPTO:
         virDomainCryptoDefFree(def->data.crypto);
         break;
+    case VIR_DOMAIN_DEVICE_PSTORE:
+        virDomainPstoreDefFree(def->data.pstore);
+        break;
     case VIR_DOMAIN_DEVICE_LAST:
     case VIR_DOMAIN_DEVICE_NONE:
         break;
@@ -3997,6 +4016,8 @@ void virDomainDefFree(virDomainDef *def)

     virDomainIOMMUDefFree(def->iommu);

+    virDomainPstoreDefFree(def->pstore);
+
     g_free(def->idmap.uidmap);
     g_free(def->idmap.gidmap);

@@ -4554,6 +4575,8 @@ virDomainDeviceGetInfo(const virDomainDeviceDef *device)
         return &device->data.vsock->info;
     case VIR_DOMAIN_DEVICE_CRYPTO:
         return &device->data.crypto->info;
+    case VIR_DOMAIN_DEVICE_PSTORE:
+        return &device->data.pstore->info;

     /* The following devices do not contain virDomainDeviceInfo */
     case VIR_DOMAIN_DEVICE_LEASE:
@@ -4659,6 +4682,9 @@ virDomainDeviceSetData(virDomainDeviceDef *device,
     case VIR_DOMAIN_DEVICE_CRYPTO:
         device->data.crypto = devicedata;
         break;
+    case VIR_DOMAIN_DEVICE_PSTORE:
+        device->data.pstore = devicedata;
+        break;
     case VIR_DOMAIN_DEVICE_NONE:
     case VIR_DOMAIN_DEVICE_LAST:
         break;
@@ -4877,6 +4903,13 @@ virDomainDeviceInfoIterateFlags(virDomainDef *def,
             return rc;
     }

+    device.type = VIR_DOMAIN_DEVICE_PSTORE;
+    if (def->pstore) {
+        device.data.pstore = def->pstore;
+        if ((rc = cb(def, &device, &def->pstore->info, opaque)) != 0)
+            return rc;
+    }
+
     /* If the flag below is set, make sure @cb can handle @info being NULL */
     if (iteratorFlags & DOMAIN_DEVICE_ITERATE_MISSING_INFO) {
         device.type = VIR_DOMAIN_DEVICE_GRAPHICS;
@@ -4936,6 +4969,7 @@ virDomainDeviceInfoIterateFlags(virDomainDef *def,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         break;
     }
 #endif
@@ -14006,6 +14040,40 @@ virDomainCryptoDefParseXML(virDomainXMLOption *xmlopt,
 }


+static virDomainPstoreDef *
+virDomainPstoreDefParseXML(virDomainXMLOption *xmlopt,
+                           xmlNodePtr node,
+                           xmlXPathContextPtr ctxt,
+                           unsigned int flags)
+{
+    g_autoptr(virDomainPstoreDef) def = NULL;
+    VIR_XPATH_NODE_AUTORESTORE(ctxt)
+
+        def = g_new0(virDomainPstoreDef, 1);
+
+    ctxt->node = node;
+
+    if (virXMLPropEnum(node, "backend",
+                       virDomainPstoreBackendTypeFromString,
+                       VIR_XML_PROP_REQUIRED,
+                       &def->backend) < 0) {
+        return NULL;
+    }
+
+    def->path = virXPathString("string(./path)", ctxt);
+
+    if (virDomainParseMemory("./size", "./size/@unit", ctxt,
+                             &def->size, true, false) < 0) {
+        return NULL;
+    }
+
+    if (virDomainDeviceInfoParseXML(xmlopt, node, ctxt, &def->info, flags) < 0)
+        return NULL;
+
+    return g_steal_pointer(&def);
+}
+
+
 static int
 virDomainDeviceDefParseType(const char *typestr,
                             virDomainDeviceType *type)
@@ -14185,6 +14253,12 @@ virDomainDeviceDefParse(const char *xmlStr,
                                                             flags)))
             return NULL;
         break;
+    case VIR_DOMAIN_DEVICE_PSTORE:
+        if (!(dev->data.pstore = virDomainPstoreDefParseXML(xmlopt, node,
+                                                            ctxt, flags))) {
+            return NULL;
+        }
+        break;
     case VIR_DOMAIN_DEVICE_NONE:
     case VIR_DOMAIN_DEVICE_LAST:
         break;
@@ -19532,6 +19606,22 @@ virDomainDefParseXML(xmlXPathContextPtr ctxt,
     }
     VIR_FREE(nodes);

+    if ((n = virXPathNodeSet("./devices/pstore", ctxt, &nodes)) < 0)
+        return NULL;
+
+    if (n > 1) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("only a single pstore device is supported"));
+        return NULL;
+    }
+
+    if (n > 0) {
+        if (!(def->pstore = virDomainPstoreDefParseXML(xmlopt, nodes[0],
+                                                       ctxt, flags)))
+            return NULL;
+    }
+    VIR_FREE(nodes);
+
     /* analysis of the user namespace mapping */
     if ((n = virXPathNodeSet("./idmap/uid", ctxt, &nodes)) < 0)
         return NULL;
@@ -21404,6 +21494,33 @@ virDomainVsockDefCheckABIStability(virDomainVsockDef *src,
 }


+static bool
+virDomainPstoreDefCheckABIStability(virDomainPstoreDef *src,
+                                    virDomainPstoreDef *dst)
+{
+    if (src->backend != dst->backend) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("Target pstore device backend '%1$s' does not match source '%2$s'"),
+                       virDomainPstoreBackendTypeToString(dst->backend),
+                       virDomainPstoreBackendTypeToString(src->backend));
+        return false;
+    }
+
+    if (src->size != dst->size) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("Target pstore size '%1$llu' does not match source '%2$llu'"),
+                       dst->size,
+                       src->size);
+        return false;
+    }
+
+    if (!virDomainDeviceInfoCheckABIStability(&src->info, &dst->info))
+        return false;
+
+    return true;
+}
+
+
 static bool
 virDomainDefVcpuCheckAbiStability(virDomainDef *src,
                                   virDomainDef *dst)
@@ -21863,6 +21980,17 @@ virDomainDefCheckABIStabilityFlags(virDomainDef *src,
         !virDomainVsockDefCheckABIStability(src->vsock, dst->vsock))
         goto error;

+    if (!!src->pstore != !!dst->pstore) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("Target domain pstore device count does not match source"));
+        goto error;
+    }
+
+    if (src->pstore &&
+        !virDomainPstoreDefCheckABIStability(src->pstore, dst->pstore)) {
+        goto error;
+    }
+
     if (xmlopt && xmlopt->abi.domain &&
         !xmlopt->abi.domain(src, dst))
         goto error;
@@ -21903,6 +22031,7 @@ virDomainDefCheckABIStabilityFlags(virDomainDef *src,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         break;
     }
 #endif
@@ -27849,6 +27978,26 @@ virDomainDefFormatFeatures(virBuffer *buf,
     return 0;
 }

+static int
+virDomainPstoreDefFormat(virBuffer *buf,
+                         virDomainPstoreDef *pstore,
+                         unsigned int flags)
+{
+    g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
+    g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
+
+    virBufferAsprintf(&attrBuf, " backend='%s'",
+                      virDomainPstoreBackendTypeToString(pstore->backend));
+
+    virBufferAsprintf(&childBuf, "<path>%s</path>\n", pstore->path);
+    virBufferAsprintf(&childBuf, "<size unit='KiB'>%llu</size>\n", pstore->size);
+    virDomainDeviceInfoFormat(&childBuf, &pstore->info, flags);
+
+    virXMLFormatElement(buf, "pstore", &attrBuf, &childBuf);
+    return 0;
+}
+
+
 int
 virDomainDefFormatInternal(virDomainDef *def,
                            virDomainXMLOption *xmlopt,
@@ -28320,6 +28469,9 @@ virDomainDefFormatInternalSetRootName(virDomainDef *def,
     if (def->vsock)
         virDomainVsockDefFormat(buf, def->vsock);

+    if (def->pstore)
+        virDomainPstoreDefFormat(buf, def->pstore, flags);
+
     virBufferAdjustIndent(buf, -2);
     virBufferAddLit(buf, "</devices>\n");

@@ -28479,6 +28631,7 @@ virDomainDeviceIsUSB(virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     break;
     }

diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 8283493dfc..546c14e7b0 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -87,6 +87,7 @@ typedef enum {
     VIR_DOMAIN_DEVICE_VSOCK,
     VIR_DOMAIN_DEVICE_AUDIO,
     VIR_DOMAIN_DEVICE_CRYPTO,
+    VIR_DOMAIN_DEVICE_PSTORE,

     VIR_DOMAIN_DEVICE_LAST
 } virDomainDeviceType;
@@ -120,6 +121,7 @@ struct _virDomainDeviceDef {
         virDomainVsockDef *vsock;
         virDomainAudioDef *audio;
         virDomainCryptoDef *crypto;
+        virDomainPstoreDef *pstore;
     } data;
 };

@@ -2983,6 +2985,19 @@ struct _virDomainVirtioOptions {
     virTristateSwitch page_per_vq;
 };

+typedef enum {
+    VIR_DOMAIN_PSTORE_BACKEND_ACPI_ERST,
+
+    VIR_DOMAIN_PSTORE_BACKEND_LAST
+} virDomainPstoreBackend;
+
+struct _virDomainPstoreDef {
+    virDomainPstoreBackend backend;
+    unsigned long long size;
+    char *path;
+    virDomainDeviceInfo info;
+};
+

 #define SCSI_SUPER_WIDE_BUS_MAX_CONT_UNIT 64
 #define SCSI_WIDE_BUS_MAX_CONT_UNIT 16
@@ -3159,6 +3174,7 @@ struct _virDomainDef {
     virDomainRedirFilterDef *redirfilter;
     virDomainIOMMUDef *iommu;
     virDomainVsockDef *vsock;
+    virDomainPstoreDef *pstore;

     void *namespaceData;
     virXMLNamespace ns;
@@ -3598,6 +3614,8 @@ void virDomainVsockDefFree(virDomainVsockDef *vsock);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainVsockDef, virDomainVsockDefFree);
 void virDomainCryptoDefFree(virDomainCryptoDef *def);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainCryptoDef, virDomainCryptoDefFree);
+void virDomainPstoreDefFree(virDomainPstoreDef *def);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainPstoreDef, virDomainPstoreDefFree);
 void virDomainNetTeamingInfoFree(virDomainNetTeamingInfo *teaming);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(virDomainNetTeamingInfo, virDomainNetTeamingInfoFree);
 void virDomainNetPortForwardFree(virDomainNetPortForward *pf);
@@ -4268,6 +4286,7 @@ VIR_ENUM_DECL(virDomainCryptoBackend);
 VIR_ENUM_DECL(virDomainShmemModel);
 VIR_ENUM_DECL(virDomainShmemRole);
 VIR_ENUM_DECL(virDomainLaunchSecurity);
+VIR_ENUM_DECL(virDomainPstoreBackend);
 /* from libvirt.h */
 VIR_ENUM_DECL(virDomainState);
 VIR_ENUM_DECL(virDomainNostateReason);
diff --git a/src/conf/domain_postparse.c b/src/conf/domain_postparse.c
index 112795ea65..bf33f29638 100644
--- a/src/conf/domain_postparse.c
+++ b/src/conf/domain_postparse.c
@@ -757,6 +757,7 @@ virDomainDeviceDefPostParseCommon(virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         ret = 0;
         break;

diff --git a/src/conf/domain_validate.c b/src/conf/domain_validate.c
index 8209e8fdae..39b8d67928 100644
--- a/src/conf/domain_validate.c
+++ b/src/conf/domain_validate.c
@@ -3022,6 +3022,33 @@ virDomainTPMDevValidate(const virDomainTPMDef *tpm)
 }


+static int
+virDomainPstoreDefValidate(const virDomainPstoreDef *pstore)
+{
+    if (pstore->backend != VIR_DOMAIN_PSTORE_BACKEND_ACPI_ERST) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("unsupported backend for pstore device: %1$s"),
+                       virDomainPstoreBackendTypeToString(pstore->backend));
+        return -1;
+    }
+
+    if (pstore->path == NULL || pstore->path[0] == '\0') {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("missing path for ACPI ERST pstore device"));
+        return -1;
+    }
+
+    if (pstore->size < 4 ||
+        !VIR_IS_POW2(pstore->size)) {
+        virReportError(VIR_ERR_XML_ERROR, "%s",
+                       _("invalid size of ACPI ERST pstore device"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
 static int
 virDomainDeviceInfoValidate(const virDomainDeviceDef *dev)
 {
@@ -3132,6 +3159,9 @@ virDomainDeviceDefValidateInternal(const virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_TPM:
         return virDomainTPMDevValidate(dev->data.tpm);

+    case VIR_DOMAIN_DEVICE_PSTORE:
+        return virDomainPstoreDefValidate(dev->data.pstore);
+
     case VIR_DOMAIN_DEVICE_LEASE:
     case VIR_DOMAIN_DEVICE_WATCHDOG:
     case VIR_DOMAIN_DEVICE_HUB:
diff --git a/src/conf/schemas/domaincommon.rng b/src/conf/schemas/domaincommon.rng
index ab5374d5f0..200009efa5 100644
--- a/src/conf/schemas/domaincommon.rng
+++ b/src/conf/schemas/domaincommon.rng
@@ -6255,6 +6255,28 @@
     </element>
   </define>

+  <define name="pstore">
+    <element name="pstore">
+      <attribute name="backend">
+        <value>acpi-erst</value>
+      </attribute>
+      <interleave>
+        <element name="path">
+          <ref name="absFilePath"/>
+        </element>
+        <element name="size">
+          <ref name="scaledInteger"/>
+        </element>
+        <optional>
+          <ref name="address"/>
+        </optional>
+        <optional>
+          <ref name="alias"/>
+        </optional>
+      </interleave>
+    </element>
+  </define>
+
   <define name="hostdev">
     <element name="hostdev">
       <interleave>
@@ -6724,6 +6746,9 @@
         <optional>
           <ref name="vsock"/>
         </optional>
+        <optional>
+          <ref name="pstore"/>
+        </optional>
       </interleave>
     </element>
   </define>
diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h
index d8e7c5278c..f18ebcca10 100644
--- a/src/conf/virconftypes.h
+++ b/src/conf/virconftypes.h
@@ -260,6 +260,8 @@ typedef struct _virDomainVsockDef virDomainVsockDef;

 typedef struct _virDomainCryptoDef virDomainCryptoDef;

+typedef struct _virDomainPstoreDef virDomainPstoreDef;
+
 typedef struct _virDomainWatchdogDef virDomainWatchdogDef;

 typedef struct _virDomainXMLOption virDomainXMLOption;
diff --git a/src/hyperv/hyperv_driver.c b/src/hyperv/hyperv_driver.c
index 7580c6a06c..43ccb9cbd7 100644
--- a/src/hyperv/hyperv_driver.c
+++ b/src/hyperv/hyperv_driver.c
@@ -3133,6 +3133,7 @@ hypervDomainAttachDeviceFlags(virDomainPtr domain, const char *xml, unsigned int
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_INTERNAL_ERROR,
                        _("Attaching devices of type %1$d is not implemented"), dev->type);
         return -1;
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index c35366c9e1..d15d6a6a9d 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -616,6 +616,8 @@ virDomainPausedReasonTypeToString;
 virDomainPMSuspendedReasonTypeFromString;
 virDomainPMSuspendedReasonTypeToString;
 virDomainProcessCapsFeatureTypeToString;
+virDomainPstoreBackendTypeFromString;
+virDomainPstoreBackendTypeToString;
 virDomainRedirdevBusTypeFromString;
 virDomainRedirdevBusTypeToString;
 virDomainRedirdevDefFind;
diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c
index 7dcae58413..e72553603d 100644
--- a/src/libxl/libxl_driver.c
+++ b/src/libxl/libxl_driver.c
@@ -3505,6 +3505,7 @@ libxlDomainAttachDeviceLive(libxlDriverPrivate *driver,
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("device type '%1$s' cannot be attached"),
                            virDomainDeviceTypeToString(dev->type));
@@ -3613,6 +3614,7 @@ libxlDomainAttachDeviceConfig(virDomainDef *vmdef, virDomainDeviceDef *dev)
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                            _("persistent attach of device is not supported"));
             return -1;
@@ -3981,6 +3983,7 @@ libxlDomainDetachDeviceLive(libxlDriverPrivate *driver,
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("device type '%1$s' cannot be detached"),
                            virDomainDeviceTypeToString(dev->type));
@@ -4071,6 +4074,7 @@ libxlDomainDetachDeviceConfig(virDomainDef *vmdef, virDomainDeviceDef *dev)
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                            _("persistent detach of device is not supported"));
             return -1;
@@ -4133,6 +4137,7 @@ libxlDomainUpdateDeviceLive(virDomainObj *vm, virDomainDeviceDef *dev)
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("device type '%1$s' cannot be updated"),
                            virDomainDeviceTypeToString(dev->type));
@@ -4195,6 +4200,7 @@ libxlDomainUpdateDeviceConfig(virDomainDef *vmdef, virDomainDeviceDef *dev)
         case VIR_DOMAIN_DEVICE_VSOCK:
         case VIR_DOMAIN_DEVICE_AUDIO:
         case VIR_DOMAIN_DEVICE_CRYPTO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                            _("persistent update of device is not supported"));
             return -1;
diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c
index f76d09e8a9..534e257f30 100644
--- a/src/lxc/lxc_driver.c
+++ b/src/lxc/lxc_driver.c
@@ -3056,6 +3056,7 @@ lxcDomainAttachDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
          virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                         _("persistent attach of device is not supported"));
          break;
@@ -3121,6 +3122,7 @@ lxcDomainUpdateDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                        _("persistent update of device is not supported"));
         break;
@@ -3202,6 +3204,7 @@ lxcDomainDetachDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_CRYPTO:
     case VIR_DOMAIN_DEVICE_AUDIO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                        _("persistent detach of device is not supported"));
         break;
@@ -3303,6 +3306,7 @@ lxcDomainAttachDeviceMknodHelper(pid_t pid G_GNUC_UNUSED,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_INTERNAL_ERROR,
                        _("Unexpected device type %1$d"),
                        data->def->type);
@@ -3974,6 +3978,7 @@ lxcDomainAttachDeviceLive(virLXCDriver *driver,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                        _("device type '%1$s' cannot be attached"),
                        virDomainDeviceTypeToString(dev->type));
@@ -4391,6 +4396,7 @@ lxcDomainDetachDeviceLive(virLXCDriver *driver,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                        _("device type '%1$s' cannot be detached"),
                        virDomainDeviceTypeToString(dev->type));
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 684de3f701..def124cc27 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -973,6 +973,7 @@ qemuBuildVirtioDevGetConfigDev(const virDomainDeviceDef *device,
         case VIR_DOMAIN_DEVICE_MEMORY:
         case VIR_DOMAIN_DEVICE_IOMMU:
         case VIR_DOMAIN_DEVICE_AUDIO:
+        case VIR_DOMAIN_DEVICE_PSTORE:
         case VIR_DOMAIN_DEVICE_LAST:
         default:
             break;
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 2134b11038..a8d7a1e525 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -6358,6 +6358,7 @@ qemuDomainDeviceDefPostParse(virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         ret = 0;
         break;

@@ -10360,6 +10361,7 @@ qemuDomainPrepareChardevSourceOne(virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         break;
     }

@@ -12290,6 +12292,7 @@ qemuDomainDeviceBackendChardevForeachOne(virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         /* no chardev backend */
         break;
     }
diff --git a/src/qemu/qemu_domain_address.c b/src/qemu/qemu_domain_address.c
index 251f5b7e1a..970ae3949d 100644
--- a/src/qemu/qemu_domain_address.c
+++ b/src/qemu/qemu_domain_address.c
@@ -470,6 +470,7 @@ qemuDomainDeviceSupportZPCI(virDomainDeviceDef *device)
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
         break;

     case VIR_DOMAIN_DEVICE_NONE:
@@ -1002,6 +1003,9 @@ qemuDomainDeviceCalculatePCIConnectFlags(virDomainDeviceDef *dev,
         }
         break;

+    case VIR_DOMAIN_DEVICE_PSTORE:
+        return pciFlags;
+
         /* These devices don't ever connect with PCI */
     case VIR_DOMAIN_DEVICE_NVRAM:
     case VIR_DOMAIN_DEVICE_TPM:
@@ -2424,6 +2428,13 @@ qemuDomainAssignDevicePCISlots(virDomainDef *def,
         }
     }

+    if (def->pstore &&
+        virDeviceInfoPCIAddressIsWanted(&def->pstore->info)) {
+        if (qemuDomainPCIAddressReserveNextAddr(addrs,
+                                                &def->pstore->info) < 0)
+            return -1;
+    }
+
     return 0;
 }

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 9f3013e231..e473018ef2 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -6863,6 +6863,7 @@ qemuDomainAttachDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_PANIC:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
          virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                         _("persistent attach of device '%1$s' is not supported"),
@@ -7081,6 +7082,7 @@ qemuDomainDetachDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_PANIC:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("persistent detach of device '%1$s' is not supported"),
@@ -7206,6 +7208,7 @@ qemuDomainUpdateDeviceConfig(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("persistent update of device '%1$s' is not supported"),
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 1f4620d833..af4229d507 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -3452,6 +3452,7 @@ qemuDomainAttachDeviceLive(virDomainObj *vm,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("live attach of device '%1$s' is not supported"),
@@ -5282,6 +5283,7 @@ qemuDomainRemoveAuditDevice(virDomainObj *vm,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         /* libvirt doesn't yet support detaching these devices */
         break;
@@ -5386,6 +5388,7 @@ qemuDomainRemoveDevice(virQEMUDriver *driver,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("don't know how to remove a %1$s device"),
@@ -6270,6 +6273,7 @@ qemuDomainDetachDeviceLive(virDomainObj *vm,
     case VIR_DOMAIN_DEVICE_IOMMU:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("live detach of device '%1$s' is not supported"),
@@ -7259,6 +7263,7 @@ qemuDomainUpdateDeviceLive(virDomainObj *vm,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                        _("live update of device '%1$s' is not supported"),
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index 8840306bfd..0e8f0f977f 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -4669,6 +4669,29 @@ qemuValidateDomainDeviceDefCrypto(virDomainCryptoDef *crypto,
 }


+static int
+qemuValidateDomainDeviceDefPstore(virDomainPstoreDef *pstore,
+                                  const virDomainDef *def G_GNUC_UNUSED,
+                                  virQEMUCaps *qemuCaps)
+{
+    if (pstore->backend == VIR_DOMAIN_PSTORE_BACKEND_ACPI_ERST &&
+        !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ACPI_ERST)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("acpi-erst backend of pstore device is not supported"));
+        return -1;
+    }
+
+    if (pstore->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_NONE &&
+        pstore->info.type != VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("ACPI ERST device must reside on a PCI bus"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
 static int
 qemuSoundCodecTypeToCaps(int type)
 {
@@ -5372,6 +5395,9 @@ qemuValidateDomainDeviceDef(const virDomainDeviceDef *dev,
     case VIR_DOMAIN_DEVICE_CRYPTO:
         return qemuValidateDomainDeviceDefCrypto(dev->data.crypto, def, qemuCaps);

+    case VIR_DOMAIN_DEVICE_PSTORE:
+        return qemuValidateDomainDeviceDefPstore(dev->data.pstore, def, qemuCaps);
+
     case VIR_DOMAIN_DEVICE_LEASE:
     case VIR_DOMAIN_DEVICE_PANIC:
     case VIR_DOMAIN_DEVICE_NONE:
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index df6a86ea44..7cb77f044d 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -10294,6 +10294,7 @@ testDomainUpdateDevice(virDomainDef *vmdef,
     case VIR_DOMAIN_DEVICE_VSOCK:
     case VIR_DOMAIN_DEVICE_AUDIO:
     case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_PSTORE:
     case VIR_DOMAIN_DEVICE_LAST:
         virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
                        _("persistent update of device '%1$s' is not supported"),
diff --git a/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.args b/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.args
new file mode 100644
index 0000000000..d7c4708acb
--- /dev/null
+++ b/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.args
@@ -0,0 +1,36 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/var/lib/libvirt/qemu/domain--1-guest \
+USER=test \
+LOGNAME=test \
+XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-guest/.local/share \
+XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-guest/.cache \
+XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-guest/.config \
+/usr/bin/qemu-system-x86_64 \
+-name guest=guest,debug-threads=on \
+-S \
+-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-guest/master-key.aes"}' \
+-machine pc-q35-9.0,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=on \
+-accel kvm \
+-cpu qemu64 \
+-m size=1048576k \
+-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":1073741824}' \
+-overcommit mem-lock=off \
+-smp 1,sockets=1,cores=1,threads=1 \
+-uuid 63840878-0deb-4095-97e6-fc444d9bc9fa \
+-display none \
+-no-user-config \
+-nodefaults \
+-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
+-mon chardev=charmonitor,id=monitor,mode=control \
+-rtc base=utc \
+-no-shutdown \
+-boot strict=on \
+-device '{"driver":"pcie-root-port","port":8,"chassis":1,"id":"pci.1","bus":"pcie.0","multifunction":true,"addr":"0x1"}' \
+-device '{"driver":"pcie-pci-bridge","id":"pci.2","bus":"pci.1","addr":"0x0"}' \
+-device '{"driver":"pcie-root-port","port":9,"chassis":3,"id":"pci.3","bus":"pcie.0","addr":"0x1.0x1"}' \
+-audiodev '{"id":"audio1","driver":"none"}' \
+-global ICH9-LPC.noreboot=off \
+-watchdog-action reset \
+-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
+-msg timestamp=on
diff --git a/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.xml b/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.xml
new file mode 120000
index 0000000000..11ade68605
--- /dev/null
+++ b/tests/qemuxmlconfdata/pstore-acpi-erst.x86_64-latest.xml
@@ -0,0 +1 @@
+pstore-acpi-erst.xml
\ No newline at end of file
diff --git a/tests/qemuxmlconfdata/pstore-acpi-erst.xml b/tests/qemuxmlconfdata/pstore-acpi-erst.xml
new file mode 100644
index 0000000000..9b9ba266b2
--- /dev/null
+++ b/tests/qemuxmlconfdata/pstore-acpi-erst.xml
@@ -0,0 +1,53 @@
+<domain type='kvm'>
+  <name>guest</name>
+  <uuid>63840878-0deb-4095-97e6-fc444d9bc9fa</uuid>
+  <memory unit='KiB'>1048576</memory>
+  <currentMemory unit='KiB'>1048576</currentMemory>
+  <vcpu placement='static'>1</vcpu>
+  <os>
+    <type arch='x86_64' machine='pc-q35-9.0'>hvm</type>
+    <boot dev='hd'/>
+  </os>
+  <features>
+    <acpi/>
+  </features>
+  <cpu mode='custom' match='exact' check='none'>
+    <model fallback='forbid'>qemu64</model>
+  </cpu>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>destroy</on_crash>
+  <devices>
+    <emulator>/usr/bin/qemu-system-x86_64</emulator>
+    <controller type='usb' index='0' model='none'/>
+    <controller type='sata' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
+    </controller>
+    <controller type='pci' index='0' model='pcie-root'/>
+    <controller type='pci' index='1' model='pcie-root-port'>
+      <model name='pcie-root-port'/>
+      <target chassis='1' port='0x8'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
+    </controller>
+    <controller type='pci' index='2' model='pcie-to-pci-bridge'>
+      <model name='pcie-pci-bridge'/>
+      <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
+    </controller>
+    <controller type='pci' index='3' model='pcie-root-port'>
+      <model name='pcie-root-port'/>
+      <target chassis='3' port='0x9'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    <input type='mouse' bus='ps2'/>
+    <input type='keyboard' bus='ps2'/>
+    <audio id='1' type='none'/>
+    <watchdog model='itco' action='reset'/>
+    <memballoon model='none'/>
+    <pstore backend='acpi-erst'>
+      <path>/tmp/guest_acpi_esrt</path>
+      <size unit='KiB'>8</size>
+      <address type='pci' domain='0x0000' bus='0x02' slot='0x01' function='0x0'/>
+    </pstore>
+  </devices>
+</domain>
diff --git a/tests/qemuxmlconftest.c b/tests/qemuxmlconftest.c
index cc984440ea..68d1127cf4 100644
--- a/tests/qemuxmlconftest.c
+++ b/tests/qemuxmlconftest.c
@@ -2949,6 +2949,7 @@ mymain(void)
     DO_TEST_CAPS_LATEST("mtp-usb-device")
     DO_TEST_CAPS_LATEST("net-usb")
     DO_TEST_CAPS_LATEST("sound-device-virtio")
+    DO_TEST_CAPS_LATEST("pstore-acpi-erst")

     DO_TEST_CAPS_LATEST_FAILURE("disk-network-iscsi-zero-hosts-invalid")
     DO_TEST_CAPS_LATEST_PARSE_ERROR("hostdev-scsi-vhost-rawio-invalid")
--
2.44.2

Reviewed-by: Kristina Hanicova <khanicov@redhat.com>

Kristina