[PATCH 2/6] conf: Add firmware blob configuration

Michal Privoznik mprivozn at redhat.com
Wed Jun 3 17:01:34 UTC 2020


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




More information about the libvir-list mailing list