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 GPUdma-buf and a regular file with synchronous read/write ioctls,submitting the transfer as direct I/O (REQ_DMABUF bios) throughthe file system's iomap DIO path without a page cache bouncebuffer.To compile this driver as a module, choose M here: the modulewill 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 */