conf: Add firmware blob configuration

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@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Michal Privoznik 2020-06-04 14:15:40 +02:00
parent b44898dd31
commit 3dda889a44
10 changed files with 400 additions and 111 deletions

View File

@ -479,6 +479,10 @@
&lt;entry&gt;otherappname:more arbitrary data&lt;/entry&gt; &lt;entry&gt;otherappname:more arbitrary data&lt;/entry&gt;
&lt;/oemStrings&gt; &lt;/oemStrings&gt;
&lt;/sysinfo&gt; &lt;/sysinfo&gt;
&lt;sysinfo type='fwcfg'&gt;
&lt;entry name='opt/com.example/name'&gt;example value&lt;/entry&gt;
&lt;entry name='opt/com.coreos/config' file='/tmp/provision.ign'/&gt;
&lt;/sysinfo&gt;
...</pre> ...</pre>
<p> <p>
@ -593,6 +597,34 @@
</dd> </dd>
</dl> </dl>
</dd> </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 &lt;smbios/&gt; mode under &lt;os/&gt;.
<span class="since">Since 6.5.0</span>
<pre>
&lt;smbios type='fwcfg'&gt;
&lt;entry name='opt/com.example/name'&gt;example value&lt;/entry&gt;
&lt;entry name='opt/com.coreos/config' file='/tmp/provision.ign'/&gt;
&lt;/smbios&gt;
</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> </dl>
<h3><a id="elementsCPUAllocation">CPU Allocation</a></h3> <h3><a id="elementsCPUAllocation">CPU Allocation</a></h3>

View File

@ -46,9 +46,9 @@
<optional> <optional>
<ref name="cpu"/> <ref name="cpu"/>
</optional> </optional>
<optional> <zeroOrMore>
<ref name="sysinfo"/> <ref name="sysinfo"/>
</optional> </zeroOrMore>
<ref name="os"/> <ref name="os"/>
<ref name="clock"/> <ref name="clock"/>
<ref name="resources"/> <ref name="resources"/>
@ -5511,6 +5511,8 @@
--> -->
<define name="sysinfo"> <define name="sysinfo">
<element name="sysinfo"> <element name="sysinfo">
<choice>
<group>
<attribute name="type"> <attribute name="type">
<value>smbios</value> <value>smbios</value>
</attribute> </attribute>
@ -5573,6 +5575,31 @@
</element> </element>
</optional> </optional>
</interleave> </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> </element>
</define> </define>

View File

