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;/oemStrings&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>
<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 &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>
<h3><a id="elementsCPUAllocation">CPU Allocation</a></h3>

View File

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

View File

@ -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,16 +15710,115 @@ 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
virSysinfoParseXML(xmlNodePtr node,
xmlXPathContextPtr ctxt,
unsigned char *domUUID,
bool uuid_generated)
xmlXPathContextPtr ctxt,
unsigned char *domUUID,
bool uuid_generated)
{
VIR_XPATH_NODE_AUTORESTORE(ctxt);
virSysinfoDefPtr def;
xmlNodePtr tmpnode;
g_autofree char *type = NULL;
g_autofree char *typeStr = NULL;
int type;
ctxt->node = node;
@ -15730,45 +15831,32 @@ virSysinfoParseXML(xmlNodePtr node,
if (VIR_ALLOC(def) < 0)
return NULL;
type = virXMLPropString(node, "type");
if (type == NULL) {
typeStr = virXMLPropString(node, "type");
if (typeStr == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("sysinfo must contain a type attribute"));
goto error;
}
if ((def->type = virSysinfoTypeFromString(type)) < 0) {
if ((type = virSysinfoTypeFromString(typeStr)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown sysinfo type '%s'"), type);
_("unknown sysinfo type '%s'"), typeStr);
goto error;
}
def->type = type;
/* Extract BIOS related metadata */
if ((tmpnode = virXPathNode("./bios[1]", ctxt)) != NULL) {
if (virSysinfoBIOSParseXML(tmpnode, ctxt, &def->bios) < 0)
switch (def->type) {
case VIR_SYSINFO_SMBIOS:
if (virSysinfoParseSMBIOSDef(def, ctxt, domUUID, uuid_generated) < 0)
goto error;
}
break;
/* Extract system related metadata */
if ((tmpnode = virXPathNode("./system[1]", ctxt)) != NULL) {
if (virSysinfoSystemParseXML(tmpnode, ctxt, &def->system,
domUUID, uuid_generated) < 0)
case VIR_SYSINFO_FWCFG:
if (virSysinfoParseFWCfgDef(def, node, ctxt) < 0)
goto error;
}
break;
/* Extract system base board metadata */
if (virSysinfoBaseBoardParseXML(ctxt, &def->baseBoard, &def->nbaseBoard) < 0)
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;
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",

View File

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

View File

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

View File

@ -43,6 +43,7 @@ VIR_LOG_INIT("util.sysinfo");
VIR_ENUM_IMPL(virSysinfo,
VIR_SYSINFO_LAST,
"smbios",
"fwcfg"
);
static const char *sysinfoSysinfo = "/proc/sysinfo";
@ -1513,6 +1514,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)
@ -1535,13 +1570,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);

View File

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

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("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");