* [PATCH 0/2] fpga: add dma-buf based FPGA programming interface
@ 2026-08-16 15:07 Aravind Thokala
2026-08-16 15:07 ` [PATCH 1/2] fpga: Add dma-buf interface for FPGA programming Aravind Thokala
2026-08-16 15:07 ` [PATCH 2/2] fpga: versal: add dma-buf programming support Aravind Thokala
0 siblings, 2 replies; 3+ messages in thread
From: Aravind Thokala @ 2026-08-16 15:07 UTC (permalink / raw)
To: mdf, yilun.xu, trix, sumit.semwal, christian.koenig
Cc: corbet, skhan, linux-kernel, linux-fpga, linux-media, dri-devel,
linaro-mm-sig, linux-doc, git, aravind.thokala,
nava.kishore.manne
Some systems need to load large FPGA configuration images. The FPGA
subsystem allows loading images from the filesystem, but this requires
the entire image to be loaded into kernel memory first. For drivers
that need a DMA-capable buffer for programming, the data is then
copied again into DMA memory. This creates needless memory pressure
and delays due to the extra copy.
This series adds dma-buf support that allows userspace to allocate a
buffer directly from a DMA heap, write the FPGA image into it, and
pass the file descriptor to the kernel via ioctl — skipping the
intermediate kernel buffer entirely.
Userspace flow:
1. Allocate buffer from /dev/dma_heap/ (e.g., CMA heap)
2. mmap the buffer and write the FPGA image into it
3. ioctl(/dev/fpgaX, FPGA_IOCTL_LOAD_DMA_BUF, &dmabuf_fd)
The dma-buf logic is implemented as a separate layer on top of the
FPGA manager, keeping buffer management separate from the write path.
Individual FPGA drivers opt in by calling fpga_dmabuf_register().
---
This work is based on the approach discussed in [1].
[1] https://lore.kernel.org/all/20231122053035.3758124-1-nava.kishore.manne@amd.com/
---
Aravind Thokala (2):
fpga: Add dma-buf interface for FPGA programming
fpga: versal: add dma-buf programming support
.../userspace-api/ioctl/ioctl-number.rst | 1 +
MAINTAINERS | 1 +
drivers/fpga/Kconfig | 9 +
drivers/fpga/Makefile | 2 +
drivers/fpga/fpga-dmabuf.c | 198 ++++++++++++++++++
drivers/fpga/versal-fpga.c | 33 ++-
include/linux/fpga/fpga-dmabuf.h | 22 ++
include/linux/fpga/fpga-mgr.h | 1 +
include/uapi/linux/fpga.h | 15 ++
9 files changed, 281 insertions(+), 1 deletion(-)
create mode 100644 drivers/fpga/fpga-dmabuf.c
create mode 100644 include/linux/fpga/fpga-dmabuf.h
create mode 100644 include/uapi/linux/fpga.h
--
2.34.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] fpga: Add dma-buf interface for FPGA programming
2026-08-16 15:07 [PATCH 0/2] fpga: add dma-buf based FPGA programming interface Aravind Thokala
@ 2026-08-16 15:07 ` Aravind Thokala
2026-08-16 15:07 ` [PATCH 2/2] fpga: versal: add dma-buf programming support Aravind Thokala
1 sibling, 0 replies; 3+ messages in thread
From: Aravind Thokala @ 2026-08-16 15:07 UTC (permalink / raw)
To: mdf, yilun.xu, trix, sumit.semwal, christian.koenig
Cc: corbet, skhan, linux-kernel, linux-fpga, linux-media, dri-devel,
linaro-mm-sig, linux-doc, git, aravind.thokala,
nava.kishore.manne
Loading FPGA images through the firmware interface requires the image
to be loaded into kernel memory first and then copied into DMA memory.
This extra copy creates needless memory pressure and delays.
Add a module that allows userspace to pass a pre-allocated DMA buffer
directly to the FPGA manager via ioctl, eliminating the extra copy.
A miscdevice (/dev/fpgaX) is created for each FPGA manager that
registers dma-buf support. The module imports the dma-buf fd, maps it
for DMA, and calls the FPGA manager load API with the scatter-gather
table.
Co-developed-by: Nava kishore Manne <nava.kishore.manne@amd.com>
Signed-off-by: Nava kishore Manne <nava.kishore.manne@amd.com>
Signed-off-by: Aravind Thokala <aravind.thokala@amd.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
MAINTAINERS | 1 +
drivers/fpga/Kconfig | 9 +
drivers/fpga/Makefile | 2 +
drivers/fpga/fpga-dmabuf.c | 198 ++++++++++++++++++
include/linux/fpga/fpga-dmabuf.h | 22 ++
include/linux/fpga/fpga-mgr.h | 1 +
include/uapi/linux/fpga.h | 15 ++
8 files changed, 249 insertions(+)
create mode 100644 drivers/fpga/fpga-dmabuf.c
create mode 100644 include/linux/fpga/fpga-dmabuf.h
create mode 100644 include/uapi/linux/fpga.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 3f0ef1e27eb0..f8537dd23463 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -170,6 +170,7 @@ Code Seq# Include File Comments
'I' all linux/isdn.h conflict!
'I' 00-0F drivers/isdn/divert/isdn_divert.h conflict!
'I' 40-4F linux/mISDNif.h conflict!
+'J' 01 uapi/linux/fpga.h FPGA DMA buffer interface
'K' all linux/kd.h
'L' 00-1F linux/loop.h conflict!
'L' 10-1F drivers/scsi/mpt3sas/mpt3sas_ctl.h conflict!
diff --git a/MAINTAINERS b/MAINTAINERS
index 5114e6db7307..7551dfa35d1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10217,6 +10217,7 @@ F: Documentation/driver-api/fpga/
F: Documentation/fpga/
F: drivers/fpga/
F: include/linux/fpga/
+F: include/uapi/linux/fpga.h
FRAMEBUFFER CONSOLE
M: Helge Deller <deller@gmx.de>
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig
index 748fc210c135..e3ad791c9808 100644
--- a/drivers/fpga/Kconfig
+++ b/drivers/fpga/Kconfig
@@ -157,6 +157,15 @@ config OF_FPGA_REGION
Support for loading FPGA images by applying a Device Tree
overlay.
+config FPGA_DMA_BUF
+ tristate "FPGA DMA Buffer Support"
+ depends on DMA_SHARED_BUFFER
+ help
+ Support for programming FPGAs from a pre-allocated DMA buffer
+ shared via dma-buf. A miscdevice (/dev/fpgaX) accepts a dma-buf
+ file descriptor via ioctl to load the FPGA image without
+ redundant memory copies.
+
config FPGA_DFL
tristate "FPGA Device Feature List (DFL) support"
select FPGA_BRIDGE
diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile
index 6f5798b27e0d..e52154296d2d 100644
--- a/drivers/fpga/Makefile
+++ b/drivers/fpga/Makefile
@@ -40,6 +40,8 @@ obj-$(CONFIG_XILINX_PR_DECOUPLER) += xilinx-pr-decoupler.o
# High Level Interfaces
obj-$(CONFIG_FPGA_REGION) += fpga-region.o
obj-$(CONFIG_OF_FPGA_REGION) += of-fpga-region.o
+# FPGA DMA Buffer Interface
+obj-$(CONFIG_FPGA_DMA_BUF) += fpga-dmabuf.o
# FPGA Device Feature List Support
obj-$(CONFIG_FPGA_DFL) += dfl.o
diff --git a/drivers/fpga/fpga-dmabuf.c b/drivers/fpga/fpga-dmabuf.c
new file mode 100644
index 000000000000..0e92eb134698
--- /dev/null
+++ b/drivers/fpga/fpga-dmabuf.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * FPGA Manager DMA Buffer Support
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+#include <linux/capability.h>
+#include <linux/dma-buf.h>
+#include <linux/fpga/fpga-dmabuf.h>
+#include <linux/fpga/fpga-mgr.h>
+#include <linux/kref.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <uapi/linux/fpga.h>
+
+struct fpga_dmabuf_priv {
+ struct kref ref;
+ struct mutex lock; /* serializes ioctl against teardown */
+ bool dead;
+ struct fpga_manager *mgr;
+ struct miscdevice miscdev;
+};
+
+static void fpga_dmabuf_free(struct kref *ref)
+{
+ struct fpga_dmabuf_priv *priv = container_of(ref,
+ struct fpga_dmabuf_priv, ref);
+
+ put_device(&priv->mgr->dev);
+ kfree(priv->miscdev.name);
+ kfree(priv);
+}
+
+static int fpga_dmabuf_load(struct fpga_manager *mgr, int buffd)
+{
+ struct fpga_image_info *info;
+ struct dma_buf_attachment *attach;
+ struct dma_buf *dmabuf;
+ struct sg_table *sgt;
+ int ret;
+
+ dmabuf = dma_buf_get(buffd);
+ if (IS_ERR(dmabuf))
+ return PTR_ERR(dmabuf);
+
+ info = fpga_image_info_alloc(mgr->dev.parent);
+ if (!info) {
+ ret = -ENOMEM;
+ goto err_put;
+ }
+
+ attach = dma_buf_attach(dmabuf, mgr->dev.parent);
+ if (IS_ERR(attach)) {
+ ret = PTR_ERR(attach);
+ goto err_free_info;
+ }
+
+ sgt = dma_buf_map_attachment_unlocked(attach, DMA_TO_DEVICE);
+ if (IS_ERR(sgt)) {
+ ret = PTR_ERR(sgt);
+ goto err_detach;
+ }
+
+ info->sgt = sgt;
+
+ ret = fpga_mgr_lock(mgr);
+ if (ret)
+ goto err_unmap;
+
+ ret = fpga_mgr_load(mgr, info);
+ fpga_mgr_unlock(mgr);
+
+err_unmap:
+ dma_buf_unmap_attachment_unlocked(attach, sgt, DMA_TO_DEVICE);
+err_detach:
+ dma_buf_detach(dmabuf, attach);
+err_free_info:
+ fpga_image_info_free(info);
+err_put:
+ dma_buf_put(dmabuf);
+
+ return ret;
+}
+
+static int fpga_dmabuf_open(struct inode *inode, struct file *file)
+{
+ struct miscdevice *miscdev = file->private_data;
+ struct fpga_dmabuf_priv *priv;
+
+ priv = container_of(miscdev, struct fpga_dmabuf_priv, miscdev);
+ kref_get(&priv->ref);
+ file->private_data = priv;
+
+ return 0;
+}
+
+static int fpga_dmabuf_release(struct inode *inode, struct file *file)
+{
+ struct fpga_dmabuf_priv *priv = file->private_data;
+
+ kref_put(&priv->ref, fpga_dmabuf_free);
+ return 0;
+}
+
+static long fpga_dmabuf_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct fpga_dmabuf_priv *priv = file->private_data;
+ int buffd;
+ int ret;
+
+ switch (cmd) {
+ case FPGA_IOCTL_LOAD_DMA_BUF:
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+ if (copy_from_user(&buffd, (void __user *)arg, sizeof(buffd)))
+ return -EFAULT;
+
+ mutex_lock(&priv->lock);
+ if (priv->dead) {
+ mutex_unlock(&priv->lock);
+ return -ENODEV;
+ }
+ ret = fpga_dmabuf_load(priv->mgr, buffd);
+ mutex_unlock(&priv->lock);
+ return ret;
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations fpga_dmabuf_fops = {
+ .owner = THIS_MODULE,
+ .open = fpga_dmabuf_open,
+ .release = fpga_dmabuf_release,
+ .unlocked_ioctl = fpga_dmabuf_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+int fpga_dmabuf_register(struct fpga_manager *mgr)
+{
+ struct fpga_dmabuf_priv *priv;
+ int ret;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ kref_init(&priv->ref);
+ mutex_init(&priv->lock);
+ get_device(&mgr->dev);
+ priv->mgr = mgr;
+ priv->miscdev.minor = MISC_DYNAMIC_MINOR;
+ priv->miscdev.name = kstrdup(dev_name(&mgr->dev), GFP_KERNEL);
+ if (!priv->miscdev.name) {
+ put_device(&mgr->dev);
+ kfree(priv);
+ return -ENOMEM;
+ }
+ priv->miscdev.fops = &fpga_dmabuf_fops;
+ priv->miscdev.parent = &mgr->dev;
+
+ ret = misc_register(&priv->miscdev);
+ if (ret) {
+ kfree(priv->miscdev.name);
+ put_device(&mgr->dev);
+ kfree(priv);
+ return ret;
+ }
+
+ mgr->dmabuf_priv = priv;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fpga_dmabuf_register);
+
+void fpga_dmabuf_unregister(struct fpga_manager *mgr)
+{
+ struct fpga_dmabuf_priv *priv = mgr->dmabuf_priv;
+
+ if (!priv)
+ return;
+
+ mutex_lock(&priv->lock);
+ priv->dead = true;
+ mutex_unlock(&priv->lock);
+
+ misc_deregister(&priv->miscdev);
+ mgr->dmabuf_priv = NULL;
+ kref_put(&priv->ref, fpga_dmabuf_free);
+}
+EXPORT_SYMBOL_GPL(fpga_dmabuf_unregister);
+
+MODULE_IMPORT_NS("DMA_BUF");
+MODULE_DESCRIPTION("FPGA DMA Buffer Interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fpga/fpga-dmabuf.h b/include/linux/fpga/fpga-dmabuf.h
new file mode 100644
index 000000000000..bf54bdfdf968
--- /dev/null
+++ b/include/linux/fpga/fpga-dmabuf.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+#ifndef _LINUX_FPGA_DMABUF_H
+#define _LINUX_FPGA_DMABUF_H
+
+struct fpga_manager;
+
+#if IS_REACHABLE(CONFIG_FPGA_DMA_BUF)
+int fpga_dmabuf_register(struct fpga_manager *mgr);
+void fpga_dmabuf_unregister(struct fpga_manager *mgr);
+#else
+static inline int fpga_dmabuf_register(struct fpga_manager *mgr)
+{
+ return 0;
+}
+
+static inline void fpga_dmabuf_unregister(struct fpga_manager *mgr) {}
+#endif
+
+#endif /* _LINUX_FPGA_DMABUF_H */
diff --git a/include/linux/fpga/fpga-mgr.h b/include/linux/fpga/fpga-mgr.h
index 0d4fe068f3d8..38af47f60503 100644
--- a/include/linux/fpga/fpga-mgr.h
+++ b/include/linux/fpga/fpga-mgr.h
@@ -213,6 +213,7 @@ struct fpga_manager {
const struct fpga_manager_ops *mops;
struct module *mops_owner;
void *priv;
+ void *dmabuf_priv;
};
#define to_fpga_manager(d) container_of(d, struct fpga_manager, dev)
diff --git a/include/uapi/linux/fpga.h b/include/uapi/linux/fpga.h
new file mode 100644
index 000000000000..868650c66526
--- /dev/null
+++ b/include/uapi/linux/fpga.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * FPGA userspace API
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+#ifndef _UAPI_LINUX_FPGA_H
+#define _UAPI_LINUX_FPGA_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define FPGA_IOCTL_LOAD_DMA_BUF _IOW('J', 1, __s32)
+
+#endif /* _UAPI_LINUX_FPGA_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] fpga: versal: add dma-buf programming support
2026-08-16 15:07 [PATCH 0/2] fpga: add dma-buf based FPGA programming interface Aravind Thokala
2026-08-16 15:07 ` [PATCH 1/2] fpga: Add dma-buf interface for FPGA programming Aravind Thokala
@ 2026-08-16 15:07 ` Aravind Thokala
1 sibling, 0 replies; 3+ messages in thread
From: Aravind Thokala @ 2026-08-16 15:07 UTC (permalink / raw)
To: mdf, yilun.xu, trix, sumit.semwal, christian.koenig
Cc: corbet, skhan, linux-kernel, linux-fpga, linux-media, dri-devel,
linaro-mm-sig, linux-doc, git, aravind.thokala,
nava.kishore.manne
The existing .write callback allocates a DMA-coherent buffer and
copies the image into it before passing the address to firmware. With
dma-buf, the buffer is already DMA-mapped by userspace.
Add a .write_sg callback that extracts the DMA address directly from
the scatter-gather table and passes it to zynqmp_pm_load_pdi(),
avoiding the intermediate copy. Register the dma-buf interface in
probe with devres-managed cleanup.
Co-developed-by: Nava kishore Manne <nava.kishore.manne@amd.com>
Signed-off-by: Nava kishore Manne <nava.kishore.manne@amd.com>
Signed-off-by: Aravind Thokala <aravind.thokala@amd.com>
---
drivers/fpga/versal-fpga.c | 33 ++++++++++++++++++++++++++++++++-
1 file changed, 32 insertions(+), 1 deletion(-)
diff --git a/drivers/fpga/versal-fpga.c b/drivers/fpga/versal-fpga.c
index e6189106c468..bdaa15a6c991 100644
--- a/drivers/fpga/versal-fpga.c
+++ b/drivers/fpga/versal-fpga.c
@@ -4,6 +4,7 @@
*/
#include <linux/dma-mapping.h>
+#include <linux/fpga/fpga-dmabuf.h>
#include <linux/fpga/fpga-mgr.h>
#include <linux/io.h>
#include <linux/kernel.h>
@@ -37,11 +38,34 @@ static int versal_fpga_ops_write(struct fpga_manager *mgr,
return ret;
}
+static int versal_fpga_ops_write_sg(struct fpga_manager *mgr,
+ struct sg_table *sgt)
+{
+ dma_addr_t dma_addr;
+
+ /* zynqmp_pm_load_pdi() takes a single base address */
+ if (sgt->nents != 1) {
+ dev_err(&mgr->dev, "dma-buf has %u segments, need exactly 1\n",
+ sgt->nents);
+ return -EINVAL;
+ }
+
+ dma_addr = sg_dma_address(sgt->sgl);
+
+ return zynqmp_pm_load_pdi(PDI_SRC_DDR, dma_addr);
+}
+
static const struct fpga_manager_ops versal_fpga_ops = {
.write_init = versal_fpga_ops_write_init,
.write = versal_fpga_ops_write,
+ .write_sg = versal_fpga_ops_write_sg,
};
+static void versal_fpga_dmabuf_remove(void *data)
+{
+ fpga_dmabuf_unregister(data);
+}
+
static int versal_fpga_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -56,7 +80,14 @@ static int versal_fpga_probe(struct platform_device *pdev)
mgr = devm_fpga_mgr_register(dev, "Xilinx Versal FPGA Manager",
&versal_fpga_ops, NULL);
- return PTR_ERR_OR_ZERO(mgr);
+ if (IS_ERR(mgr))
+ return PTR_ERR(mgr);
+
+ ret = fpga_dmabuf_register(mgr);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, versal_fpga_dmabuf_remove, mgr);
}
static const struct of_device_id versal_fpga_of_match[] = {
--
2.34.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-16 15:07 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-16 15:07 [PATCH 0/2] fpga: add dma-buf based FPGA programming interface Aravind Thokala
2026-08-16 15:07 ` [PATCH 1/2] fpga: Add dma-buf interface for FPGA programming Aravind Thokala
2026-08-16 15:07 ` [PATCH 2/2] fpga: versal: add dma-buf programming support Aravind Thokala
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox