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 <[email protected]>
Signed-off-by: Nava kishore Manne <[email protected]>
Signed-off-by: Aravind Thokala <[email protected]>
---
 .../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 <[email protected]>
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

Reply via email to