Driver for accessing qemu fwcfg via hostctl

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

Driver for accessing qemu fwcfg via hostctl

Stefan Fritsch
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 [hidden email] .
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 <[hidden email]>
+ *
+ * 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