Hi,

I have written this driver that allows to access qemu's fwcfg key/value 
store with the hostctl tool. At the moment, it's usefulness to me is not 
very high because it uses acpi to detect the device, qemu only exposes the 
device in the acpi tables in the q35 machine type, but libvirt configures 
virtio 1.0 devices for q35 by default and openbsd does not yet support 
virtio 1.0. But if you start qemu without libvirt or do the required 
libvirt tweaks, you can use it.

Now, since vmd has gained fwcfg support, maybe it can be useful for 
someone? I guess one would need to add the non-acpi detection method 
(which I think reads from some known port and expects a 'qemu' response). 
Also, hostctl does not handle binary values at the moment.

Any opinions if this should go in now? If the alternative detection method 
would be added, where would be a good location for the driver file? 
Probably not acpi.

Cheers,
Stefan

Example usage:

# hostctl -f /dev/pvbus1 /                                               
bootorder
etc/acpi/rsdp
etc/acpi/tables
etc/boot-fail-wait
etc/e820
etc/msr_feature_control
etc/smbios/smbios-anchor
etc/smbios/smbios-tables
etc/smi/features-ok
etc/smi/requested-features
etc/smi/supported-features
etc/system-states
etc/table-loader
etc/tpm/log
genroms/kvmvapic.bin
opt/foobar
vgaroms/sgabios.bin


# hostctl -f /dev/pvbus1 opt/foobar
I am a string


diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index d204dd889c9..9e59fe9429f 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -25,8 +25,8 @@ MAN=  aac.4 ac97.4 acphy.4 acrtc.4 \
        eap.4 ec.4 eephy.4 ef.4 eg.4 ehci.4 eisa.4 el.4 em.4 emc.4 gcu.4 \
        emu.4 enc.4 endrun.4 envy.4 eoip.4 ep.4 epic.4 esa.4 \
        eso.4 ess.4 et.4 etherip.4 etphy.4 ex.4 exphy.4 exrtc.4 \
-       fanpwr.4 fd.4 fdc.4 fec.4 fins.4 fintek.4 fms.4 fuse.4 fxp.4 gdt.4 \
-       gentbi.4 gem.4 gif.4 \
+       fanpwr.4 fd.4 fdc.4 fec.4 fins.4 fintek.4 fms.4 fuse.4 fwcfg.4 fxp.4 \
+       gdt.4 gentbi.4 gem.4 gif.4 \
        glenv.4 gpio.4 gpiodcf.4 gpioiic.4 gpioow.4 gpr.4 gre.4 gscsio.4 \
        hds.4 hiclock.4 hidwusb.4 hifn.4 hil.4 hilid.4 hilkbd.4 hilms.4 \
        hireset.4 hitemp.4 hme.4 hotplug.4 hsq.4 \
diff --git a/share/man/man4/fwcfg.4 b/share/man/man4/fwcfg.4
new file mode 100644
index 00000000000..b02e16e1725
--- /dev/null
+++ b/share/man/man4/fwcfg.4
@@ -0,0 +1,44 @@
+.\"    $OpenBSD: $
+.\"
+.\" Copyright (c) 2018 Stefan Fritsch
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: $
+.Dt FWCFG 4
+.Os
+.Sh NAME
+.Nm fwcfg
+.Nd QEMU fw_cfg configuration interface
+.Sh SYNOPSIS
+.Cd "fwcfg0 at acpi?"
+.Sh DESCRIPTION
+.Nm
+provides access to the QEMU Firmware Configuration (fw_cfg) Device via the
+pvbus driver.
+Values can be read using
+.Xr hostctl 8
+.Sh SEE ALSO
+.Xr intro 4 ,
+.Xr pvbus 4 ,
+.Xr hostctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Ox 6.4 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Stefan Fritsch Aq Mt s...@openbsd.org .
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index dd880f70213..1a06e93246d 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -88,6 +88,8 @@ hyperv0       at pvbus?               # Hyper-V guest
 hvn*   at hyperv?              # Hyper-V NetVSC
 hvs*   at hyperv?              # Hyper-V StorVSC
 
+fwcfg0         at acpi?        # QEMU fw_cfg
+
 option         PCIVERBOSE
 option         USBVERBOSE
 
diff --git a/sys/dev/acpi/files.acpi b/sys/dev/acpi/files.acpi
index bd5c3e95268..6118852146e 100644
--- a/sys/dev/acpi/files.acpi
+++ b/sys/dev/acpi/files.acpi
@@ -193,3 +193,8 @@ file        dev/acpi/acpisurface.c          acpisurface
 # IPMI
 attach ipmi at acpi with ipmi_acpi
 file   dev/acpi/ipmi_acpi.c            ipmi_acpi
+
+# QEMU fw_cfg device
+device fwcfg
+attach fwcfg at acpi
+file   dev/acpi/fwcfg.c                fwcfg
diff --git a/sys/dev/acpi/fwcfg.c b/sys/dev/acpi/fwcfg.c
new file mode 100644
index 00000000000..54a53f0210f
--- /dev/null
+++ b/sys/dev/acpi/fwcfg.c
@@ -0,0 +1,280 @@
+/* $OpenBSD: $ */
+/*
+ * Copyright (c) 2018 Stefan Fritsch <s...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+
+#include <dev/acpi/acpireg.h>
+#include <dev/acpi/acpivar.h>
+#include <dev/acpi/acpidev.h>
+#include <dev/acpi/amltypes.h>
+#include <dev/acpi/dsdt.h>
+
+#include "pvbus.h"
+#if NPVBUS > 0
+#include <dev/pv/pvvar.h>
+#endif
+
+
+#define ACPI_DEV_QEMU_FW_CFG "QEMU0002"
+
+#define FW_CFG_REG_SELECT      0x0000
+#define FW_CFG_REG_DATA                0x0001
+#define FW_CFG_REG_DMA         0x0004
+
+#define FW_CFG_SIGNATURE_STR1  "QEMU"
+#define FW_CFG_SIGNATURE_STR2  0x51454d5520434647ULL
+
+#define FW_CFG_SIGNATURE       0x0000
+#define FW_CFG_ID              0x0001
+#define FW_CFG_FILE_DIR                0x0019
+
+const char *fwcfg_hids[] = {
+       ACPI_DEV_QEMU_FW_CFG,
+       NULL
+};
+
+struct fwcfg_file {            /* an individual file entry */
+       uint32_t size;          /* size of referenced fw_cfg item, big-endian */
+       uint16_t select;        /* selector key of fw_cfg item, big-endian */
+       uint16_t reserved;
+       char name[56];          /* fw_cfg item name, NUL-terminated ascii */
+};
+
+struct fwcfg_files {           /* the entire file directory fw_cfg item */
+       uint32_t count;         /* number of entries, in big-endian format */
+};
+
+struct fwcfg_softc {
+       struct device           sc_dev;
+
+       bus_space_tag_t         sc_iot;
+       bus_space_handle_t      sc_ioh;
+       bus_size_t              sc_port_max;
+       bus_size_t              sc_port_len;
+
+       struct acpi_softc       *sc_acpi;
+       struct aml_node         *sc_devnode;
+};
+
+int    fwcfg_match(struct device *, void *, void *);
+void   fwcfg_attach(struct device *, struct device *, void *);
+
+struct cfattach fwcfg_ca = {
+       sizeof(struct fwcfg_softc), fwcfg_match, fwcfg_attach,
+       NULL, NULL
+};
+
+struct cfdriver fwcfg_cd = {
+       NULL, "fwcfg", DV_DULL
+};
+
+struct fwcfg_softc *fwcfg_sc = NULL;
+
+int    fwcfg_parse_resources(int, union acpi_resource *, void *);
+void   fwcfg_select(struct fwcfg_softc *, uint16_t);
+void   fwcfg_read(struct fwcfg_softc *, void *, int);
+int    fwcfg_find_file(struct fwcfg_softc *, const char *, uint16_t *, 
uint32_t *);
+int    fwcfg_kvop(void *, int, char *, char *, size_t);
+
+int
+fwcfg_match(struct device *parent, void *match, void *aux)
+{
+       struct acpi_attach_args *aa = aux;
+       struct cfdata           *cf = match;
+
+       return (acpi_matchhids(aa, fwcfg_hids, cf->cf_driver->cd_name));
+}
+
+void
+fwcfg_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct fwcfg_softc      *sc = (struct fwcfg_softc *)self;
+       struct acpi_attach_args *aa = aux;
+       struct aml_value         res;
+       char buf[4];
+
+       sc->sc_acpi = (struct acpi_softc *)parent;
+       sc->sc_devnode = aa->aaa_node;
+
+       if (aml_evalname(sc->sc_acpi, sc->sc_devnode, "_CRS", 0, NULL, &res)) {
+               printf(": no _CRS\n");
+               return;
+       }
+
+       aml_parse_resource(&res, fwcfg_parse_resources, sc);
+       aml_freevalue(&res);
+
+       if (sc->sc_port_max == 0 || sc->sc_port_len == 0) {
+               printf(": cannot parse _CRS\n");
+               return;
+       }
+
+       if (bus_space_map(sc->sc_iot, sc->sc_port_max, sc->sc_port_len, 0, 
&sc->sc_ioh)) {
+               printf(": failed to map regs\n");
+               return;
+       }
+
+       fwcfg_select(sc, FW_CFG_SIGNATURE);
+       fwcfg_read(sc, buf, sizeof(buf));
+       if (memcmp(buf, FW_CFG_SIGNATURE_STR1, sizeof(buf))) {
+               printf(": wrong signature\n");
+               return;
+       }
+
+       printf("\n");
+
+#if NPVBUS > 0
+       pvbus_register_kvop(PVBUS_QEMU_FWCFG, fwcfg_kvop, sc);
+#endif
+}
+
+int
+fwcfg_parse_resources(int crsidx, union acpi_resource *crs, void *arg)
+{
+       struct fwcfg_softc *sc = arg;
+       int type = AML_CRSTYPE(crs);
+
+       switch (crsidx) {
+       case 0:
+               if (type != SR_IOPORT) {
+                       printf("%s: Unexpected resource #%d type %d\n",
+                           DEVNAME(sc), crsidx, type);
+                       break;
+               }
+               sc->sc_iot = sc->sc_acpi->sc_iot;
+               sc->sc_port_max = crs->sr_ioport._max;
+               sc->sc_port_len = crs->sr_ioport._len;
+
+               break;
+       default:
+               printf("%s: invalid resource #%d type %d\n",
+                   DEVNAME(sc), crsidx, type);
+               return 0;
+       }
+
+       return 0;
+}
+
+void
+fwcfg_select(struct fwcfg_softc *sc, uint16_t select)
+{
+       bus_space_write_2(sc->sc_iot, sc->sc_ioh, FW_CFG_REG_SELECT, select);
+}
+
+void
+fwcfg_read(struct fwcfg_softc *sc, void *buf, int len)
+{
+       bus_space_read_multi_1(sc->sc_iot, sc->sc_ioh, FW_CFG_REG_DATA, buf, 
len);
+}
+
+int
+fwcfg_find_file(struct fwcfg_softc *sc, const char *name, uint16_t *select, 
uint32_t *size)
+{
+       struct fwcfg_files hdr;
+       int count;
+
+       if (sc->sc_iot == NULL)
+               return 1;
+       if (strlen(name) > 55)
+               return 1;
+       fwcfg_select(sc, FW_CFG_FILE_DIR);
+       fwcfg_read(sc, &hdr, sizeof(hdr));
+
+       count = betoh32(hdr.count);
+       while (count > 0) {
+               struct fwcfg_file entry;
+               fwcfg_read(sc, &entry, sizeof(entry));
+               if (strcmp(entry.name, name) == 0) {
+                       *select = betoh16(entry.select);
+                       *size = betoh32(entry.size);
+                       return 0;
+               }
+               count--;
+       }
+       return 1;
+}
+
+int
+fwcfg_list(struct fwcfg_softc *sc, const char *prefix, char *buf, size_t 
buflen)
+{
+       struct fwcfg_files hdr;
+       int count, plen = strlen(prefix);
+       int found = 0;
+
+       if (sc->sc_iot == NULL)
+               return 1;
+       if (strlen(prefix) > 55)
+               return 1;
+       if (buflen == 0)
+               return 0;
+       buf[0] = '\0';
+
+       fwcfg_select(sc, FW_CFG_FILE_DIR);
+       fwcfg_read(sc, &hdr, sizeof(hdr));
+
+       count = betoh32(hdr.count);
+       while (count > 0) {
+               struct fwcfg_file entry;
+               fwcfg_read(sc, &entry, sizeof(entry));
+               if (strncmp(entry.name, prefix, plen) == 0) {
+                       if (found)
+                               strlcat(buf, "\n", buflen);
+                       strlcat(buf, entry.name, buflen);
+                       found++;
+               }
+               count--;
+       }
+       return !found;
+}
+
+
+int
+fwcfg_kvop(void *arg, int op, char *key, char *value, size_t valuelen)
+{
+       struct fwcfg_softc *sc = arg;
+       uint16_t select;
+       uint32_t size;
+
+       if (op != PVBUS_KVREAD)
+               return EOPNOTSUPP;
+
+       if (strlen(key) == 0)
+               return EINVAL;
+
+       memset(value, 0, valuelen);
+       /*
+        * There is no real concept of directories, using "/" as
+        * separator in the keys is just convention.
+        * If a key with a trailing slash does not exist, we try
+        * a listing.
+        */
+       if (fwcfg_find_file(sc, key, &select, &size) == 0) {
+               fwcfg_select(sc, select);
+               size = MIN(size, valuelen);
+               fwcfg_read(sc, value, size);
+               return 0;
+       } else if (strcmp(key, "/") == 0) {
+               if (fwcfg_list(sc, "", value, valuelen) == 0)
+                       return 0;
+       } else if (strlen(key) >= 1 && key[strlen(key)-1] == '/') {
+               if (fwcfg_list(sc, key, value, valuelen) == 0)
+                       return 0;
+       }
+
+       return ENOENT;
+}
diff --git a/sys/dev/pv/pvbus.c b/sys/dev/pv/pvbus.c
index 885cf702c20..37b87270791 100644
--- a/sys/dev/pv/pvbus.c
+++ b/sys/dev/pv/pvbus.c
@@ -91,6 +91,7 @@ struct pvbus_type {
        { "XenVMMXenVMM",       "Xen",  pvbus_xen, pvbus_xen_print },
        { "bhyve bhyve ",       "bhyve" },
        { VMM_HV_SIGNATURE,     "OpenBSD" },
+       { NULL,                 "QEMU-FWCFG" },
 };
 
 struct bus_dma_tag pvbus_dma_tag = {
@@ -154,6 +155,15 @@ pvbus_attach(struct device *parent, struct device *self, 
void *aux)
        config_search(pvbus_search, self, sc);
 }
 
