mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Li Wang <liwang@kylinos.cn>
To: Arnd Bergmann <arnd@arndb.de>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Sumit Semwal" <sumit.semwal@linaro.org>,
	"Christian König" <christian.koenig@amd.com>,
	"Pavel Begunkov" <asml.silence@gmail.com>,
	linux-media@vger.kernel.org, dri-devel@lists.freedesktop.org,
	linaro-mm-sig@lists.linaro.org, io-uring@vger.kernel.org,
	linux-kernel@vger.kernel.org,
	"Mengmeng Zhao" <zhaomengmeng@kylinos.cn>,
	"Li Wang" <liwang@kylinos.cn>
Subject: [RFC PATCH v2 1/1] misc: fgds: introduce GPU-File direct I/O via dma-buf
Date: Tue, 27 Oct 2026 17:08:53 +0800	[thread overview]
Message-ID: <20261027090853.30099-2-liwang@kylinos.cn> (raw)
In-Reply-To: <20261027090853.30099-1-liwang@kylinos.cn>

From: Mengmeng Zhao <zhaomengmeng@kylinos.cn>

This patch introduces a driver that allows moving data between a GPU
dma-buf and a regular file on NVME without staging it through the page 
cache. Userspace registers the GPU dma-buf with the device, then issues 
read and write requests via the device's ioctls. Each transfer is direct 
I/O: an ITER_DMABUF_MAP iterator is passed to the file's read_iter or
write_iter, and the filesystem's iomap path submits REQ_DMABUF bios that
already carry the dma addresses.

Signed-off-by: Mengmeng Zhao <zhaomengmeng@kylinos.cn>
Signed-off-by: Li Wang <liwang@kylinos.cn>
---
 .../userspace-api/ioctl/ioctl-number.rst      |   2 +
 drivers/dma-buf/dma-buf-io.c                  |   3 +
 drivers/misc/Kconfig                          |  16 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/fgds.c                           | 506 ++++++++++++++++++
 fs/iomap/buffered-io.c                        |  11 +
 fs/iomap/direct-io.c                          |  23 +-
 fs/xfs/xfs_file.c                             |   4 +
 include/linux/iov_iter.h                      |   4 +
 include/uapi/linux/fgds.h                     | 124 +++++
 lib/iov_iter.c                                |   1 +
 mm/filemap.c                                  |  22 +-
 12 files changed, 713 insertions(+), 4 deletions(-)
 create mode 100644 drivers/misc/fgds.c
 create mode 100644 include/uapi/linux/fgds.h

diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 2fc53093752d..5f93a252e773 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -306,6 +306,8 @@ Code  Seq#    Include File                                             Comments
 'v'   20-27  arch/powerpc/include/uapi/asm/vas-api.h                   VAS API
 'v'   C0-FF  linux/meye.h                                              conflict!
 'w'   all                                                              CERN SCI driver
+'x'   00-0F  linux/fgds.h                                              Fast GPU Direct Storage
+                                                                       <mailto:liwang@kylinos.cn>
 'y'   00-1F                                                            packet based user level communications
                                                                        <mailto:zapman@interlan.net>
 'z'   00-3F                                                            CAN bus card conflict!
diff --git a/drivers/dma-buf/dma-buf-io.c b/drivers/dma-buf/dma-buf-io.c
index 8440151de860..2ee4fc683c74 100644
--- a/drivers/dma-buf/dma-buf-io.c
+++ b/drivers/dma-buf/dma-buf-io.c
@@ -135,6 +135,7 @@ struct dma_buf_io_map *dma_buf_io_create_map(struct dma_buf_io_ctx *ctx)
 	dma_resv_unlock(ctx->dmabuf->resv);
 	return map;
 }
+EXPORT_SYMBOL_NS_GPL(dma_buf_io_create_map, "DMA_BUF");
 
 static void dma_buf_io_ctx_shutdown(struct dma_buf_io_ctx *ctx)
 {
@@ -190,6 +191,7 @@ int dma_buf_io_ctx_create(struct file *file,
 	*out_ctx = ctx;
 	return 0;
 }
+EXPORT_SYMBOL_NS_GPL(dma_buf_io_ctx_create, "DMA_BUF");
 
 /**
  * dma_buf_io_ctx_release - Destroy the I/O context
@@ -211,6 +213,7 @@ void dma_buf_io_ctx_release(struct dma_buf_io_ctx *ctx)
 	dma_buf_put(ctx->dmabuf);
 	kfree(ctx);
 }
+EXPORT_SYMBOL_NS_GPL(dma_buf_io_ctx_release, "DMA_BUF");
 
 /**
  * dma_buf_io_invalidate_mappings - Invalidate the current map
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 7364931dad3a..b85fd64d27ad 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -568,6 +568,22 @@ config MCHP_LAN966X_PCI
 	    - lan966x-miim (MDIO_MSCC_MIIM)
 	    - lan966x-switch (LAN966X_SWITCH)
 
+config FGDS
+	tristate "Fast GPU Direct Storage support"
+	depends on DMA_SHARED_BUFFER && BLOCK
+	default n
+	help
+	  This driver exposes a misc device that moves data between a GPU
+	  dma-buf and a regular file with synchronous read/write ioctls,
+	  submitting the transfer as direct I/O (REQ_DMABUF bios) through
+	  the file system's iomap DIO path without a page cache bounce
+	  buffer.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called fgds.
+
+	  If unsure, say N.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e8d8d5d88c0d..cd98ef3d008e 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -70,4 +70,5 @@ obj-$(CONFIG_MCHP_LAN966X_PCI)	+= lan966x-pci.o
 obj-y				+= keba/
 obj-y				+= amd-sbi/
 obj-$(CONFIG_MISC_RP1)		+= rp1/
+obj-$(CONFIG_FGDS)		+= fgds.o
 obj-$(CONFIG_INTEL_SSEI)	+= issei/
diff --git a/drivers/misc/fgds.c b/drivers/misc/fgds.c
new file mode 100644
index 000000000000..3929a6514d23
--- /dev/null
+++ b/drivers/misc/fgds.c
@@ -0,0 +1,506 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Fast GPU Direct Storage via dma-buf.
+ *
+ * Copyright (C) 2026 KylinSoft. Co., Ltd. All rights reserved.
+ *
+ * Synchronous read/write ioctls that move data between a GPU dma-buf and
+ * a regular file.
+ */
+
+#include <linux/blkdev.h>
+#include <linux/dma-buf.h>
+#include <linux/dma-buf-io.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/fileattr.h>
+#include <linux/fs.h>
+#include <linux/fsnotify.h>
+#include <linux/magic.h>
+#include <linux/math.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/pagemap.h>
+#include <linux/sched/xacct.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/uio.h>
+#include <linux/xarray.h>
+
+#include <linux/fgds.h>
+
+static struct miscdevice fgds_misc;
+
+/* One registered buffer: a dma-buf bound to a block device. */
+struct fgds_reg {
+	struct kref		kref;
+	struct dma_buf_io_ctx	*ctx;
+	struct file		*bdev_file;
+	struct block_device	*bdev;
+	u64			size;
+};
+
+/* State of one /dev/fgds open file description; handles are private to it. */
+struct fgds_state {
+	struct xarray		regs;
+	/* protects @regs and, with the reg kref, the lifetime of entries */
+	struct mutex		lock;
+};
+
+static void fgds_reg_release(struct kref *kref)
+{
+	struct fgds_reg *reg = container_of(kref, struct fgds_reg, kref);
+
+	dma_buf_io_ctx_release(reg->ctx);
+	fput(reg->bdev_file);
+	kfree(reg);
+}
+
+static void fgds_reg_put(struct fgds_reg *reg)
+{
+	kref_put(&reg->kref, fgds_reg_release);
+}
+
+static struct fgds_state *fgds_state(struct file *file)
+{
+	return file->private_data;
+}
+
+/*
+ * File and file-system level gate.
+ */
+static int fgds_gate(struct file *ffile)
+{
+	struct inode *inode = file_inode(ffile);
+	struct super_block *sb = inode->i_sb;
+	struct file_kattr fa;
+
+	if (!S_ISREG(inode->i_mode))
+		return -EINVAL;
+	if (sb->s_magic != XFS_SUPER_MAGIC)
+		return -EOPNOTSUPP;
+	if (IS_DAX(inode))
+		return -EOPNOTSUPP;
+	if (mapping_stable_writes(inode->i_mapping))
+		return -EOPNOTSUPP;
+	if (!sb->s_bdev)
+		return -EOPNOTSUPP;
+	if (bdev_is_zoned(sb->s_bdev))
+		return -EOPNOTSUPP;
+	if (!sb->s_bdev->bd_disk->fops->init_dma_buf_io_ctx)
+		return -EOPNOTSUPP;
+
+	/*
+	 * Realtime inodes live on a separate device whose DMA setup is not
+	 * covered here.
+	 */
+	if (vfs_fileattr_get(ffile->f_path.dentry, &fa))
+		return -EOPNOTSUPP;
+	if (fa.fsx_xflags & FS_XFLAG_REALTIME)
+		return -EOPNOTSUPP;
+	return 0;
+}
+
+static unsigned int fgds_file_align(struct inode *inode, bool write)
+{
+	unsigned int lbs = bdev_logical_block_size(inode->i_sb->s_bdev);
+
+	if (!write)
+		return lbs;
+	return max(1U << inode->i_blkbits, lbs);
+}
+
+static unsigned int fgds_dma_align(struct inode *inode)
+{
+	return bdev_dma_alignment(inode->i_sb->s_bdev) + 1;
+}
+
+static struct fgds_reg *fgds_reg_get(struct fgds_state *state, u32 handle)
+{
+	struct fgds_reg *reg;
+
+	mutex_lock(&state->lock);
+	reg = xa_load(&state->regs, handle);
+	if (reg)
+		kref_get(&reg->kref);
+	mutex_unlock(&state->lock);
+	return reg;
+}
+
+static int fgds_unregister_handle(struct fgds_state *state, u32 handle)
+{
+	struct fgds_reg *reg;
+
+	mutex_lock(&state->lock);
+	reg = xa_erase(&state->regs, handle);
+	mutex_unlock(&state->lock);
+	if (reg)
+		fgds_reg_put(reg);
+	return reg ? 0 : -EINVAL;
+}
+
+static int fgds_register(struct file *file, struct fgds_register_args __user *uarg)
+{
+	struct fgds_register_args args;
+	struct fgds_state *state = fgds_state(file);
+	struct dma_buf_io_ctx *ctx = NULL;
+	struct block_device *bdev;
+	struct file *bdev_file;
+	struct dma_buf *dbuf;
+	struct fd bfdf;
+	struct fgds_reg *reg;
+	u32 id;
+	int ret;
+
+	if (copy_from_user(&args, uarg, sizeof(args)))
+		return -EFAULT;
+	if (args.handle || args.pad || args.reserved)
+		return -EINVAL;
+
+	dbuf = dma_buf_get(args.dfd);
+	if (IS_ERR(dbuf))
+		return PTR_ERR(dbuf);
+
+	bfdf = fdget(args.bdev_fd);
+	if (fd_empty(bfdf)) {
+		ret = -EBADF;
+		goto out_dmabuf;
+	}
+	bdev_file = fd_file(bfdf);
+	if (!S_ISBLK(file_inode(bdev_file)->i_mode)) {
+		ret = -EINVAL;
+		goto out_fdput;
+	}
+	/* blkdev_init_dma_buf_io_ctx() needs O_DIRECT. */
+	if (!(bdev_file->f_flags & O_DIRECT)) {
+		ret = -EINVAL;
+		goto out_fdput;
+	}
+
+	bdev = file_bdev(bdev_file);
+	if (!bdev->bd_disk->fops->init_dma_buf_io_ctx) {
+		ret = -EOPNOTSUPP;
+		goto out_fdput;
+	}
+
+	ret = -ENOMEM;
+	reg = kzalloc_obj(*reg);
+	if (!reg)
+		goto out_fdput;
+
+	get_file(bdev_file);
+
+	ret = dma_buf_io_ctx_create(bdev_file, dbuf, DMA_BIDIRECTIONAL, &ctx);
+	if (ret) {
+		dev_err(fgds_misc.this_device,
+			"dma-buf-io context on %pg failed: %pe\n",
+			bdev, ERR_PTR(ret));
+		goto out_file;
+	}
+
+	kref_init(&reg->kref);
+	reg->ctx = ctx;
+	reg->bdev_file = bdev_file;
+	reg->bdev = bdev;
+	reg->size = dbuf->size;
+
+	mutex_lock(&state->lock);
+	ret = xa_alloc(&state->regs, &id, reg, XA_LIMIT(1, UINT_MAX),
+		       GFP_KERNEL);
+	if (ret) {
+		mutex_unlock(&state->lock);
+		goto out_ctx;
+	}
+
+	args.handle = id;
+	/* the window must stay inside the buffer and within 32 bits */
+	args.max_doffset = min_t(u64, reg->size, UINT_MAX);
+
+	if (copy_to_user(uarg, &args, sizeof(args))) {
+		xa_erase(&state->regs, id);
+		mutex_unlock(&state->lock);
+		ret = -EFAULT;
+		goto out_ctx;
+	}
+	mutex_unlock(&state->lock);
+
+	fdput(bfdf);
+	dma_buf_put(dbuf);
+	return 0;
+
+out_ctx:
+	dma_buf_io_ctx_release(ctx);
+out_file:
+	fput(bdev_file);
+	kfree(reg);
+out_fdput:
+	fdput(bfdf);
+out_dmabuf:
+	dma_buf_put(dbuf);
+	return ret;
+}
+
+static int fgds_unregister(struct file *file, struct fgds_unregister_args __user *uarg)
+{
+	struct fgds_unregister_args args;
+
+	if (copy_from_user(&args, uarg, sizeof(args)))
+		return -EFAULT;
+	if (args.reserved)
+		return -EINVAL;
+	return fgds_unregister_handle(fgds_state(file), args.handle);
+}
+
+/*
+ * Launch one read_iter()/write_iter() call.
+ * Returns the number of bytes the file system moved, or a negative
+ * errno; a short read means the end of the file.
+ */
+static ssize_t fgds_do_rw(struct fgds_state *state,
+			  const struct fgds_rw_args *args, bool write)
+{
+	struct fgds_reg *reg;
+	struct dma_buf_io_map *map;
+	struct file *ffile;
+	struct inode *inode;
+	struct iov_iter iter;
+	struct kiocb kiocb;
+	struct fd fdf;
+	ssize_t (*iter_fn)(struct kiocb *kiocb, struct iov_iter *i);
+	unsigned int file_align, dma_align;
+	u64 length = args->length;
+	u64 dbuf_end;
+	loff_t offset, end;
+	ssize_t ret;
+
+	if (args->flags)
+		return -EINVAL;
+	if (length > MAX_RW_COUNT)
+		return -EINVAL;
+
+	offset = args->offset;
+	if (offset < 0 || check_add_overflow(offset, (loff_t)length, &end))
+		return -EINVAL;
+	/*
+	 * The block layer's dma-buf iterator truncates the segment offset to
+	 * 32 bits, and the window must stay inside the registered buffer.
+	 */
+	if (check_add_overflow(args->doffset, length, &dbuf_end))
+		return -EINVAL;
+	if (dbuf_end > UINT_MAX)
+		return -EINVAL;
+
+	fdf = fdget(args->ffd);
+	if (fd_empty(fdf))
+		return -EBADF;
+	ffile = fd_file(fdf);
+	inode = file_inode(ffile);
+
+	ret = -EINVAL;
+	if (!S_ISREG(inode->i_mode) || (ffile->f_flags & O_APPEND))
+		goto out_fdput;
+
+	if (write) {
+		if (!(ffile->f_mode & FMODE_WRITE) ||
+		    !(ffile->f_mode & FMODE_CAN_WRITE))
+			goto out_fdput;
+		iter_fn = ffile->f_op->write_iter;
+	} else {
+		if (!(ffile->f_mode & FMODE_READ) ||
+		    !(ffile->f_mode & FMODE_CAN_READ))
+			goto out_fdput;
+		iter_fn = ffile->f_op->read_iter;
+	}
+	if (!iter_fn) {
+		ret = -EOPNOTSUPP;
+		goto out_fdput;
+	}
+
+	ret = fgds_gate(ffile);
+	if (ret)
+		goto out_fdput;
+
+	reg = fgds_reg_get(state, args->handle);
+	if (!reg) {
+		ret = -EINVAL;
+		goto out_fdput;
+	}
+
+	if (inode->i_sb->s_bdev != reg->bdev) {
+		ret = -EXDEV;
+		goto out_reg;
+	}
+
+	file_align = fgds_file_align(inode, write);
+	dma_align = fgds_dma_align(inode);
+	if ((offset | (loff_t)length) & (file_align - 1) ||
+	    (args->doffset | (loff_t)length) & (dma_align - 1) ||
+	    dbuf_end > reg->size ||
+	    (!write && end > i_size_read(inode))) {
+		ret = -EINVAL;
+		goto out_reg;
+	}
+
+	ret = rw_verify_area(write ? WRITE : READ, ffile, &offset, length);
+	if (ret)
+		goto out_reg;
+	if (write)
+		inc_syscw(current);
+	else
+		inc_syscr(current);
+
+	if (!length) {
+		ret = 0;
+		goto out_reg;
+	}
+
+	map = dma_buf_io_get_map(reg->ctx, false);
+	if (IS_ERR(map)) {
+		ret = PTR_ERR(map);
+		if (ret != -EAGAIN)
+			dev_err_ratelimited(fgds_misc.this_device,
+					    "dma-buf mapping failed on %pg: %pe\n",
+					    reg->bdev, ERR_PTR(ret));
+		goto out_reg;
+	}
+
+	/*
+	 * A direct I/O read would leave any cached pages of the range
+	 * stale, so flush and drop them before submitting.
+	 */
+	if (!write) {
+		ret = filemap_write_and_wait_range(ffile->f_mapping,
+						   offset, end - 1);
+		if (ret)
+			goto out_map;
+		ret = invalidate_inode_pages2_range(ffile->f_mapping,
+						    offset >> PAGE_SHIFT,
+						    (end - 1) >> PAGE_SHIFT);
+		if (ret) {
+			/*
+			 * Report a retryable error instead of letting
+			 * -EBUSY escape.
+			 */
+			if (ret == -EBUSY)
+				ret = -EAGAIN;
+			goto out_map;
+		}
+	}
+
+	if (write)
+		file_start_write(ffile);
+	iov_iter_dmabuf_map(&iter, write ? ITER_SOURCE : ITER_DEST,
+			    map, args->doffset, length);
+	init_sync_kiocb(&kiocb, ffile);
+	kiocb.ki_flags |= IOCB_DIRECT;
+	if (write)
+		kiocb.ki_flags |= IOCB_WRITE;
+	kiocb.ki_pos = offset;
+
+	ret = iter_fn(&kiocb, &iter);
+	if (WARN_ON_ONCE(ret == -EIOCBQUEUED))
+		ret = -EIO;
+	if (write)
+		file_end_write(ffile);
+
+	if (ret > 0) {
+		if (write) {
+			fsnotify_modify(ffile);
+			add_wchar(current, ret);
+		} else {
+			fsnotify_access(ffile);
+			add_rchar(current, ret);
+		}
+	}
+
+out_map:
+	dma_buf_io_map_drop(map);
+out_reg:
+	fgds_reg_put(reg);
+out_fdput:
+	fdput(fdf);
+	return ret;
+}
+
+static long fgds_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct fgds_state *state = fgds_state(file);
+	struct fgds_rw_args args;
+
+	switch (cmd) {
+	case FGDS_IOCTL_WRITE:
+	case FGDS_IOCTL_READ:
+		if (copy_from_user(&args, (void __user *)arg, sizeof(args)))
+			return -EFAULT;
+		return fgds_do_rw(state, &args, cmd == FGDS_IOCTL_WRITE);
+	case FGDS_IOCTL_REGISTER_BUF:
+		return fgds_register(file, (struct fgds_register_args __user *)arg);
+	case FGDS_IOCTL_UNREGISTER_BUF:
+		return fgds_unregister(file, (struct fgds_unregister_args __user *)arg);
+	}
+	return -ENOTTY;
+}
+
+static int fgds_open(struct inode *inode, struct file *file)
+{
+	struct fgds_state *state;
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return -ENOMEM;
+
+	xa_init_flags(&state->regs, XA_FLAGS_ALLOC);
+	mutex_init(&state->lock);
+	file->private_data = state;
+	return 0;
+}
+
+static int fgds_release(struct inode *inode, struct file *file)
+{
+	struct fgds_state *state = fgds_state(file);
+	struct fgds_reg *reg;
+	unsigned long id;
+
+	xa_for_each(&state->regs, id, reg) {
+		xa_erase(&state->regs, id);
+		fgds_reg_put(reg);
+	}
+	xa_destroy(&state->regs);
+	mutex_destroy(&state->lock);
+	kfree(state);
+	return 0;
+}
+
+static const struct file_operations fgds_fops = {
+	.owner		= THIS_MODULE,
+	.open		= fgds_open,
+	.release	= fgds_release,
+	.unlocked_ioctl	= fgds_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
+};
+
+static struct miscdevice fgds_misc = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= "fgds",
+	.fops	= &fgds_fops,
+};
+
+static int __init fgds_init(void)
+{
+	return misc_register(&fgds_misc);
+}
+
+static void __exit fgds_exit(void)
+{
+	misc_deregister(&fgds_misc);
+}
+
+module_init(fgds_init);
+module_exit(fgds_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("DMA_BUF");
+MODULE_AUTHOR("Mengmeng Zhao <zhaomengmeng@kylinos.cn>, Li Wang <liwang@kylinos.cn>");
+MODULE_DESCRIPTION("Fast GPU Direct Storage");
+MODULE_VERSION("1.0.0");
diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c
index 0a5ebfda90f1..a7ab28edab98 100644
--- a/fs/iomap/buffered-io.c
+++ b/fs/iomap/buffered-io.c
@@ -1304,6 +1304,17 @@ iomap_file_buffered_write(struct kiocb *iocb, struct iov_iter *i,
 	};
 	ssize_t ret;
 
+	/*
+	 * The copy helpers have no source for a dma-buf iterator: the copy
+	 * would move zero bytes, ->write_end() would reject the untouched
+	 * folio, fault_in_iov_iter_readable() reports the iterator as
+	 * readable, and the loop below would never advance.  Refuse here
+	 * rather than at each caller's entry point, so no path can reach
+	 * the copy with such an iterator.
+	 */
+	if (iov_iter_is_dmabuf_map(i))
+		return -EOPNOTSUPP;
+
 	if (iocb->ki_flags & IOCB_NOWAIT)
 		iter.flags |= IOMAP_NOWAIT;
 	if (iocb->ki_flags & IOCB_DONTCACHE)
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index 8b4039d16ce8..eccc07d53e9c 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -595,8 +595,16 @@ static int iomap_dio_bio_iter(struct iomap_iter *iter, struct iomap_dio *dio)
 
 static int iomap_dio_hole_iter(struct iomap_iter *iter, struct iomap_dio *dio)
 {
-	loff_t length = iov_iter_zero(iomap_length(iter), dio->submit.iter);
+	loff_t length;
 
+	/*
+	 * dmabuf maps do not support zero-filling holes or
+	 * unwritten extents.
+	 */
+	if (iov_iter_is_dmabuf_map(dio->submit.iter))
+		return -EOPNOTSUPP;
+
+	length = iov_iter_zero(iomap_length(iter), dio->submit.iter);
 	dio->size += length;
 	if (!length)
 		return -EFAULT;
@@ -683,8 +691,8 @@ static int iomap_dio_iter(struct iomap_iter *iter, struct iomap_dio *dio)
  * the correct total number of bytes transferred; this is essential for
  * completing partial requests asynchronously.
  *
- * Returns -ENOTBLK In case of a page invalidation invalidation failure for
- * writes.  The callers needs to fall back to buffered I/O in this case.
+ * Returns -ENOTBLK in case of a page cache invalidation failure for writes.
+ * The caller needs to fall back to buffered I/O in this case.
  */
 struct iomap_dio *
 __iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
@@ -794,6 +802,15 @@ __iomap_dio_rw(struct kiocb *iocb, struct iov_iter *iter,
 					 * the caller tries again.
 					 */
 					ret = -EAGAIN;
+				} else if (iov_iter_is_dmabuf_map(iter)) {
+					/*
+					 * Avoid -ENOTBLK (buffered fallback)
+					 * as dmabuf are not supported. Map
+					 * -EBUSY to -EAGAIN for transient
+					 * retires.
+					 */
+					if (ret == -EBUSY)
+						ret = -EAGAIN;
 				} else {
 					/* fall back to buffered write */
 					ret = -ENOTBLK;
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 426a67b813a7..f16e9cb63e9e 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -1211,6 +1211,10 @@ xfs_file_write_iter(
 			return ret;
 	}
 
+	/* zoned and buffered I/O is not supported for dmabuf */
+	if (iov_iter_is_dmabuf_map(from))
+		return -EOPNOTSUPP;
+
 	if (xfs_is_zoned_inode(ip))
 		return xfs_file_buffered_write_zoned(iocb, from);
 	return xfs_file_buffered_write(iocb, from);
diff --git a/include/linux/iov_iter.h b/include/linux/iov_iter.h
index 2f91884d733b..a3b99e6d8255 100644
--- a/include/linux/iov_iter.h
+++ b/include/linux/iov_iter.h
@@ -376,6 +376,10 @@ size_t iterate_and_advance_kernel(struct iov_iter *iter, size_t len, void *priv,
 		return iterate_folioq(iter, len, priv, priv2, step);
 	if (iov_iter_is_xarray(iter))
 		return iterate_xarray(iter, len, priv, priv2, step);
+	if (iov_iter_is_dmabuf_map(iter)) {
+		WARN_ON_ONCE(1);
+		return 0;
+	}
 	return iterate_discard(iter, len, priv, priv2, step);
 }
 
diff --git a/include/uapi/linux/fgds.h b/include/uapi/linux/fgds.h
new file mode 100644
index 000000000000..9a721c92cde2
--- /dev/null
+++ b/include/uapi/linux/fgds.h
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Fast GPU Direct Storage user-kernel ABI definitions.
+ *
+ * Copyright (C) 2026 KylinSoft. Co., Ltd. All rights reserved.
+ *
+ * Synchronous read/write ioctls that move data between a GPU dma-buf and
+ * a regular file.
+ */
+#ifndef _FGDS_H
+#define _FGDS_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/**
+ * struct fgds_register_args - arguments for %FGDS_IOCTL_REGISTER_BUF
+ *
+ * @dfd:         dma-buf file descriptor exported by the GPU runtime.
+ * @bdev_fd:     file descriptor of the underlying block device (the
+ *               device the target files live on), opened with %O_DIRECT.
+ *               The device is named separately from the file because:
+ *               building the dma-buf I/O context needs a file for the
+ *               device itself, and the device side requires %O_DIRECT
+ *               on that file, which a regular file descriptor cannot
+ *               provide.
+ * @handle:      Returned handle for use with the other ioctls.  Handles
+ *               are private to the /dev/fgds file descriptor they were
+ *               created on and are released automatically when that
+ *               descriptor is closed.  Output.
+ * @pad:         Must be 0.
+ * @max_doffset: Largest accepted value of @doffset plus @length: the
+ *               window must stay inside the registered dma-buf and
+ *               within the 32-bit offset bound of the block-layer
+ *               dma-buf iterator, so this reports the smaller of the
+ *               dma-buf size and %UINT_MAX.  Output.
+ * @reserved:    Must be 0.  Extension slot for further output fields.
+ *
+ * A registered handle keeps the device attachment alive, and once a
+ * transfer has mapped the buffer that mapping is held for the handle's
+ * lifetime.
+ *
+ * Returns 0, or a negative errno.
+ */
+struct fgds_register_args {
+	__s32 dfd;
+	__s32 bdev_fd;
+	__u32 handle;
+	__u32 pad;
+	__u64 max_doffset;
+	__u64 reserved;
+};
+
+/**
+ * struct fgds_unregister_args - arguments for %FGDS_IOCTL_UNREGISTER_BUF
+ *
+ * @handle:   Handle returned by %FGDS_IOCTL_REGISTER_BUF.  Input.
+ * @reserved: Must be 0.
+ *
+ * Returns 0, or -EINVAL if @handle is not registered on this file
+ * descriptor.
+ */
+struct fgds_unregister_args {
+	__u32 handle;
+	__u32 reserved;
+};
+
+/**
+ * struct fgds_rw_args - arguments for %FGDS_IOCTL_WRITE / %FGDS_IOCTL_READ
+ *
+ * @handle:      Handle from %FGDS_IOCTL_REGISTER_BUF naming both the
+ *               dma-buf and the block device to transfer between.  Input.
+ * @ffd:         Regular file descriptor.  %O_DIRECT is not required on
+ *               it, fgds forces the direct I/O path itself.  Its file
+ *               system's device must match the one registered with
+ *               @handle or the transfer fails with %EXDEV.  Input.
+ * @offset:      Byte offset inside the file. Input.
+ * @length:      Transfer length in bytes. Unlike pread() and pwrite(),
+ *               which silently truncate an oversized request, fgds rejects
+ *               it with %EINVAL. Input.
+ * @doffset:     Byte offset inside the dma-buf, i.e. the start of the
+ *               window the transfer reads from or writes to. Input.
+ * @flags:       Must be 0.  Input.
+ *
+ * Like pread() and pwrite(), the ioctl returns the number of bytes
+ * transferred or a negative errno. A return value below @length means a
+ * read stopped at the end of the file, or a write clamped by the calling
+ * process' RLIMIT_FSIZE.
+ *
+ * Contract and failure modes:
+ *
+ * - Alignment is enforced on two independent axes, both answered by
+ *   statx() with %STATX_DIOALIGN.  The file side applies to @offset and
+ *   @length: the device logical block size for a read, the max of that
+ *   and the file system block size for a write. The buffer side applies
+ *   to @doffset and @length: the device DMA alignment. A transfer must
+ *   satisfy both axes at once.
+ * - Ranges backed by file holes or unwritten extents are not supported
+ *   for reads and fail with %EOPNOTSUPP: there is no page cache bounce
+ *   to serve zeros from, and GPU memory is generally not CPU accessible.
+ * - Writes past the end of the file go through the file system's own
+ *   direct I/O extend path; no preallocation is performed by fgds.  An
+ *   application that wants the space reserved must call fallocate()
+ *   itself first.
+ * - Reads flush and drop any page cache of the transferred range before
+ *   submitting, so subsequent buffered reads observe the fresh data.
+ */
+struct fgds_rw_args {
+	__u32 handle;
+	__s32 ffd;
+	__u64 offset;
+	__u64 length;
+	__u64 doffset;
+	__u64 flags;
+};
+
+#define FGDS_IOCTL_MAGIC	'x'
+
+#define FGDS_IOCTL_WRITE		_IOW(FGDS_IOCTL_MAGIC, 1, struct fgds_rw_args)
+#define FGDS_IOCTL_READ			_IOW(FGDS_IOCTL_MAGIC, 2, struct fgds_rw_args)
+#define FGDS_IOCTL_REGISTER_BUF		_IOWR(FGDS_IOCTL_MAGIC, 3, struct fgds_register_args)
+#define FGDS_IOCTL_UNREGISTER_BUF	_IOW(FGDS_IOCTL_MAGIC, 4, struct fgds_unregister_args)
+
+#endif /* _FGDS_H */
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index 9b46f9ada8f9..b19bf7f5e4cf 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -790,6 +790,7 @@ void iov_iter_dmabuf_map(struct iov_iter *i, unsigned int direction,
 		.iov_offset = off,
 	};
 }
+EXPORT_SYMBOL_GPL(iov_iter_dmabuf_map);
 
 /**
  * iov_iter_discard - Initialise an I/O iterator that discards data
diff --git a/mm/filemap.c b/mm/filemap.c
index 6afec636881f..ab8a5391a699 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3013,6 +3013,14 @@ generic_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
 			return retval;
 	}
 
+	/*
+	 * Buffered I/O read is not supported for dmabuf; a short direct
+	 * read before this point stands on its own, as in
+	 * direct_write_fallback().
+	 */
+	if (iov_iter_is_dmabuf_map(iter))
+		return retval > 0 ? retval : -EOPNOTSUPP;
+
 	return filemap_read(iocb, iter, retval);
 }
 EXPORT_SYMBOL(generic_file_read_iter);
@@ -4300,8 +4308,14 @@ generic_file_direct_write(struct kiocb *iocb, struct iov_iter *from)
 	 */
 	written = kiocb_invalidate_pages(iocb, write_len);
 	if (written) {
-		if (written == -EBUSY)
+		if (written == -EBUSY) {
+			/*
+			 * Avoid fallback to buffered write for dmabuf.
+			 */
+			if (iov_iter_is_dmabuf_map(from))
+				return -EAGAIN;
 			return 0;
+		}
 		return written;
 	}
 
@@ -4353,6 +4367,12 @@ ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 	long status = 0;
 	ssize_t written = 0;
 
+	/*
+	 * Buffered write is not supported for dmabuf.
+	 */
+	if (iov_iter_is_dmabuf_map(i))
+		return -EOPNOTSUPP;
+
 	do {
 		struct folio *folio;
 		size_t offset;		/* Offset into folio */
-- 
2.34.1


  reply	other threads:[~2026-10-27  9:09 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-08 13:15 [RFC PATCH] misc: fgds: enable GPU-NVMe direct I/O via POSIX and io_uring Li Wang
2026-09-09  6:10 ` Greg Kroah-Hartman
2026-09-09 10:42   ` Li Wang
2026-09-09 13:35     ` Greg Kroah-Hartman
2026-09-09 13:45       ` Christian König
2026-09-10  3:57         ` Li Wang
2026-09-10  8:35           ` Christian König
2026-09-12 12:45           ` Pavel Begunkov
2026-10-27  9:08 ` [RFC PATCH v2 0/1] misc: fgds: introduce GPU-File direct I/O via dma-buf Li Wang
2026-10-27  9:08   ` Li Wang [this message]
2026-10-27 13:03     ` [RFC PATCH v2 1/1] " Jens Axboe
2026-10-27 13:12       ` Greg Kroah-Hartman
2026-10-27 13:12     ` Christian König
2026-10-27 13:54   ` [RFC PATCH v2 0/1] " Pavel Begunkov
2026-10-28  3:01     ` Li Wang

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=20261027090853.30099-2-liwang@kylinos.cn \
    --to=liwang@kylinos.cn \
    --cc=arnd@arndb.de \
    --cc=asml.silence@gmail.com \
    --cc=christian.koenig@amd.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=io-uring@vger.kernel.org \
    --cc=linaro-mm-sig@lists.linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=sumit.semwal@linaro.org \
    --cc=zhaomengmeng@kylinos.cn \
    /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

all inboxes | Powered by JetHome®