On 10/27/26 10:08, Li Wang wrote:
> From: Mengmeng Zhao <[email protected]>
> 
> 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 <[email protected]>
> Signed-off-by: Li Wang <[email protected]>
> ---
>  .../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:[email protected]>
>  'y'   00-1F                                                            
> packet based user level communications
>                                                                         
> <mailto:[email protected]>
>  '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 <[email protected]>, Li Wang 
> <[email protected]>");
> +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 */

Reply via email to