From: Ahmad Fatoum <a.fatoum@barebox.org>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@barebox.org>
Subject: [PATCH 5/6] firmware: qemu_fw_cfg: add proper DMA and PIO bidirectional operating modes
Date: Thu, 15 Jan 2026 13:06:13 +0100 [thread overview]
Message-ID: <20260115120748.3433463-6-a.fatoum@barebox.org> (raw)
In-Reply-To: <20260115120748.3433463-1-a.fatoum@barebox.org>
We have a strange hodgepodge of DMA and PIO right now: We write always
via DMA, but we read via PIO, except that we skip leading bytes to reach
the intended offset via the DMA API.
This doesn't make much sense, because if DMA is unavailable, all of it
is. Rework this by checking the feature flag and either using DMA all
the way if possible or PIO as fallback.
For added flexibility, it's possible to switch between DMA and PIO at
runtime using the fw_cfg.pio device parameter.
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
drivers/firmware/qemu_fw_cfg.c | 231 ++++++++++++++++++++++++---------
1 file changed, 169 insertions(+), 62 deletions(-)
diff --git a/drivers/firmware/qemu_fw_cfg.c b/drivers/firmware/qemu_fw_cfg.c
index 33cc5bc54ce9..7a699539d672 100644
--- a/drivers/firmware/qemu_fw_cfg.c
+++ b/drivers/firmware/qemu_fw_cfg.c
@@ -35,11 +35,12 @@ struct fw_cfg {
void __iomem *reg_data;
void __iomem *reg_dma;
struct cdev cdev;
- loff_t next_read_offset;
u32 sel;
bool is_mmio;
struct fw_cfg_dma_access __iomem *acc_virt;
dma_addr_t acc_dma;
+ u32 rev;
+ int use_pio;
};
static struct fw_cfg *to_fw_cfg(struct cdev *cdev)
@@ -70,7 +71,6 @@ static int fw_cfg_ioctl(struct cdev *cdev, unsigned int request, void *buf)
switch (request) {
case FW_CFG_SELECT:
fw_cfg->sel = *(u16 *)buf;
- fw_cfg->next_read_offset = 0;
break;
default:
ret = -ENOTTY;
@@ -79,6 +79,105 @@ static int fw_cfg_ioctl(struct cdev *cdev, unsigned int request, void *buf)
return 0;
}
+static inline bool fw_cfg_dma_enabled(struct fw_cfg *fw_cfg)
+{
+ return (fw_cfg->rev & FW_CFG_VERSION_DMA) && fw_cfg->reg_dma;
+}
+
+/* qemu fw_cfg device is sync today, but spec says it may become async */
+static void fw_cfg_wait_for_control(struct fw_cfg_dma_access *d)
+{
+ for (;;) {
+ u32 ctrl = be32_to_cpu(READ_ONCE(d->control));
+
+ /* do not reorder the read to d->control */
+ /* rmb(); */
+ if ((ctrl & ~FW_CFG_DMA_CTL_ERROR) == 0)
+ return;
+
+ cpu_relax();
+ }
+}
+
+static ssize_t fw_cfg_dma_transfer(struct fw_cfg *fw_cfg,
+ void *address, u32 length, u32 control)
+{
+ phys_addr_t dma;
+ struct fw_cfg_dma_access *d = NULL;
+ ssize_t ret = length;
+
+ d = dma_alloc(sizeof(*d));
+ if (!d) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ /* fw_cfg device does not need IOMMU protection, so use physical addresses */
+ *d = (struct fw_cfg_dma_access) {
+ .address = cpu_to_be64(address ? virt_to_phys(address) : 0),
+ .length = cpu_to_be32(length),
+ .control = cpu_to_be32(control)
+ };
+
+ dma = virt_to_phys(d);
+
+ iowrite32be((u64)dma >> 32, fw_cfg->reg_dma);
+ /* force memory to sync before notifying device via MMIO */
+ /* wmb(); */
+ iowrite32be(dma, fw_cfg->reg_dma + 4);
+
+ fw_cfg_wait_for_control(d);
+
+ if (be32_to_cpu(READ_ONCE(d->control)) & FW_CFG_DMA_CTL_ERROR) {
+ ret = -EIO;
+ }
+
+end:
+ dma_free(d);
+
+ return ret;
+}
+
+static ssize_t fw_cfg_dma_transfer_at(struct fw_cfg *fw_cfg,
+ void *buf, u32 count, loff_t pos,
+ u32 direction)
+{
+ int ret;
+
+ if (pos == 0) {
+ ret = fw_cfg_dma_transfer(fw_cfg, buf, count,
+ fw_cfg->sel << 16 |
+ FW_CFG_DMA_CTL_SELECT | direction);
+ } else {
+ fw_cfg_select(fw_cfg);
+ ret = fw_cfg_dma_transfer(fw_cfg, NULL, pos, FW_CFG_DMA_CTL_SKIP);
+ if (ret >= 0)
+ ret = fw_cfg_dma_transfer(fw_cfg, buf, count, direction);
+ }
+
+ return ret;
+}
+
+static ssize_t fw_cfg_dma_read(struct cdev *cdev, void *buf,
+ size_t count, loff_t pos, unsigned long flags)
+{
+ return fw_cfg_dma_transfer_at(to_fw_cfg(cdev), buf, count, pos,
+ FW_CFG_DMA_CTL_READ);
+}
+
+static ssize_t fw_cfg_dma_write(struct cdev *cdev, const void *buf,
+ size_t count, loff_t pos, unsigned long flags)
+{
+ return fw_cfg_dma_transfer_at(to_fw_cfg(cdev), (void *)buf, count, pos,
+ FW_CFG_DMA_CTL_WRITE);
+}
+
+static struct cdev_operations fw_cfg_dma_ops = {
+ .read = fw_cfg_dma_read,
+ .write = fw_cfg_dma_write,
+ .ioctl = fw_cfg_ioctl,
+};
+
static void reads_n(const void __iomem *src, void *dst,
resource_size_t count, int rwsize)
{
@@ -104,83 +203,72 @@ static void ins_n(unsigned long src, void *dst,
}
}
-static void fw_cfg_do_dma(struct fw_cfg *fw_cfg, dma_addr_t address,
- u32 count, u32 control)
-{
- struct fw_cfg_dma_access __iomem *acc = fw_cfg->acc_virt;
-
- acc->address = cpu_to_be64(address);
- acc->length = cpu_to_be32(count);
- acc->control = cpu_to_be32(control);
-
- iowrite64be(fw_cfg->acc_dma, fw_cfg->reg_dma);
-
- while (ioread32be(&acc->control) & ~FW_CFG_DMA_CTL_ERROR)
- ;
-}
-
-static ssize_t fw_cfg_read(struct cdev *cdev, void *buf, size_t count,
- loff_t pos, unsigned long flags)
+static ssize_t fw_cfg_pio_read(struct cdev *cdev, void *buf, size_t count,
+ loff_t pos, unsigned long flags)
{
struct fw_cfg *fw_cfg = to_fw_cfg(cdev);
unsigned rdsize = FIELD_GET(O_RWSIZE_MASK, flags) ?: 8;
- if (!pos || pos != fw_cfg->next_read_offset) {
- fw_cfg_select(fw_cfg);
- fw_cfg->next_read_offset = 0;
- }
-
if (!IS_ALIGNED(pos, rdsize) || !IS_ALIGNED(count, rdsize))
rdsize = 1;
+ fw_cfg_select(fw_cfg);
+ while (pos-- > 0)
+ ioread8(fw_cfg->reg_data);
+
if (fw_cfg->is_mmio)
reads_n(fw_cfg->reg_data, buf, count, rdsize);
else
ins_n((ulong)fw_cfg->reg_data, buf, count, rdsize);
- fw_cfg->next_read_offset += count;
return count;
}
-static ssize_t fw_cfg_write(struct cdev *cdev, const void *buf, size_t count,
- loff_t pos, unsigned long flags)
+static void writes_n(void __iomem *dst, const void *src,
+ resource_size_t count, int rwsize)
{
- struct fw_cfg *fw_cfg = to_fw_cfg(cdev);
- struct device *dev = cdev->dev;
- void *dma_buf;
- dma_addr_t mapping;
- int ret = 0;
-
- dma_buf = dma_alloc(count);
- if (!dma_buf)
- return -ENOMEM;
-
- memcpy(dma_buf, buf, count);
-
- mapping = dma_map_single(dev, dma_buf, count, DMA_TO_DEVICE);
- if (dma_mapping_error(dev, mapping)) {
- ret = -EFAULT;
- goto free_buf;
+ switch (rwsize) {
+ case 1: writesb(dst, src, count / 1); break;
+ case 2: writesw(dst, src, count / 2); break;
+ case 4: writesl(dst, src, count / 4); break;
+#ifdef CONFIG_64BIT
+ case 8: writesq(dst, src, count / 8); break;
+#endif
}
-
- fw_cfg->next_read_offset = 0;
-
- fw_cfg_do_dma(fw_cfg, DMA_ERROR_CODE, pos, FW_CFG_DMA_CTL_SKIP |
- FW_CFG_DMA_CTL_SELECT | fw_cfg->sel << 16);
-
- fw_cfg_do_dma(fw_cfg, mapping, count, FW_CFG_DMA_CTL_WRITE |
- FW_CFG_DMA_CTL_SELECT | fw_cfg->sel << 16);
-
- dma_unmap_single(dev, mapping, count, DMA_FROM_DEVICE);
-free_buf:
- dma_free(dma_buf);
-
- return ret ?: count;
}
-static struct cdev_operations fw_cfg_ops = {
- .read = fw_cfg_read,
- .write = fw_cfg_write,
+static void outs_n(unsigned long dst, const void *src,
+ resource_size_t count, int rwsize)
+{
+ switch (rwsize) {
+ case 1: outsb(dst, src, count / 1); break;
+ case 2: outsw(dst, src, count / 2); break;
+ case 4: outsl(dst, src, count / 4); break;
+ /* No insq, so just do 32-bit accesses */
+ case 8: outsl(dst, src, count / 4); break;
+ }
+}
+
+static ssize_t fw_cfg_pio_write(struct cdev *cdev, const void *buf, size_t count,
+ loff_t pos, unsigned long flags)
+{
+ struct fw_cfg *fw_cfg = to_fw_cfg(cdev);
+ unsigned wrsize = FIELD_GET(O_RWSIZE_MASK, flags) ?: 8;
+
+ if (!IS_ALIGNED(pos, wrsize) || !IS_ALIGNED(count, wrsize))
+ wrsize = 1;
+
+ if (fw_cfg->is_mmio)
+ writes_n(fw_cfg->reg_data, buf, count, wrsize);
+ else
+ outs_n((ulong)fw_cfg->reg_data, buf, count, wrsize);
+
+ return count;
+}
+
+static struct cdev_operations fw_cfg_pio_ops = {
+ .read = fw_cfg_pio_read,
+ .write = fw_cfg_pio_write,
.ioctl = fw_cfg_ioctl,
};
@@ -191,12 +279,20 @@ static int fw_cfg_param_select(struct param_d *p, void *priv)
return fw_cfg->sel <= U16_MAX ? 0 : -EINVAL;
}
+static int fw_cfg_param_use_pio(struct param_d *p, void *priv)
+{
+ struct fw_cfg *fw_cfg = priv;
+ fw_cfg->cdev.ops = fw_cfg->use_pio ? &fw_cfg_pio_ops : &fw_cfg_dma_ops;
+ return 0;
+}
+
static int fw_cfg_probe(struct device *dev)
{
struct device_node *np = dev_of_node(dev);
struct resource *parent_res, *iores;
char sig[FW_CFG_SIG_SIZE];
struct fw_cfg *fw_cfg;
+ __le32 rev;
int ret;
fw_cfg = xzalloc(sizeof(*fw_cfg));
@@ -225,20 +321,28 @@ static int fw_cfg_probe(struct device *dev)
/* verify fw_cfg device signature */
fw_cfg->sel = FW_CFG_SIGNATURE;
- fw_cfg_read(&fw_cfg->cdev, sig, FW_CFG_SIG_SIZE, 0, 0);
+ fw_cfg_pio_read(&fw_cfg->cdev, sig, FW_CFG_SIG_SIZE, 0, 0);
if (memcmp(sig, "QEMU", FW_CFG_SIG_SIZE) != 0) {
ret = np ? -EILSEQ : -ENODEV;
goto err;
}
+ fw_cfg->sel = FW_CFG_ID;
+ ret = fw_cfg_pio_read(&fw_cfg->cdev, &rev, sizeof(rev), 0, 0);
+ if (ret < 0)
+ goto err;
+
+ fw_cfg->rev = le32_to_cpu(rev);
+ fw_cfg->use_pio = !fw_cfg_dma_enabled(fw_cfg);
+
fw_cfg->acc_virt = dma_alloc_coherent(DMA_DEVICE_BROKEN,
sizeof(*fw_cfg->acc_virt), &fw_cfg->acc_dma);
fw_cfg->cdev.name = "fw_cfg";
fw_cfg->cdev.flags = DEVFS_IS_CHARACTER_DEV;
fw_cfg->cdev.size = 0;
- fw_cfg->cdev.ops = &fw_cfg_ops;
+ fw_cfg->cdev.ops = fw_cfg->use_pio ? &fw_cfg_pio_ops : &fw_cfg_dma_ops;
fw_cfg->cdev.dev = dev;
fw_cfg->cdev.filetype = filetype_qemu_fw_cfg;
@@ -255,6 +359,9 @@ static int fw_cfg_probe(struct device *dev)
dev_add_param_uint32(dev, "selector", fw_cfg_param_select,
NULL, &fw_cfg->sel, "%u", fw_cfg);
+ dev_add_param_bool(dev, "pio", fw_cfg_param_use_pio, NULL,
+ &fw_cfg->use_pio, fw_cfg);
+
dev->priv = fw_cfg;
return 0;
--
2.47.3
next prev parent reply other threads:[~2026-01-15 12:08 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-15 12:06 [PATCH 0/6] firmware: qemu_fw_cfg: improve support Ahmad Fatoum
2026-01-15 12:06 ` [PATCH 1/6] firmware: qemu_fw_cfg: use wider PIO reads if applicable Ahmad Fatoum
2026-01-15 12:06 ` [PATCH 2/6] param: support uuid/guid parameter type Ahmad Fatoum
2026-01-15 12:06 ` [PATCH 3/6] lib: smbios: add support for setting product UUID Ahmad Fatoum
2026-01-15 12:06 ` [PATCH 4/6] common: boards: qemu: process some standard fw_cfg keys Ahmad Fatoum
2026-01-19 11:13 ` Sascha Hauer
2026-01-19 11:33 ` Ahmad Fatoum
2026-01-15 12:06 ` Ahmad Fatoum [this message]
2026-01-15 12:06 ` [PATCH 6/6] ARM: configs: multi: enable QEMU FW_CFG Ahmad Fatoum
2026-01-19 12:11 ` [PATCH 0/6] firmware: qemu_fw_cfg: improve support Sascha Hauer
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260115120748.3433463-6-a.fatoum@barebox.org \
--to=a.fatoum@barebox.org \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox