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(®->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(®->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(®->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 */
next prev 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®