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