mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Christian König" <christian.koenig@amd.com>
To: Li Wang <liwang@kylinos.cn>, Arnd Bergmann <arnd@arndb.de>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Sumit Semwal <sumit.semwal@linaro.org>,
	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>
Subject: Re: [RFC PATCH v2 1/1] misc: fgds: introduce GPU-File direct I/O via dma-buf
Date: Tue, 27 Oct 2026 14:12:37 +0100	[thread overview]
Message-ID: <fa64ae97-72a0-4c0f-9988-18500936a8a8@amd.com> (raw)
In-Reply-To: <20261027090853.30099-2-liwang@kylinos.cn>

On 10/27/26 10:08, Li Wang wrote:
> 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.

Why in the world is that still useful with Pavel work on io_uring?

> 
> 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");

Clear NAK to that. Those are FS layer helper functionalities for DMA-buf and not something drivers should ever need to mess with.

Regards,
Christian.

>  
>  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 */


  parent reply	other threads:[~2026-10-27 13:12 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   ` [RFC PATCH v2 1/1] " Li Wang
2026-10-27 13:03     ` Jens Axboe
2026-10-27 13:12       ` Greg Kroah-Hartman
2026-10-27 13:12     ` Christian König [this message]
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=fa64ae97-72a0-4c0f-9988-18500936a8a8@amd.com \
    --to=christian.koenig@amd.com \
    --cc=arnd@arndb.de \
    --cc=asml.silence@gmail.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=liwang@kylinos.cn \
    --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®