+void
+pvbus_register_kvop(int hvid, int (*kvop)(void *, int, char *, char *, 
size_t), void *arg)
+{
+       KASSERT(hvid < PVBUS_MAX);
+       pvbus_hv[hvid].hv_kvop = kvop;
+       pvbus_hv[hvid].hv_arg = arg;
+       pvbus_hv[hvid].hv_base = 1;
+}
+
 void
 pvbus_identify(void)
 {
diff --git a/sys/dev/pv/pvvar.h b/sys/dev/pv/pvvar.h
index 4e23ae52bd5..07a65668be1 100644
--- a/sys/dev/pv/pvvar.h
+++ b/sys/dev/pv/pvvar.h
@@ -38,6 +38,7 @@ enum {
        PVBUS_XEN,
        PVBUS_BHYVE,
        PVBUS_OPENBSD,
+       PVBUS_QEMU_FWCFG,
 
        PVBUS_MAX
 };
@@ -81,6 +82,7 @@ int    pvbus_probe(void);
 void    pvbus_init_cpu(void);
 void    pvbus_reboot(struct device *);
 void    pvbus_shutdown(struct device *);
+void    pvbus_register_kvop(int, int (*)(void *, int, char *, char *, size_t), 
void *);
 
 #endif /* _KERNEL */
 #endif /* _DEV_PV_PVBUS_H_ */
diff --git a/usr.sbin/hostctl/hostctl.8 b/usr.sbin/hostctl/hostctl.8
index 18de9bafbcf..f45aac5e5d9 100644
--- a/usr.sbin/hostctl/hostctl.8
+++ b/usr.sbin/hostctl/hostctl.8
@@ -141,6 +141,20 @@ OSVersion
 ProcessorArchitecture
 # hostctl Auto/FullyQualifiedDomainName `hostname`
 .Ed
+.Pp
+Access to the QEMU fw_cfg interface is provided by the
+.Xr fwcfg 4
+driver.
+Keys can be set using the
+.Fl fw_cfg
+QEMU command line option.
+Only reading is supported.
+Available keys can be listed:
+.Bd -literal -offset indent
+# hostctl opt/
+opt/foo
+opt/bar
+.Ed
 .Sh SEE ALSO
 .Xr pvbus 4
 .Sh HISTORY

Reply via email to