QEMU has -fw_cfg which allows users to tweak how firmware
configures itself and/or provide new configuration blobs.
Introduce new <firmware/> element as a direct child of <domain/>
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 | 30 ++++++++
docs/schemas/domaincommon.rng | 24 +++++++
src/conf/domain_conf.c | 104 ++++++++++++++++++++++++++++
src/conf/domain_conf.h | 11 +++
src/conf/virconftypes.h | 3 +
tests/qemuxml2argvdata/fw_cfg.xml | 40 +++++++++++
tests/qemuxml2xmloutdata/fw_cfg.xml | 1 +
tests/qemuxml2xmltest.c | 1 +
8 files changed, 214 insertions(+)
create mode 100644 tests/qemuxml2argvdata/fw_cfg.xml
create mode 120000 tests/qemuxml2xmloutdata/fw_cfg.xml
diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in
index 33cec1e6dd..bd67b44af8 100644
--- a/docs/formatdomain.html.in
+++ b/docs/formatdomain.html.in
@@ -595,6 +595,36 @@
</dd>
</dl>
+ <h3><a id="elementsFirmware">Firmware
configuration</a></h3>
+
+ <p>
+ 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>.
+ <span class="since">Since 6.5.0</span>
+ </p>
+
+<pre>
+ <firmware>
+ <entry name="opt/com.example/name" value="example
value"/>
+ <entry name="opt/com.coreos/config"
file="/tmp/provision.ign"/>
+ </firmware>
+</pre>
+
+ <p>
+ The <code>firmware</code> element can have multiple
<code>entry</code>
+ child element. 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 have either <code>value</code> attribute (to set
+ the blob value directly), or <code>file</code> attribute (to set the
blob
+ value from the file).
+ </p>
+
<h3><a id="elementsCPUAllocation">CPU
Allocation</a></h3>
<pre>
diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng
index 6727cd743b..84c455d378 100644
--- a/docs/schemas/domaincommon.rng
+++ b/docs/schemas/domaincommon.rng
@@ -49,6 +49,9 @@
<optional>
<ref name="sysinfo"/>
</optional>
+ <optional>
+ <ref name='firmware'/>
+ </optional>
<ref name="os"/>
<ref name="clock"/>
<ref name="resources"/>
@@ -5617,6 +5620,27 @@
<data type="string"/>
</define>
+ <define name="firmware">
+ <element name="firmware">
+ <zeroOrMore>
+ <element name="entry">
+ <attribute name="name">
+ <data type="string"/>
+ </attribute>
+ <choice>
+ <attribute name="value">
+ <data type="string"/>
+ </attribute>
+ <attribute name="file">
+ <data type="string"/>
+ </attribute>
+ </choice>
+ <empty/>
+ </element>
+ </zeroOrMore>
+ </element>
+ </define>
+
<define name="acpiTable">
<element name="acpi">
<zeroOrMore>
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index ff0e7e9539..edbd00801a 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -3385,6 +3385,18 @@ virDomainSEVDefFree(virDomainSEVDefPtr def)
}
+static void
+virDomainFWCfgDefFree(virDomainFWCfgDefPtr def)
+{
+ if (!def)
+ return;
+
+ VIR_FREE(def->name);
+ VIR_FREE(def->value);
+ VIR_FREE(def->file);
+}
+
+
void virDomainDefFree(virDomainDefPtr def)
{
size_t i;
@@ -3553,6 +3565,10 @@ void virDomainDefFree(virDomainDefPtr def)
virSysinfoDefFree(def->sysinfo);
+ for (i = 0; i < def->nfw_cfgs; i++)
+ virDomainFWCfgDefFree(&def->fw_cfgs[i]);
+ VIR_FREE(def->fw_cfgs);
+
virDomainRedirFilterDefFree(def->redirfilter);
for (i = 0; i < def->nshmems; i++)
@@ -20921,6 +20937,89 @@ virDomainMemorytuneDefParse(virDomainDefPtr def,
}
+static int
+virDomainFWCfgDefParse(virDomainDefPtr def,
+ xmlXPathContextPtr ctxt)
+{
+ g_autofree xmlNodePtr *nodes = NULL;
+ int n;
+ size_t i;
+
+ if ((n = virXPathNodeSet("./firmware/entry", ctxt, &nodes)) < 0)
+ return -1;
+
+ if (n == 0)
+ return 0;
+
+ def->fw_cfgs = g_new0(virDomainFWCfgDef, n);
+
+ for (i = 0; i < n; i++) {
+ g_autofree char *name = NULL;
+ g_autofree char *value = NULL;
+ g_autofree char *file = NULL;
+
+ if (!(name = virXMLPropString(nodes[i], "name"))) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("Firmware entry is missing 'name'
attribute"));
+ goto error;
+ }
+
+ value = virXMLPropString(nodes[i], "value");
+ file = virXMLPropString(nodes[i], "file");
+
+ if (!value && !file) {
+ virReportError(VIR_ERR_XML_ERROR, "%s",
+ _("Firmware entry must have either 'value' or
"
+ "'file' attribute"));
+ goto error;
+ }
+
+ 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(&file);
+ def->nfw_cfgs++;
+ }
+
+ return 0;
+
+ error:
+ while (def->nfw_cfgs)
+ virDomainFWCfgDefFree(&def->fw_cfgs[--def->nfw_cfgs]);
+ VIR_FREE(def->fw_cfgs);
+ return -1;
+}
+
+
+static void
+virDomainFWCfgDefFormat(virBufferPtr buf,
+ const virDomainDef *def)
+{
+ size_t i;
+
+ if (def->nfw_cfgs == 0)
+ return;
+
+ virBufferAddLit(buf, "<firmware>\n");
+ virBufferAdjustIndent(buf, 2);
+
+ for (i = 0; i < def->nfw_cfgs; i++) {
+ const virDomainFWCfgDef *f = &def->fw_cfgs[i];
+
+ virBufferAsprintf(buf, "<entry name='%s' ", f->name);
+
+ if (f->value)
+ virBufferEscapeString(buf, "value='%s'", f->value);
+ else
+ virBufferEscapeString(buf, "file='%s'", f->file);
+
+ virBufferAddLit(buf, "/>\n");
+ }
+
+ virBufferAdjustIndent(buf, -2);
+ virBufferAddLit(buf, "</firmware>\n");
+}
+
+
static virDomainDefPtr
virDomainDefParseXML(xmlDocPtr xml,
xmlXPathContextPtr ctxt,
@@ -22202,6 +22301,9 @@ virDomainDefParseXML(xmlDocPtr xml,
def->os.smbios_mode = mode;
}
+ if (virDomainFWCfgDefParse(def, ctxt) < 0)
+ goto error;
+
if (virDomainKeyWrapDefParseXML(def, ctxt) < 0)
goto error;
@@ -29512,6 +29614,8 @@ virDomainDefFormatInternalSetRootName(virDomainDefPtr def,
if (def->sysinfo)
ignore_value(virSysinfoFormat(buf, def->sysinfo));
+ virDomainFWCfgDefFormat(buf, def);
+
if (def->os.bootloader) {
virBufferEscapeString(buf,
"<bootloader>%s</bootloader>\n",
def->os.bootloader);
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index e152c599ca..2dad4dc08a 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2481,6 +2481,14 @@ struct _virDomainVirtioOptions {
virTristateSwitch packed;
};
+
+struct _virDomainFWCfgDef {
+ char *name;
+ char *value;
+ char *file;
+};
+
+
/*
* Guest VM main configuration
*
@@ -2624,6 +2632,9 @@ struct _virDomainDef {
size_t npanics;
virDomainPanicDefPtr *panics;
+ size_t nfw_cfgs;
+ virDomainFWCfgDefPtr fw_cfgs;
+
/* Only 1 */
virDomainWatchdogDefPtr watchdog;
virDomainMemballoonDefPtr memballoon;
diff --git a/src/conf/virconftypes.h b/src/conf/virconftypes.h
index 1c62cde251..89440e1ac8 100644
--- a/src/conf/virconftypes.h
+++ b/src/conf/virconftypes.h
@@ -359,3 +359,6 @@ typedef virDomainXMLPrivateDataCallbacks
*virDomainXMLPrivateDataCallbacksPtr;
typedef struct _virDomainXenbusControllerOpts virDomainXenbusControllerOpts;
typedef virDomainXenbusControllerOpts *virDomainXenbusControllerOptsPtr;
+
+typedef struct _virDomainFWCfgDef virDomainFWCfgDef;
+typedef virDomainFWCfgDef *virDomainFWCfgDefPtr;
diff --git a/tests/qemuxml2argvdata/fw_cfg.xml b/tests/qemuxml2argvdata/fw_cfg.xml
new file mode 100644
index 0000000000..ff3d5b9693
--- /dev/null
+++ b/tests/qemuxml2argvdata/fw_cfg.xml
@@ -0,0 +1,40 @@
+<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>
+ <firmware>
+ <entry name='opt/com.example/name' value='example value'/>
+ <entry name='opt/com.coreos/config'
file='/tmp/provision.ign'/>
+ </firmware>
+ <os>
+ <type arch='i686' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </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/fw_cfg.xml b/tests/qemuxml2xmloutdata/fw_cfg.xml
new file mode 120000
index 0000000000..d6921a9c64
--- /dev/null
+++ b/tests/qemuxml2xmloutdata/fw_cfg.xml
@@ -0,0 +1 @@
+../qemuxml2argvdata/fw_cfg.xml
\ No newline at end of file
diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c
index dcc7b29ded..3d3b65534b 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("fw_cfg", NONE);
DO_TEST_CAPS_LATEST("os-firmware-bios");
DO_TEST_CAPS_LATEST("os-firmware-efi");
--
2.26.2