QEMU has -fw_cfg which allows users to tweak how firmware
configures itself and/or provide new configuration blobs.
Introduce new <sysinfo/> type "fwcfg" that will hold these
new blobs.
It's possible to either specify new value as a string or
provide a filename which contents then serve as the value.
Signed-off-by: Michal Privoznik <mprivozn(a)redhat.com>
---
docs/formatdomain.html.in | 32 +++
docs/schemas/domaincommon.rng | 145 +++++++------
src/conf/domain_conf.c | 193 ++++++++++++++----
src/conf/domain_conf.h | 4 +-
src/qemu/qemu_command.c | 10 +-
src/util/virsysinfo.c | 52 ++++-
src/util/virsysinfo.h | 16 +-
tests/qemuxml2argvdata/smbios-type-fwcfg.xml | 63 ++++++
.../qemuxml2xmloutdata/smbios-type-fwcfg.xml | 1 +
tests/qemuxml2xmltest.c | 1 +
10 files changed, 403 insertions(+), 114 deletions(-)
create mode 100644 tests/qemuxml2argvdata/smbios-type-fwcfg.xml
create mode 120000 tests/qemuxml2xmloutdata/smbios-type-fwcfg.xml
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index 6ebf19ae57..20c28a47e3 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -479,6 +479,10 @@
<entry>otherappname:more arbitrary data</entry>
</oemStrings>
</sysinfo>
+<sysinfo type='fwcfg'>
+ <entry name='opt/com.example/name'>example
value</entry>
+ <entry name='opt/com.coreos/config'
file='/tmp/provision.ign'/>
+</sysinfo>
...</pre>
<p>
@@ -593,6 +597,34 @@
</dd>
</dl>
</dd>
+
+ <dt><code>fwcfg</code></dt>
+ <dd>
+ Some hypervisors provide unified way to tweak how firmware configures
+ itself, or may contain tables to be installed for the guest OS, for
+ instance boot order, ACPI, SMBIOS, etc. It even allows users to define
+ their own config blobs. In case of QEMU, these then appear under domain's
+ sysfs, under <code>/sys/firmware/qemu_fw_cfg</code>. Note, that these
+ values apply regardless the <smbios/> mode under
<os/>.
+ <span class="since">Since 6.5.0</span>
+
+<pre>
+ <smbios type='fwcfg'>
+ <entry name='opt/com.example/name'>example
value</entry>
+ <entry name='opt/com.coreos/config'
file='/tmp/provision.ign'/>
+ </smbios>
+</pre>
+
+ The <code>smbios</code> element can have multiple
<code>entry</code>
+ child elements. Each element then has mandatory <code>name</code>
+ attribute, which defines the name of the blob and must begin with
+ <code>"opt/"</code> and to avoid clashing with other names
is advised to
+ be in form <code>"opt/$RFQDN/$name"</code> where
<code>$RFQDN</code> is a
+ reverse fully qualified domain name you control.
+ Then, the element can either contain the value (to set the blob value
+ directly), or <code>file</code> attribute (to set the blob value from
+ the file).
+ </dd>
</dl>
<h3><a id="elementsCPUAllocation">CPU
Allocation</a></h3>
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index 19242a9a32..e3bf7f5d55 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -46,9 +46,9 @@
<optional>
<ref name="cpu"/>
</optional>
- <optional>
+ <zeroOrMore>
<ref name="sysinfo"/>
- </optional>
+ </zeroOrMore>
<ref name="os"/>
<ref name="clock"/>
<ref name="resources"/>
@@ -5511,68 +5511,95 @@
-->
<define name="sysinfo">
<element name="sysinfo">
- <attribute name="type">
- <value>smbios</value>
- </attribute>
- <interleave>
- <optional>
- <element name="bios">
- <oneOrMore>
- <element name="entry">
- <attribute name="name">
- <ref name="sysinfo-bios-name"/>
- </attribute>
- <ref name="sysinfo-value"/>
+ <choice>
+ <group>
+ <attribute name="type">
+ <value>smbios</value>
+ </attribute>
+ <interleave>
+ <optional>
+ <element name="bios">
+ <oneOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <ref name="sysinfo-bios-name"/>
+ </attribute>
+ <ref name="sysinfo-value"/>
+ </element>
+ </oneOrMore>
</element>
- </oneOrMore>
- </element>
- </optional>
- <optional>
- <element name="system">
- <oneOrMore>
- <element name="entry">
- <attribute name="name">
- <ref name="sysinfo-system-name"/>
- </attribute>
- <ref name="sysinfo-value"/>
+ </optional>
+ <optional>
+ <element name="system">
+ <oneOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <ref name="sysinfo-system-name"/>
+ </attribute>
+ <ref name="sysinfo-value"/>
+ </element>
+ </oneOrMore>
</element>
- </oneOrMore>
- </element>
- </optional>
- <zeroOrMore>
- <element name="baseBoard">
- <oneOrMore>
- <element name="entry">
- <attribute name="name">
- <ref name="sysinfo-baseBoard-name"/>
- </attribute>
- <ref name="sysinfo-value"/>
+ </optional>
+ <zeroOrMore>
+ <element name="baseBoard">
+ <oneOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <ref name="sysinfo-baseBoard-name"/>
+ </attribute>
+ <ref name="sysinfo-value"/>
+ </element>
+ </oneOrMore>
</element>
- </oneOrMore>
- </element>
- </zeroOrMore>
- <optional>
- <element name="chassis">
- <oneOrMore>
- <element name="entry">
- <attribute name="name">
- <ref name="sysinfo-chassis-name"/>
- </attribute>
- <ref name="sysinfo-value"/>
+ </zeroOrMore>
+ <optional>
+ <element name="chassis">
+ <oneOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <ref name="sysinfo-chassis-name"/>
+ </attribute>
+ <ref name="sysinfo-value"/>
+ </element>
+ </oneOrMore>
</element>
- </oneOrMore>
- </element>
- </optional>
- <optional>
- <element name="oemStrings">
- <oneOrMore>
- <element name="entry">
- <ref name="sysinfo-value"/>
+ </optional>
+ <optional>
+ <element name="oemStrings">
+ <oneOrMore>
+ <element name="entry">
+ <ref name="sysinfo-value"/>
+ </element>
+ </oneOrMore>
</element>
- </oneOrMore>
- </element>
- </optional>
- </interleave>
+ </optional>
+ </interleave>
+ </group>
+ <group>
+ <attribute name="type">
+ <value>fwcfg</value>
+ </attribute>
+ <zeroOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <data type="string"/>
+ </attribute>
+ <choice>
+ <group>
+ <attribute name="file">
+ <data type="string"/>
+ </attribute>
+ <empty/>
+ </group>
+ <group>
+ <ref name="sysinfo-value"/>
+ </group>
+ </choice>
+ </element>
+ </zeroOrMore>
+ </group>
+ </choice>
</element>
</define>
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 57a5b7befe..e9336fd72d 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -3551,7 +3551,9 @@ void virDomainDefFree(virDomainDefPtr def)
virDomainNumaFree(def->numa);
- virSysinfoDefFree(def->sysinfo);
+ for (i = 0; i < def->nsysinfo; i++)
+ virSysinfoDefFree(def->sysinfo[i]);
+ VIR_FREE(def->sysinfo);
virDomainRedirFilterDefFree(def->redirfilter);
@@ -15708,67 +15710,153 @@ virSysinfoChassisParseXML(xmlNodePtr node,
}
-static virSysinfoDefPtr
-virSysinfoParseXML(xmlNodePtr node,
- xmlXPathContextPtr ctxt,
- unsigned char *domUUID,
- bool uuid_generated)
+static int
+virSysinfoParseSMBIOSDef(virSysinfoDefPtr def,
+ xmlXPathContextPtr ctxt,
+ unsigned char *domUUID,
+ bool uuid_generated)
{
- VIR_XPATH_NODE_AUTORESTORE(ctxt);
- virSysinfoDefPtr def;
xmlNodePtr tmpnode;
- g_autofree char *type = NULL;
-
- ctxt->node = node;
-
- if (!virXMLNodeNameEqual(node, "sysinfo")) {
- virReportError(VIR_ERR_XML_ERROR, "%s",
- _("XML does not contain expected 'sysinfo'
element"));
- return NULL;
- }
-
- if (VIR_ALLOC(def) < 0)
- return NULL;
-
- type = virXMLPropString(node, "type");
- if (type == NULL) {
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("sysinfo must contain a type attribute"));
- goto error;
- }
- if ((def->type = virSysinfoTypeFromString(type)) < 0) {
- virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
- _("unknown sysinfo type '%s'"), type);
- goto error;
- }
/* Extract BIOS related metadata */
if ((tmpnode = virXPathNode("./bios[1]", ctxt)) != NULL) {
if (virSysinfoBIOSParseXML(tmpnode, ctxt, &def->bios) < 0)
- goto error;
+ return -1;
}
/* Extract system related metadata */
if ((tmpnode = virXPathNode("./system[1]", ctxt)) != NULL) {
if (virSysinfoSystemParseXML(tmpnode, ctxt, &def->system,
domUUID, uuid_generated) < 0)
- goto error;
+ return -1;
}
/* Extract system base board metadata */
if (virSysinfoBaseBoardParseXML(ctxt, &def->baseBoard,
&def->nbaseBoard) < 0)
- goto error;
+ return -1;
/* Extract chassis related metadata */
if ((tmpnode = virXPathNode("./chassis[1]", ctxt)) != NULL) {
if (virSysinfoChassisParseXML(tmpnode, ctxt, &def->chassis) < 0)
- goto error;
+ return -1;
}
/* Extract system related metadata */
if ((tmpnode = virXPathNode("./oemStrings[1]", ctxt)) != NULL) {
if (virSysinfoOEMStringsParseXML(tmpnode, ctxt, &def->oemStrings) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+virSysinfoParseFWCfgDef(virSysinfoDefPtr def,
+ xmlNodePtr node,
+ xmlXPathContextPtr ctxt)
+{
+ VIR_XPATH_NODE_AUTORESTORE(ctxt);
+ g_autofree xmlNodePtr *nodes = NULL;
+ int n;
+ size_t i;
+
+ ctxt->node = node;
+
+ if ((n = virXPathNodeSet("./entry", ctxt, &nodes)) < 0)
+ return -1;
+
+ if (n == 0)
+ return 0;
+
+ def->fw_cfgs = g_new0(virSysinfoFWCfgDef, n);
+
+ for (i = 0; i < n; i++) {
+ g_autofree char *name = NULL;
+ g_autofree char *value = NULL;
+ g_autofree char *file = NULL;
+ g_autofree char *sanitizedFile = NULL;
+
+ if (!(name = virXMLPropString(nodes[i], "name"))) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("Firmware entry is missing 'name'
attribute"));
+ return -1;
+ }
+
+ value = virXMLNodeContentString(nodes[i]);
+ file = virXMLPropString(nodes[i], "file");
+
+ if (virStringIsEmpty(value))
+ VIR_FREE(value);
+
+ if (!value && !file) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("Firmware entry must have either value or "
+ "'file' attribute"));
+ return -1;
+ }
+
+ if (file)
+ sanitizedFile = virFileSanitizePath(file);
+
+ def->fw_cfgs[i].name = g_steal_pointer(&name);
+ def->fw_cfgs[i].value = g_steal_pointer(&value);
+ def->fw_cfgs[i].file = g_steal_pointer(&sanitizedFile);
+ def->nfw_cfgs++;
+ }
+
+ return 0;
+}
+
+
+static virSysinfoDefPtr
+virSysinfoParseXML(xmlNodePtr node,
+ xmlXPathContextPtr ctxt,
+ unsigned char *domUUID,
+ bool uuid_generated)
+{
+ VIR_XPATH_NODE_AUTORESTORE(ctxt);
+ virSysinfoDefPtr def;
+ g_autofree char *typeStr = NULL;
+ int type;
+
+ ctxt->node = node;
+
+ if (!virXMLNodeNameEqual(node, "sysinfo")) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("XML does not contain expected 'sysinfo'
element"));
+ return NULL;
+ }
+
+ if (VIR_ALLOC(def) < 0)
+ return NULL;
+
+ typeStr = virXMLPropString(node, "type");
+ if (typeStr == NULL) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("sysinfo must contain a type attribute"));
+ goto error;
+ }
+ if ((type = virSysinfoTypeFromString(typeStr)) < 0) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("unknown sysinfo type '%s'"), typeStr);
+ goto error;
+ }
+ def->type = type;
+
+ switch (def->type) {
+ case VIR_SYSINFO_SMBIOS:
+ if (virSysinfoParseSMBIOSDef(def, ctxt, domUUID, uuid_generated) < 0)
goto error;
+ break;
+
+ case VIR_SYSINFO_FWCFG:
+ if (virSysinfoParseFWCfgDef(def, node, ctxt) < 0)
+ goto error;
+ break;
+
+ case VIR_SYSINFO_LAST:
+ break;
}
return def;
@@ -22173,6 +22261,7 @@ virDomainDefParseXML(xmlDocPtr xml,
def->idmap.ngidmap = n;
}
+ VIR_FREE(nodes);
if ((def->idmap.uidmap && !def->idmap.gidmap) ||
(!def->idmap.uidmap && def->idmap.gidmap)) {
@@ -22181,13 +22270,21 @@ virDomainDefParseXML(xmlDocPtr xml,
goto error;
}
- if ((node = virXPathNode("./sysinfo[1]", ctxt)) != NULL) {
- def->sysinfo = virSysinfoParseXML(node, ctxt,
- def->uuid, uuid_generated);
+ if ((n = virXPathNodeSet("./sysinfo", ctxt, &nodes)) < 0)
+ goto error;
- if (def->sysinfo == NULL)
+ def->sysinfo = g_new0(virSysinfoDefPtr, n);
+
+ for (i = 0; i < n; i++) {
+ virSysinfoDefPtr sysinfo = virSysinfoParseXML(nodes[i], ctxt,
+ def->uuid, uuid_generated);
+
+ if (!sysinfo)
goto error;
+
+ def->sysinfo[def->nsysinfo++] = sysinfo;
}
+ VIR_FREE(nodes);
if ((tmp = virXPathString("string(./os/smbios/@mode)", ctxt))) {
int mode;
@@ -24072,8 +24169,16 @@ virDomainDefCheckABIStabilityFlags(virDomainDefPtr src,
if (!virCPUDefIsEqual(src->cpu, dst->cpu, true))
goto error;
- if (!virSysinfoIsEqual(src->sysinfo, dst->sysinfo))
- goto error;
+ if (src->nsysinfo != dst->nsysinfo) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("Target domain count of sysinfo does not match
source"));
+ goto error;
+ }
+
+ for (i = 0; i < src->nsysinfo; i++) {
+ if (!virSysinfoIsEqual(src->sysinfo[i], dst->sysinfo[i]))
+ goto error;
+ }
if (src->ndisks != dst->ndisks) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
@@ -29507,8 +29612,8 @@ virDomainDefFormatInternalSetRootName(virDomainDefPtr def,
if (def->resource)
virDomainResourceDefFormat(buf, def->resource);
- if (def->sysinfo)
- ignore_value(virSysinfoFormat(buf, def->sysinfo));
+ for (i = 0; i < def->nsysinfo; i++)
+ virSysinfoFormat(buf, def->sysinfo[i]);
if (def->os.bootloader) {
virBufferEscapeString(buf,
"<bootloader>%s</bootloader>\n",
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index e152c599ca..bda8fb6bce 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2624,13 +2624,15 @@ struct _virDomainDef {
size_t npanics;
virDomainPanicDefPtr *panics;
+ size_t nsysinfo;
+ virSysinfoDefPtr *sysinfo;
+
/* Only 1 */
virDomainWatchdogDefPtr watchdog;
virDomainMemballoonDefPtr memballoon;
virDomainNVRAMDefPtr nvram;
virDomainTPMDefPtr tpm;
virCPUDefPtr cpu;
- virSysinfoDefPtr sysinfo;
virDomainRedirFilterDefPtr redirfilter;
virDomainIOMMUDefPtr iommu;
virDomainVsockDefPtr vsock;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index d9e99d9d1a..86db7d0606 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -5736,13 +5736,19 @@ qemuBuildSmbiosCommandLine(virCommandPtr cmd,
/* Host and guest uuid must differ, by definition of UUID. */
skip_uuid = true;
} else if (def->os.smbios_mode == VIR_DOMAIN_SMBIOS_SYSINFO) {
- if (def->sysinfo == NULL) {
+ for (i = 0; i < def->nsysinfo; i++) {
+ if (def->sysinfo[i]->type == VIR_SYSINFO_SMBIOS) {
+ source = def->sysinfo[i];
+ break;
+ }
+ }
+
+ if (!source) {
virReportError(VIR_ERR_XML_ERROR,
_("Domain '%s' sysinfo are not available"),
def->name);
return -1;
}
- source = def->sysinfo;
/* domain_conf guaranteed that system_uuid matches guest uuid. */
}
if (source != NULL) {
diff --git a/src/util/virsysinfo.c b/src/util/virsysinfo.c
index 41f4d1cff9..5b0ad2e6ce 100644
--- a/src/util/virsysinfo.c
+++ b/src/util/virsysinfo.c
@@ -43,6 +43,7 @@ VIR_LOG_INIT("util.sysinfo");
VIR_ENUM_IMPL(virSysinfo,
VIR_SYSINFO_LAST,
"smbios",
+ "fwcfg"
);
static const char *sysinfoDmidecode = DMIDECODE;
@@ -1436,6 +1437,40 @@ virSysinfoOEMStringsFormat(virBufferPtr buf,
virSysinfoOEMStringsDefPtr def)
virBufferAddLit(buf, "</oemStrings>\n");
}
+
+static void
+virSysinfoFormatSMBIOS(virBufferPtr buf,
+ virSysinfoDefPtr def)
+{
+ virSysinfoBIOSFormat(buf, def->bios);
+ virSysinfoSystemFormat(buf, def->system);
+ virSysinfoBaseBoardFormat(buf, def->baseBoard, def->nbaseBoard);
+ virSysinfoChassisFormat(buf, def->chassis);
+ virSysinfoProcessorFormat(buf, def);
+ virSysinfoMemoryFormat(buf, def);
+ virSysinfoOEMStringsFormat(buf, def->oemStrings);
+}
+
+
+static void
+virSysinfoFormatFWCfg(virBufferPtr buf,
+ virSysinfoDefPtr def)
+{
+ size_t i;
+
+ for (i = 0; i < def->nfw_cfgs; i++) {
+ const virSysinfoFWCfgDef *f = &def->fw_cfgs[i];
+
+ virBufferAsprintf(buf, "<entry name='%s'", f->name);
+
+ if (f->file)
+ virBufferEscapeString(buf, " file='%s'/>\n",
f->file);
+ else
+ virBufferEscapeString(buf, ">%s</entry>\n", f->value);
+ }
+}
+
+
/**
* virSysinfoFormat:
* @buf: buffer to append output to (may use auto-indentation)
@@ -1458,13 +1493,16 @@ virSysinfoFormat(virBufferPtr buf, virSysinfoDefPtr def)
return -1;
}
- virSysinfoBIOSFormat(&childrenBuf, def->bios);
- virSysinfoSystemFormat(&childrenBuf, def->system);
- virSysinfoBaseBoardFormat(&childrenBuf, def->baseBoard, def->nbaseBoard);
- virSysinfoChassisFormat(&childrenBuf, def->chassis);
- virSysinfoProcessorFormat(&childrenBuf, def);
- virSysinfoMemoryFormat(&childrenBuf, def);
- virSysinfoOEMStringsFormat(&childrenBuf, def->oemStrings);
+ switch (def->type) {
+ case VIR_SYSINFO_SMBIOS:
+ virSysinfoFormatSMBIOS(&childrenBuf, def);
+ break;
+ case VIR_SYSINFO_FWCFG:
+ virSysinfoFormatFWCfg(&childrenBuf, def);
+ break;
+ case VIR_SYSINFO_LAST:
+ break;
+ }
virBufferAsprintf(&attrBuf, " type='%s'", type);
diff --git a/src/util/virsysinfo.h b/src/util/virsysinfo.h
index f1d280e1c9..6b25969a4b 100644
--- a/src/util/virsysinfo.h
+++ b/src/util/virsysinfo.h
@@ -27,6 +27,7 @@
typedef enum {
VIR_SYSINFO_SMBIOS,
+ VIR_SYSINFO_FWCFG,
VIR_SYSINFO_LAST
} virSysinfoType;
@@ -112,11 +113,20 @@ struct _virSysinfoOEMStringsDef {
char **values;
};
+typedef struct _virSysinfoFWCfgDef virSysinfoFWCfgDef;
+typedef virSysinfoFWCfgDef *virSysinfoFWCfgDefPtr;
+struct _virSysinfoFWCfgDef {
+ char *name;
+ char *value;
+ char *file;
+};
+
typedef struct _virSysinfoDef virSysinfoDef;
typedef virSysinfoDef *virSysinfoDefPtr;
struct _virSysinfoDef {
- int type;
+ virSysinfoType type;
+ /* The following members are valid for type == VIR_SYSINFO_SMBIOS */
virSysinfoBIOSDefPtr bios;
virSysinfoSystemDefPtr system;
@@ -132,6 +142,10 @@ struct _virSysinfoDef {
virSysinfoMemoryDefPtr memory;
virSysinfoOEMStringsDefPtr oemStrings;
+
+ /* The following members are valid for type == VIR_SYSINFO_FWCFG */
+ size_t nfw_cfgs;
+ virSysinfoFWCfgDefPtr fw_cfgs;
};
virSysinfoDefPtr virSysinfoRead(void);
diff --git a/tests/qemuxml2argvdata/smbios-type-fwcfg.xml
b/tests/qemuxml2argvdata/smbios-type-fwcfg.xml
new file mode 100644
index 0000000000..72da0fe045
--- /dev/null
+++ b/tests/qemuxml2argvdata/smbios-type-fwcfg.xml
@@ -0,0 +1,63 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory unit='KiB'>219100</memory>
+ <currentMemory unit='KiB'>219100</currentMemory>
+ <vcpu placement='static'>1</vcpu>
+ <sysinfo type='smbios'>
+ <bios>
+ <entry name='vendor'>LENOVO</entry>
+ <entry name='version'>6FET82WW (3.12 )</entry>
+ </bios>
+ <system>
+ <entry name='manufacturer'>Fedora</entry>
+ <entry name='product'>Virt-Manager</entry>
+ <entry name='version'>0.8.2-3.fc14</entry>
+ <entry
name='serial'>32dfcb37-5af1-552b-357c-be8c3aa38310</entry>
+ <entry
name='uuid'>c7a5fdbd-edaf-9455-926a-d65c16db1809</entry>
+ <entry name='sku'>1234567890</entry>
+ <entry name='family'>Red Hat</entry>
+ </system>
+ <baseBoard>
+ <entry name='manufacturer'>Lenovo</entry>
+ <entry name='product'>20BE0061MC</entry>
+ <entry name='version'>0B98401 Pro</entry>
+ <entry name='serial'>W1KS427111E</entry>
+ <entry name='location'>Not Available</entry>
+ </baseBoard>
+ </sysinfo>
+ <sysinfo type='fwcfg'>
+ <entry name='opt/com.example/name'>example value</entry>
+ <entry name='opt/com.coreos/config'
file='/tmp/provision.ign'/>
+ </sysinfo>
+ <os>
+ <type arch='i686' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ <smbios mode='sysinfo'/>
+ </os>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>destroy</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-i386</emulator>
+ <disk type='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='hda' bus='ide'/>
+ <address type='drive' controller='0' bus='0'
target='0' unit='0'/>
+ </disk>
+ <controller type='ide' index='0'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x1'/>
+ </controller>
+ <controller type='usb' index='0'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='virtio'>
+ <address type='pci' domain='0x0000' bus='0x00'
slot='0x03' function='0x0'/>
+ </memballoon>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2xmloutdata/smbios-type-fwcfg.xml
b/tests/qemuxml2xmloutdata/smbios-type-fwcfg.xml
new file mode 120000
index 0000000000..09a2682910
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/smbios-type-fwcfg.xml
@@ -0,0 +1 @@
+../qemuxml2argvdata/smbios-type-fwcfg.xml
\ No newline at end of file
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index dcc7b29ded..157e686f2a 100644
--- a/tests/qemuxml2xmltest.c
+++ b/tests/qemuxml2xmltest.c
@@ -1125,6 +1125,7 @@ mymain(void)
DO_TEST("shmem-plain-doorbell", NONE);
DO_TEST("smbios", NONE);
DO_TEST("smbios-multiple-type2", NONE);
+ DO_TEST("smbios-type-fwcfg", NONE);
DO_TEST_CAPS_LATEST("os-firmware-bios");
DO_TEST_CAPS_LATEST("os-firmware-efi");
--
2.26.2