@ -3551,7 +3551,9 @@ void virDomainDefFree(virDomainDefPtr def)
virDomainNumaFree(def->numa); virDomainNumaFree(def->numa);
virSysinfoDefFree(def->sysinfo); for (i = 0; i < def->nsysinfo; i++)
virSysinfoDefFree(def->sysinfo[i]);
VIR_FREE(def->sysinfo);
virDomainRedirFilterDefFree(def->redirfilter); virDomainRedirFilterDefFree(def->redirfilter);
@ -15708,6 +15710,105 @@ virSysinfoChassisParseXML(xmlNodePtr node,
} }
static int
virSysinfoParseSMBIOSDef(virSysinfoDefPtr def,
xmlXPathContextPtr ctxt,
unsigned char *domUUID,
bool uuid_generated)
{
xmlNodePtr tmpnode;
/* Extract BIOS related metadata */
if ((tmpnode = virXPathNode("./bios[1]", ctxt)) != NULL) {
if (virSysinfoBIOSParseXML(tmpnode, ctxt, &def->bios) < 0)
return -1;
}
/* Extract system related metadata */
if ((tmpnode = virXPathNode("./system[1]", ctxt)) != NULL) {
if (virSysinfoSystemParseXML(tmpnode, ctxt, &def->system,
domUUID, uuid_generated) < 0)
return -1;
}
/* Extract system base board metadata */
if (virSysinfoBaseBoardParseXML(ctxt, &def->baseBoard, &def->nbaseBoard) < 0)
return -1;
/* Extract chassis related metadata */
if ((tmpnode = virXPathNode("./chassis[1]", ctxt)) != NULL) {
if (virSysinfoChassisParseXML(tmpnode, ctxt, &def->chassis) < 0)
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 static virSysinfoDefPtr
virSysinfoParseXML(xmlNodePtr node, virSysinfoParseXML(xmlNodePtr node,
xmlXPathContextPtr ctxt, xmlXPathContextPtr ctxt,
@ -15716,8 +15817,8 @@ virSysinfoParseXML(xmlNodePtr node,
{ {
VIR_XPATH_NODE_AUTORESTORE(ctxt); VIR_XPATH_NODE_AUTORESTORE(ctxt);
virSysinfoDefPtr def; virSysinfoDefPtr def;
xmlNodePtr tmpnode; g_autofree char *typeStr = NULL;
g_autofree char *type = NULL; int type;
ctxt->node = node; ctxt->node = node;
@ -15730,45 +15831,32 @@ virSysinfoParseXML(xmlNodePtr node,
if (VIR_ALLOC(def) < 0) if (VIR_ALLOC(def) < 0)
return NULL; return NULL;
type = virXMLPropString(node, "type"); typeStr = virXMLPropString(node, "type");
if (type == NULL) { if (typeStr == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("sysinfo must contain a type attribute")); _("sysinfo must contain a type attribute"));
goto error; goto error;
} }
if ((def->type = virSysinfoTypeFromString(type)) < 0) { if ((type = virSysinfoTypeFromString(typeStr)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown sysinfo type '%s'"), type); _("unknown sysinfo type '%s'"), typeStr);
goto error; goto error;
} }
def->type = type;
/* Extract BIOS related metadata */ switch (def->type) {
if ((tmpnode = virXPathNode("./bios[1]", ctxt)) != NULL) { case VIR_SYSINFO_SMBIOS:
if (virSysinfoBIOSParseXML(tmpnode, ctxt, &def->bios) < 0) if (virSysinfoParseSMBIOSDef(def, ctxt, domUUID, uuid_generated) < 0)
goto error; goto error;
} break;
/* Extract system related metadata */ case VIR_SYSINFO_FWCFG:
if ((tmpnode = virXPathNode("./system[1]", ctxt)) != NULL) { if (virSysinfoParseFWCfgDef(def, node, ctxt) < 0)
if (virSysinfoSystemParseXML(tmpnode, ctxt, &def->system,
domUUID, uuid_generated) < 0)
goto error; goto error;
} break;
/* Extract system base board metadata */ case VIR_SYSINFO_LAST:
if (virSysinfoBaseBoardParseXML(ctxt, &def->baseBoard, &def->nbaseBoard) < 0) break;
goto error;
/* Extract chassis related metadata */
if ((tmpnode = virXPathNode("./chassis[1]", ctxt)) != NULL) {
if (virSysinfoChassisParseXML(tmpnode, ctxt, &def->chassis) < 0)
goto error;
}
/* Extract system related metadata */
if ((tmpnode = virXPathNode("./oemStrings[1]", ctxt)) != NULL) {
if (virSysinfoOEMStringsParseXML(tmpnode, ctxt, &def->oemStrings) < 0)
goto error;
} }
return def; return def;
@ -22173,6 +22261,7 @@ virDomainDefParseXML(xmlDocPtr xml,
def->idmap.ngidmap = n; def->idmap.ngidmap = n;
} }
VIR_FREE(nodes);
if ((def->idmap.uidmap && !def->idmap.gidmap) || if ((def->idmap.uidmap && !def->idmap.gidmap) ||
(!def->idmap.uidmap && def->idmap.gidmap)) { (!def->idmap.uidmap && def->idmap.gidmap)) {
@ -22181,13 +22270,21 @@ virDomainDefParseXML(xmlDocPtr xml,
goto error; goto error;
} }
if ((node = virXPathNode("./sysinfo[1]", ctxt)) != NULL) { if ((n = virXPathNodeSet("./sysinfo", ctxt, &nodes)) < 0)
def->sysinfo = virSysinfoParseXML(node, ctxt, goto error;
def->sysinfo = g_new0(virSysinfoDefPtr, n);
for (i = 0; i < n; i++) {
virSysinfoDefPtr sysinfo = virSysinfoParseXML(nodes[i], ctxt,
def->uuid, uuid_generated); def->uuid, uuid_generated);
if (def->sysinfo == NULL) if (!sysinfo)
goto error; goto error;
def->sysinfo[def->nsysinfo++] = sysinfo;
} }
VIR_FREE(nodes);
if ((tmp = virXPathString("string(./os/smbios/@mode)", ctxt))) { if ((tmp = virXPathString("string(./os/smbios/@mode)", ctxt))) {
int mode; int mode;
@ -24072,8 +24169,16 @@ virDomainDefCheckABIStabilityFlags(virDomainDefPtr src,
if (!virCPUDefIsEqual(src->cpu, dst->cpu, true)) if (!virCPUDefIsEqual(src->cpu, dst->cpu, true))
goto error; goto error;
if (!virSysinfoIsEqual(src->sysinfo, dst->sysinfo)) if (src->nsysinfo != dst->nsysinfo) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Target domain count of sysinfo does not match source"));
goto error; goto error;
}
for (i = 0; i < src->nsysinfo; i++) {
if (!virSysinfoIsEqual(src->sysinfo[i], dst->sysinfo[i]))
goto error;
}
if (src->ndisks != dst->ndisks) { if (src->ndisks != dst->ndisks) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
@ -29507,8 +29612,8 @@ virDomainDefFormatInternalSetRootName(virDomainDefPtr def,
if (def->resource) if (def->resource)
virDomainResourceDefFormat(buf, def->resource); virDomainResourceDefFormat(buf, def->resource);
if (def->sysinfo) for (i = 0; i < def->nsysinfo; i++)
ignore_value(virSysinfoFormat(buf, def->sysinfo)); virSysinfoFormat(buf, def->sysinfo[i]);
if (def->os.bootloader) { if (def->os.bootloader) {
virBufferEscapeString(buf, "<bootloader>%s</bootloader>\n", virBufferEscapeString(buf, "<bootloader>%s</bootloader>\n",

View File

@ -2624,13 +2624,15 @@ struct _virDomainDef {
size_t npanics; size_t npanics;
virDomainPanicDefPtr *panics; virDomainPanicDefPtr *panics;
size_t nsysinfo;
virSysinfoDefPtr *sysinfo;
/* Only 1 */ /* Only 1 */
virDomainWatchdogDefPtr watchdog; virDomainWatchdogDefPtr watchdog;
virDomainMemballoonDefPtr memballoon; virDomainMemballoonDefPtr memballoon;
virDomainNVRAMDefPtr nvram; virDomainNVRAMDefPtr nvram;
virDomainTPMDefPtr tpm; virDomainTPMDefPtr tpm;
virCPUDefPtr cpu; virCPUDefPtr cpu;
virSysinfoDefPtr sysinfo;
virDomainRedirFilterDefPtr redirfilter; virDomainRedirFilterDefPtr redirfilter;
virDomainIOMMUDefPtr iommu; virDomainIOMMUDefPtr iommu;
virDomainVsockDefPtr vsock; virDomainVsockDefPtr vsock;

View File

@ -5736,13 +5736,19 @@ qemuBuildSmbiosCommandLine(virCommandPtr cmd,
/* Host and guest uuid must differ, by definition of UUID. */ /* Host and guest uuid must differ, by definition of UUID. */
skip_uuid = true; skip_uuid = true;
} else if (def->os.smbios_mode == VIR_DOMAIN_SMBIOS_SYSINFO) { } 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, virReportError(VIR_ERR_XML_ERROR,
_("Domain '%s' sysinfo are not available"), _("Domain '%s' sysinfo are not available"),
def->name); def->name);
return -1; return -1;
} }
source = def->sysinfo;
/* domain_conf guaranteed that system_uuid matches guest uuid. */ /* domain_conf guaranteed that system_uuid matches guest uuid. */
} }
if (source != NULL) { if (source != NULL) {

View File

@ -43,6 +43,7 @@ VIR_LOG_INIT("util.sysinfo");
VIR_ENUM_IMPL(virSysinfo, VIR_ENUM_IMPL(virSysinfo,
VIR_SYSINFO_LAST, VIR_SYSINFO_LAST,
"smbios", "smbios",
"fwcfg"
); );
static const char *sysinfoSysinfo = "/proc/sysinfo"; static const char *sysinfoSysinfo = "/proc/sysinfo";
@ -1513,6 +1514,40 @@ virSysinfoOEMStringsFormat(virBufferPtr buf, virSysinfoOEMStringsDefPtr def)
virBufferAddLit(buf, "</oemStrings>\n"); 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: * virSysinfoFormat:
* @buf: buffer to append output to (may use auto-indentation) * @buf: buffer to append output to (may use auto-indentation)
@ -1535,13 +1570,16 @@ virSysinfoFormat(virBufferPtr buf, virSysinfoDefPtr def)
return -1; return -1;
} }
virSysinfoBIOSFormat(&childrenBuf, def->bios); switch (def->type) {
virSysinfoSystemFormat(&childrenBuf, def->system); case VIR_SYSINFO_SMBIOS:
virSysinfoBaseBoardFormat(&childrenBuf, def->baseBoard, def->nbaseBoard); virSysinfoFormatSMBIOS(&childrenBuf, def);
virSysinfoChassisFormat(&childrenBuf, def->chassis); break;
virSysinfoProcessorFormat(&childrenBuf, def); case VIR_SYSINFO_FWCFG:
virSysinfoMemoryFormat(&childrenBuf, def); virSysinfoFormatFWCfg(&childrenBuf, def);
virSysinfoOEMStringsFormat(&childrenBuf, def->oemStrings); break;
case VIR_SYSINFO_LAST:
break;
}
virBufferAsprintf(&attrBuf, " type='%s'", type); virBufferAsprintf(&attrBuf, " type='%s'", type);

View File

@ -27,6 +27,7 @@
typedef enum { typedef enum {
VIR_SYSINFO_SMBIOS, VIR_SYSINFO_SMBIOS,
VIR_SYSINFO_FWCFG,
VIR_SYSINFO_LAST VIR_SYSINFO_LAST
} virSysinfoType; } virSysinfoType;
@ -112,11 +113,20 @@ struct _virSysinfoOEMStringsDef {
char **values; char **values;
}; };
typedef struct _virSysinfoFWCfgDef virSysinfoFWCfgDef;
typedef virSysinfoFWCfgDef *virSysinfoFWCfgDefPtr;
struct _virSysinfoFWCfgDef {
char *name;
char *value;
char *file;
};
typedef struct _virSysinfoDef virSysinfoDef; typedef struct _virSysinfoDef virSysinfoDef;
typedef virSysinfoDef *virSysinfoDefPtr; typedef virSysinfoDef *virSysinfoDefPtr;
struct _virSysinfoDef { struct _virSysinfoDef {
int type; virSysinfoType type;
/* The following members are valid for type == VIR_SYSINFO_SMBIOS */
virSysinfoBIOSDefPtr bios; virSysinfoBIOSDefPtr bios;
virSysinfoSystemDefPtr system; virSysinfoSystemDefPtr system;
@ -132,6 +142,10 @@ struct _virSysinfoDef {
virSysinfoMemoryDefPtr memory; virSysinfoMemoryDefPtr memory;
virSysinfoOEMStringsDefPtr oemStrings; virSysinfoOEMStringsDefPtr oemStrings;
/* The following members are valid for type == VIR_SYSINFO_FWCFG */
size_t nfw_cfgs;
virSysinfoFWCfgDefPtr fw_cfgs;
}; };
virSysinfoDefPtr virSysinfoRead(void); virSysinfoDefPtr virSysinfoRead(void);

View File

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

View File

@ -0,0 +1 @@
../qemuxml2argvdata/smbios-type-fwcfg.xml

View File

@ -1125,6 +1125,7 @@ mymain(void)
DO_TEST("shmem-plain-doorbell", NONE); DO_TEST("shmem-plain-doorbell", NONE);
DO_TEST("smbios", NONE); DO_TEST("smbios", NONE);
DO_TEST("smbios-multiple-type2", 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-bios");
DO_TEST_CAPS_LATEST("os-firmware-efi"); DO_TEST_CAPS_LATEST("os-firmware-efi");