NetBSD Problem Report #58639
From www@netbsd.org Sun Aug 25 14:02:06 2024
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
client-signature RSA-PSS (2048 bits) client-digest SHA256)
(Client CN "mail.NetBSD.org", Issuer "mail.NetBSD.org CA" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id 517CA1A923F
for <gnats-bugs@gnats.NetBSD.org>; Sun, 25 Aug 2024 14:02:06 +0000 (UTC)
Message-Id: <20240825140205.0F6321A9242@mollari.NetBSD.org>
Date: Sun, 25 Aug 2024 14:02:05 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: qemufwcfg(4), mount_qemufwcfg(8): writes are not supported
X-Send-Pr-Version: www-1.0
>Number: 58639
>Category: kern
>Synopsis: qemufwcfg(4), mount_qemufwcfg(8): writes are not supported
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sun Aug 25 14:05:00 +0000 2024
>Last-Modified: Sun Aug 25 21:00:02 +0000 2024
>Originator: Taylor R Campbell
>Release: current, 10, 9
>Organization:
The NetQemuFwrite Configuration
>Environment:
>Description:
Writes to the qemu firmware configuration device or file system are not supported, even though qemu allows them for some nodes:
https://www.qemu.org/docs/master/specs/fw_cfg.html
>How-To-Repeat:
try to write to a qemu fw cfg node
>Fix:
Yes, please!
Need to implement the DMA interface.
>Audit-Trail:
From: Taylor R Campbell <riastradh@NetBSD.org>
To: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Cc:
Subject: Re: kern/58639: qemufwcfg(4), mount_qemufwcfg(8): writes are not supported
Date: Sun, 25 Aug 2024 20:55:50 +0000
This is a multi-part message in MIME format.
--=_doTlMKKkYbLBtme6fab/51tYZGWhbizH
The attached patch implements some of the missing write support.
There's a small snag: the current ioctl(SET_INDEX) and read interface
don't really play nicely with file offsets, or pread(2). It doesn't
really pose a problem for current low-volume use, but I think we
should introduce a new ioctl(SET_INDEX_SEEKABLE) or something to make
this cleaner -- if you use that, then the VM's data offset is tracked
by the file offset and seeks work and we can use the DMA interface for
reads too.
I stopped here because I discovered it's not actually useful for
testing changes to the VM generation ID -- you can't write to that
from the guest -- which was my motivation for looking at qemufwcfg in
the first place (PR 58632).
--=_doTlMKKkYbLBtme6fab/51tYZGWhbizH
Content-Type: text/plain; charset="ISO-8859-1"; name="pr58639-qemufwcfgwrite"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="pr58639-qemufwcfgwrite.patch"
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1724594546 0
# Sun Aug 25 14:02:26 2024 +0000
# Branch trunk
# Node ID 553cb2960ab36427d16ffda89f4c871a4eda1959
# Parent cf7a8f9687ea781207542c43a006460dc134ea3b
# EXP-Topic riastradh-pr58639-qemufwcfgwrite
qemufwcfg(4): KNF and sprinkle needed includes.
No functional change intended.
PR kern/58639: qemufwcfg(4), mount_qemufwcfg(8): writes are not
supported
diff -r cf7a8f9687ea -r 553cb2960ab3 sys/dev/ic/qemufwcfg.c
--- a/sys/dev/ic/qemufwcfg.c Sat Aug 24 07:24:34 2024 +0000
+++ b/sys/dev/ic/qemufwcfg.c Sun Aug 25 14:02:26 2024 +0000
@@ -1,4 +1,4 @@
-/* $NetBSD: qemufwcfg.c,v 1.3 2024/04/06 13:42:18 skrll Exp $ */
+/* $NetBSD: qemufwcfg.c,v 1.3 2024/04/06 13:42:18 skrll Exp $ */
=20
/*-
* Copyright (c) 2017 Jared McNeill <jmcneill@invisible.ca>
@@ -71,13 +71,16 @@ static dev_type_ioctl(fwcfg_ioctl);
static void
fwcfg_select(struct fwcfg_softc *sc, uint16_t index)
{
- bus_space_write_2(sc->sc_bst, sc->sc_bsh, FWCFG_SEL_REG, FWCFG_SEL_SWAP(i=
ndex));
+
+ bus_space_write_2(sc->sc_bst, sc->sc_bsh, FWCFG_SEL_REG,
+ FWCFG_SEL_SWAP(index));
}
=20
static int
fwcfg_open(dev_t dev, int flag, int mode, lwp_t *l)
{
- struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd, FW=
CFGUNIT(dev));
+ struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd,
+ FWCFGUNIT(dev));
int error;
=20
if (sc =3D=3D NULL)
@@ -97,7 +100,8 @@ fwcfg_open(dev_t dev, int flag, int mode
static int
fwcfg_close(dev_t dev, int flag, int mode, lwp_t *l)
{
- struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd, FW=
CFGUNIT(dev));
+ struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd,
+ FWCFGUNIT(dev));
int error;
=20
if (sc =3D=3D NULL)
@@ -117,7 +121,8 @@ fwcfg_close(dev_t dev, int flag, int mod
static int
fwcfg_read(dev_t dev, struct uio *uio, int flags)
{
- struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd, FW=
CFGUNIT(dev));
+ struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd,
+ FWCFGUNIT(dev));
uint8_t buf[64];
size_t count;
int error =3D 0;
@@ -127,7 +132,8 @@ fwcfg_read(dev_t dev, struct uio *uio, i
=20
while (uio->uio_resid > 0) {
count =3D uimin(sizeof(buf), uio->uio_resid);
- bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG, buf, coun=
t);
+ bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG,
+ buf, count);
error =3D uiomove(buf, count, uio);
if (error !=3D 0)
break;
@@ -139,7 +145,8 @@ fwcfg_read(dev_t dev, struct uio *uio, i
static int
fwcfg_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
{
- struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd, FW=
CFGUNIT(dev));
+ struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd,
+ FWCFGUNIT(dev));
uint16_t index;
=20
if (sc =3D=3D NULL)
@@ -179,6 +186,8 @@ fwcfg_attach(struct fwcfg_softc *sc)
=20
/* Read signature */
fwcfg_select(sc, FW_CFG_SIGNATURE);
- bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG, sig, sizeo=
f(sig));
- aprint_verbose_dev(sc->sc_dev, "<%c%c%c%c>\n", sig[0], sig[1], sig[2], si=
g[3]);
+ bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG,
+ sig, sizeof(sig));
+ aprint_verbose_dev(sc->sc_dev, "<%c%c%c%c>\n",
+ sig[0], sig[1], sig[2], sig[3]);
}
diff -r cf7a8f9687ea -r 553cb2960ab3 sys/dev/ic/qemufwcfgvar.h
--- a/sys/dev/ic/qemufwcfgvar.h Sat Aug 24 07:24:34 2024 +0000
+++ b/sys/dev/ic/qemufwcfgvar.h Sun Aug 25 14:02:26 2024 +0000
@@ -29,6 +29,11 @@
#ifndef _QEMUFWCFGVAR_H
#define _QEMUFWCFGVAR_H
=20
+#include <sys/bus.h>
+#include <sys/device_if.h>
+#include <sys/mutex.h>
+#include <sys/stdbool.h>
+
struct fwcfg_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1724617724 0
# Sun Aug 25 20:28:44 2024 +0000
# Branch trunk
# Node ID 2d2ce7a8cd570c3782acc4f08ea5ade90a2407ed
# Parent 553cb2960ab36427d16ffda89f4c871a4eda1959
# EXP-Topic riastradh-pr58639-qemufwcfgwrite
WIP: qemufwcfg(4), mount_qemufwcfg(8): Add write support.
PR kern/58639: qemufwcfg(4), mount_qemufwcfg(8): writes are not
supported
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sbin/mount_qemufwcfg/fwcfg.c
--- a/sbin/mount_qemufwcfg/fwcfg.c Sun Aug 25 14:02:26 2024 +0000
+++ b/sbin/mount_qemufwcfg/fwcfg.c Sun Aug 25 20:28:44 2024 +0000
@@ -58,24 +58,17 @@ struct fwcfg_file {
=20
static int fwcfg_fd;
static mode_t fwcfg_dir_mask =3D 0555;
-static mode_t fwcfg_file_mask =3D 0444;
+static mode_t fwcfg_file_mask =3D 0644;
static uid_t fwcfg_uid;
static gid_t fwcfg_gid;
=20
static virtdir_t fwcfg_virtdir;
=20
-static void
+static int
set_index(uint16_t index)
{
- if (ioctl(fwcfg_fd, FWCFGIO_SET_INDEX, &index) !=3D 0)
- err(EXIT_FAILURE, "failed to set index 0x%04x", index);
-}
=20
-static void
-read_data(void *buf, size_t buflen)
-{
- if (read(fwcfg_fd, buf, buflen) !=3D (ssize_t)buflen)
- err(EXIT_FAILURE, "failed to read data");
+ return ioctl(fwcfg_fd, FWCFGIO_SET_INDEX, &index);
}
=20
static int
@@ -144,6 +137,7 @@ fwcfg_read(const char *path, char *buf,=20
{
virt_dirent_t *ep;
uint8_t tmp[64];
+ ssize_t nread;
=20
if ((ep =3D virtdir_find(&fwcfg_virtdir, path, strlen(path))) =3D=3D NULL)
return -ENOENT;
@@ -151,28 +145,65 @@ fwcfg_read(const char *path, char *buf,=20
if (ep->select =3D=3D 0)
return -ENOENT;
=20
- set_index(ep->select);
+ if (set_index(ep->select) =3D=3D -1)
+ return -errno;
=20
/* Seek to correct offset */
while (offset > 0) {
const size_t len =3D MIN(sizeof(tmp), (size_t)offset);
- read_data(tmp, len);
- offset -=3D (off_t)len;
+
+ nread =3D read(fwcfg_fd, tmp, len);
+ if (nread =3D=3D -1)
+ return -errno;
+ if (nread =3D=3D 0)
+ return 0;
+ offset -=3D (off_t)nread;
}
=20
/* Read the data */
- read_data(buf, size);
+ nread =3D read(fwcfg_fd, buf, MIN(size, INT_MAX));
+ if (nread =3D=3D -1)
+ return -errno;
=20
return (int)size;
}
=20
static int
+fwcfg_write(const char *path, const char *buf, size_t size, off_t offset,
+ struct fuse_file_info *fi)
+{
+ virt_dirent_t *ep;
+ ssize_t nwrit;
+
+ if ((ep =3D virtdir_find(&fwcfg_virtdir, path, strlen(path))) =3D=3D NULL)
+ return -ENOENT;
+
+ if (ep->select =3D=3D 0)
+ return -ENOENT;
+
+ if (set_index(ep->select) =3D=3D -1)
+ return -errno;
+
+ nwrit =3D pwrite(fwcfg_fd, buf, MIN(size, INT_MAX), offset);
+ if (nwrit =3D=3D -1)
+ return -errno;
+
+ return (int)nwrit;
+}
+
+static int
fwcfg_statfs(const char *path, struct statvfs *st)
{
uint32_t count;
+ ssize_t nread;
=20
- set_index(FW_CFG_FILE_DIR);
- read_data(&count, sizeof(count));
+ if (set_index(FW_CFG_FILE_DIR) =3D=3D -1)
+ return -errno;
+ nread =3D read(fwcfg_fd, &count, sizeof(count));
+ if (nread =3D=3D -1)
+ return -errno;
+ if (nread !=3D sizeof(count))
+ return -EIO;
=20
memset(st, 0, sizeof(*st));
st->f_files =3D be32toh(count);
@@ -185,6 +216,8 @@ static struct fuse_operations fwcfg_ops=20
.readdir =3D fwcfg_readdir,
.open =3D fwcfg_open,
.read =3D fwcfg_read,
+ .write =3D fwcfg_write,
+ .truncate =3D fwcfg_truncate,
.statfs =3D fwcfg_statfs,
};
=20
@@ -195,16 +228,26 @@ build_tree(virtdir_t *v)
struct fwcfg_file f;
uint32_t count, n;
struct stat st;
+ ssize_t nread;
=20
memset(&st, 0, sizeof(st));
st.st_uid =3D fwcfg_uid;
st.st_gid =3D fwcfg_gid;
virtdir_init(v, NULL, &st, &st, &st);
=20
- set_index(FW_CFG_FILE_DIR);
- read_data(&count, sizeof(count));
+ if (set_index(FW_CFG_FILE_DIR) =3D=3D -1)
+ err(EXIT_FAILURE, "failed to select file directory");
+ nread =3D read(fwcfg_fd, &count, sizeof(count));
+ if (nread =3D=3D -1)
+ err(EXIT_FAILURE, "failed to enumerate file directory");
+ if ((size_t)nread !=3D sizeof(count))
+ errx(EXIT_FAILURE, "truncated file directory");
for (n =3D 0; n < be32toh(count); n++) {
- read_data(&f, sizeof(f));
+ nread =3D read(fwcfg_fd, &f, sizeof(f));
+ if (nread =3D=3D -1)
+ err(EXIT_FAILURE, "failed to read file directory");
+ if ((size_t)nread !=3D sizeof(f))
+ errx(EXIT_FAILURE, "truncated file directory");
snprintf(path, sizeof(path), "/%s", f.name);
virtdir_add(v, path, strlen(path), 'f', NULL,
be32toh(f.size), be16toh(f.select));
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sys/dev/acpi/qemufwcfg_acpi.c
--- a/sys/dev/acpi/qemufwcfg_acpi.c Sun Aug 25 14:02:26 2024 +0000
+++ b/sys/dev/acpi/qemufwcfg_acpi.c Sun Aug 25 20:28:44 2024 +0000
@@ -104,6 +104,9 @@ fwcfg_acpi_attach(device_t parent, devic
return;
}
=20
+ sc->sc_dmat =3D BUS_DMA_TAG_VALID(aa->aa_dmat64) ? aa->aa_dmat64
+ : aa->aa_dmat;
+
fwcfg_attach(sc);
=20
pmf_device_register(self, NULL, NULL);
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sys/dev/fdt/qemufwcfg_fdt.c
--- a/sys/dev/fdt/qemufwcfg_fdt.c Sun Aug 25 14:02:26 2024 +0000
+++ b/sys/dev/fdt/qemufwcfg_fdt.c Sun Aug 25 20:28:44 2024 +0000
@@ -78,6 +78,7 @@ fwcfg_fdt_attach(device_t parent, device
=20
sc->sc_dev =3D self;
sc->sc_bst =3D faa->faa_bst;
+ sc->sc_dmat =3D faa->faa_dmat;
=20
if (bus_space_map(sc->sc_bst, base, size, 0, &sc->sc_bsh) !=3D 0) {
aprint_error_dev(self, "couldn't map registers\n");
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sys/dev/ic/qemufwcfg.c
--- a/sys/dev/ic/qemufwcfg.c Sun Aug 25 14:02:26 2024 +0000
+++ b/sys/dev/ic/qemufwcfg.c Sun Aug 25 20:28:44 2024 +0000
@@ -64,6 +64,7 @@
static dev_type_open(fwcfg_open);
static dev_type_close(fwcfg_close);
static dev_type_read(fwcfg_read);
+static dev_type_write(fwcfg_write);
static dev_type_ioctl(fwcfg_ioctl);
=20
#define FWCFGUNIT(d) minor(d)
@@ -72,8 +73,97 @@ static void
fwcfg_select(struct fwcfg_softc *sc, uint16_t index)
{
=20
+ mutex_enter(&sc->sc_lock);
bus_space_write_2(sc->sc_bst, sc->sc_bsh, FWCFG_SEL_REG,
FWCFG_SEL_SWAP(index));
+ sc->sc_index =3D index;
+ sc->sc_dataoff =3D 0;
+ mutex_exit(&sc->sc_lock);
+}
+
+/*
+ * fwcfg_dma(sc)
+ *
+ * Caller has initialized sc->sc_dmadesc with a DMA operation.
+ * Trigger the DMA operation and wait for it to complete, fail, or
+ * time out.
+ *
+ * Currently qemu does these DMA operations synchronously, so
+ * timeout should never happen, but the spec allows the host to do
+ * asynchronous operations in the future.
+ */
+static int
+fwcfg_dma(struct fwcfg_softc *sc)
+{
+ uint32_t control;
+ unsigned timo =3D 1000; /* up to 1ms */
+
+ /*
+ * Caller initialized a DMA descriptor, so make sure the device
+ * will see it before we trigger DMA (BUS_DMASYNC_PREWRITE),
+ * and make sure we are ready to read what the device put there
+ * when DMA completes (BUS_DMASYNC_PREREAD).
+ */
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmadescmap,
+ 0, sizeof(*sc->sc_dmadesc),
+ BUS_DMASYNC_PREWRITE|BUS_DMASYNC_PREREAD);
+
+ /*
+ * Trigger DMA by writing the DMA descriptor's bus address to
+ * the DMA address register, in big-endian -- high 32 bits
+ * first, low 32 bits next.
+ */
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, FWCFG_DMA_ADDR,
+ htobe32(BUS_ADDR_HI32(sc->sc_dmadescmap->dm_segs[0].ds_addr)));
+ bus_space_write_4(sc->sc_bst, sc->sc_bsh, FWCFG_DMA_ADDR + 4,
+ htobe32(BUS_ADDR_LO32(sc->sc_dmadescmap->dm_segs[0].ds_addr)));
+
+ /*
+ * We have finished writing the descriptor to the device
+ * (BUS_DMASYNC_POSTWRITE); make sure we are ready to read what
+ * the device put there now that DMA has complteed
+ * (BUS_DMASYNC_POSTREAD).
+ */
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmadescmap,
+ 0, sizeof(*sc->sc_dmadesc),
+ BUS_DMASYNC_POSTWRITE|BUS_DMASYNC_POSTREAD);
+
+ /*
+ * Wait until one of:
+ *
+ * (a) The transfer has successfully completed, zeroing the
+ * control register.
+ *
+ * (b) Something went wrong, and the device set the ERROR bit.
+ *
+ * (c) We have timed out and given up.
+ */
+ while ((control =3D atomic_load_relaxed(&sc->sc_dmadesc->qd_control))
+ !=3D 0) {
+ if (control & FWCFG_DMA_CONTROL_ERROR)
+ return EIO;
+ if (--timo =3D=3D 0)
+ return ETIMEDOUT;
+
+ /*
+ * Pause for 1us before trying again.
+ */
+ DELAY(1);
+
+ /*
+ * If anything changed, make sure we observe updates
+ * from the device (BUS_DMASYNC_POSTREAD).
+ */
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmadescmap,
+ offsetof(struct fwcfg_dma, qd_control),
+ sizeof(sc->sc_dmadesc->qd_control),
+ BUS_DMASYNC_POSTREAD);
+ }
+
+ /*
+ * Success!
+ */
+ return 0;
}
=20
static int
@@ -130,15 +220,150 @@ fwcfg_read(dev_t dev, struct uio *uio, i
if (sc =3D=3D NULL)
return ENXIO;
=20
+ /*
+ * Take the lock to serialize access to the device's data
+ * offset.
+ */
+ mutex_enter(&sc->sc_lock);
+
+ /*
+ * Reject transfers that aren't at the device's current data
+ * offset.
+ *
+ * XXX Do these with the DMA interface instead, if supported.
+ *
+ * XXX OOPS -- This breaks userland compat.
+ */
+ if (0 && uio->uio_offset !=3D sc->sc_dataoff) {
+ error =3D ENODEV;
+ goto out;
+ }
+
+ /*
+ * Grab chunks of up to 64 bytes at a time by reading bytes
+ * sequentially from the data register, and uiomove them out to
+ * the user.
+ */
while (uio->uio_resid > 0) {
count =3D uimin(sizeof(buf), uio->uio_resid);
bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG,
buf, count);
+ sc->sc_dataoff +=3D count;
error =3D uiomove(buf, count, uio);
if (error !=3D 0)
break;
}
=20
+out: mutex_exit(&sc->sc_lock);
+ return error;
+}
+
+static int
+fwcfg_write(dev_t dev, struct uio *uio, int flags)
+{
+ struct fwcfg_softc * const sc =3D device_lookup_private(&qemufwcfg_cd,
+ FWCFGUNIT(dev));
+ bool loaded =3D false;
+ size_t xfersz =3D 0;
+ int error;
+
+ if (sc =3D=3D NULL)
+ return ENXIO;
+
+ /*
+ * Reject writes to hosts that don't support the DMA interface.
+ *
+ * Extremely old versions of qemu support writes through the
+ * traditional interface too. If anyone still cares about qemu
+ * v2.4, we can consider doing that.
+ */
+ if ((sc->sc_id & FW_CFG_ID_DMA_IF) =3D=3D 0)
+ return ENODEV;
+
+ /*
+ * Reject writes to negative space or to bytes beyond the
+ * 32-bit address space, since we can't seek past there and
+ * presumably qemu doesn't hold any data beyond.
+ */
+ if (uio->uio_offset < 0 ||
+ uio->uio_offset >=3D UINT32_MAX)
+ return EIO;
+
+ /*
+ * Take the lock to serialize access to the device's data
+ * offset and DMA.
+ */
+ mutex_enter(&sc->sc_lock);
+
+ /*
+ * If the transfer isn't to the device's current data offset,
+ * reset to the start by re-selecting the index, and skip bytes
+ * up to the transfer's offset.
+ */
+ if (uio->uio_offset !=3D sc->sc_dataoff) {
+ memset(sc->sc_dmadesc, 0, sizeof(*sc->sc_dmadesc));
+ sc->sc_dmadesc->qd_control =3D FWCFG_DMA_CONTROL_SKIP |
+ FWCFG_DMA_CONTROL_SELECT |
+ __SHIFTIN(sc->sc_index, FWCFG_DMA_CONTROL_INDEX);
+ sc->sc_dmadesc->qd_length =3D (uint32_t)uio->uio_offset;
+ sc->sc_dmadesc->qd_address =3D 0;
+ error =3D fwcfg_dma(sc);
+ if (error)
+ goto out;
+ }
+
+ /*
+ * Load and sync the DMA map for write transfer from the user's
+ * buffer.
+ */
+ error =3D bus_dmamap_load_uio(sc->sc_dmat, sc->sc_dmadatamap, uio,
+ BUS_DMA_NOWAIT);
+ if (error)
+ goto out;
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmadatamap,
+ 0, sc->sc_dmadatamap->dm_mapsize,
+ BUS_DMASYNC_PREWRITE);
+ loaded =3D true;
+
+ /*
+ * Determine the number of bytes we can actually transfer:
+ *
+ * 1. Limit it to the residual size of the transfer, uio_resid.
+ * 2. Limit it to the size of the DMA map. 3. Limit it to end
+ * at an offset of 2^32 - 1, since we can't seek past there and
+ * presumably qemu doesn't hold any data beyond.
+ */
+ xfersz =3D MIN(MIN(uio->uio_resid, sc->sc_dmadatamap->dm_mapsize),
+ UINT32_MAX - uio->uio_offset);
+
+ /*
+ * Initialize a DMA operation for write transfer from the
+ * user's data buffer.
+ */
+ memset(sc->sc_dmadesc, 0, sizeof(*sc->sc_dmadesc));
+ sc->sc_dmadesc->qd_control =3D FWCFG_DMA_CONTROL_WRITE;
+ sc->sc_dmadesc->qd_length =3D xfersz;
+ sc->sc_dmadesc->qd_address =3D sc->sc_dmadatamap->dm_segs[0].ds_addr;
+ error =3D fwcfg_dma(sc);
+ if (error)
+ goto out;
+
+ /*
+ * Success! Advance the transfer and the device's data offset.
+ */
+ uio->uio_resid -=3D xfersz;
+ uio->uio_offset +=3D xfersz;
+ sc->sc_dataoff +=3D xfersz;
+ error =3D 0;
+
+out: if (loaded) {
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmadatamap,
+ 0, sc->sc_dmadatamap->dm_mapsize,
+ BUS_DMASYNC_POSTWRITE);
+ bus_dmamap_unload(sc->sc_dmat, sc->sc_dmadatamap);
+ }
+
+ mutex_exit(&sc->sc_lock);
return error;
}
=20
@@ -166,7 +391,7 @@ const struct cdevsw qemufwcfg_cdevsw =3D {
.d_open =3D fwcfg_open,
.d_close =3D fwcfg_close,
.d_read =3D fwcfg_read,
- .d_write =3D nowrite,
+ .d_write =3D fwcfg_write,
.d_ioctl =3D fwcfg_ioctl,
.d_stop =3D nostop,
.d_tty =3D notty,
@@ -181,13 +406,162 @@ void
fwcfg_attach(struct fwcfg_softc *sc)
{
char sig[4];
+ uint8_t id_le[4];
+ char idstr[128];
+ int error;
=20
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
=20
- /* Read signature */
+ /*
+ * Read signature and id. Print the signature and the
+ * supported features.
+ */
fwcfg_select(sc, FW_CFG_SIGNATURE);
bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG,
sig, sizeof(sig));
- aprint_verbose_dev(sc->sc_dev, "<%c%c%c%c>\n",
- sig[0], sig[1], sig[2], sig[3]);
+ fwcfg_select(sc, FW_CFG_ID);
+ bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, FWCFG_DATA_REG,
+ id_le, sizeof(id_le));
+ sc->sc_id =3D le32dec(id_le);
+ snprintb(idstr, sizeof(idstr), FW_CFG_ID_FMT, sc->sc_id);
+ aprint_verbose_dev(sc->sc_dev, "<%c%c%c%c> features=3D%s\n",
+ sig[0], sig[1], sig[2], sig[3], idstr);
+
+ /*
+ * If the traditional interface is not supported, report an
+ * error.
+ */
+ if ((sc->sc_id & FW_CFG_ID_TRAD_IF) =3D=3D 0) {
+ aprint_error_dev(sc->sc_dev,
+ "traditional interface not supported\n");
+ }
+
+ /*
+ * If DMA is supported, create a DMA descriptor and DMA maps.
+ */
+ if (sc->sc_id & FW_CFG_ID_DMA_IF) do {
+ uint64_t dmasig;
+ int rseg =3D 0;
+ void *kva =3D NULL;
+ bool loaded =3D false;
+
+ /*
+ * Verify DMA is supported: DMA address register should
+ * read out 0x51454d5520434647, or `QEMU CFG' in
+ * big-endian.
+ */
+ dmasig =3D be32toh(bus_space_read_4(sc->sc_bst, sc->sc_bsh,
+ FWCFG_DMA_ADDR + 4));
+ dmasig |=3D (uint64_t)be32toh(bus_space_read_4(sc->sc_bst,
+ sc->sc_bsh, FWCFG_DMA_ADDR)) << 32;
+ if (dmasig !=3D 0x51454d5520434647) {
+ aprint_error_dev(sc->sc_dev, "bad DMA signature:"
+ " 0x%016"PRIx64"\n",
+ dmasig);
+ goto nodma;
+ }
+
+ /*
+ * Allocate memory for the DMA descriptor.
+ */
+ error =3D bus_dmamem_alloc(sc->sc_dmat, sizeof(*sc->sc_dmadesc),
+ /*align*/_Alignof(*sc->sc_dmadesc), /*boundary*/0,
+ &sc->sc_dmadescseg, /*nseg*/1, &rseg,
+ BUS_DMA_ALLOCNOW|BUS_DMA_WAITOK);
+ if (error) {
+ aprint_error_dev(sc->sc_dev, "bus_dmamem_alloc: %d\n",
+ error);
+ goto nodma;
+ }
+ KASSERTMSG(rseg =3D=3D 1, "rseg=3D%d", rseg);
+
+ /*
+ * Map the DMA descriptor memory into KVA.
+ */
+ error =3D bus_dmamem_map(sc->sc_dmat, &sc->sc_dmadescseg, 1,
+ sizeof(*sc->sc_dmadesc), &kva,
+ BUS_DMA_ALLOCNOW|BUS_DMA_WAITOK);
+ if (error) {
+ aprint_error_dev(sc->sc_dev, "bus_dmamem_map: %d\n",
+ error);
+ goto nodma;
+ }
+ sc->sc_dmadesc =3D kva;
+
+ /*
+ * Create a DMA map for the DMA descriptor so we can
+ * punch its bus address into the DMA address register.
+ */
+ error =3D bus_dmamap_create(sc->sc_dmat, sizeof(*sc->sc_dmadesc),
+ /*nseg*/1, /*maxsegsz*/sizeof(*sc->sc_dmadesc),
+ /*boundary*/0,
+ BUS_DMA_ALLOCNOW|BUS_DMA_WAITOK,
+ &sc->sc_dmadescmap);
+ if (error) {
+ aprint_error_dev(sc->sc_dev, "bus_dmamap_create(desc):"
+ " %d\n",
+ error);
+ goto nodma;
+ }
+
+ /*
+ * Load the DMA descriptor into the DMA map so we can
+ * get the bus address to punch into the DMA address
+ * register.
+ */
+ error =3D bus_dmamap_load(sc->sc_dmat, sc->sc_dmadescmap,
+ sc->sc_dmadesc, sizeof(*sc->sc_dmadesc), NULL,
+ BUS_DMA_ALLOCNOW|BUS_DMA_WAITOK);
+ if (error) {
+ aprint_error_dev(sc->sc_dev, "bus_dmamap_load: %d\n",
+ error);
+ goto nodma;
+ }
+ loaded =3D true;
+
+ /*
+ * Create a DMA map for a transfer of up to one page in
+ * a single segment.
+ */
+ error =3D bus_dmamap_create(sc->sc_dmat, PAGE_SIZE,
+ /*nseg*/1, /*maxsegsz*/PAGE_SIZE,
+ /*boundary*/PAGE_SIZE,
+ BUS_DMA_ALLOCNOW|BUS_DMA_WAITOK,
+ &sc->sc_dmadatamap);
+ if (error) {
+ aprint_error_dev(sc->sc_dev, "bus_dmamap_create(data):"
+ " %d\n",
+ error);
+ goto nodma;
+ }
+
+ /*
+ * Success!
+ */
+ break;
+
+nodma: aprint_error_dev(sc->sc_dev, "disabling DMA\n");
+ sc->sc_id &=3D FW_CFG_ID_DMA_IF;
+ if (sc->sc_dmadatamap) {
+ bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmadatamap);
+ sc->sc_dmadatamap =3D NULL;
+ }
+ if (loaded) {
+ bus_dmamap_unload(sc->sc_dmat, sc->sc_dmadescmap);
+ loaded =3D false;
+ }
+ if (sc->sc_dmadescmap) {
+ bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmadescmap);
+ sc->sc_dmadescmap =3D NULL;
+ }
+ if (sc->sc_dmadesc) {
+ bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmadesc,
+ sizeof(*sc->sc_dmadesc));
+ sc->sc_dmadesc =3D NULL;
+ }
+ if (rseg) {
+ bus_dmamem_free(sc->sc_dmat, &sc->sc_dmadescseg, 1);
+ rseg =3D 0;
+ }
+ } while (0);
}
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sys/dev/ic/qemufwcfgio.h
--- a/sys/dev/ic/qemufwcfgio.h Sun Aug 25 14:02:26 2024 +0000
+++ b/sys/dev/ic/qemufwcfgio.h Sun Aug 25 20:28:44 2024 +0000
@@ -34,6 +34,12 @@
/* Fixed selector keys */
#define FW_CFG_SIGNATURE 0x0000 /* Signature */
#define FW_CFG_ID 0x0001 /* Revision / feature bitmap */
+#define FW_CFG_ID_TRAD_IF __BIT(0) /* Traditional interface */
+#define FW_CFG_ID_DMA_IF __BIT(1) /* DMA interface */
+#define FW_CFG_ID_FMT "\177\020" \
+ "b\000" "TRAD_IF\0" \
+ "b\001" "DMA_IF\0" \
+ "\0"
#define FW_CFG_FILE_DIR 0x0019 /* File directory */
#define FW_CFG_FILE_FIRST 0x0020 /* First file in directory */
=20
diff -r 553cb2960ab3 -r 2d2ce7a8cd57 sys/dev/ic/qemufwcfgvar.h
--- a/sys/dev/ic/qemufwcfgvar.h Sun Aug 25 14:02:26 2024 +0000
+++ b/sys/dev/ic/qemufwcfgvar.h Sun Aug 25 20:28:44 2024 +0000
@@ -29,18 +29,42 @@
#ifndef _QEMUFWCFGVAR_H
#define _QEMUFWCFGVAR_H
=20
+#include <sys/types.h>
+
#include <sys/bus.h>
+#include <sys/cdefs.h>
#include <sys/device_if.h>
#include <sys/mutex.h>
-#include <sys/stdbool.h>
+
+struct fwcfg_dma { /* FWCfgDmaAccess */
+ uint32_t qd_control;
+#define FWCFG_DMA_CONTROL_ERROR __BIT(0)
+#define FWCFG_DMA_CONTROL_READ __BIT(1)
+#define FWCFG_DMA_CONTROL_SKIP __BIT(2)
+#define FWCFG_DMA_CONTROL_SELECT __BIT(3)
+#define FWCFG_DMA_CONTROL_WRITE __BIT(4)
+#define FWCFG_DMA_CONTROL_INDEX __BITS(16,31)
+ uint32_t qd_length;
+ uint64_t qd_address;
+};
=20
struct fwcfg_softc {
device_t sc_dev;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
+ bus_dma_tag_t sc_dmat;
+
+ uint32_t sc_id;
=20
kmutex_t sc_lock;
bool sc_open;
+ uint16_t sc_index;
+ off_t sc_dataoff;
+
+ bus_dmamap_t sc_dmadescmap;
+ bus_dma_segment_t sc_dmadescseg;
+ struct fwcfg_dma *sc_dmadesc;
+ bus_dmamap_t sc_dmadatamap;
};
=20
void fwcfg_attach(struct fwcfg_softc *);
--=_doTlMKKkYbLBtme6fab/51tYZGWhbizH--
(Contact us)
$NetBSD: query-full-pr,v 1.47 2022/09/11 19:34:41 kim Exp $
$NetBSD: gnats_config.sh,v 1.9 2014/08/02 14:16:04 spz Exp $
Copyright © 1994-2024
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.