Add dmabuf read/write via io_uring
Subsystems (16)
v4 cover Add dmabuf read/write via io_uring
The patch set allows to register a dmabuf to an io_uring instance for a specified file and use it with io_uring read / write requests. The infrastructure is not tied to io_uring and there could be more users in the future. A similar idea was attempted some years ago by Keith [1], from where I borrowed a good number of changes, and later was brough up by Tushar and Vishal.
It's an opt-in feature for files, and they need to implement a new file operation to use it. Only NVMe block devices are supported in this series. The user API is built on top of io_uring's "registered buffers", where a dmabuf is registered in a special way, but after it can be used as any other "registered buffer" with IORING_OP_{READ,WRITE}_FIXED requests. It's created via a new file operation and the resulted map is then passed through the I/O stack in a new iterator type. There is some additional infrastructure to glue it together, count requests, manage lifetime and implement invalidation.
Tushar, William, Phil as well as Kanchan, Anuj and Nitesh did a lot of testing and experimentation with different devices. Some earlier benchmarks by Anuj for IOMMU optimisations with udmabuf showed:
STRICT: before = 570 KIOPS, after = 5.01 MIOPS
LAZY: before = 1.93 MIOPS, after = 5.01 MIOPS
PASSTHROUGH: before = 5.01 MIOPS, after = 5.01 MIOPS
# Patch set structure:
- Patches 1-2 introduce internal API and infrastructure mediating io_uring and target subsystem / devices
- Patches 3-7: block layer support + prep patches
- Patches 8-9 implement NVMe support
- Patches 10-14 add io_uring support and uapi. Patch 14 specifically enables dma-buf support, the rest are preparation patches.
There are some liburing tests that can serve as an example:
git: https://github.com/isilence/liburing.git rw-dmabuf-tests-v4
url: https://github.com/isilence/liburing/tree/rw-dmabuf-tests-v4
[1] https://lore.kernel.org/io-uring/[email protected]/
v4: - Add sgl support from Anuj
- Move it under drivers/dma-buf/ and rename
- Fix mis-sized allocations
- Fix io_uring re-import mishandling
- Drop map before io_uring "task work"
- Move blk-mq callback to block_device_operations
- Convert bio flag to REQ_OP*
- Other small changes
v3: https://lore.kernel.org/io-uring/[email protected]/
- Rework io_uring registration
- Move token/map infrastructure code out of blk-mq
- Simplify callbacks: remove a separate blk-mq table, which was mostly just forwarding calls (to nvme).
- Don't skip dma sync depending on request direction
- Fix a couple of hangs
- Rename s/dma/dmabuf/
- Other small changes
v2: - Don't pass raw dma addresses, wrap it into a driver specific object
- Split into two objects: token and map
- Implement move_notify
Anuj Gupta (1):
nvme-pci: add SGL support for the dmabuf path
Pavel Begunkov (13):
dma-buf: introduce initial file I/O infrastructure
iov_iter: add iterator type for dmabuf maps
block: rename bi_bvec_done
block: always adjust bi_offset on bio_advance_iter
block: move bvec init into __bio_clone
block: introduce dma map backed bio type
block: forward init_dma_buf_io_ctx to drivers
nvme-pci: implement dma_token backed requests io_uring/rsrc: introduce buf registration structure io_uring/rsrc: extend buffer update io_uring/rsrc: add uncloneable regbuf flag io_uring/rsrc: add regbuf import flags io_uring/rsrc: add dmabuf backed registered buffers
Documentation/block/biovecs.rst | 8 +- block/bio.c | 30 ++- block/blk-merge.c | 23 +- block/blk-mq-dma.c | 2 +- block/blk.h | 2 +- block/fops.c | 16 ++ drivers/block/loop.c | 2 +- drivers/block/zloop.c | 2 +- drivers/dma-buf/Makefile | 2 +- drivers/dma-buf/dma-buf-io.c | 275 ++++++++++++++++++++ drivers/md/dm-io-rewind.c | 16 +- drivers/md/dm-pcache/segment.c | 4 +- drivers/nvdimm/btt.c | 2 +- drivers/nvme/host/core.c | 12 + drivers/nvme/host/nvme.h | 2 + drivers/nvme/host/pci.c | 448 +++++++++++++++++++++++++++++++- drivers/nvme/host/tcp.c | 2 +- fs/btrfs/misc.h | 2 +- include/linux/bio.h | 22 +- include/linux/blk-mq.h | 7 + include/linux/blk_types.h | 14 +- include/linux/blkdev.h | 2 + include/linux/bvec.h | 17 +- include/linux/dma-buf-io.h | 91 +++++++ include/linux/fs.h | 2 + include/linux/io_uring_types.h | 5 + include/linux/uio.h | 11 + include/uapi/linux/io_uring.h | 31 ++- io_uring/io_uring.c | 3 +- io_uring/net.c | 4 +- io_uring/rsrc.c | 281 +++++++++++++++++--- io_uring/rsrc.h | 45 +++- io_uring/rw.c | 6 +- lib/iov_iter.c | 31 ++- net/ceph/messenger.c | 4 +- 35 files changed, 1315 insertions(+), 111 deletions(-) create mode 100644 drivers/dma-buf/dma-buf-io.c create mode 100644 include/linux/dma-buf-io.h
Signature · 1 line
2.54.0
PATCH v4 1/14 dma-buf: introduce initial file I/O infrastructure
The goal is to be able to natively use dma-buf in the read-write / IO path. This patch adds basic building blocks serving as a glue and API between drivers and upper layer subsystems providing the uAPI. Later patches implement it for NVMe raw block devices and expose it to the user space via io_uring.
There are two main objects. struct dma_buf_io_ctx and struct dma_buf_io_map. The ctx is used during initial registration and serves as an interface between the upper layer user like io_uring and to the importer subsystem / driver. The map represents the actual dma map established for the target device[s] with dma_buf_map_attachment() and stored in a device specific format. The context is created via a new file operation ->init_dma_buf_io_ctx.
The ctx-map separation exists to support map invalidation (see dma_buf_io_invalidate_mappings()). A ctx can create multiple maps during its lifetime, but there can only be no more than one (active) map attached to it. Invalidation drops the active map if present, and the next map will only be attempted to be created once there is a new request that wants to use the dma-buf IO ctx.
The primary task of the dma_buf_io_map object is to count requests using it and to wait for their completion when we want to destroy the DMA map. Invalidation is delayed by inserting a dma fence, which is signaled when all flight requests are completed.
[un]mapping and any work with dma addresses is delegated to the importer driver via an ops table stored in the ctx, see struct dma_buf_io_ops. Only the target driver / subsystem knows about devices it wants to use the dma-buf with, especially in case of multi-device filesystems or stacking in the future.
--- drivers/dma-buf/Makefile | 2 +- drivers/dma-buf/dma-buf-io.c | 275 +++++++++++++++++++++++++++++++++++ include/linux/dma-buf-io.h | 91 ++++++++++++ include/linux/fs.h | 2 + 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 drivers/dma-buf/dma-buf-io.c create mode 100644 include/linux/dma-buf-io.h
drivers/dma-buf/Makefile +1 -1
@@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \ - dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o + dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o dma-buf-io.o obj-$(CONFIG_DMABUF_HEAPS) += dma-heap.o obj-$(CONFIG_DMABUF_HEAPS) += heaps/ obj-$(CONFIG_SYNC_FILE) += sync_file.o
drivers/dma-buf/dma-buf-io.c new file +275 -0
275 lines not on this page · raw message
include/linux/dma-buf-io.h new file +91 -0
91 lines not on this page · raw message
include/linux/fs.h +2 -0
@@ -1914,6 +1914,7 @@ struct dir_context {#define COPY_FILE_SPLICE (1 << 0)struct io_uring_cmd ; +struct dma_buf_io_ctx ;struct offset_ctx ;typedef unsigned int __bitwise fop_flags_t ;
@@ -1962,6 +1963,7 @@ struct file_operations {int (*uring_cmd_iopoll)(struct io_uring_cmd *,struct io_comp_batch *,unsigned int poll_flags);int (*mmap_prepare)(struct vm_area_desc *); +int (*init_dma_buf_io_ctx)(struct file *,struct dma_buf_io_ctx *); } __randomize_layout;/* Supports async buffered reads */
Signature · 1 line
2.54.0
Christoph Hellwig
maintainerRe: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
The method name feels a bit convoluted, but given all the previous discussions I don't care too strongly. I'll leave the dma-buf side review to those who understand it.
Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 3 lines · Christoph Hellwig
The method name feels a bit convoluted, but given all the previous discussions I don't care too strongly. I'll leave the dma-buf side review to those who understand it.
I assume you mean this:
+ int (*init_dma_buf_io_ctx)(struct file *, struct dma_buf_io_ctx *);
I agree, and all dma_buf_io_[ctx,map] look clunky, but I don't see what I can drop out of the name. Suggestions? Maybe I at least should make the fs op sth like "register_dma_buf".
Signature · 1 line
Pavel Begunkov
Christoph Hellwig
maintainerRe: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 6 lines · Pavel Begunkov
Quoted 3 lines · Christoph Hellwig
The method name feels a bit convoluted, but given all the previous discussions I don't care too strongly. I'll leave the dma-buf side review to those who understand it.
I assume you mean this:
Yes.
Quoted 6 lines
include/linux/fs.h +1
-0+int (*init_dma_buf_io_ctx)(struct file *,struct dma_buf_io_ctx *);I agree, and all dma_buf_io_[ctx,map] look clunky, but I don't see what I can drop out of the name. Suggestions? Maybe I at least should make the fs op sth like "
register_dma_buf".
I just remember scares from the last discussion :)
register_dma_buf sounds fine to be, but unless I misremember there were objections to that before.
Quoted 3 lines
Signature · 2 lines
-- Pavel Begunkov
---end quoted text---
Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 13 lines · Christoph Hellwig
Quoted 6 lines · Pavel Begunkov
Quoted 3 lines · Christoph Hellwig
The method name feels a bit convoluted, but given all the previous discussions I don't care too strongly. I'll leave the dma-buf side review to those who understand it.
I assume you mean this:
Yes.
Quoted 3 lines
include/linux/fs.h +1
-0+int (*init_dma_buf_io_ctx)(struct file *,struct dma_buf_io_ctx *);
Quoted 8 lines
Quoted 3 lines
I agree, and all dma_buf_io_[ctx,map] look clunky, but I don't see what I can drop out of the name. Suggestions? Maybe I at least should make the fs op sth like "
register_dma_buf".I just remember scares from the last discussion :)
register_dma_bufsounds fine to be, but unless I misremember there were objections to that before.
I'm going to respin the set in a couple of days, just going to leave it as is unless we find better options.
Signature · 1 line
Pavel Begunkov
Christian König
maintainerRe: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
In reply to Pavel BegunkovQuoted 63 lines · Pavel Begunkov
The goal is to be able to natively use dma-buf in the read-write / IO path. This patch adds basic building blocks serving as a glue and API between drivers and upper layer subsystems providing the uAPI. Later patches implement it for NVMe raw block devices and expose it to the user space via
io_uring.There are two main objects. struct
dma_buf_io_ctxand structdma_buf_io_map. The ctx is used during initial registration and serves as an interface between the upper layer user likeio_uringand to the importer subsystem / driver. The map represents the actual dma map established for the target device[s] withdma_buf_map_attachment()and stored in a device specific format. The context is created via a new file operation ->init_dma_buf_io_ctx.The ctx-map separation exists to support map invalidation (see
dma_buf_io_invalidate_mappings()). A ctx can create multiple maps during its lifetime, but there can only be no more than one (active) map attached to it. Invalidation drops the active map if present, and the next map will only be attempted to be created once there is a new request that wants to use the dma-buf IO ctx.The primary task of the
dma_buf_io_mapobject is to count requests using it and to wait for their completion when we want to destroy the DMA map. Invalidation is delayed by inserting a dma fence, which is signaled when all flight requests are completed.[un]mapping and any work with dma addresses is delegated to the importer driver via an ops table stored in the ctx, see struct
dma_buf_io_ops. Only the target driver / subsystem knows about devices it wants to use the dma-buf with, especially in case of multi-device filesystems or stacking in the future.Signed-off-by Pavel Begunkov <[email protected]>--- drivers/dma-buf/Makefile | 2 +- drivers/dma-buf/dma-buf-io.c | 275 +++++++++++++++++++++++++++++++++++ include/linux/dma-buf-io.h | 91 ++++++++++++ include/linux/fs.h | 2 + 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 drivers/dma-buf/dma-buf-io.c create mode 100644 include/linux/dma-buf-io.hdrivers/dma-buf/Makefile +1
-1@@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \ - dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o + dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o dma-buf-io.o obj-$(CONFIG_DMABUF_HEAPS) += dma-heap.o obj-$(CONFIG_DMABUF_HEAPS) += heaps/ obj-$(CONFIG_SYNC_FILE) += sync_file.odrivers/dma-buf/dma-buf-io.c new file +3
-0@@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Common infrastructure for supporing dma-buf in the I/O path.
drivers/dma-buf/dma-buf-io.c line 13
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+* +* Copyright (C) 2026 Pavel Begunkov <[email protected]> +*/ +#include <linux/dma-buf-io.h> +#include <linux/dma-resv.h> + +struct dma_buf_io_fence { +struct dma_fence base; +spinlock_t lock; +};
Upstream has change to allow embedding the spinlock into the dma_fence, so this structure here is most likely not necessary any more.
Quoted 33 lines
drivers/dma-buf/dma-buf-io.c new file +33
-0+ +static const char *dma_buf_io_fence_drv_name (struct dma_fence *fence) +{ +/* default fence release kfree's the base pointer */ +BUILD_BUG_ON (offsetof(struct dma_buf_io_fence , base)); + +return "dma-buf-io-ctx" ; +} + +static const char *dma_buf_io_fence_timeline_name (struct dma_fence *fence) +{ +return "dma-buf-io-ctx" ; +} + +const struct dma_fence_ops dma_buf_io_fence_ops = { + .get_driver_name = dma_buf_io_fence_drv_name, + .get_timeline_name = dma_buf_io_fence_timeline_name, +}; + +static void dma_buf_io_ctx_destroy_work (struct work_struct *work) +{ + struct dma_buf_io_ctx *ctx = container_of(work, struct dma_buf_io_ctx, + release_work); + +if (WARN_ON_ONCE (refcount_read (&ctx->refs))) +return ; + + ctx->dev_ops->release (ctx); +dma_buf_put (ctx->dmabuf); +kfree (ctx); +} + +static void dma_buf_io_map_release_work (struct work_struct *work)
drivers/dma-buf/dma-buf-io.c line 56
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+{ + struct dma_buf_io_map *map = container_of(work, struct dma_buf_io_map, + release_work); +struct dma_buf_io_fence *fence = map->fence; +struct dma_buf_io_ctx *ctx = map->ctx; +struct dma_buf *dmabuf = ctx->dmabuf; + +/* the release path must wait for fences */ +if (WARN_ON_ONCE (refcount_read (&ctx->refs) ==0 )) +return ;
Stuff like that is usually illegal.
And why are you using refcount directly instead of kref?
drivers/dma-buf/dma-buf-io.c line 66
Quoted 11 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+ +/* Prevent from destoying the ctx while unmapping */ +refcount_inc (&ctx->refs); + +/* +* There are no more requests using the map, we can signal the fence. +* It should be done before taking the resv lock as someone could be +* waiting for the fence while holding the lock. +*/ +dma_fence_signal (&fence->base);
Signaling fences has a whole bunch of very strict rules associated with it. E.g. you can't alocate memory for example.
Are you sure you actually need and want a dma_fence here?
drivers/dma-buf/dma-buf-io.c line 72
Quoted 6 lines
drivers/dma-buf/dma-buf-io.c new file +6
-0+ +dma_resv_lock (dmabuf->resv,NULL ); + ctx->dev_ops->unmap (ctx, map); +dma_resv_unlock (dmabuf->resv); + +dma_fence_put (&fence->base);
You should probably set map->fence to NULL after that.
Quoted 48 lines
drivers/dma-buf/dma-buf-io.c new file +48
-0+percpu_ref_exit (&map->refs); +kfree (map); + +if (refcount_dec_and_test (&ctx->refs)) { +/* +* Destruction needs to wait for I/O and dma fences. Defer it to +* simplify locking. +*/ +INIT_WORK (&ctx->release_work, dma_buf_io_ctx_destroy_work); +queue_work (system_wq, &ctx->release_work); + } +} + +static void dma_buf_io_map_refs_release (struct percpu_ref *ref) +{ + struct dma_buf_io_map *map = container_of(ref, struct dma_buf_io_map, refs); + +/* might sleep, use a worker */ +INIT_WORK (&map->release_work, dma_buf_io_map_release_work); +queue_work (system_wq, &map->release_work); +} + +int dma_buf_io_init_map (struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map) +{ +struct dma_buf_io_fence *fence =NULL ; +int ret; + + fence =kzalloc (sizeof (*fence),GFP_KERNEL ); +if (!fence) +return -ENOMEM ; + + ret =percpu_ref_init (&map->refs, dma_buf_io_map_refs_release,0 ,GFP_KERNEL ); +if (ret) { +kfree (fence); +return ret; + } + +spin_lock_init (&fence->lock); +dma_fence_init (&fence->base, &dma_buf_io_fence_ops, &fence->lock, + ctx->fence_ctx,atomic_inc_return (&ctx->fence_seq)); + map->fence = fence; + map->ctx = ctx; +return 0 ; +} +EXPORT_SYMBOL_NS_GPL (dma_buf_io_init_map,"DMA_BUF" ); + +struct dma_buf_io_map *dma_buf_io_create_map (struct dma_buf_io_ctx *ctx) +{
drivers/dma-buf/dma-buf-io.c line 130
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+struct dma_buf *dmabuf = ctx->dmabuf; +struct dma_buf_io_map *map; +long ret; + +retry: +/* +* ->dmabuf_map() will be calling dma_buf_map_attachment(), for which +* we'll need to wait for fences. Do a bit nicer and try to wait +* without the resv lock first. +*/
Clear NAK to that. Always wait while holding the resv lock if you can!
It is absolutely not beneficial to do this outside of the lock and usually just hides problems instead and prevent fixing them.
Quoted 57 lines
drivers/dma-buf/dma-buf-io.c new file +55
-0+ ret =dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + true,MAX_SCHEDULE_TIMEOUT ); +if (!ret) + ret = -EAGAIN ; +if (ret <0 ) +return ERR_PTR (ret); + + ret =dma_resv_lock_interruptible (dmabuf->resv,NULL ); +if (ret) +return ERR_PTR (ret); + + map =dma_buf_io_get_map (ctx); +if (map) { + ret =0 ; + goto out; + } + +if (dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + true,0 ) <0 ) { +dma_resv_unlock (dmabuf->resv); + goto retry; + } + + map = ctx->dev_ops->map (ctx); +if (IS_ERR (map)) { + ret =PTR_ERR (map); + goto out; + } + +percpu_ref_get (&map->refs); +rcu_assign_pointer (ctx->map, map); +out: +dma_resv_unlock (dmabuf->resv); +if (ret <0 ) +return ERR_PTR (ret); +return map; +} + +static void dma_buf_io_drop_map (struct dma_buf_io_ctx *ctx) +{ +struct dma_buf *dmabuf = ctx->dmabuf; +struct dma_buf_io_map *map; +int ret; + +dma_resv_assert_held (dmabuf->resv); + + map =rcu_dereference_protected (ctx->map, +dma_resv_held (dmabuf->resv)); +if (!map) +return ; +rcu_assign_pointer (ctx->map,NULL ); + + ret =dma_resv_reserve_fences (dmabuf->resv,1 ); +if (WARN_ON_ONCE (ret)) { +struct dma_fence *fence = &map->fence->base;
drivers/dma-buf/dma-buf-io.c line 195
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+ +dma_fence_get (fence); +percpu_ref_kill (&map->refs); +dma_fence_wait (fence, false); +dma_fence_put (fence); +return ; + } + +dma_resv_add_fence (dmabuf->resv, &map->fence->base, +DMA_RESV_USAGE_KERNEL );
That sequence is clearly incorrect!
The fence must be created after dma_resv_reserve_fences(), otherwise you definately have an illegal memory operation here.
Regards,
Christian.
Quoted 197 lines
drivers/dma-buf/dma-buf-io.c new file +80
-0+/* +* Delay destruction until all inflight requests using the map arefence-> +* gone. It'll also signal the fence then. +*/ +percpu_ref_kill (&map->refs); +} + +void dma_buf_io_invalidate_mappings (struct dma_buf_io_ctx *ctx) +{ +dma_buf_io_drop_map (ctx); +} +EXPORT_SYMBOL_NS_GPL (dma_buf_io_invalidate_mappings,"DMA_BUF" ); + +static void dma_buf_io_ctx_release_work (struct work_struct *work) +{ + struct dma_buf_io_ctx *ctx = container_of(work, struct dma_buf_io_ctx, + release_work); +struct dma_buf *dmabuf = ctx->dmabuf; +long ret; + +dma_resv_lock (dmabuf->resv,NULL ); +/* Remove the last map, there should be no new ones going forward. */ +dma_buf_io_drop_map (ctx); +dma_resv_unlock (dmabuf->resv); + +/* Wait until all maps are destroyed. */ + ret =dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + false,MAX_SCHEDULE_TIMEOUT ); + +if (WARN_ON_ONCE (ret <=0 )) +return ; +if (WARN_ON_ONCE (rcu_dereference_protected (ctx->map, true))) +return ; + +if (refcount_dec_and_test (&ctx->refs)) +dma_buf_io_ctx_destroy_work (&ctx->release_work); +} + +void dma_buf_io_ctx_release (struct dma_buf_io_ctx *ctx) +{ +/* +* Destruction needs to wait for I/O and dma fences. Defer it to +* simplify locking. +*/ +INIT_WORK (&ctx->release_work, dma_buf_io_ctx_release_work); +queue_work (system_wq, &ctx->release_work); +} + +int dma_buf_io_ctx_create (struct file *file, +struct dma_buf_io_ctx *ctx, +struct dma_buf *dmabuf, +enum dma_data_direction dir) +{ +int ret; + +if (!file->f_op->init_dma_buf_io_ctx) +return -EOPNOTSUPP ; + +memset (ctx,0 ,sizeof (*ctx)); + ctx->fence_ctx =dma_fence_context_alloc (1 ); + ctx->dir = dir; + ctx->dmabuf = dmabuf; +refcount_set (&ctx->refs,1 ); +get_dma_buf (dmabuf); + + ret = file->f_op->init_dma_buf_io_ctx (file, ctx); +if (ret) { +memset (ctx,0 ,sizeof (*ctx)); +dma_buf_put (dmabuf); +return ret; + } + +if (WARN_ON_ONCE (!ctx->dev_ops || + !ctx->dev_ops->map || + !ctx->dev_ops->unmap || + !ctx->dev_ops->release)) +return -EINVAL ; + +return ret; +}include/linux/dma-buf-io.h new file +91
-0@@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DMA_BUF_IO_H__ +#define __DMA_BUF_IO_H__ + +#include <linux/dma-buf.h> + +struct dma_buf_io_fence ; +struct dma_buf_io_ctx ; +struct dma_buf_io_map ; + +struct dma_buf_io_ops { +/* +* Create a new map for the given ctx. Called with the reservation +* lock held. +*/ +struct dma_buf_io_map *(*map)(struct dma_buf_io_ctx *ctx); + +/* +* Clean up device specific parts of the @map. Called with the +* reservation lock held. +*/ +void (*unmap)(struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map); + +/* +* The user tries to destroy the ctx. Release all device specific +* parts of the token. +*/ +void (*release)(struct dma_buf_io_ctx *); +}; + +struct dma_buf_io_map { +/* +* Counts attached requests and other users. Device specific unmapping +* is deferred until all refs are dropped. +*/ +struct percpu_ref refs; + +struct work_struct release_work; +struct dma_buf_io_fence *fence; +struct dma_buf_io_ctx *ctx; +}; + +struct dma_buf_io_ctx { +struct dma_buf_io_map __rcu *map; +struct dma_buf *dmabuf; +enum dma_data_direction dir; + +atomic_t fence_seq; +u64 fence_ctx; +struct work_struct release_work; +refcount_t refs; + +void *dev_priv; +const struct dma_buf_io_ops *dev_ops; +}; + +int dma_buf_io_ctx_create (struct file *file, +struct dma_buf_io_ctx *ctx, +struct dma_buf *dmabuf, +enum dma_data_direction dir); +void dma_buf_io_ctx_release (struct dma_buf_io_ctx *ctx); + +struct dma_buf_io_map *dma_buf_io_create_map (struct dma_buf_io_ctx *ctx); + +static inline struct dma_buf_io_map * +dma_buf_io_get_map (struct dma_buf_io_ctx *ctx) +{ +struct dma_buf_io_map *map; + +guard (rcu)(); + + map =rcu_dereference (ctx->map); +if (unlikely (!map || !percpu_ref_tryget_live_rcu (&map->refs))) +return NULL ; + +return map; +} + +static inline void dma_buf_io_map_drop (struct dma_buf_io_map *map) +{ +percpu_ref_put (&map->refs); +} + +/* +* Device API +*/ + +void dma_buf_io_invalidate_mappings (struct dma_buf_io_ctx *ctx); +int dma_buf_io_init_map (struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map); + +#endif include/linux/fs.h +2
-0@@ -1914,6 +1914,7 @@ struct dir_context {#define COPY_FILE_SPLICE (1 << 0)struct io_uring_cmd ; +struct dma_buf_io_ctx ;struct offset_ctx ;typedef unsigned int __bitwise fop_flags_t ;@@ -1962,6 +1963,7 @@ struct file_operations {int (*uring_cmd_iopoll)(struct io_uring_cmd *,struct io_comp_batch *,unsigned int poll_flags);int (*mmap_prepare)(struct vm_area_desc *); +int (*init_dma_buf_io_ctx)(struct file *,struct dma_buf_io_ctx *); } __randomize_layout;/* Supports async buffered reads */
Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
On 8/5/26 09:27, Christian König wrote:
...>> +struct dma_buf_io_fence {
Quoted 5 lines
drivers/dma-buf/dma-buf-io.c line 13
Quoted 3 lines
drivers/dma-buf/dma-buf-io.c new file +3
-0+struct dma_fence base; +spinlock_t lock; +};Upstream has change to allow embedding the spinlock into the
dma_fence, so this structure here is most likely not necessary any more.
ok
Quoted 7 lines
drivers/dma-buf/dma-buf-io.c line 21
Quoted 7 lines
drivers/dma-buf/dma-buf-io.c new file +7
-0+static const char *dma_buf_io_fence_drv_name (struct dma_fence *fence) +{ +/* default fence release kfree's the base pointer */ +BUILD_BUG_ON (offsetof(struct dma_buf_io_fence , base)); + +return "dma-buf-io-ctx" ; +}
...>> +static void dma_buf_io_map_release_work(struct work_struct *work)
Quoted 4 lines
Quoted 4 lines
drivers/dma-buf/dma-buf-io.c new file +4
-0+{ + struct dma_buf_io_map *map = container_of(work, struct dma_buf_io_map, + release_work); +struct dma_buf_io_fence *fence = map->fence;
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c line 56
Quoted 6 lines
drivers/dma-buf/dma-buf-io.c new file +6
-0+struct dma_buf_io_ctx *ctx = map->ctx; +struct dma_buf *dmabuf = ctx->dmabuf; + +/* the release path must wait for fences */ +if (WARN_ON_ONCE (refcount_read (&ctx->refs) ==0 )) +return ;Stuff like that is usually illegal.
Should be fine, it's just a warning. The map holds a ctx reference so can't be 0. IIRC, it was synchronised a bit differently before. I can kill it, refcount_inc() has the same warning anyway.
Quoted 1 line
And why are you using refcount directly instead of kref?
Not sure it'd make much difference here.
Quoted 15 lines
drivers/dma-buf/dma-buf-io.c line 66
Quoted 12 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+ +/* Prevent from destoying the ctx while unmapping */ +refcount_inc (&ctx->refs); + +/* +* There are no more requests using the map, we can signal the fence. +* It should be done before taking the resv lock as someone could be +* waiting for the fence while holding the lock. +*/ +dma_fence_signal (&fence->base);Signaling fences has a whole bunch of very strict rules associated with it. E.g. you can't alocate memory for example.
Quoted 1 line
Are you sure you actually need and want a
dma_fencehere?
Waiting for potentially a ton of IO synchronously on invalidate sounds like a bad idea though. Hmm.
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c line 72
Quoted 6 lines
drivers/dma-buf/dma-buf-io.c new file +6
-0+ +dma_resv_lock (dmabuf->resv,NULL ); + ctx->dev_ops->unmap (ctx, map); +dma_resv_unlock (dmabuf->resv); + +dma_fence_put (&fence->base);You should probably set
map->fenceto NULL after that.
The map is freed two lines below, but I can add it as a defensive measure.
Quoted 2 lines
drivers/dma-buf/dma-buf-io.c line 74
Quoted 2 lines
drivers/dma-buf/dma-buf-io.c new file +2
-0+percpu_ref_exit (&map->refs); +kfree (map);
...>> +struct dma_buf_io_map *dma_buf_io_create_map(struct dma_buf_io_ctx *ctx)
Quoted 14 lines
drivers/dma-buf/dma-buf-io.c line 130
Quoted 11 lines
drivers/dma-buf/dma-buf-io.c new file +11
-0+{ +struct dma_buf *dmabuf = ctx->dmabuf; +struct dma_buf_io_map *map; +long ret; + +retry: +/* +* ->dmabuf_map() will be calling dma_buf_map_attachment(), for which +* we'll need to wait for fences. Do a bit nicer and try to wait +* without the resv lock first. +*/ Clear NAK to that. Always wait while holding the resv lock if you can!
Quoted 1 line
It is absolutely not beneficial to do this outside of the lock and usually just hides problems instead and prevent fixing them.
Ok
...
Quoted 16 lines
drivers/dma-buf/dma-buf-io.c line 195
Quoted 13 lines
drivers/dma-buf/dma-buf-io.c new file +13
-0+ ret =dma_resv_reserve_fences (dmabuf->resv,1 ); +if (WARN_ON_ONCE (ret)) { +struct dma_fence *fence = &map->fence->base; + +dma_fence_get (fence); +percpu_ref_kill (&map->refs); +dma_fence_wait (fence, false); +dma_fence_put (fence); +return ; + } + +dma_resv_add_fence (dmabuf->resv, &map->fence->base, +DMA_RESV_USAGE_KERNEL );That sequence is clearly incorrect!
Quoted 1 line
The fence must be created after
dma_resv_reserve_fences(), otherwise you definately have an illegal memory operation here.
I'm not sure what you mean, can you elaborate? I only cared about pre-allocating it to avoid allocations here. We add / signal the fence only once, no reuse. The map is going to be killed here, and if we create a new map, it'll have its own fence.
I can move the dma_fence_init() call here if that makes a difference?
Signature · 1 line
Pavel Begunkov
Christian König
maintainerRe: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 11 lines · Pavel Begunkov
Quoted 8 lines · Christian König
drivers/dma-buf/dma-buf-io.c line 72
Quoted 6 lines
drivers/dma-buf/dma-buf-io.c new file +6
-0+ + dma_resv_lock(dmabuf->resv, NULL); + ctx->dev_ops->unmap(ctx, map); + dma_resv_unlock(dmabuf->resv); + + dma_fence_put(&fence->base);You should probably set
map->fenceto NULL after that.
Quoted 2 lines
The map is freed two lines below, but I can add it as a defensive measure.
In that case it's ok, I've just haven't seen the kfree(map) below.
Quoted 17 lines
...
Quoted 16 lines
drivers/dma-buf/dma-buf-io.c line 195
Quoted 13 lines
+ ret =
dma_resv_reserve_fences(dmabuf->resv, 1); + if (WARN_ON_ONCE(ret)) {
+ structdma_fence*fence =&map->fence->base;drivers/dma-buf/dma-buf-io.c new file +10
-0+ + dma_fence_get(fence); + percpu_ref_kill(&map->refs); + dma_fence_wait(fence, false); + dma_fence_put(fence); + return; + } + + dma_resv_add_fence(dmabuf->resv, &map->fence->base, + DMA_RESV_USAGE_KERNEL);That sequence is clearly incorrect!
Quoted 8 lines
Quoted 1 line
The fence must be created after
dma_resv_reserve_fences(), otherwise you definately have an illegal memory operation here.I'm not sure what you mean, can you elaborate? I only cared about pre-allocating it to avoid allocations here. We add / signal the fence only once, no reuse. The map is going to be killed here, and if we create a new map, it'll have its own fence.
I can move the
dma_fence_init()call here if that makes a difference?
Yeah that is a good start, but you might need a bit more.
Here is a summary of the usual procedure you need to follow when implementing a dma_fence backend:
1. Allocate your operation object, in this case here it's your mapping I think.
2. Prepare your operation, including all memory allocations.
3. Call dma_resv_reserve_fences() to reserve a fence slot.
4. Allocate and init your dma_fence object.
After this step no memory allocation is allowed any more until your dma_fence object signals.
The only exception is optional logging or crash dumping using GFP_NOWAIT (can fail trivially!) or minimal allocations using GFP_ATOMIC if you absolutely have to.
5. dma_resv_add_fence() to publish the fence.
6. dma_resv_unlock().
Having a dma_fence is certainly nice to have, but the tricky part is that memory allocations using GFP_KERNEL (or GFP_IO, GFP_FS etc...) can cycle back and wait for your dma_fence to signal which essentially can cause a deadlock very deeply inside memory management.
Since those deadlocks happen only on memory contention situations they are usually just hard to reproduce but still totally break your neck if you manage to mess this up. So that needs to be super carefully implemented.
Regards,
Christian.
Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 43 lines · Christian König
Quoted 11 lines · Pavel Begunkov
Quoted 8 lines · Christian König
drivers/dma-buf/dma-buf-io.c line 72
Quoted 6 lines
drivers/dma-buf/dma-buf-io.c new file +6
-0+ + dma_resv_lock(dmabuf->resv, NULL); + ctx->dev_ops->unmap(ctx, map); + dma_resv_unlock(dmabuf->resv); + + dma_fence_put(&fence->base);You should probably set
map->fenceto NULL after that.Quoted 2 lines
The map is freed two lines below, but I can add it as a defensive measure.
In that case it's ok, I've just haven't seen the kfree(map) below.
Quoted 17 lines
...
Quoted 16 lines
drivers/dma-buf/dma-buf-io.c line 195
Quoted 13 lines
+ ret =
dma_resv_reserve_fences(dmabuf->resv, 1); + if (WARN_ON_ONCE(ret)) {
+ structdma_fence*fence =&map->fence->base;drivers/dma-buf/dma-buf-io.c new file +10
-0+ + dma_fence_get(fence); + percpu_ref_kill(&map->refs); + dma_fence_wait(fence, false); + dma_fence_put(fence); + return; + } + + dma_resv_add_fence(dmabuf->resv, &map->fence->base, + DMA_RESV_USAGE_KERNEL);That sequence is clearly incorrect!
Quoted 8 lines
Quoted 1 line
The fence must be created after
dma_resv_reserve_fences(), otherwise you definately have an illegal memory operation here.I'm not sure what you mean, can you elaborate? I only cared about pre-allocating it to avoid allocations here. We add / signal the fence only once, no reuse. The map is going to be killed here, and if we create a new map, it'll have its own fence.
I can move the
dma_fence_init()call here if that makes a difference?
Quoted 8 lines
Yeah that is a good start, but you might need a bit more.
Here is a summary of the usual procedure you need to follow when implementing a
dma_fencebackend:1. Allocate your operation object, in this case here it's your mapping I think.
2. Prepare your operation, including all memory allocations.
3. Calldma_resv_reserve_fences()to reserve a fence slot.
4. Allocate and init yourdma_fenceobject.
Looks I forgot it reply here, sorry for that. I killed dma fences in v6 in favour of synchronous waiting, it turned to be less trouble even outside of these fencing rules as I can rely on dma-resv lock less in the code.
Signature · 1 line
Pavel Begunkov
Re: [Linaro-mm-sig] Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
In reply to Christian KönigQuoted 129 lines · Christian König
Quoted 63 lines · Pavel Begunkov
The goal is to be able to natively use dma-buf in the read-write / IO path. This patch adds basic building blocks serving as a glue and API between drivers and upper layer subsystems providing the uAPI. Later patches implement it for NVMe raw block devices and expose it to the user space via
io_uring.There are two main objects. struct
dma_buf_io_ctxand structdma_buf_io_map. The ctx is used during initial registration and serves as an interface between the upper layer user likeio_uringand to the importer subsystem / driver. The map represents the actual dma map established for the target device[s] withdma_buf_map_attachment()and stored in a device specific format. The context is created via a new file operation ->init_dma_buf_io_ctx.The ctx-map separation exists to support map invalidation (see
dma_buf_io_invalidate_mappings()). A ctx can create multiple maps during its lifetime, but there can only be no more than one (active) map attached to it. Invalidation drops the active map if present, and the next map will only be attempted to be created once there is a new request that wants to use the dma-buf IO ctx.The primary task of the
dma_buf_io_mapobject is to count requests using it and to wait for their completion when we want to destroy the DMA map. Invalidation is delayed by inserting a dma fence, which is signaled when all flight requests are completed.[un]mapping and any work with dma addresses is delegated to the importer driver via an ops table stored in the ctx, see struct
dma_buf_io_ops. Only the target driver / subsystem knows about devices it wants to use the dma-buf with, especially in case of multi-device filesystems or stacking in the future.Signed-off-by Pavel Begunkov <[email protected]>--- drivers/dma-buf/Makefile | 2 +- drivers/dma-buf/dma-buf-io.c | 275 +++++++++++++++++++++++++++++++++++ include/linux/dma-buf-io.h | 91 ++++++++++++ include/linux/fs.h | 2 + 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 drivers/dma-buf/dma-buf-io.c create mode 100644 include/linux/dma-buf-io.hdrivers/dma-buf/Makefile +1
-1@@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \ - dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o + dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o dma-buf-io.o obj-$(CONFIG_DMABUF_HEAPS) += dma-heap.o obj-$(CONFIG_DMABUF_HEAPS) += heaps/ obj-$(CONFIG_SYNC_FILE) += sync_file.odrivers/dma-buf/dma-buf-io.c new file +3
-0@@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Common infrastructure for supporing dma-buf in the I/O path. drivers/dma-buf/dma-buf-io.c line 13
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+* +* Copyright (C) 2026 Pavel Begunkov <[email protected]> +*/ +#include <linux/dma-buf-io.h> +#include <linux/dma-resv.h> + +struct dma_buf_io_fence { +struct dma_fence base; +spinlock_t lock; +};Upstream has change to allow embedding the spinlock into the
dma_fence, so this structure here is most likely not necessary any more.Quoted 33 lines
drivers/dma-buf/dma-buf-io.c new file +33
-0+ +static const char *dma_buf_io_fence_drv_name (struct dma_fence *fence) +{ +/* default fence release kfree's the base pointer */ +BUILD_BUG_ON (offsetof(struct dma_buf_io_fence , base)); + +return "dma-buf-io-ctx" ; +} + +static const char *dma_buf_io_fence_timeline_name (struct dma_fence *fence) +{ +return "dma-buf-io-ctx" ; +} + +const struct dma_fence_ops dma_buf_io_fence_ops = { + .get_driver_name = dma_buf_io_fence_drv_name, + .get_timeline_name = dma_buf_io_fence_timeline_name, +}; + +static void dma_buf_io_ctx_destroy_work (struct work_struct *work) +{ + struct dma_buf_io_ctx *ctx = container_of(work, struct dma_buf_io_ctx, + release_work); + +if (WARN_ON_ONCE (refcount_read (&ctx->refs))) +return ; + + ctx->dev_ops->release (ctx); +dma_buf_put (ctx->dmabuf); +kfree (ctx); +} + +static void dma_buf_io_map_release_work (struct work_struct *work)drivers/dma-buf/dma-buf-io.c line 56
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+{ + struct dma_buf_io_map *map = container_of(work, struct dma_buf_io_map, + release_work); +struct dma_buf_io_fence *fence = map->fence; +struct dma_buf_io_ctx *ctx = map->ctx; +struct dma_buf *dmabuf = ctx->dmabuf; + +/* the release path must wait for fences */ +if (WARN_ON_ONCE (refcount_read (&ctx->refs) ==0 )) +return ;Stuff like that is usually illegal.
And why are you using refcount directly instead of kref?
Quoted 3 lines
drivers/dma-buf/dma-buf-io.c new file +3
-0+ +/* Prevent from destoying the ctx while unmapping */ +refcount_inc (&ctx->refs);
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c line 66
Quoted 7 lines
drivers/dma-buf/dma-buf-io.c new file +7
-0+ +/* +* There are no more requests using the map, we can signal the fence. +* It should be done before taking the resv lock as someone could be +* waiting for the fence while holding the lock. +*/ +dma_fence_signal (&fence->base);Signaling fences has a whole bunch of very strict rules associated with it. E.g. you can't alocate memory for example.
Yes, and the rules around signaling fences from worker threads become interesting as well. In practice, the entire workqueue (or any work item scheduled on that workqueue) effectively becomes part of the fence signaling and reclaim path.
Quoted 4 lines
Are you sure you actually need and want a
dma_fencehere?drivers/dma-buf/dma-buf-io.c line 68
Quoted 2 lines
drivers/dma-buf/dma-buf-io.c new file +2
-0+ +dma_resv_lock (dmabuf->resv,NULL );
So this is illegal because code is allowed to hold dma-resv locks while waiting on dma-fences. See dma_resv_lockdep / __dma_fence_might_wait / dma_fence_begin_signalling.
Quoted 19 lines
drivers/dma-buf/dma-buf-io.c line 72
Quoted 4 lines
drivers/dma-buf/dma-buf-io.c new file +4
-0+ ctx->dev_ops->unmap (ctx, map); +dma_resv_unlock (dmabuf->resv); + +dma_fence_put (&fence->base);You should probably set
map->fenceto NULL after that.Quoted 12 lines
drivers/dma-buf/dma-buf-io.c new file +12
-0+percpu_ref_exit (&map->refs); +kfree (map); + +if (refcount_dec_and_test (&ctx->refs)) { +/* +* Destruction needs to wait for I/O and dma fences. Defer it to +* simplify locking. +*/ +INIT_WORK (&ctx->release_work, dma_buf_io_ctx_destroy_work); +queue_work (system_wq, &ctx->release_work); + } +}
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c line 92
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c new file +8
-0+ +static void dma_buf_io_map_refs_release (struct percpu_ref *ref) +{ + struct dma_buf_io_map *map = container_of(ref, struct dma_buf_io_map, refs); + +/* might sleep, use a worker */ +INIT_WORK (&map->release_work, dma_buf_io_map_release_work); +queue_work (system_wq, &map->release_work);
You can't guarantee that a GFP_KERNEL allocation won't be performed from a system worker thread, so you can't safely signal a fence from one. The pathological case is when all threads in system_wq are running work items that perform GFP_KERNEL allocations, enter reclaim, and then wait on a fence that is signaled by another work item queued on system_wq. Since all worker threads are occupied, the signaling work item cannot be scheduled, resulting in a deadlock. We actually hit this exact deadlock early in Xe.
So, as Christian says, think carefully about whether you really need a dma-fence here. If you do, then you need to play by the rules.
Matt
Quoted 319 lines
Quoted 28 lines
drivers/dma-buf/dma-buf-io.c new file +28
-0+} + +int dma_buf_io_init_map (struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map) +{ +struct dma_buf_io_fence *fence =NULL ; +int ret; + + fence =kzalloc (sizeof (*fence),GFP_KERNEL ); +if (!fence) +return -ENOMEM ; + + ret =percpu_ref_init (&map->refs, dma_buf_io_map_refs_release,0 ,GFP_KERNEL ); +if (ret) { +kfree (fence); +return ret; + } + +spin_lock_init (&fence->lock); +dma_fence_init (&fence->base, &dma_buf_io_fence_ops, &fence->lock, + ctx->fence_ctx,atomic_inc_return (&ctx->fence_seq)); + map->fence = fence; + map->ctx = ctx; +return 0 ; +} +EXPORT_SYMBOL_NS_GPL (dma_buf_io_init_map,"DMA_BUF" ); + +struct dma_buf_io_map *dma_buf_io_create_map (struct dma_buf_io_ctx *ctx) +{drivers/dma-buf/dma-buf-io.c line 130
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+struct dma_buf *dmabuf = ctx->dmabuf; +struct dma_buf_io_map *map; +long ret; + +retry: +/* +* ->dmabuf_map() will be calling dma_buf_map_attachment(), for which +* we'll need to wait for fences. Do a bit nicer and try to wait +* without the resv lock first. +*/ Clear NAK to that. Always wait while holding the resv lock if you can!
It is absolutely not beneficial to do this outside of the lock and usually just hides problems instead and prevent fixing them.
Quoted 57 lines
drivers/dma-buf/dma-buf-io.c new file +55
-0+ ret =dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + true,MAX_SCHEDULE_TIMEOUT ); +if (!ret) + ret = -EAGAIN ; +if (ret <0 ) +return ERR_PTR (ret); + + ret =dma_resv_lock_interruptible (dmabuf->resv,NULL ); +if (ret) +return ERR_PTR (ret); + + map =dma_buf_io_get_map (ctx); +if (map) { + ret =0 ; + goto out; + } + +if (dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + true,0 ) <0 ) { +dma_resv_unlock (dmabuf->resv); + goto retry; + } + + map = ctx->dev_ops->map (ctx); +if (IS_ERR (map)) { + ret =PTR_ERR (map); + goto out; + } + +percpu_ref_get (&map->refs); +rcu_assign_pointer (ctx->map, map); +out: +dma_resv_unlock (dmabuf->resv); +if (ret <0 ) +return ERR_PTR (ret); +return map; +} + +static void dma_buf_io_drop_map (struct dma_buf_io_ctx *ctx) +{ +struct dma_buf *dmabuf = ctx->dmabuf; +struct dma_buf_io_map *map; +int ret; + +dma_resv_assert_held (dmabuf->resv); + + map =rcu_dereference_protected (ctx->map, +dma_resv_held (dmabuf->resv)); +if (!map) +return ; +rcu_assign_pointer (ctx->map,NULL ); + + ret =dma_resv_reserve_fences (dmabuf->resv,1 ); +if (WARN_ON_ONCE (ret)) { +struct dma_fence *fence = &map->fence->base;drivers/dma-buf/dma-buf-io.c line 195
Quoted 10 lines
drivers/dma-buf/dma-buf-io.c new file +10
-0+ +dma_fence_get (fence); +percpu_ref_kill (&map->refs); +dma_fence_wait (fence, false); +dma_fence_put (fence); +return ; + } + +dma_resv_add_fence (dmabuf->resv, &map->fence->base, +DMA_RESV_USAGE_KERNEL );That sequence is clearly incorrect!
The fence must be created after
dma_resv_reserve_fences(), otherwise you definately have an illegal memory operation here.Regards,
Christian.Quoted 190 lines
drivers/dma-buf/dma-buf-io.c new file +80
-0+/* +* Delay destruction until all inflight requests using the map arefence-> +* gone. It'll also signal the fence then. +*/ +percpu_ref_kill (&map->refs); +} + +void dma_buf_io_invalidate_mappings (struct dma_buf_io_ctx *ctx) +{ +dma_buf_io_drop_map (ctx); +} +EXPORT_SYMBOL_NS_GPL (dma_buf_io_invalidate_mappings,"DMA_BUF" ); + +static void dma_buf_io_ctx_release_work (struct work_struct *work) +{ + struct dma_buf_io_ctx *ctx = container_of(work, struct dma_buf_io_ctx, + release_work); +struct dma_buf *dmabuf = ctx->dmabuf; +long ret; + +dma_resv_lock (dmabuf->resv,NULL ); +/* Remove the last map, there should be no new ones going forward. */ +dma_buf_io_drop_map (ctx); +dma_resv_unlock (dmabuf->resv); + +/* Wait until all maps are destroyed. */ + ret =dma_resv_wait_timeout (dmabuf->resv,DMA_RESV_USAGE_KERNEL , + false,MAX_SCHEDULE_TIMEOUT ); + +if (WARN_ON_ONCE (ret <=0 )) +return ; +if (WARN_ON_ONCE (rcu_dereference_protected (ctx->map, true))) +return ; + +if (refcount_dec_and_test (&ctx->refs)) +dma_buf_io_ctx_destroy_work (&ctx->release_work); +} + +void dma_buf_io_ctx_release (struct dma_buf_io_ctx *ctx) +{ +/* +* Destruction needs to wait for I/O and dma fences. Defer it to +* simplify locking. +*/ +INIT_WORK (&ctx->release_work, dma_buf_io_ctx_release_work); +queue_work (system_wq, &ctx->release_work); +} + +int dma_buf_io_ctx_create (struct file *file, +struct dma_buf_io_ctx *ctx, +struct dma_buf *dmabuf, +enum dma_data_direction dir) +{ +int ret; + +if (!file->f_op->init_dma_buf_io_ctx) +return -EOPNOTSUPP ; + +memset (ctx,0 ,sizeof (*ctx)); + ctx->fence_ctx =dma_fence_context_alloc (1 ); + ctx->dir = dir; + ctx->dmabuf = dmabuf; +refcount_set (&ctx->refs,1 ); +get_dma_buf (dmabuf); + + ret = file->f_op->init_dma_buf_io_ctx (file, ctx); +if (ret) { +memset (ctx,0 ,sizeof (*ctx)); +dma_buf_put (dmabuf); +return ret; + } + +if (WARN_ON_ONCE (!ctx->dev_ops || + !ctx->dev_ops->map || + !ctx->dev_ops->unmap || + !ctx->dev_ops->release)) +return -EINVAL ; + +return ret; +}include/linux/dma-buf-io.h new file +91
-0@@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DMA_BUF_IO_H__ +#define __DMA_BUF_IO_H__ + +#include <linux/dma-buf.h> + +struct dma_buf_io_fence ; +struct dma_buf_io_ctx ; +struct dma_buf_io_map ; + +struct dma_buf_io_ops { +/* +* Create a new map for the given ctx. Called with the reservation +* lock held. +*/ +struct dma_buf_io_map *(*map)(struct dma_buf_io_ctx *ctx); + +/* +* Clean up device specific parts of the @map. Called with the +* reservation lock held. +*/ +void (*unmap)(struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map); + +/* +* The user tries to destroy the ctx. Release all device specific +* parts of the token. +*/ +void (*release)(struct dma_buf_io_ctx *); +}; + +struct dma_buf_io_map { +/* +* Counts attached requests and other users. Device specific unmapping +* is deferred until all refs are dropped. +*/ +struct percpu_ref refs; + +struct work_struct release_work; +struct dma_buf_io_fence *fence; +struct dma_buf_io_ctx *ctx; +}; + +struct dma_buf_io_ctx { +struct dma_buf_io_map __rcu *map; +struct dma_buf *dmabuf; +enum dma_data_direction dir; + +atomic_t fence_seq; +u64 fence_ctx; +struct work_struct release_work; +refcount_t refs; + +void *dev_priv; +const struct dma_buf_io_ops *dev_ops; +}; + +int dma_buf_io_ctx_create (struct file *file, +struct dma_buf_io_ctx *ctx, +struct dma_buf *dmabuf, +enum dma_data_direction dir); +void dma_buf_io_ctx_release (struct dma_buf_io_ctx *ctx); + +struct dma_buf_io_map *dma_buf_io_create_map (struct dma_buf_io_ctx *ctx); + +static inline struct dma_buf_io_map * +dma_buf_io_get_map (struct dma_buf_io_ctx *ctx) +{ +struct dma_buf_io_map *map; + +guard (rcu)(); + + map =rcu_dereference (ctx->map); +if (unlikely (!map || !percpu_ref_tryget_live_rcu (&map->refs))) +return NULL ; + +return map; +} + +static inline void dma_buf_io_map_drop (struct dma_buf_io_map *map) +{ +percpu_ref_put (&map->refs); +} + +/* +* Device API +*/ + +void dma_buf_io_invalidate_mappings (struct dma_buf_io_ctx *ctx); +int dma_buf_io_init_map (struct dma_buf_io_ctx *ctx,struct dma_buf_io_map *map); + +#endif include/linux/fs.h +1
-0@@ -1914,6 +1914,7 @@ struct dir_context {#define COPY_FILE_SPLICE (1 << 0)struct io_uring_cmd ; +struct dma_buf_io_ctx ;struct offset_ctx ;typedef unsigned int __bitwise fop_flags_t ;include/linux/fs.h struct file_operations { · line 1969
Quoted 7 lines
include/linux/fs.h +1
-0@@ -1962,6 +1963,7 @@ struct file_operations {int (*uring_cmd_iopoll)(struct io_uring_cmd *,struct io_comp_batch *,unsigned int poll_flags);int (*mmap_prepare)(struct vm_area_desc *); +int (*init_dma_buf_io_ctx)(struct file *,struct dma_buf_io_ctx *); } __randomize_layout;/* Supports async buffered reads */ _______________________________________________
Linaro-mm-sig mailing list -- [email protected] To unsubscribe send an email to [email protected]
Re: [Linaro-mm-sig] Re: [PATCH v4 01/14] dma-buf: introduce initial file I/O infrastructure
Quoted 2 lines · Matthew Brost
Quoted 1 line · Christian König
On 7/28/26 23:29, Pavel Begunkov wrote:
...>>> + /*
Quoted 60 lines
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c line 66
Quoted 5 lines
drivers/dma-buf/dma-buf-io.c new file +5
-0+* There are no more requests using the map, we can signal the fence. +* It should be done before taking the resv lock as someone could be +* waiting for the fence while holding the lock. +*/ +dma_fence_signal (&fence->base);Signaling fences has a whole bunch of very strict rules associated with it. E.g. you can't alocate memory for example.
Yes, and the rules around signaling fences from worker threads become interesting as well. In practice, the entire workqueue (or any work item scheduled on that workqueue) effectively becomes part of the fence signaling and reclaim path.
Quoted 4 lines
Are you sure you actually need and want a
dma_fencehere?Review
Quoted 2 lines
(quoted diff) +2
-0+ + dma_resv_lock(dmabuf->resv, NULL);So this is illegal because code is allowed to hold dma-resv locks while waiting on dma-fences. See
dma_resv_lockdep/__dma_fence_might_wait/dma_fence_begin_signalling.Quoted 19 lines
drivers/dma-buf/dma-buf-io.c line 72
Quoted 4 lines
drivers/dma-buf/dma-buf-io.c new file +4
-0+ ctx->dev_ops->unmap (ctx, map); +dma_resv_unlock (dmabuf->resv); + +dma_fence_put (&fence->base);You should probably set
map->fenceto NULL after that.Quoted 12 lines
drivers/dma-buf/dma-buf-io.c new file +12
-0+percpu_ref_exit (&map->refs); +kfree (map); + +if (refcount_dec_and_test (&ctx->refs)) { +/* +* Destruction needs to wait for I/O and dma fences. Defer it to +* simplify locking. +*/ +INIT_WORK (&ctx->release_work, dma_buf_io_ctx_destroy_work); +queue_work (system_wq, &ctx->release_work); + } +}Quoted 8 lines
drivers/dma-buf/dma-buf-io.c line 92
Quoted 8 lines
drivers/dma-buf/dma-buf-io.c new file +8
-0+ +static void dma_buf_io_map_refs_release (struct percpu_ref *ref) +{ + struct dma_buf_io_map *map = container_of(ref, struct dma_buf_io_map, refs); + +/* might sleep, use a worker */ +INIT_WORK (&map->release_work, dma_buf_io_map_release_work); +queue_work (system_wq, &map->release_work);You can't guarantee that a
GFP_KERNELallocation won't be performed from a system worker thread, so you can't safely signal a fence from one. The pathological case is when all threads insystem_wqare running work items that performGFP_KERNELallocations, enter reclaim, and then wait on a fence that is signaled by another work item queued onsystem_wq. Since all worker threads are occupied, the signaling work item cannot be scheduled, resulting in a deadlock. We actually hit this exact deadlock early in Xe.
Quoted 2 lines
So, as Christian says, think carefully about whether you really need a dma-fence here. If you do, then you need to play by the rules.
I started with moving it out of wq, which was a good idea anyway, but as mentioned in another email, in the end I just got rid of fences as I believe Christian was suggesting / hinting on. I cc'ed you on v6 if you'd be curious.
Signature · 1 line
Pavel Begunkov
PATCH v4 2/14 iov_iter: add iterator type for dmabuf maps
In reply to Pavel BegunkovIntroduce a new iterator type for dmabuf maps. The map in an opaque object with internals and format specific to the subsystem / driver, and only it can use that subsystem / driver for issuing IO. The task of the middle layers is to pass the map / iterator further down, maybe doing basic splitting and length checking. The iterator can only be used by operations of the file the associated map was created for.
--- include/linux/uio.h | 11 +++++++++++ lib/iov_iter.c | 29 +++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-)
include/linux/uio.h +11 -0
@@ -12,6 +12,7 @@struct page ;struct folio_queue ; +struct dma_buf_io_map ;typedef unsigned int __bitwise iov_iter_extraction_t ;
@@ -29,6 +30,7 @@ enum iter_type {ITER_FOLIOQ ,ITER_XARRAY ,ITER_DISCARD , + ITER_DMABUF_MAP, };#define ITER_SOURCE 1 // == WRITE
@@ -71,6 +73,7 @@ struct iov_iter {const struct folio_queue *folioq;struct xarray *xarray;void __user *ubuf; +struct dma_buf_io_map *dmabuf_map; };size_t count; };
@@ -155,6 +158,11 @@ static inline bool iov_iter_is_xarray(const struct iov_iter *i)return iov_iter_type (i) ==ITER_XARRAY ; } +static inline bool iov_iter_is_dmabuf_map (const struct iov_iter *i) +{ +return iov_iter_type (i) ==ITER_DMABUF_MAP ; +} +static inline unsigned char iov_iter_rw (const struct iov_iter *i) {return i->data_source ?WRITE :READ ;
@@ -300,6 +308,9 @@ void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction, unsigned int first_slot, unsigned int offset, size_t count);void iov_iter_xarray (struct iov_iter *i,unsigned int direction,struct xarray *xarray,loff_t start,size_t count); +void iov_iter_dmabuf_map (struct iov_iter *i,unsigned int direction, +struct dma_buf_io_map *map, +loff_t off,size_t count);ssize_t iov_iter_get_pages2 (struct iov_iter *i,struct page **pages,size_t maxsize,unsigned maxpages,size_t *start); ssize_t iov_iter_get_pages_alloc2(struct iov_iter *i, struct page ***pages,
lib/iov_iter.c +23 -6
@@ -575,7 +575,8 @@ void iov_iter_advance(struct iov_iter *i, size_t size) {if (unlikely (i->count < size)) size = i->count; -if (likely (iter_is_ubuf (i)) ||unlikely (iov_iter_is_xarray (i))) { +if (likely (iter_is_ubuf (i)) ||unlikely (iov_iter_is_xarray (i)) || +unlikely (iov_iter_is_dmabuf_map (i))) { i->iov_offset += size; i->count -= size; }else if (likely (iter_is_iovec (i) ||iov_iter_is_kvec (i))) {
@@ -631,7 +632,8 @@ void iov_iter_revert(struct iov_iter *i, size_t unroll)return ; } unroll -= i->iov_offset; -if (iov_iter_is_xarray (i) ||iter_is_ubuf (i)) { +if (iov_iter_is_xarray (i) ||iter_is_ubuf (i) || +iov_iter_is_dmabuf_map (i)) {BUG ();/* We should never go beyond the start of the specified * range since we might then be straying into pages that * aren't pinned.
@@ -775,6 +777,20 @@ void iov_iter_xarray(struct iov_iter *i, unsigned int direction, }EXPORT_SYMBOL (iov_iter_xarray); +void iov_iter_dmabuf_map (struct iov_iter *i,unsigned int direction, +struct dma_buf_io_map *map, +loff_t off,size_t count) +{ +WARN_ON (direction & ~(READ |WRITE )); + *i = (struct iov_iter ){ + .iter_type =ITER_DMABUF_MAP , + .data_source = direction, + .dmabuf_map = map, + .count = count, + .iov_offset = off, + }; +} +/** * iov_iter_discard - Initialise an I/O iterator that discards data * @i: The iterator to initialise.
@@ -841,7 +857,7 @@ static unsigned long iov_iter_alignment_bvec(const struct iov_iter *i)unsigned long iov_iter_alignment (const struct iov_iter *i) { -if (likely (iter_is_ubuf (i))) { +if (likely (iter_is_ubuf (i)) ||iov_iter_is_dmabuf_map (i)) {size_t size = i->count;if (size)return ((unsigned long )i->ubuf + i->iov_offset) | size;
@@ -872,7 +888,7 @@ unsigned long iov_iter_gap_alignment(const struct iov_iter *i)size_t size = i->count;unsigned k; -if (iter_is_ubuf (i)) +if (iter_is_ubuf (i) ||iov_iter_is_dmabuf_map (i))return 0 ;if (WARN_ON (!iter_is_iovec (i)))
@@ -1469,11 +1485,12 @@ EXPORT_SYMBOL_GPL(import_ubuf);void iov_iter_restore (struct iov_iter *i,struct iov_iter_state *state) {if (WARN_ON_ONCE (!iov_iter_is_bvec (i) && !iter_is_iovec (i) && - !iter_is_ubuf (i)) && !iov_iter_is_kvec (i)) + !iter_is_ubuf (i) && !iov_iter_is_kvec (i) && + !iov_iter_is_dmabuf_map (i)))return ; i->iov_offset = state->iov_offset; i->count = state->count; -if (iter_is_ubuf (i)) +if (iter_is_ubuf (i) ||iov_iter_is_dmabuf_map (i))return ;/* * For the *vec iters, nr_segs + iov is constant - if we increment
Signature · 1 line
2.54.0
Re: [PATCH v4 02/14] iov_iter: add iterator type for dmabuf maps
Quoted 9 lines · Pavel Begunkov
Introduce a new iterator type for dmabuf maps. The map in an opaque object with internals and format specific to the subsystem / driver, and only it can use that subsystem / driver for issuing IO. The task of the middle layers is to pass the map / iterator further down, maybe doing basic splitting and length checking. The iterator can only be used by operations of the file the associated map was created for.
Suggested-by Keith Busch <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>
Looks good:
I'm also pretty sure I reviewed this and vairous other patches before..
Re: [PATCH v4 02/14] iov_iter: add iterator type for dmabuf maps
Quoted 8 lines · Christoph Hellwig
Quoted 7 lines · Pavel Begunkov
Introduce a new iterator type for dmabuf maps. The map in an opaque object with internals and format specific to the subsystem / driver, and only it can use that subsystem / driver for issuing IO. The task of the middle layers is to pass the map / iterator further down, maybe doing basic splitting and length checking. The iterator can only be used by operations of the file the associated map was created for.
Quoted 6 lines
Quoted 2 lines
Suggested-by Keith Busch <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>Looks good:
Reviewed-by Christoph Hellwig <[email protected]>
Thanks,
Quoted 2 lines
I'm also pretty sure I reviewed this and vairous other patches before..
There was a lot of shuffling, so I dropped the tag after a 10th amendment to the patch as I didn't know whether you want to keep it. I'll keep it all for v5 unless anything conceptual changes.
Signature · 1 line
Pavel Begunkov
PATCH v4 3/14 block: rename bi_bvec_done
In reply to Pavel Begunkovstruct bvec_iter::bi_bvec_done is used an offset in the current bvec, let's rename it accordingly for better clarity. We're also going to use it for non-bvec based iteration, so drop the "bvec" part.
--- Documentation/block/biovecs.rst | 8 ++++---- block/bio.c | 4 ++-- block/blk-merge.c | 8 ++++---- block/blk-mq-dma.c | 2 +- block/blk.h | 2 +- drivers/block/loop.c | 2 +- drivers/block/zloop.c | 2 +- drivers/md/dm-io-rewind.c | 10 +++++----- drivers/md/dm-pcache/segment.c | 4 ++-- drivers/nvdimm/btt.c | 2 +- drivers/nvme/host/tcp.c | 2 +- fs/btrfs/misc.h | 2 +- include/linux/bvec.h | 14 +++++++------- io_uring/net.c | 4 ++-- lib/iov_iter.c | 2 +- net/ceph/messenger.c | 4 ++-- 16 files changed, 36 insertions(+), 36 deletions(-)
Documentation/block/biovecs.rst +4 -4
@@ -16,16 +16,16 @@ bv_len by the number of bytes completed in that biovec. In the new scheme of things, everything that must be mutated in order to partially complete a bio is segregated into struct bvec_iter: bi_sector, bi_size and bi_idx have been moved there; and instead of modifying bv_offset -and bv_len, struct bvec_iter has bi_bvec_done, which represents the number of +and bv_len, struct bvec_iter has bi_offset, which represents the number of bytes completed in the current bvec. There are a bunch of new helper macros for hiding the gory details - in particular, presenting the illusion of partially completed biovecs so that -normal code doesn't have to deal with bi_bvec_done. +normal code doesn't have to deal with bi_offset. * Driver code should no longer refer to biovecs directly; we now have bio_iovec() and bio_iter_iovec() macros that return literal struct biovecs, - constructed from the raw biovecs but taking into account bi_bvec_done and + constructed from the raw biovecs but taking into account bi_offset and bi_size. bio_for_each_segment() has been updated to take a bvec_iter argument
@@ -101,7 +101,7 @@ Other implications: I.e. instead of using bio_iovec_idx() (or bio->bi_iovec[bio->bi_idx]), you now use bio_iter_iovec(), which takes a bvec_iter and returns a literal struct bio_vec - constructed on the fly from the raw biovec but - taking into account bi_bvec_done (and bi_size). + taking into account bi_offset (and bi_size). * bi_vcnt can't be trusted or relied upon by driver code - i.e. anything that doesn't actually own the bio. The reason is twofold: firstly, it's not
block/bio.c +2 -2
@@ -229,7 +229,7 @@ void bio_init(struct bio *bio, struct block_device *bdev, struct bio_vec *table, bio->bi_iter.bi_sector =0 ; bio->bi_iter.bi_size =0 ; bio->bi_iter.bi_idx =0 ; - bio->bi_iter.bi_bvec_done =0 ; + bio->bi_iter.bi_offset =0 ; bio->bi_end_io =NULL ; bio->bi_private =NULL ;#ifdef CONFIG_BLK_CGROUP
@@ -1188,7 +1188,7 @@ void bio_iov_bvec_set(struct bio *bio, const struct iov_iter *iter) bio->bi_io_vec = (struct bio_vec *)iter->bvec; bio->bi_iter.bi_idx =0 ; - bio->bi_iter.bi_bvec_done = iter->iov_offset; + bio->bi_iter.bi_offset = iter->iov_offset; bio->bi_iter.bi_size =iov_iter_count (iter);bio_set_flag (bio,BIO_CLONED ); }
block/blk-merge.c +4 -4
@@ -33,7 +33,7 @@ static inline void bio_get_last_bvec(struct bio *bio, struct bio_vec *bv)bio_advance_iter (bio, &iter, iter.bi_size); -if (!iter.bi_bvec_done) +if (!iter.bi_offset) idx = iter.bi_idx -1 ;else /* in the middle of bvec */ idx = iter.bi_idx;
@@ -41,11 +41,11 @@ static inline void bio_get_last_bvec(struct bio *bio, struct bio_vec *bv) *bv = bio->bi_io_vec[idx];/* -* iter.bi_bvec_done records actual length of the last bvec +* iter.bi_offset records actual length of the last bvec * if this bio ends in the middle of one io vector */ -if (iter.bi_bvec_done) - bv->bv_len = iter.bi_bvec_done; +if (iter.bi_offset) + bv->bv_len = iter.bi_offset; } static inline bool bio_will_gap(struct request_queue *q,
block/blk-mq-dma.c +1 -1
@@ -44,7 +44,7 @@ static bool blk_map_iter_next(struct request *req, struct blk_map_iter *iter,* one could be merged into it. This typically happens when moving to * the next bio, but some callers also don't pack bvecs tight. */ -while (!iter->iter.bi_size || !iter->iter.bi_bvec_done) { +while (!iter->iter.bi_size || !iter->iter.bi_offset) {struct bio_vec next;if (!__blk_map_iter_next (iter))
block/blk.h +1 -1
@@ -406,7 +406,7 @@ static inline bool bio_may_need_split(struct bio *bio,return true; bv =__bvec_iter_bvec (bio->bi_io_vec, bio->bi_iter); -if (bio->bi_iter.bi_size > bv->bv_len - bio->bi_iter.bi_bvec_done) +if (bio->bi_iter.bi_size > bv->bv_len - bio->bi_iter.bi_offset)return true;if ((bv->bv_offset | bv->bv_len) & lim->dma_alignment)return true;
drivers/block/loop.c +1 -1
@@ -378,7 +378,7 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd,iov_iter_bvec (&iter, rw,__bvec_iter_bvec (rq->bio->bi_io_vec, rq->bio->bi_iter), nr_bvec,blk_rq_bytes (rq)); - iter.iov_offset = rq->bio->bi_iter.bi_bvec_done; + iter.iov_offset = rq->bio->bi_iter.bi_offset; }atomic_set (&cmd->ref,2 );
drivers/block/zloop.c +1 -1
@@ -553,7 +553,7 @@ static int zloop_do_rw(struct zloop_cmd *cmd)iov_iter_bvec (&iter, rw,__bvec_iter_bvec (rq->bio->bi_io_vec, rq->bio->bi_iter), nr_bvec,blk_rq_bytes (rq)); - iter.iov_offset = rq->bio->bi_iter.bi_bvec_done; + iter.iov_offset = rq->bio->bi_iter.bi_offset; } cmd->iocb.ki_pos = (cmd->sector - zone->start) <<SECTOR_SHIFT ;
drivers/md/dm-io-rewind.c +5 -5
@@ -16,12 +16,12 @@ static inline bool dm_bvec_iter_rewind(const struct bio_vec *bv,int idx; iter->bi_size += bytes; -if (bytes <= iter->bi_bvec_done) { - iter->bi_bvec_done -= bytes; +if (bytes <= iter->bi_offset) { + iter->bi_offset -= bytes;return true; } - bytes -= iter->bi_bvec_done; + bytes -= iter->bi_offset; idx = iter->bi_idx -1 ;while (idx >=0 && bytes && bytes > bv[idx].bv_len) {
@@ -32,13 +32,13 @@ static inline bool dm_bvec_iter_rewind(const struct bio_vec *bv,if (WARN_ONCE (idx <0 && bytes,"Attempted to rewind iter beyond bvec's boundaries\n" )) { iter->bi_size -= bytes; - iter->bi_bvec_done =0 ; + iter->bi_offset =0 ; iter->bi_idx =0 ;return false; } iter->bi_idx = idx; - iter->bi_bvec_done = bv[idx].bv_len - bytes; + iter->bi_offset = bv[idx].bv_len - bytes;return true; }
drivers/md/dm-pcache/segment.c +2 -2
@@ -14,7 +14,7 @@ int segment_copy_to_bio(struct pcache_segment *segment,iov_iter_bvec (&iter,ITER_DEST , &bio->bi_io_vec[bio->bi_iter.bi_idx],bio_segments (bio), bio->bi_iter.bi_size); - iter.iov_offset = bio->bi_iter.bi_bvec_done; + iter.iov_offset = bio->bi_iter.bi_offset;if (bio_off)iov_iter_advance (&iter, bio_off);
@@ -35,7 +35,7 @@ int segment_copy_from_bio(struct pcache_segment *segment,iov_iter_bvec (&iter,ITER_SOURCE , &bio->bi_io_vec[bio->bi_iter.bi_idx],bio_segments (bio), bio->bi_iter.bi_size); - iter.iov_offset = bio->bi_iter.bi_bvec_done; + iter.iov_offset = bio->bi_iter.bi_offset;if (bio_off)iov_iter_advance (&iter, bio_off);
drivers/nvdimm/btt.c +1 -1
@@ -1155,7 +1155,7 @@ static int btt_rw_integrity(struct btt *btt, struct bio_integrity_payload *bip, bv =bvec_iter_bvec (bip->bip_vec, bip->bip_iter);/* * The 'bv' obtained from bvec_iter_bvec has its .bv_len and -* .bv_offset already adjusted for iter->bi_bvec_done, and we +* .bv_offset already adjusted for iter->bi_offset, and we * can use those directly */
drivers/nvme/host/tcp.c +1 -1
@@ -357,7 +357,7 @@ static void nvme_tcp_init_iter(struct nvme_tcp_request *req,iov_iter_bvec (&req->iter, dir,__bvec_iter_bvec (bio->bi_io_vec, bio->bi_iter), nr_bvec, bio->bi_iter.bi_size); - req->iter.iov_offset = bio->bi_iter.bi_bvec_done; + req->iter.iov_offset = bio->bi_iter.bi_offset; } }
fs/btrfs/misc.h +1 -1
@@ -74,7 +74,7 @@ static inline struct bvec_iter init_bvec_iter_for_bio(struct bio *bio) .bi_sector = 0, .bi_size = bio_size, .bi_idx = 0, - .bi_bvec_done = 0, + .bi_offset = 0, }; }
include/linux/bvec.h +7 -7
@@ -110,7 +110,7 @@ struct bvec_iter {/* * Current offset in the bvec entry pointed to by `bi_idx`. */ -unsigned int bi_bvec_done; +unsigned int bi_offset; } __packed__aligned(4) ;struct bvec_iter_all {
@@ -135,14 +135,14 @@ mp_bvec_iter_page(const struct bio_vec *bvecs, const struct bvec_iter iter) static __always_inline unsigned intmp_bvec_iter_len (const struct bio_vec *bvecs,const struct bvec_iter iter) { -return min (__bvec_iter_bvec (bvecs, iter)->bv_len - iter.bi_bvec_done, +return min (__bvec_iter_bvec (bvecs, iter)->bv_len - iter.bi_offset, iter.bi_size); } static __always_inline unsigned intmp_bvec_iter_offset (const struct bio_vec *bvecs,const struct bvec_iter iter) { -return __bvec_iter_bvec (bvecs, iter)->bv_offset + iter.bi_bvec_done; +return __bvec_iter_bvec (bvecs, iter)->bv_offset + iter.bi_offset; }static __always_inline unsigned int
@@ -204,7 +204,7 @@ static inline bool bvec_iter_advance(const struct bio_vec *bv, } iter->bi_size -= bytes; - bytes += iter->bi_bvec_done; + bytes += iter->bi_offset;while (bytes && bytes >= bv[idx].bv_len) { bytes -= bv[idx].bv_len;
@@ -212,7 +212,7 @@ static inline bool bvec_iter_advance(const struct bio_vec *bv, } iter->bi_idx = idx; - iter->bi_bvec_done = bytes; + iter->bi_offset = bytes;return true; }
@@ -223,13 +223,13 @@ static inline bool bvec_iter_advance(const struct bio_vec *bv,static inline void bvec_iter_advance_single (const struct bio_vec *bv,struct bvec_iter *iter,unsigned int bytes) { -unsigned int done = iter->bi_bvec_done + bytes; +unsigned int done = iter->bi_offset + bytes;if (done == bv[iter->bi_idx].bv_len) { done =0 ; iter->bi_idx++; } - iter->bi_bvec_done = done; + iter->bi_offset = done; iter->bi_size -= bytes; }
io_uring/net.c +2 -2
@@ -1471,7 +1471,7 @@ static int io_sg_from_iter(struct sk_buff *skb,return zerocopy_fill_skb_from_iter (skb, from, length); bi.bi_size =min (from->count, length); - bi.bi_bvec_done = from->iov_offset; + bi.bi_offset = from->iov_offset; bi.bi_idx =0 ;while (bi.bi_size && frag <MAX_SKB_FRAGS ) {
@@ -1490,7 +1490,7 @@ static int io_sg_from_iter(struct sk_buff *skb, from->bvec += bi.bi_idx; from->nr_segs -= bi.bi_idx; from->count -= copied; - from->iov_offset = bi.bi_bvec_done; + from->iov_offset = bi.bi_offset; skb->data_len += copied; skb->len += copied;
lib/iov_iter.c +1 -1
@@ -1642,7 +1642,7 @@ static ssize_t iov_iter_extract_bvec_pages(struct iov_iter *i, } bi.bi_idx =0 ; bi.bi_size = maxsize; - bi.bi_bvec_done = skip; + bi.bi_offset = skip; maxpages =want_pages_array (pages, maxsize, skip, maxpages);
net/ceph/messenger.c +2 -2
@@ -762,7 +762,7 @@ static bool ceph_msg_data_bio_advance(struct ceph_msg_data_cursor *cursor,if (!cursor->resid)return false;/* no more data */ -if (!bytes || (it->iter.bi_size && it->iter.bi_bvec_done && +if (!bytes || (it->iter.bi_size && it->iter.bi_offset && page ==bio_iter_page (it->bio, it->iter)))return false;/* more bytes to process in this segment */
@@ -817,7 +817,7 @@ static bool ceph_msg_data_bvecs_advance(struct ceph_msg_data_cursor *cursor,if (!cursor->resid)return false;/* no more data */ -if (!bytes || (cursor->bvec_iter.bi_bvec_done && +if (!bytes || (cursor->bvec_iter.bi_offset && page ==bvec_iter_page (bvecs, cursor->bvec_iter)))return false;/* more bytes to process in this segment */
Signature · 1 line
2.54.0
Re: [PATCH v4 03/14] block: rename bi_bvec_done
Looks good:
PATCH v4 4/14 block: always adjust bi_offset on bio_advance_iter
In reply to Pavel BegunkovExtend bio_no_advance_iter() iteration to also increment the offset. It's not currently needed because the currently listed request types don't have a buffer, but will be useful once we add bios backed by dma-buf, which don't have a bvec but work with a single range.
--- drivers/md/dm-io-rewind.c | 6 ++++-- include/linux/bio.h | 12 ++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-)
drivers/md/dm-io-rewind.c +4 -2
@@ -113,10 +113,12 @@ static inline void dm_bio_rewind_iter(const struct bio *bio, iter->bi_sector -= bytes >>9 ;/* No advance means no rewind */ -if (bio_no_advance_iter (bio)) +if (bio_no_advance_iter (bio)) { iter->bi_size += bytes; -else + iter->bi_offset -= bytes; + }else {dm_bvec_iter_rewind (bio->bi_io_vec, iter, bytes); + } }/**
include/linux/bio.h +8 -4
@@ -113,11 +113,13 @@ static inline void bio_advance_iter(const struct bio *bio, { iter->bi_sector += bytes >>9 ; -if (bio_no_advance_iter (bio)) +if (bio_no_advance_iter (bio)) { iter->bi_size -= bytes; -else + iter->bi_offset += bytes; + }else {bvec_iter_advance (bio->bi_io_vec, iter, bytes);/* TODO: It is reasonable to complete bio with error here. */ + } }/* @bytes should be less or equal to bvec[i->bi_idx].bv_len */
@@ -127,10 +129,12 @@ static inline void bio_advance_iter_single(const struct bio *bio, { iter->bi_sector += bytes >>9 ; -if (bio_no_advance_iter (bio)) +if (bio_no_advance_iter (bio)) { iter->bi_size -= bytes; -else + iter->bi_offset += bytes; + }else {bvec_iter_advance_single (bio->bi_io_vec, iter, bytes); + } }void __bio_advance (struct bio *,unsigned bytes);
Signature · 1 line
2.54.0
Re: [PATCH v4 04/14] block: always adjust bi_offset on bio_advance_iter
Looks good:
PATCH v4 5/14 block: move bvec init into __bio_clone
In reply to Pavel BegunkovConsolidate bi_io_vec assignment for cloning in __bio_clone to keep any further changes in one place.
--- block/bio.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
block/bio.c +2 -3
@@ -860,6 +860,7 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) bio->bi_write_hint = bio_src->bi_write_hint; bio->bi_write_stream = bio_src->bi_write_stream; bio->bi_iter = bio_src->bi_iter; + bio->bi_io_vec = bio_src->bi_io_vec;if (bio->bi_bdev) { if (bio->bi_bdev == bio_src->bi_bdev &&
@@ -902,8 +903,6 @@ struct bio *bio_alloc_clone(struct block_device *bdev, struct bio *bio_src,bio_put (bio);return NULL ; } - bio->bi_io_vec = bio_src->bi_io_vec; -return bio; }EXPORT_SYMBOL (bio_alloc_clone);
@@ -923,7 +922,7 @@ int bio_init_clone(struct block_device *bdev, struct bio *bio, {int ret; -bio_init (bio, bdev, bio_src->bi_io_vec,0 , bio_src->bi_opf); +bio_init (bio, bdev,NULL ,0 , bio_src->bi_opf); ret =__bio_clone (bio, bio_src, gfp);if (ret)bio_uninit (bio);
Signature · 1 line
2.54.0
Re: [PATCH v4 05/14] block: move bvec init into __bio_clone
Looks good:
PATCH v4 6/14 block: introduce dma map backed bio type
In reply to Pavel BegunkovPremapped buffers don't require a generic bio_vec since these have already been dma mapped. Repurpose the bi_io_vec space to strore dmabuf maps as they are mutually exclusive.
--- block/bio.c | 23 ++++++++++++++++++++++- block/blk-merge.c | 15 +++++++++++++++ block/fops.c | 2 ++ include/linux/bio.h | 10 ++++++---- include/linux/blk-mq.h | 7 +++++++ include/linux/blk_types.h | 14 +++++++++++++- include/linux/bvec.h | 3 ++- 7 files changed, 67 insertions(+), 7 deletions(-)
block/bio.c +22 -1
@@ -860,7 +860,12 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) bio->bi_write_hint = bio_src->bi_write_hint; bio->bi_write_stream = bio_src->bi_write_stream; bio->bi_iter = bio_src->bi_iter; - bio->bi_io_vec = bio_src->bi_io_vec; + +if (op_is_dmabuf (bio->bi_opf)) { + bio->bi_dmabuf_map = bio_src->bi_dmabuf_map; + }else { + bio->bi_io_vec = bio_src->bi_io_vec; + }if (bio->bi_bdev) { if (bio->bi_bdev == bio_src->bi_bdev &&
@@ -1192,6 +1197,17 @@ void bio_iov_bvec_set(struct bio *bio, const struct iov_iter *iter)bio_set_flag (bio,BIO_CLONED ); } +void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter) +{ +WARN_ON_ONCE (bio->bi_max_vecs); + + bio->bi_dmabuf_map = iter->dmabuf_map; + bio->bi_vcnt =0 ; + bio->bi_iter.bi_offset = iter->iov_offset; + bio->bi_iter.bi_size =iov_iter_count (iter); + bio->bi_opf |=REQ_NOMERGE |REQ_DMABUF ; +} +/* * Aligns the bio size to the len_align_mask, releasing excessive bio vecs that * __bio_iov_iter_get_pages may have inserted, and reverts the trimmed length
@@ -1254,6 +1270,11 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,iov_iter_advance (iter, bio->bi_iter.bi_size);return 0 ; } +if (iov_iter_is_dmabuf_map (iter)) { +bio_dmabuf_map_set (bio, iter); +iov_iter_advance (iter, bio->bi_iter.bi_size); +return 0 ; + }if (iov_iter_extract_will_pin (iter))bio_set_flag (bio,BIO_PAGE_PINNED );
block/blk-merge.c +15 -0
@@ -346,6 +346,20 @@ int bio_split_io_at(struct bio *bio, const struct queue_limits *lim, len_align_mask |= (bc->bc_key->crypto_cfg.data_unit_size -1 ); } +if (op_is_dmabuf (bio->bi_opf)) { +/* single contiguous range into the dma-buf */ + nsegs =1 ; + +if ((bio->bi_iter.bi_offset & start_align_mask) || + (bio->bi_iter.bi_size & len_align_mask)) +return -EINVAL ; +if (bio->bi_iter.bi_size > max_bytes) { + bytes = max_bytes; + goto split; + } + goto out; + } +bio_for_each_bvec (bv, bio, iter) {if (bv.bv_offset & start_align_mask || bv.bv_len & len_align_mask)
@@ -376,6 +390,7 @@ int bio_split_io_at(struct bio *bio, const struct queue_limits *lim, bvprvp = &bvprv; } +out: *segs = nsegs; bio->bi_bvec_gap_bit =ffs (gaps);return 0 ;
block/fops.c +2 -0
@@ -348,6 +348,8 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb,* bio_iov_iter_get_pages() and set the bvec directly. */ bio_iov_bvec_set (bio, iter); + }else if (iov_iter_is_dmabuf_map (iter)) { +bio_dmabuf_map_set (bio, iter); }else { ret =blkdev_iov_iter_get_pages (bio, iter, bdev);if (unlikely (ret))
include/linux/bio.h +6 -4
@@ -80,7 +80,8 @@ static inline bool bio_no_advance_iter(const struct bio *bio) {return bio_op (bio) ==REQ_OP_DISCARD ||bio_op (bio) ==REQ_OP_SECURE_ERASE || -bio_op (bio) ==REQ_OP_WRITE_ZEROES ; +bio_op (bio) ==REQ_OP_WRITE_ZEROES || +op_is_dmabuf (bio->bi_opf); }static inline void *bio_data (struct bio *bio)
@@ -399,12 +400,12 @@ static inline void bio_wouldblock_error(struct bio *bio)/* * Calculate number of bvec segments that should be allocated to fit data -* pointed by @iter. If @iter is backed by bvec it's going to be reused -* instead of allocating a new one. +* pointed by @iter. If @iter is backed by a bvec or a dmabuf, the bvec array / +* the dma map are going to be reused, and so no extra allocation is required. */ static inline int bio_iov_vecs_to_alloc (struct iov_iter *iter,int max_segs) { -if (iov_iter_is_bvec (iter)) +if (iov_iter_is_bvec (iter) ||iov_iter_is_dmabuf_map (iter))return 0 ;return iov_iter_npages (iter, max_segs); }
@@ -484,6 +485,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter, unsigned len_align_mask);void bio_iov_bvec_set (struct bio *bio,const struct iov_iter *iter); +void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter);void __bio_release_pages (struct bio *bio,bool mark_dirty);extern void bio_set_pages_dirty (struct bio *bio);extern void bio_check_pages_dirty (struct bio *bio);
include/linux/blk-mq.h +7 -0
@@ -1017,6 +1017,13 @@ static inline void *blk_mq_rq_to_pdu(struct request *rq)return rq +1 ; } +static inline bool blk_mq_rq_is_dmabuf (struct request *rq) +{ +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return false; +return rq->bio &&op_is_dmabuf (rq->bio->bi_opf); +} +static inline struct blk_mq_hw_ctx *queue_hctx (struct request_queue *q,int id) {struct blk_mq_hw_ctx *hctx;
include/linux/blk_types.h +13 -1
@@ -233,7 +233,12 @@ struct bio {atomic_t __bi_remaining;/* The actual vec list, preserved by bio_reset() */ -struct bio_vec *bi_io_vec; +union { +struct bio_vec *bi_io_vec; +/* Driver specific dma map, valid IFF REQ_DMABUF is set */ +struct dma_buf_io_map *bi_dmabuf_map; + }; +struct bvec_iter bi_iter;union {
@@ -401,6 +406,7 @@ enum req_flag_bits { __REQ_DRV,/* for driver use */ __REQ_FS_PRIVATE,/* for file system (submitter) use */ __REQ_ATOMIC,/* for atomic write operations */ + __REQ_DMABUF, /* Using premmaped dma buffers *//* * Command specific flags, keep last: */
@@ -433,6 +439,7 @@ enum req_flag_bits {#define REQ_DRV (__force blk_opf_t)(1ULL << __REQ_DRV)#define REQ_FS_PRIVATE (__force blk_opf_t)(1ULL << __REQ_FS_PRIVATE)#define REQ_ATOMIC (__force blk_opf_t)(1ULL << __REQ_ATOMIC) +#define REQ_DMABUF (__force blk_opf_t)(1ULL << __REQ_DMABUF)#define REQ_NOUNMAP (__force blk_opf_t)(1ULL << __REQ_NOUNMAP)
@@ -486,6 +493,11 @@ static inline bool op_is_discard(blk_opf_t op)return (op &REQ_OP_MASK ) ==REQ_OP_DISCARD ; } +static inline bool op_is_dmabuf (blk_opf_t op) +{ +return op &REQ_DMABUF ; +} +/* * Check if a bio or request operation is a zone management operation. */
include/linux/bvec.h +2 -1
@@ -108,7 +108,8 @@ struct bvec_iter {unsigned int bi_idx;/* -* Current offset in the bvec entry pointed to by `bi_idx`. +* Current offset in the bvec entry pointed to by `bi_idx` or into +* a dma-buf map. */ unsigned int bi_offset; } __packed__aligned(4) ;
Signature · 1 line
2.54.0
Christoph Hellwig
maintainerRe: [PATCH v4 06/14] block: introduce dma map backed bio type
Review
Quoted 7 lines
(quoted diff) +6
-1- bio->bi_io_vec = bio_src->bi_io_vec; + + if (op_is_dmabuf(bio->bi_opf)) { + bio->bi_dmabuf_map = bio_src->bi_dmabuf_map; + } else { + bio->bi_io_vec = bio_src->bi_io_vec; + }
No need for the braces. But given that these are the fields why even bother doing both sides and not rely on the union?
Review
Quoted 9 lines
(quoted diff) +9
-0+void bio_dmabuf_map_set(struct bio *bio, struct iov_iter *iter) +{ + WARN_ON_ONCE(bio->bi_max_vecs); + + bio->bi_dmabuf_map = iter->dmabuf_map; + bio->bi_vcnt = 0; + bio->bi_iter.bi_offset = iter->iov_offset; + bio->bi_iter.bi_size = iov_iter_count(iter); + bio->bi_opf |= REQ_NOMERGE | REQ_DMABUF;
This seems to be largely copied from bio_iov_bvec_set, but misses the REQ_CLONE there that we should probably set as well to indicate that the data descriptor is not owned (although not setting it is not really a bug).
What about merging these two into a single and easier to use interface like this:
block/bio.c +11 -25
@@ -860,12 +860,9 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) bio->bi_write_hint = bio_src->bi_write_hint; bio->bi_write_stream = bio_src->bi_write_stream; bio->bi_iter = bio_src->bi_iter; - -if (op_is_dmabuf (bio->bi_opf)) { - bio->bi_dmabuf_map = bio_src->bi_dmabuf_map; - }else { - bio->bi_io_vec = bio_src->bi_io_vec; - } +static_assert (offsetof(struct bio , bi_io_vec) == + offsetof(struct bio , bi_dmabuf_map)); + bio->bi_io_vec = bio_src->bi_io_vec;if (bio->bi_bdev) { if (bio->bi_bdev == bio_src->bi_bdev &&
@@ -1186,26 +1183,21 @@ void __bio_release_pages(struct bio *bio, bool mark_dirty) }EXPORT_SYMBOL_GPL (__bio_release_pages); -void bio_iov_bvec_set (struct bio *bio,const struct iov_iter *iter) +bool bio_iov_iter_set (struct bio *bio,const struct iov_iter *iter) {WARN_ON_ONCE (bio->bi_max_vecs); +if (!iov_iter_is_bvec (iter) && !iov_iter_is_dmabuf_map (iter)) +return false; + bio->bi_io_vec = (struct bio_vec *)iter->bvec; bio->bi_iter.bi_idx =0 ; bio->bi_iter.bi_offset = iter->iov_offset; bio->bi_iter.bi_size =iov_iter_count (iter);bio_set_flag (bio,BIO_CLONED ); -} - -void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter) -{ -WARN_ON_ONCE (bio->bi_max_vecs); - - bio->bi_dmabuf_map = iter->dmabuf_map; - bio->bi_vcnt =0 ; - bio->bi_iter.bi_offset = iter->iov_offset; - bio->bi_iter.bi_size =iov_iter_count (iter); - bio->bi_opf |=REQ_NOMERGE |REQ_DMABUF ; +if (iov_iter_is_dmabuf_map (iter)) + bio->bi_opf |=REQ_NOMERGE |REQ_DMABUF ; +return true; }/*
@@ -1265,13 +1257,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,if (WARN_ON_ONCE (bio_flagged (bio,BIO_CLONED )))return -EIO ; -if (iov_iter_is_bvec (iter)) { -bio_iov_bvec_set (bio, iter); -iov_iter_advance (iter, bio->bi_iter.bi_size); -return 0 ; - } -if (iov_iter_is_dmabuf_map (iter)) { -bio_dmabuf_map_set (bio, iter); +if (bio_iov_iter_set (bio, iter)) {iov_iter_advance (iter, bio->bi_iter.bi_size);return 0 ; }
block/blk-map.c +1 -1
@@ -473,7 +473,7 @@ static int blk_rq_map_user_bvec(struct request *rq, const struct iov_iter *iter) bio =blk_rq_map_bio_alloc (rq,0 ,GFP_KERNEL );if (!bio)return -ENOMEM ; -bio_iov_bvec_set (bio, iter); +bio_iov_iter_set (bio, iter); ret =blk_rq_append_bio (rq, bio);if (ret)
block/fops.c +6 -11
@@ -340,17 +340,12 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb, bio->bi_end_io = blkdev_bio_end_io_async; bio->bi_ioprio = iocb->ki_ioprio; -if (iov_iter_is_bvec (iter)) { -/* -* Users don't rely on the iterator being in any particular -* state for async I/O returning -EIOCBQUEUED, hence we can -* avoid expensive iov_iter_advance(). Bypass -* bio_iov_iter_get_pages() and set the bvec directly. -*/ -bio_iov_bvec_set (bio, iter); - }else if (iov_iter_is_dmabuf_map (iter)) { -bio_dmabuf_map_set (bio, iter); - }else { +/* +* Users don't rely on the iterator being in any particular state for +* async I/O returning -EIOCBQUEUED, hence we can avoid the expensive +* iov_iter_advance() if we could set the bvec/dmabuf directly. +*/ +if (!bio_iov_iter_set (bio, iter)) { ret =blkdev_iov_iter_get_pages (bio, iter, bdev);if (unlikely (ret)) goto out_bio_put;
include/linux/bio.h +1 -2
@@ -484,8 +484,7 @@ int bdev_rw_virt(struct block_device *bdev, sector_t sector, void *data,int bio_iov_iter_get_pages (struct bio *bio,struct iov_iter *iter,unsigned len_align_mask); -void bio_iov_bvec_set (struct bio *bio,const struct iov_iter *iter); -void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter); +bool bio_iov_iter_set (struct bio *bio,const struct iov_iter *iter);void __bio_release_pages (struct bio *bio,bool mark_dirty);extern void bio_set_pages_dirty (struct bio *bio);extern void bio_check_pages_dirty (struct bio *bio);
Re: [PATCH v4 06/14] block: introduce dma map backed bio type
Quoted 9 lines · Christoph Hellwig
Review
Quoted 7 lines
(quoted diff) +6
-1- bio->bi_io_vec = bio_src->bi_io_vec; + + if (op_is_dmabuf(bio->bi_opf)) { + bio->bi_dmabuf_map = bio_src->bi_dmabuf_map; + } else { + bio->bi_io_vec = bio_src->bi_io_vec; + }No need for the braces.
Got it,
But given that these are the fields why
Quoted 1 line
even bother doing both sides and not rely on the union?
Compiles into the same, but comparing to a comment makes the compiler to check it for us if anything happens.
Noticed static_assert() below, that works as well, though I think the current version is nicer b/c it preserves assignment types for the compiler. Strict aliasing is obviously not a thing, so don't have a strong opinion.
Quoted 15 lines
Review
Quoted 9 lines
(quoted diff) +9
-0+void bio_dmabuf_map_set(struct bio *bio, struct iov_iter *iter) +{ + WARN_ON_ONCE(bio->bi_max_vecs); + + bio->bi_dmabuf_map = iter->dmabuf_map; + bio->bi_vcnt = 0; + bio->bi_iter.bi_offset = iter->iov_offset; + bio->bi_iter.bi_size = iov_iter_count(iter); + bio->bi_opf |= REQ_NOMERGE | REQ_DMABUF;This seems to be largely copied from
bio_iov_bvec_set, but misses theREQ_CLONEthere that we should probably set as well to indicate that the data descriptor is not owned (although not setting it is not really a bug).
Quoted 2 lines
What about merging these two into a single and easier to use interface like this:
No opinion on this, I can add it, probably as another prep patch.
Quoted 124 lines
block/bio.c +11
-25@@ -860,12 +860,9 @@ static int __bio_clone(struct bio *bio, struct bio *bio_src, gfp_t gfp) bio->bi_write_hint = bio_src->bi_write_hint; bio->bi_write_stream = bio_src->bi_write_stream; bio->bi_iter = bio_src->bi_iter; - -if (op_is_dmabuf (bio->bi_opf)) { - bio->bi_dmabuf_map = bio_src->bi_dmabuf_map; - }else { - bio->bi_io_vec = bio_src->bi_io_vec; - } +static_assert (offsetof(struct bio , bi_io_vec) == + offsetof(struct bio , bi_dmabuf_map)); + bio->bi_io_vec = bio_src->bi_io_vec;if (bio->bi_bdev) { if (bio->bi_bdev == bio_src->bi_bdev &&@@ -1186,26 +1183,21 @@ void __bio_release_pages(struct bio *bio, bool mark_dirty) }EXPORT_SYMBOL_GPL (__bio_release_pages); -void bio_iov_bvec_set (struct bio *bio,const struct iov_iter *iter) +bool bio_iov_iter_set (struct bio *bio,const struct iov_iter *iter) {WARN_ON_ONCE (bio->bi_max_vecs); +if (!iov_iter_is_bvec (iter) && !iov_iter_is_dmabuf_map (iter)) +return false; + bio->bi_io_vec = (struct bio_vec *)iter->bvec; bio->bi_iter.bi_idx =0 ; bio->bi_iter.bi_offset = iter->iov_offset; bio->bi_iter.bi_size =iov_iter_count (iter);bio_set_flag (bio,BIO_CLONED ); -} - -void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter) -{ -WARN_ON_ONCE (bio->bi_max_vecs); - - bio->bi_dmabuf_map = iter->dmabuf_map; - bio->bi_vcnt =0 ; - bio->bi_iter.bi_offset = iter->iov_offset; - bio->bi_iter.bi_size =iov_iter_count (iter); - bio->bi_opf |=REQ_NOMERGE |REQ_DMABUF ; +if (iov_iter_is_dmabuf_map (iter)) + bio->bi_opf |=REQ_NOMERGE |REQ_DMABUF ; +return true; }/* @@ -1265,13 +1257,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,if (WARN_ON_ONCE (bio_flagged (bio,BIO_CLONED )))return -EIO ; -if (iov_iter_is_bvec (iter)) { -bio_iov_bvec_set (bio, iter); -iov_iter_advance (iter, bio->bi_iter.bi_size); -return 0 ; - } -if (iov_iter_is_dmabuf_map (iter)) { -bio_dmabuf_map_set (bio, iter); +if (bio_iov_iter_set (bio, iter)) {iov_iter_advance (iter, bio->bi_iter.bi_size);return 0 ; }block/blk-map.c +1
-1@@ -473,7 +473,7 @@ static int blk_rq_map_user_bvec(struct request *rq, const struct iov_iter *iter) bio =blk_rq_map_bio_alloc (rq,0 ,GFP_KERNEL );if (!bio)return -ENOMEM ; -bio_iov_bvec_set (bio, iter); +bio_iov_iter_set (bio, iter); ret =blk_rq_append_bio (rq, bio);if (ret)block/fops.c +6
-11@@ -340,17 +340,12 @@ static ssize_t __blkdev_direct_IO_async(struct kiocb *iocb, bio->bi_end_io = blkdev_bio_end_io_async; bio->bi_ioprio = iocb->ki_ioprio; -if (iov_iter_is_bvec (iter)) { -/* -* Users don't rely on the iterator being in any particular -* state for async I/O returning -EIOCBQUEUED, hence we can -* avoid expensive iov_iter_advance(). Bypass -* bio_iov_iter_get_pages() and set the bvec directly. -*/ -bio_iov_bvec_set (bio, iter); - }else if (iov_iter_is_dmabuf_map (iter)) { -bio_dmabuf_map_set (bio, iter); - }else { +/* +* Users don't rely on the iterator being in any particular state for +* async I/O returning -EIOCBQUEUED, hence we can avoid the expensive +* iov_iter_advance() if we could set the bvec/dmabuf directly. +*/ +if (!bio_iov_iter_set (bio, iter)) { ret =blkdev_iov_iter_get_pages (bio, iter, bdev);if (unlikely (ret)) goto out_bio_put;include/linux/bio.h +1
-2@@ -484,8 +484,7 @@ int bdev_rw_virt(struct block_device *bdev, sector_t sector, void *data,int bio_iov_iter_get_pages (struct bio *bio,struct iov_iter *iter,unsigned len_align_mask); -void bio_iov_bvec_set (struct bio *bio,const struct iov_iter *iter); -void bio_dmabuf_map_set (struct bio *bio,struct iov_iter *iter); +bool bio_iov_iter_set (struct bio *bio,const struct iov_iter *iter);void __bio_release_pages (struct bio *bio,bool mark_dirty);extern void bio_set_pages_dirty (struct bio *bio);extern void bio_check_pages_dirty (struct bio *bio);
Signature · 1 line
Pavel Begunkov
PATCH v4 7/14 block: forward init_dma_buf_io_ctx to drivers
In reply to Pavel BegunkovAdd a simple proxy implementation of init_dma_buf_io_ctx() forwarding the call to a new struct block_device_operations operation.
--- block/fops.c | 14 ++++++++++++++ include/linux/blkdev.h | 2 ++ 2 files changed, 16 insertions(+)
block/fops.c +14 -0
@@ -926,6 +926,19 @@ static int blkdev_mmap_prepare(struct vm_area_desc *desc)return generic_file_mmap_prepare (desc); } +static int blkdev_init_dma_buf_io_ctx (struct file *file, +struct dma_buf_io_ctx *ctx) +{ +struct block_device *bdev =file_bdev (file); +struct gendisk *disk = bdev->bd_disk; + +if (!(file->f_flags &O_DIRECT )) +return -EINVAL ; +if (!disk->fops->init_dma_buf_io_ctx) +return -EINVAL ; +return disk->fops->init_dma_buf_io_ctx (bdev, ctx); +} +const struct file_operations def_blk_fops = { .open = blkdev_open, .release = blkdev_release,
@@ -944,6 +957,7 @@ const struct file_operations def_blk_fops = { .fallocate = blkdev_fallocate, .uring_cmd = blkdev_uring_cmd, .fop_flags = FOP_BUFFER_RASYNC, + .init_dma_buf_io_ctx = blkdev_init_dma_buf_io_ctx, };static __init int blkdev_init (void )
include/linux/blkdev.h +2 -0
@@ -1682,6 +1682,8 @@ struct block_device_operations {/* returns the length of the identifier or a negative errno: */ int (*get_unique_id)(struct gendisk *disk,u8 id[16 ],enum blk_unique_id id_type); +int (*init_dma_buf_io_ctx)(struct block_device *, +struct dma_buf_io_ctx *);struct module *owner;const struct pr_ops *pr_ops;
Signature · 1 line
2.54.0
Re: [PATCH v4 07/14] block: forward init_dma_buf_io_ctx to drivers
Looks good:
PATCH v4 8/14 nvme-pci: implement dma_token backed requests
In reply to Pavel BegunkovEnable BIO_DMABUF_MAP backed requests. It creates a prp list for the dmabuf when it's mapped, which is then used to initialise requests.
--- drivers/nvme/host/core.c | 12 ++ drivers/nvme/host/nvme.h | 2 + drivers/nvme/host/pci.c | 259 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+)
drivers/nvme/host/core.c +12 -0
@@ -2676,6 +2676,17 @@ static int nvme_report_zones(struct gendisk *disk, sector_t sector,#define nvme_report_zones NULL#endif /* CONFIG_BLK_DEV_ZONED */ +static int nvme_init_dma_buf_io_ctx (struct block_device *bdev, +struct dma_buf_io_ctx *ctx) +{ +struct nvme_ns *ns = bdev->bd_disk->private_data; +struct nvme_ctrl *ctrl = ns->ctrl; + +if (!ctrl->ops->init_dma_buf_io_ctx) +return -EINVAL ; +return ctrl->ops->init_dma_buf_io_ctx (ctrl, ctx); +} +const struct block_device_operations nvme_bdev_ops = { .owner =THIS_MODULE , .ioctl = nvme_ioctl,
@@ -2686,6 +2697,7 @@ const struct block_device_operations nvme_bdev_ops = { .get_unique_id = nvme_get_unique_id, .report_zones = nvme_report_zones, .pr_ops = &nvme_pr_ops, + .init_dma_buf_io_ctx = nvme_init_dma_buf_io_ctx, }; static int nvme_wait_ready(struct nvme_ctrl *ctrl, u32 mask, u32 val,
drivers/nvme/host/nvme.h +2 -0
@@ -652,6 +652,8 @@ struct nvme_ctrl_ops {int (*get_address)(struct nvme_ctrl *ctrl,char *buf,int size);void (*print_device_info)(struct nvme_ctrl *ctrl);bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl); +int (*init_dma_buf_io_ctx)(struct nvme_ctrl *ctrl, +struct dma_buf_io_ctx *ctx);unsigned long (*get_virt_boundary)(struct nvme_ctrl *ctrl,bool is_admin); };
drivers/nvme/host/pci.c +259 -0
306 lines not on this page · raw message
Signature · 1 line
2.54.0
Christoph Hellwig
maintainerRe: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
drivers/nvme/host/core.c static int nvme_report_zones(struct gendisk *disk, sector_t sector, · line 2680
Quoted 2 lines
drivers/nvme/host/core.c +2
-0+static int nvme_init_dma_buf_io_ctx (struct block_device *bdev, +struct dma_buf_io_ctx *ctx)
Please stick to two-tab indents for function declaration continuation for the nvme code to keep the code easy to maintain.
Quoted 1 line
+#if
defined(CONFIG_DMA_SHARED_BUFFER)
This should be good old #ifdef. Same in a few other places over the series.
Modulo these minor nits the patch looks good.
Re: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
Quoted 10 lines · Christoph Hellwig
drivers/nvme/host/core.c static int nvme_report_zones(struct gendisk *disk, sector_t sector, · line 2680
Quoted 2 lines
drivers/nvme/host/core.c +2
-0+static int nvme_init_dma_buf_io_ctx (struct block_device *bdev, +struct dma_buf_io_ctx *ctx)Please stick to two-tab indents for function declaration continuation for the nvme code to keep the code easy to maintain.
Quoted 1 line
+#if
defined(CONFIG_DMA_SHARED_BUFFER)This should be good old #ifdef. Same in a few other places over the series.
Going to apply both for v5, thanks.
Quoted 1 line
Modulo these minor nits the patch looks good.
Signature · 1 line
Pavel Begunkov
Re: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
In reply to Pavel BegunkovQuoted 172 lines · Pavel Begunkov
Enable
BIO_DMABUF_MAPbacked requests. It creates a prp list for the dmabuf when it's mapped, which is then used to initialise requests.Suggested-by Keith Busch <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- drivers/nvme/host/core.c | 12 ++ drivers/nvme/host/nvme.h | 2 + drivers/nvme/host/pci.c | 259 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+)drivers/nvme/host/core.c +12
-0@@ -2676,6 +2676,17 @@ static int nvme_report_zones(struct gendisk *disk, sector_t sector,#define nvme_report_zones NULL#endif /* CONFIG_BLK_DEV_ZONED */ +static int nvme_init_dma_buf_io_ctx (struct block_device *bdev, +struct dma_buf_io_ctx *ctx) +{ +struct nvme_ns *ns = bdev->bd_disk->private_data; +struct nvme_ctrl *ctrl = ns->ctrl; + +if (!ctrl->ops->init_dma_buf_io_ctx) +return -EINVAL ; +return ctrl->ops->init_dma_buf_io_ctx (ctrl, ctx); +} +const struct block_device_operations nvme_bdev_ops = { .owner =THIS_MODULE , .ioctl = nvme_ioctl,@@ -2686,6 +2697,7 @@ const struct block_device_operations nvme_bdev_ops = { .get_unique_id = nvme_get_unique_id, .report_zones = nvme_report_zones, .pr_ops = &nvme_pr_ops, + .init_dma_buf_io_ctx = nvme_init_dma_buf_io_ctx, }; static int nvme_wait_ready(struct nvme_ctrl *ctrl, u32 mask, u32 val,drivers/nvme/host/nvme.h +2
-0@@ -652,6 +652,8 @@ struct nvme_ctrl_ops {int (*get_address)(struct nvme_ctrl *ctrl,char *buf,int size);void (*print_device_info)(struct nvme_ctrl *ctrl);bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl); +int (*init_dma_buf_io_ctx)(struct nvme_ctrl *ctrl, +struct dma_buf_io_ctx *ctx);unsigned long (*get_virt_boundary)(struct nvme_ctrl *ctrl,bool is_admin); };drivers/nvme/host/pci.c +95
-0@@ -27,6 +27,8 @@#include <linux/io-64-nonatomic-lo-hi.h> #include <linux/io-64-nonatomic-hi-lo.h> #include <linux/sed-opal.h> +#include <linux/dma-buf-io.h> +#include <linux/dma-resv.h> #include "trace.h" #include "nvme.h" @@ -393,6 +395,13 @@ struct nvme_queue {struct completion delete_done; }; +struct nvme_dmabuf_map { +struct dma_buf_io_map base; +struct sg_table *sgt; +unsigned nr_entries; +dma_addr_t dma_list[]; +}; +/* bits for iod->flags */ enum nvme_iod_flags {/* this command has been aborted by the timeout handler */ @@ -859,6 +868,138 @@ static void nvme_free_descriptors(struct request *req) } } +static inline struct nvme_dmabuf_map * +to_nvme_dmabuf_map (struct dma_buf_io_map *map) +{ + return container_of(map, struct nvme_dmabuf_map, base); +} + +static void nvme_dmabuf_map_sync_for_cpu (struct nvme_dev *nvme_dev, +struct request *req) +{ +struct device *dev = nvme_dev->dev; +enum dma_data_direction dma_dir; +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +dma_addr_t *dma_list = map->dma_list; +unsigned offset = bio->bi_iter.bi_offset; +unsigned map_idx = offset /NVME_CTRL_PAGE_SIZE ; +int length =blk_rq_payload_bytes (req) + + (offset & (NVME_CTRL_PAGE_SIZE -1 )); + + dma_dir =rq_data_dir (req) ==READ ?DMA_FROM_DEVICE :DMA_TO_DEVICE ; + +while (length >0 ) { +dma_sync_single_for_cpu (dev, dma_list[map_idx++], +NVME_CTRL_PAGE_SIZE , dma_dir); + length -=NVME_CTRL_PAGE_SIZE ; + } +} + +static void nvme_dmabuf_map_sync_for_device (struct nvme_dev *nvme_dev, +struct request *req) +{ +struct device *dev = nvme_dev->dev; +enum dma_data_direction dma_dir; +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +dma_addr_t *dma_list = map->dma_list; +unsigned offset = bio->bi_iter.bi_offset; +unsigned map_idx = offset /NVME_CTRL_PAGE_SIZE ; +int length =blk_rq_payload_bytes (req) + + (offset & (NVME_CTRL_PAGE_SIZE -1 )); + + dma_dir =rq_data_dir (req) ==READ ?DMA_FROM_DEVICE :DMA_TO_DEVICE ; + +while (length >0 ) { +dma_sync_single_for_device (dev, dma_list[map_idx++], +NVME_CTRL_PAGE_SIZE , dma_dir); + length -=NVME_CTRL_PAGE_SIZE ; + } +} + +static void nvme_rq_clean_dmabuf_map (struct nvme_dev *dev, +struct request *req) +{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); + +nvme_dmabuf_map_sync_for_cpu (dev, req); + +if (!(iod->flags &IOD_SINGLE_SEGMENT )) +nvme_free_descriptors (req); +} + +static blk_status_t nvme_rq_setup_dmabuf_map (struct request *req, +struct nvme_queue *nvmeq) +{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +unsigned bvec_done = bio->bi_iter.bi_offset; +unsigned map_idx = bvec_done /NVME_CTRL_PAGE_SIZE ; +unsigned offset = bvec_done & (NVME_CTRL_PAGE_SIZE -1 ); +int length =blk_rq_payload_bytes (req) - (NVME_CTRL_PAGE_SIZE - offset); +dma_addr_t *dma_list = map->dma_list; +u64 prp1_dma = dma_list[map_idx++] + offset; +u64 dma_addr, prp2_dma; +dma_addr_t prp_dma; +__le64 *prp_list; +unsigned i; + +nvme_dmabuf_map_sync_for_device (nvmeq->dev, req); + +if (length <=0 ) { + prp2_dma =0 ; + goto done; + } + +if (length <=NVME_CTRL_PAGE_SIZE ) {
drivers/nvme/host/pci.c static void nvme_free_descriptors(struct request *req) · line 966
Quoted 10 lines
drivers/nvme/host/pci.c +10
-0+ prp2_dma = dma_list[map_idx]; + goto done; + } + +if (DIV_ROUND_UP (length,NVME_CTRL_PAGE_SIZE ) <= +NVME_SMALL_POOL_SIZE /sizeof (__le64)) + iod->flags |=IOD_SMALL_DESCRIPTOR ; + + prp_list =dma_pool_alloc (nvme_dma_pool (nvmeq, iod),GFP_ATOMIC , + &prp_dma);
I wonder if it's possible to perform the PRP list page allocations and initialization in nvme_dma_buf_io_map() instead of the I/O path. The NVMe dma_pools are per-NUMA-node linked lists protected by spinlocks, making them significant CPU hotspots. If the dmabuf registration set up a contiguous DMA-coherent list of PRP entries for the pages of the registered buffer, any command using the dmabuf and requiring at most 1 PRP list page (i.e. length <= 2 MB) could just set its PRP list pointer to an offset into the dmabuf's PRP list.
Best,
Caleb
Quoted 195 lines
drivers/nvme/host/pci.c +154
-1+if (!prp_list) +return BLK_STS_RESOURCE ; + + iod->descriptors[iod->nr_descriptors++] = prp_list; + prp2_dma = prp_dma; + i =0 ; +for (;;) { +if (i ==NVME_CTRL_PAGE_SIZE >>3 ) { +__le64 *old_prp_list = prp_list; + + prp_list =dma_pool_alloc (nvmeq->descriptor_pools.large, +GFP_ATOMIC , &prp_dma); +if (!prp_list) + goto free_prps; + iod->descriptors[iod->nr_descriptors++] = prp_list; + prp_list[0 ] = old_prp_list[i -1 ]; + old_prp_list[i -1 ] =cpu_to_le64 (prp_dma); + i =1 ; + } + + dma_addr = dma_list[map_idx++]; + prp_list[i++] =cpu_to_le64 (dma_addr); + + length -=NVME_CTRL_PAGE_SIZE ; +if (length <=0 ) +break ; + } +done: + iod->cmd.common.dptr.prp1 =cpu_to_le64 (prp1_dma); + iod->cmd.common.dptr.prp2 =cpu_to_le64 (prp2_dma); +return BLK_STS_OK ; +free_prps: +nvme_free_descriptors (req); +return BLK_STS_RESOURCE ; +} +static void nvme_free_prps (struct request *req,unsigned int attrs) {struct nvme_iod *iod =blk_mq_rq_to_pdu (req);@@ -937,6 +1078,11 @@ static void nvme_unmap_data(struct request *req)struct device *dma_dev = nvmeq->dev->dev;unsigned int attrs =0 ; +if (blk_mq_rq_is_dmabuf (req)) { +nvme_rq_clean_dmabuf_map (nvmeq->dev, req); +return ; + } +if (iod->flags &IOD_SINGLE_SEGMENT ) {static_assert (offsetof(union nvme_data_ptr , prp1) == offsetof(union nvme_data_ptr, sgl.addr));@@ -1251,6 +1397,9 @@ static blk_status_t nvme_map_data(struct request *req)struct blk_dma_iter iter;blk_status_t ret; +if (blk_mq_rq_is_dmabuf (req)) +return nvme_rq_setup_dmabuf_map (req, nvmeq); +/* * Try to skip the DMA iterator for single segment requests, as that * significantly improves performances for small I/O sizes. @@ -2269,6 +2418,112 @@ static int nvme_create_queue(struct nvme_queue *nvmeq, int qid, bool polled)return result; } +#if defined(CONFIG_DMA_SHARED_BUFFER ) +static void nvme_dmabuf_invalidate_mappings (struct dma_buf_attachment *attach) +{ +struct dma_buf_io_ctx *ctx = attach->importer_priv; + +dma_buf_io_invalidate_mappings (ctx); +} + +const struct dma_buf_attach_ops nvme_dmabuf_importer_ops = { + .invalidate_mappings = nvme_dmabuf_invalidate_mappings, + .allow_peer2peer = true, +}; + +static struct dma_buf_io_map *nvme_dma_buf_io_map (struct dma_buf_io_ctx *ctx) +{ +unsigned nr_entries = ctx->dmabuf->size /NVME_CTRL_PAGE_SIZE ; +struct dma_buf_attachment *attach = ctx->dev_priv; +unsigned long tmp, i =0 ; +struct nvme_dmabuf_map *map; +struct scatterlist *sg; +struct sg_table *sgt; +int ret; + +dma_resv_assert_held (ctx->dmabuf->resv); + + map =kmalloc_flex (*map, dma_list, nr_entries); +if (!map) +return ERR_PTR (-ENOMEM ); + + sgt =dma_buf_map_attachment (attach, ctx->dir); +if (IS_ERR (sgt)) { + ret =PTR_ERR (sgt); + sgt =NULL ; + goto err; + } + +for_each_sgtable_dma_sg (sgt, sg, tmp) { +dma_addr_t dma_addr =sg_dma_address (sg); +unsigned long sg_len =sg_dma_len (sg); + +if (sg_len %NVME_CTRL_PAGE_SIZE ) { + ret = -EINVAL ; + goto err; + } + +while (sg_len) { + map->dma_list[i++] = dma_addr; + dma_addr +=NVME_CTRL_PAGE_SIZE ; + sg_len -=NVME_CTRL_PAGE_SIZE ; + } + } + + ret =dma_buf_io_init_map (ctx, &map->base); +if (ret) + goto err; + map->nr_entries = nr_entries; + map->sgt = sgt; +return &map->base; +err: +if (sgt) +dma_buf_unmap_attachment (attach, sgt, ctx->dir); +kfree (map); +return ERR_PTR (ret); +} + +static void nvme_dma_buf_io_unmap (struct dma_buf_io_ctx *ctx, +struct dma_buf_io_map *map_base) +{ +struct dma_buf_attachment *attach = ctx->dev_priv; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (map_base); + +dma_resv_assert_held (ctx->dmabuf->resv); + +dma_buf_unmap_attachment (attach, map->sgt, ctx->dir); +} + +static void nvme_dma_buf_io_release (struct dma_buf_io_ctx *ctx) +{ +struct dma_buf_attachment *attach = ctx->dev_priv; + +dma_buf_detach (ctx->dmabuf, attach); +} + +const struct dma_buf_io_ops nvme_dma_buf_io_ops = { + .map = nvme_dma_buf_io_map, + .unmap = nvme_dma_buf_io_unmap, + .release = nvme_dma_buf_io_release, +}; + +static int nvme_pci_init_dma_buf_io_ctx (struct nvme_ctrl *ctrl, +struct dma_buf_io_ctx *ctx) +{ +struct dma_buf_attachment *attach; +struct nvme_dev *dev =to_nvme_dev (ctrl); + + attach =dma_buf_dynamic_attach (ctx->dmabuf, dev->dev, + &nvme_dmabuf_importer_ops, ctx); +if (IS_ERR (attach)) +return PTR_ERR (attach); + + ctx->dev_priv = attach; + ctx->dev_ops = &nvme_dma_buf_io_ops; +return 0 ; +} +#endif +static const struct blk_mq_ops nvme_mq_admin_ops = { .queue_rq = nvme_queue_rq, .complete = nvme_pci_complete_rq,@@ -3563,6 +3818,9 @@ static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = { .print_device_info = nvme_pci_print_device_info, .supports_pci_p2pdma = nvme_pci_supports_pci_p2pdma, .get_virt_boundary = nvme_pci_get_virt_boundary, +#if defined(CONFIG_DMA_SHARED_BUFFER ) + .init_dma_buf_io_ctx = nvme_pci_init_dma_buf_io_ctx, +#endif };static int nvme_dev_map (struct nvme_dev *dev)@@ -4327,5 +4585,6 @@ MODULE_AUTHOR("Matthew Wilcox <[email protected]>");MODULE_LICENSE ("GPL" );MODULE_VERSION ("1.0" );MODULE_DESCRIPTION ("NVMe host PCIe transport driver" ); +MODULE_IMPORT_NS ("DMA_BUF" );module_init (nvme_init);module_exit (nvme_exit); --2.54.0
Re: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
Quoted 184 lines · Caleb Sander Mateos
Quoted 172 lines · Pavel Begunkov
Enable
BIO_DMABUF_MAPbacked requests. It creates a prp list for the dmabuf when it's mapped, which is then used to initialise requests.Suggested-by Keith Busch <[email protected]>Signed-off-by Pavel Begunkov <[email protected]>--- drivers/nvme/host/core.c | 12 ++ drivers/nvme/host/nvme.h | 2 + drivers/nvme/host/pci.c | 259 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+)drivers/nvme/host/core.c +12
-0@@ -2676,6 +2676,17 @@ static int nvme_report_zones(struct gendisk *disk, sector_t sector,#define nvme_report_zones NULL#endif /* CONFIG_BLK_DEV_ZONED */ +static int nvme_init_dma_buf_io_ctx (struct block_device *bdev, +struct dma_buf_io_ctx *ctx) +{ +struct nvme_ns *ns = bdev->bd_disk->private_data; +struct nvme_ctrl *ctrl = ns->ctrl; + +if (!ctrl->ops->init_dma_buf_io_ctx) +return -EINVAL ; +return ctrl->ops->init_dma_buf_io_ctx (ctrl, ctx); +} +const struct block_device_operations nvme_bdev_ops = { .owner =THIS_MODULE , .ioctl = nvme_ioctl,@@ -2686,6 +2697,7 @@ const struct block_device_operations nvme_bdev_ops = { .get_unique_id = nvme_get_unique_id, .report_zones = nvme_report_zones, .pr_ops = &nvme_pr_ops, + .init_dma_buf_io_ctx = nvme_init_dma_buf_io_ctx, }; static int nvme_wait_ready(struct nvme_ctrl *ctrl, u32 mask, u32 val,drivers/nvme/host/nvme.h +2
-0@@ -652,6 +652,8 @@ struct nvme_ctrl_ops {int (*get_address)(struct nvme_ctrl *ctrl,char *buf,int size);void (*print_device_info)(struct nvme_ctrl *ctrl);bool (*supports_pci_p2pdma)(struct nvme_ctrl *ctrl); +int (*init_dma_buf_io_ctx)(struct nvme_ctrl *ctrl, +struct dma_buf_io_ctx *ctx);unsigned long (*get_virt_boundary)(struct nvme_ctrl *ctrl,bool is_admin); };drivers/nvme/host/pci.c +95
-0@@ -27,6 +27,8 @@#include <linux/io-64-nonatomic-lo-hi.h> #include <linux/io-64-nonatomic-hi-lo.h> #include <linux/sed-opal.h> +#include <linux/dma-buf-io.h> +#include <linux/dma-resv.h> #include "trace.h" #include "nvme.h" @@ -393,6 +395,13 @@ struct nvme_queue {struct completion delete_done; }; +struct nvme_dmabuf_map { +struct dma_buf_io_map base; +struct sg_table *sgt; +unsigned nr_entries; +dma_addr_t dma_list[]; +}; +/* bits for iod->flags */ enum nvme_iod_flags {/* this command has been aborted by the timeout handler */ @@ -859,6 +868,138 @@ static void nvme_free_descriptors(struct request *req) } } +static inline struct nvme_dmabuf_map * +to_nvme_dmabuf_map (struct dma_buf_io_map *map) +{ + return container_of(map, struct nvme_dmabuf_map, base); +} + +static void nvme_dmabuf_map_sync_for_cpu (struct nvme_dev *nvme_dev, +struct request *req) +{ +struct device *dev = nvme_dev->dev; +enum dma_data_direction dma_dir; +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +dma_addr_t *dma_list = map->dma_list; +unsigned offset = bio->bi_iter.bi_offset; +unsigned map_idx = offset /NVME_CTRL_PAGE_SIZE ; +int length =blk_rq_payload_bytes (req) + + (offset & (NVME_CTRL_PAGE_SIZE -1 )); + + dma_dir =rq_data_dir (req) ==READ ?DMA_FROM_DEVICE :DMA_TO_DEVICE ; + +while (length >0 ) { +dma_sync_single_for_cpu (dev, dma_list[map_idx++], +NVME_CTRL_PAGE_SIZE , dma_dir); + length -=NVME_CTRL_PAGE_SIZE ; + } +} + +static void nvme_dmabuf_map_sync_for_device (struct nvme_dev *nvme_dev, +struct request *req) +{ +struct device *dev = nvme_dev->dev; +enum dma_data_direction dma_dir; +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +dma_addr_t *dma_list = map->dma_list; +unsigned offset = bio->bi_iter.bi_offset; +unsigned map_idx = offset /NVME_CTRL_PAGE_SIZE ; +int length =blk_rq_payload_bytes (req) + + (offset & (NVME_CTRL_PAGE_SIZE -1 )); + + dma_dir =rq_data_dir (req) ==READ ?DMA_FROM_DEVICE :DMA_TO_DEVICE ; + +while (length >0 ) { +dma_sync_single_for_device (dev, dma_list[map_idx++], +NVME_CTRL_PAGE_SIZE , dma_dir); + length -=NVME_CTRL_PAGE_SIZE ; + } +} + +static void nvme_rq_clean_dmabuf_map (struct nvme_dev *dev, +struct request *req) +{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); + +nvme_dmabuf_map_sync_for_cpu (dev, req); + +if (!(iod->flags &IOD_SINGLE_SEGMENT )) +nvme_free_descriptors (req); +} + +static blk_status_t nvme_rq_setup_dmabuf_map (struct request *req, +struct nvme_queue *nvmeq) +{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +unsigned bvec_done = bio->bi_iter.bi_offset; +unsigned map_idx = bvec_done /NVME_CTRL_PAGE_SIZE ; +unsigned offset = bvec_done & (NVME_CTRL_PAGE_SIZE -1 ); +int length =blk_rq_payload_bytes (req) - (NVME_CTRL_PAGE_SIZE - offset); +dma_addr_t *dma_list = map->dma_list; +u64 prp1_dma = dma_list[map_idx++] + offset; +u64 dma_addr, prp2_dma; +dma_addr_t prp_dma; +__le64 *prp_list; +unsigned i; + +nvme_dmabuf_map_sync_for_device (nvmeq->dev, req); + +if (length <=0 ) { + prp2_dma =0 ; + goto done; + } + +if (length <=NVME_CTRL_PAGE_SIZE ) {drivers/nvme/host/pci.c static void nvme_free_descriptors(struct request *req) · line 966
Quoted 10 lines
drivers/nvme/host/pci.c +10
-0+ prp2_dma = dma_list[map_idx]; + goto done; + } + +if (DIV_ROUND_UP (length,NVME_CTRL_PAGE_SIZE ) <= +NVME_SMALL_POOL_SIZE /sizeof (__le64)) + iod->flags |=IOD_SMALL_DESCRIPTOR ; + + prp_list =dma_pool_alloc (nvme_dma_pool (nvmeq, iod),GFP_ATOMIC , + &prp_dma);
Quoted 8 lines
I wonder if it's possible to perform the PRP list page allocations and initialization in
nvme_dma_buf_io_map()instead of the I/O path. The NVMedma_poolsare per-NUMA-node linked lists protected by spinlocks, making them significant CPU hotspots. If the dmabuf registration set up a contiguous DMA-coherent list of PRP entries for the pages of the registered buffer, any command using the dmabuf and requiring at most 1 PRP list page (i.e. length <= 2 MB) could just set its PRP list pointer to an offset into the dmabuf's PRP list.
Possible to an extent, predecessor patches tried that, I left it for later to keep the series simpler. I guess chaining won't work in general case.
I mentioned this to Kanchan, Anuj and Nitesh before, they were up to playing with nvme optimisations in general, and since sgl from Anuj is ready, maybe they already have a prototype somewhere for that.
Signature · 1 line
Pavel Begunkov
Re: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
Quoted 10 lines · Pavel Begunkov
Quoted 9 lines
I wonder if it's possible to perform the PRP list page allocations and initialization in
nvme_dma_buf_io_map()instead of the I/O path. The NVMedma_poolsare per-NUMA-node linked lists protected by spinlocks, making them significant CPU hotspots. If the dmabuf registration set up a contiguous DMA-coherent list of PRP entries for the pages of the registered buffer, any command using the dmabuf and requiring at most 1 PRP list page (i.e. length <= 2 MB) could just set its PRP list pointer to an offset into the dmabuf's PRP list.
Quoted 7 lines
Possible to an extent, predecessor patches tried that, I left it for later to keep the series simpler. I guess chaining won't work in general case.
I mentioned this to Kanchan, Anuj and Nitesh before, they were up to playing with nvme optimisations in general, and since sgl from Anuj is ready, maybe they already have a prototype somewhere for that.
Right, I have prototyped the PRP list optimisation before. I will send it once the initial version of this series lands, to avoid adding new review churn while the rest of the series is pretty settled.
Quoted 2 lines
Signature · 1 line
Pavel Begunkov
Re: [PATCH v4 08/14] nvme-pci: implement dma_token backed requests
Quoted 20 lines · Anuj Gupta/Anuj Gupta
Quoted 10 lines · Pavel Begunkov
Quoted 9 lines
I wonder if it's possible to perform the PRP list page allocations and initialization in
nvme_dma_buf_io_map()instead of the I/O path. The NVMedma_poolsare per-NUMA-node linked lists protected by spinlocks, making them significant CPU hotspots. If the dmabuf registration set up a contiguous DMA-coherent list of PRP entries for the pages of the registered buffer, any command using the dmabuf and requiring at most 1 PRP list page (i.e. length <= 2 MB) could just set its PRP list pointer to an offset into the dmabuf's PRP list.Quoted 7 lines
Possible to an extent, predecessor patches tried that, I left it for later to keep the series simpler. I guess chaining won't work in general case.
I mentioned this to Kanchan, Anuj and Nitesh before, they were up to playing with nvme optimisations in general, and since sgl from Anuj is ready, maybe they already have a prototype somewhere for that.
Quoted 3 lines
Right, I have prototyped the PRP list optimisation before. I will send it once the initial version of this series lands, to avoid adding new review churn while the rest of the series is pretty settled.
Sure, landing it subsequently makes sense. Glad to hear it's already in the works.
Thanks,
Caleb
PATCH v4 9/14 nvme-pci: add SGL support for the dmabuf path
In reply to Pavel BegunkovFrom: Anuj Gupta <[email protected]>
Add SGL support in addition to PRP for dmabuf-backed requests, coalescing the mapping's sg_table into NVMe SGL data descriptors.
--- drivers/nvme/host/pci.c | 191 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 4 deletions(-)
drivers/nvme/host/pci.c +187 -4
@@ -1287,12 +1287,18 @@ static blk_status_t nvme_pci_setup_data_prp(struct request *req,return BLK_STS_IOERR ; } +static void nvme_pci_sgl_set_data_addr (struct nvme_sgl_desc *sge, +dma_addr_t addr,u32 len) +{ + sge->addr =cpu_to_le64 (addr); + sge->length =cpu_to_le32 (len); + sge->type =NVME_SGL_FMT_DATA_DESC <<4 ; +} +static void nvme_pci_sgl_set_data (struct nvme_sgl_desc *sge,struct blk_dma_iter *iter) { - sge->addr =cpu_to_le64 (iter->addr); - sge->length =cpu_to_le32 (iter->len); - sge->type =NVME_SGL_FMT_DATA_DESC <<4 ; +nvme_pci_sgl_set_data_addr (sge, iter->addr, iter->len); } static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge,
@@ -1303,6 +1309,158 @@ static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, sge->type =NVME_SGL_FMT_LAST_SEG_DESC <<4 ; } +static unsigned int nvme_pci_dmabuf_sgl_nents (struct request *req, +dma_addr_t *first_dma, +u32 *first_len) +{ +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +struct scatterlist *sg; +unsigned long tmp; +size_t offset = bio->bi_iter.bi_offset; +size_t remaining =blk_rq_payload_bytes (req); +dma_addr_t last_end =0 ; +unsigned int nents =0 ; +dma_addr_t dma =0 ; +u32 len =0 ; +bool have = false; + +for_each_sgtable_dma_sg (map->sgt, sg, tmp) { +size_t sg_len =sg_dma_len (sg); +dma_addr_t addr =sg_dma_address (sg); + +if (!remaining) +break ; +if (offset >= sg_len) { + offset -= sg_len; +continue ; + } + + addr += offset; + sg_len -= offset; + offset =0 ; + +while (sg_len && remaining) { +u32 chunk =min_t (size_t, remaining, sg_len); + +if (!have || last_end != addr) { + nents++; +if (nents ==1 ) { + dma = addr; + len = chunk; + } + }else if (nents ==1 ) { + len += chunk; + } + + have = true; + last_end = addr + chunk; + addr += chunk; + sg_len -= chunk; + remaining -= chunk; + } + } + +if (unlikely (remaining)) +return 0 ; + + *first_dma = dma; + *first_len = len; +return nents; +} + +static blk_status_t nvme_rq_setup_dmabuf_sgl (struct request *req, +struct nvme_queue *nvmeq, +unsigned int entries, +dma_addr_t first_dma,u32 first_len) +{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +size_t length =blk_rq_payload_bytes (req); +struct nvme_sgl_desc *sg_list =NULL ; +dma_addr_t sgl_dma =0 , last_end =0 ; +unsigned int mapped =0 ; +unsigned long tmp; +struct scatterlist *sg; +size_t offset, remaining; +bool have = false; + +if (!entries) +return BLK_STS_IOERR ; +if (entries >NVME_MAX_SEGS ) +return BLK_STS_AGAIN ; + + iod->cmd.common.flags =NVME_CMD_SGL_METABUF ; + iod->total_len = length; + +nvme_dmabuf_map_sync_for_device (nvmeq->dev, req); + +if (entries ==1 ) { +nvme_pci_sgl_set_data_addr (&iod->cmd.common.dptr.sgl, first_dma, + first_len); +return BLK_STS_OK ; + } + +if (entries <=NVME_SMALL_POOL_SIZE /sizeof (*sg_list)) + iod->flags |=IOD_SMALL_DESCRIPTOR ; + + sg_list =dma_pool_alloc (nvme_dma_pool (nvmeq, iod),GFP_ATOMIC , + &sgl_dma); +if (!sg_list) +return BLK_STS_RESOURCE ; + iod->descriptors[iod->nr_descriptors++] = sg_list; + + offset = bio->bi_iter.bi_offset; + remaining = length; + +for_each_sgtable_dma_sg (map->sgt, sg, tmp) { +size_t sg_len =sg_dma_len (sg); +dma_addr_t addr =sg_dma_address (sg); + +if (!remaining) +break ; +if (offset >= sg_len) { + offset -= sg_len; +continue ; + } + + addr += offset; + sg_len -= offset; + offset =0 ; + +while (sg_len && remaining) { +u32 chunk =min_t (size_t, remaining, sg_len); + +if (have && last_end == addr) { +u32 old =le32_to_cpu (sg_list[mapped -1 ].length); + + sg_list[mapped -1 ].length =cpu_to_le32 (old + chunk); + }else { +if (WARN_ON_ONCE (mapped == entries)) + goto err_free; +nvme_pci_sgl_set_data_addr (&sg_list[mapped++], + addr, chunk); + } + have = true; + last_end = addr + chunk; + addr += chunk; + sg_len -= chunk; + remaining -= chunk; + } + } + +if (unlikely (remaining)) + goto err_free; + +nvme_pci_sgl_set_seg (&iod->cmd.common.dptr.sgl, sgl_dma, mapped); +return BLK_STS_OK ; +err_free: + iod->nr_descriptors--; +dma_pool_free (nvme_dma_pool (nvmeq, iod), sg_list, sgl_dma); +return BLK_STS_IOERR ; +} +static blk_status_t nvme_pci_setup_data_sgl (struct request *req,struct blk_dma_iter *iter) {
@@ -1397,8 +1555,33 @@ static blk_status_t nvme_map_data(struct request *req)struct blk_dma_iter iter;blk_status_t ret; -if (blk_mq_rq_is_dmabuf (req)) +if (blk_mq_rq_is_dmabuf (req)) { +if (use_sgl !=SGL_UNSUPPORTED ) { +dma_addr_t first_dma; +u32 first_len; +unsigned int entries; + + entries =nvme_pci_dmabuf_sgl_nents (req, &first_dma, + &first_len); + +if (use_sgl ==SGL_FORCED ) { + ret =nvme_rq_setup_dmabuf_sgl (req, nvmeq, + entries, first_dma, first_len); +return ret ==BLK_STS_AGAIN ?BLK_STS_IOERR : ret; + } + +if (sgl_threshold && entries && +DIV_ROUND_UP (blk_rq_payload_bytes (req), entries) >= + sgl_threshold) { + ret =nvme_rq_setup_dmabuf_sgl (req, nvmeq, + entries, first_dma, first_len); +if (ret !=BLK_STS_AGAIN ) +return ret; + } + } +return nvme_rq_setup_dmabuf_map (req, nvmeq); + }/* * Try to skip the DMA iterator for single segment requests, as that
Signature · 1 line
2.54.0
Christoph Hellwig
maintainerRe: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 26 lines · Pavel Begunkov
From: Anuj Gupta <[email protected]>
Add SGL support in addition to PRP for dmabuf-backed requests, coalescing the mapping's
sg_tableinto NVMe SGL data descriptors.Signed-off-by Anuj Gupta <[email protected]>[pavel: rebased]Signed-off-by Pavel Begunkov <[email protected]>--- drivers/nvme/host/pci.c | 191 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 187 insertions(+), 4 deletions(-)drivers/nvme/host/pci.c +6
-0@@ -1287,12 +1287,18 @@ static blk_status_t nvme_pci_setup_data_prp(struct request *req,return BLK_STS_IOERR ; } +static void nvme_pci_sgl_set_data_addr (struct nvme_sgl_desc *sge, +dma_addr_t addr,u32 len) +{ + sge->addr =cpu_to_le64 (addr); + sge->length =cpu_to_le32 (len); + sge->type =NVME_SGL_FMT_DATA_DESC <<4 ;
drivers/nvme/host/pci.c static blk_status_t nvme_pci_setup_data_prp(struct request *req, · line 1302
Quoted 10 lines
drivers/nvme/host/pci.c +3
-3+} +static void nvme_pci_sgl_set_data (struct nvme_sgl_desc *sge,struct blk_dma_iter *iter) { - sge->addr =cpu_to_le64 (iter->addr); - sge->length =cpu_to_le32 (iter->len); - sge->type =NVME_SGL_FMT_DATA_DESC <<4 ; +nvme_pci_sgl_set_data_addr (sge, iter->addr, iter->len); }
The naming is a bit confusing (and me passing the iter to nvme_pci_sgl_set_data is probably at faul for that). So maybe spin out a prep patch to rename the old nvme_pci_sgl_set_data to nvme_pci_dma_iter_set_sgl or so, and then add the new one as nvme_pci_sgl_set_data (as before the dma_iter conversion).
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1314
Quoted 4 lines
drivers/nvme/host/pci.c +3
-0+static unsigned int nvme_pci_dmabuf_sgl_nents (struct request *req, +dma_addr_t *first_dma, +u32 *first_len)
This is a really good example why the aligning to the opening braces produces totally unreadble code..
But I also don't understand what the use case for this function is to start with. struct sg_table tells us how many segments exist on the DMA side in the nents member, which should be just fine for the SGL threshold calculation.
Quoted 7 lines
drivers/nvme/host/pci.c +7
-0+{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +size_t length =blk_rq_payload_bytes (req); +struct nvme_sgl_desc *sg_list =NULL ; +dma_addr_t sgl_dma =0 , last_end =0 ;
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1392
Quoted 10 lines
drivers/nvme/host/pci.c +10
-0+unsigned int mapped =0 ; +unsigned long tmp; +struct scatterlist *sg; +size_t offset, remaining; +bool have = false; + +if (!entries) +return BLK_STS_IOERR ; +if (entries >NVME_MAX_SEGS ) +return BLK_STS_AGAIN ;
Given that the block layer enforced data in rw/command and the max_segments limit, why do we need the extra check here?
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1343
Quoted 8 lines
drivers/nvme/host/pci.c +8
-0+continue ; + } + + addr += offset; + sg_len -= offset; + offset =0 ; + +while (sg_len && remaining) {
These can't be false on the first iteration, so maybe turn this into a do {} while loop?
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1438
Quoted 6 lines
drivers/nvme/host/pci.c +6
-0+u32 chunk =min_t (size_t, remaining, sg_len); + +if (have && last_end == addr) { +u32 old =le32_to_cpu (sg_list[mapped -1 ].length); + + sg_list[mapped -1 ].length =cpu_to_le32 (old + chunk);
Overly long line.
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1444
Quoted 6 lines
drivers/nvme/host/pci.c +6
-0+ }else { +if (WARN_ON_ONCE (mapped == entries)) + goto err_free; +nvme_pci_sgl_set_data_addr (&sg_list[mapped++], + addr, chunk); + }
Why do we need this merging? dma_map_sg should have already done any interesting merging, or am I missing something?
Quoted 13 lines
drivers/nvme/host/pci.c +13
-0+if (use_sgl !=SGL_UNSUPPORTED ) { +dma_addr_t first_dma; +u32 first_len; +unsigned int entries; + + entries =nvme_pci_dmabuf_sgl_nents (req, &first_dma, + &first_len); + +if (use_sgl ==SGL_FORCED ) { + ret =nvme_rq_setup_dmabuf_sgl (req, nvmeq, + entries, first_dma, first_len); +return ret ==BLK_STS_AGAIN ?BLK_STS_IOERR : ret; + }
drivers/nvme/host/pci.c static blk_status_t nvme_map_data(struct request *req) · line 1581
Quoted 10 lines
drivers/nvme/host/pci.c +10
-0+ +if (sgl_threshold && entries && +DIV_ROUND_UP (blk_rq_payload_bytes (req), entries) >= + sgl_threshold) { + ret =nvme_rq_setup_dmabuf_sgl (req, nvmeq, + entries, first_dma, first_len); +if (ret !=BLK_STS_AGAIN ) +return ret; + } + }
Various overly long lines. Please factor out a helper for the decisions to use sgl vs not instead of open coding it here.
Re: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 14 lines
Quoted 14 lines
drivers/nvme/host/pci.c +8
-3+static void nvme_pci_sgl_set_data_addr (struct nvme_sgl_desc *sge, +dma_addr_t addr,u32 len) +{ + sge->addr =cpu_to_le64 (addr); + sge->length =cpu_to_le32 (len); + sge->type =NVME_SGL_FMT_DATA_DESC <<4 ; +} +static void nvme_pci_sgl_set_data (struct nvme_sgl_desc *sge,struct blk_dma_iter *iter) { - sge->addr =cpu_to_le64 (iter->addr); - sge->length =cpu_to_le32 (iter->len); - sge->type =NVME_SGL_FMT_DATA_DESC <<4 ;
Quoted 9 lines
drivers/nvme/host/pci.c static blk_status_t nvme_pci_setup_data_prp(struct request *req, · line 1302
Quoted 2 lines
drivers/nvme/host/pci.c +1
-0+nvme_pci_sgl_set_data_addr (sge, iter->addr, iter->len); }The naming is a bit confusing (and me passing the iter to
nvme_pci_sgl_set_datais probably at faul for that). So maybe spin out a prep patch to rename the oldnvme_pci_sgl_set_datatonvme_pci_dma_iter_set_sglor so, and then add the new one asnvme_pci_sgl_set_data(as before thedma_iterconversion).
Thanks for the detailed review! Will split the rename into a prep patch.
Quoted 8 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1314
Quoted 4 lines
drivers/nvme/host/pci.c +3
-0+static unsigned int nvme_pci_dmabuf_sgl_nents (struct request *req, +dma_addr_t *first_dma, +u32 *first_len)This is a really good example why the aligning to the opening braces produces totally unreadble code..
Quoted 4 lines
But I also don't understand what the use case for this function is to start with. struct
sg_tabletells us how many segments exist on the DMA side in the nents member, which should be just fine for the SGL threshold calculation.
sg_table->nents covers the entire exported buffer (<=1GiB), while a request only covers a subrange[bi_offset, bi_offset+payload). Using nents would overcount the request's segments.
Quoted 13 lines
Quoted 13 lines
drivers/nvme/host/pci.c +12
-0+{ +struct nvme_iod *iod =blk_mq_rq_to_pdu (req); +struct bio *bio = req->bio; +struct nvme_dmabuf_map *map =to_nvme_dmabuf_map (bio->bi_dmabuf_map); +size_t length =blk_rq_payload_bytes (req); +struct nvme_sgl_desc *sg_list =NULL ; +dma_addr_t sgl_dma =0 , last_end =0 ; +unsigned int mapped =0 ; +unsigned long tmp; +struct scatterlist *sg; +size_t offset, remaining; +bool have = false;
Quoted 8 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1392
Quoted 5 lines
drivers/nvme/host/pci.c +5
-0+ +if (!entries) +return BLK_STS_IOERR ; +if (entries >NVME_MAX_SEGS ) +return BLK_STS_AGAIN ;Given that the block layer enforced data in rw/command and the
max_segmentslimit, why do we need the extra check here?
A dmabuf bio reports nsegs=1 (bio_split_io_at) to the block layer, so max_segments isn't enforced against the SG entries actually spanned by the request. Hence the explicit check.
Quoted 12 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1343
Quoted 9 lines
drivers/nvme/host/pci.c +8
-0+continue ; + } + + addr += offset; + sg_len -= offset; + offset =0 ; + +while (sg_len && remaining) {These can't be false on the first iteration, so maybe turn this into a do {} while loop?
Will do.
Quoted 10 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1438
Quoted 7 lines
drivers/nvme/host/pci.c +6
-0+u32 chunk =min_t (size_t, remaining, sg_len); + +if (have && last_end == addr) { +u32 old =le32_to_cpu (sg_list[mapped -1 ].length); + + sg_list[mapped -1 ].length =cpu_to_le32 (old + chunk);Overly long line.
Quoted 9 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1444
Quoted 6 lines
drivers/nvme/host/pci.c +6
-0+ }else { +if (WARN_ON_ONCE (mapped == entries)) + goto err_free; +nvme_pci_sgl_set_data_addr (&sg_list[mapped++], + addr, chunk); + }Why do we need this merging?
dma_map_sgshould have already done any interesting merging, or am I missing something?
Will drop it.
Christoph Hellwig
maintainerRe: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 8 lines · Anuj Gupta/Anuj Gupta
Quoted 4 lines
But I also don't understand what the use case for this function is to start with. struct
sg_tabletells us how many segments exist on the DMA side in the nents member, which should be just fine for the SGL threshold calculation.
sg_table->nentscovers the entire exported buffer (<=1GiB), while a request only covers a subrange[bi_offset, bi_offset+payload). Using nents would overcount the request's segments.
Urgg, yes.
Quoted 11 lines
Quoted 7 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1392
Quoted 4 lines
drivers/nvme/host/pci.c +4
-0+if (!entries) +return BLK_STS_IOERR ; +if (entries >NVME_MAX_SEGS ) +return BLK_STS_AGAIN ;Given that the block layer enforced data in rw/command and the
max_segmentslimit, why do we need the extra check here?A dmabuf bio reports nsegs=1 (
bio_split_io_at) to the block layer, somax_segmentsisn't enforced against the SG entries actually spanned by the request. Hence the explicit check.
We'll need to expose the actual nsegs to the block layer and split based on that. Otherwise I/O might work or fail based on the device capabilities.
Re: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 20 lines · Christoph Hellwig
Quoted 8 lines · Anuj Gupta/Anuj Gupta
Quoted 4 lines
But I also don't understand what the use case for this function is to start with. struct
sg_tabletells us how many segments exist on the DMA side in the nents member, which should be just fine for the SGL threshold calculation.
sg_table->nentscovers the entire exported buffer (<=1GiB), while a request only covers a subrange[bi_offset, bi_offset+payload). Using nents would overcount the request's segments.Urgg, yes.
Quoted 8 lines
Quoted 7 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1392
Quoted 4 lines
drivers/nvme/host/pci.c +4
-0+if (!entries) +return BLK_STS_IOERR ; +if (entries >NVME_MAX_SEGS ) +return BLK_STS_AGAIN ;Given that the block layer enforced data in rw/command and the
max_segmentslimit, why do we need the extra check here?
Quoted 7 lines
Quoted 3 lines
A dmabuf bio reports nsegs=1 (
bio_split_io_at) to the block layer, somax_segmentsisn't enforced against the SG entries actually spanned by the request. Hence the explicit check.We'll need to expose the actual nsegs to the block layer and split based on that. Otherwise I/O might work or fail based on the device capabilities.
It was exposed in early version as I was passing a [{dma,len}, ...] array, but we moved from that. Maybe I should put the minimum segment size in the map structure, and (possibly over) split using that for now? Keith had this chunk in his patches:
+ int offset = offset_in_page(bio->bi_iter.bi_bvec_done);
+
+ nsegs = ALIGN(bio->bi_iter.bi_size + offset, PAGE_SIZE) >>
+ PAGE_SHIFT;
+ if (bio->bi_iter.bi_size > max_bytes) {
+ bytes = max_bytes;
+ nsegs = (bytes + offset) >> PAGE_SHIFT;
+ } else if (nsegs > lim->max_segments) {
+ nsegs = lim->max_segments;
+ bytes = PAGE_SIZE * nsegs - offset;
+ } else {
+ *segs = nsegs;
+ return NULL;
+ }
Signature · 1 line
Pavel Begunkov
Christoph Hellwig
maintainerRe: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 9 lines · Pavel Begunkov
It was exposed in early version as I was passing a [{dma,len}, ...] array, but we moved from that. Maybe I should put the minimum segment size in the map structure, and (possibly over) split using that for now? Keith had this chunk in his patches:
(quoted diff) +4
-0+ int offset = offset_in_page(bio->bi_iter.bi_bvec_done); + + nsegs = ALIGN(bio->bi_iter.bi_size + offset, PAGE_SIZE) >> + PAGE_SHIFT;
Review
Quoted 10 lines
(quoted diff) +10
-0+ if (bio->bi_iter.bi_size > max_bytes) { + bytes = max_bytes; + nsegs = (bytes + offset) >> PAGE_SHIFT; + } else if (nsegs > lim->max_segments) { + nsegs = lim->max_segments; + bytes = PAGE_SIZE * nsegs - offset; + } else { + *segs = nsegs; + return NULL; + }
This seems very pessimistic, especially for the case of the registration only having a single segment, which I'd expect to be fairly common due to P2P bar mappings, huge pages or IOMMU coalescing. So at very least we'd want to special case that, but in an idea world the caller would be required to provide a useful nr_segments for the I/O.
Re: [PATCH v4 09/14] nvme-pci: add SGL support for the dmabuf path
Quoted 16 lines · Christoph Hellwig
Quoted 15 lines · Pavel Begunkov
It was exposed in early version as I was passing a [{dma,len}, ...] array, but we moved from that. Maybe I should put the minimum segment size in the map structure, and (possibly over) split using that for now? Keith had this chunk in his patches:
(quoted diff) +10
-0+ int offset = offset_in_page(bio->bi_iter.bi_bvec_done); + + nsegs = ALIGN(bio->bi_iter.bi_size + offset, PAGE_SIZE) >> + PAGE_SHIFT; + if (bio->bi_iter.bi_size > max_bytes) { + bytes = max_bytes; + nsegs = (bytes + offset) >> PAGE_SHIFT; + } else if (nsegs > lim->max_segments) { + nsegs = lim->max_segments; + bytes = PAGE_SIZE * nsegs - offset;
Quoted 8 lines
drivers/nvme/host/pci.c static void nvme_pci_sgl_set_seg(struct nvme_sgl_desc *sge, · line 1444
Quoted 4 lines
drivers/nvme/host/pci.c +4
-0+ }else { + *segs = nsegs; +return NULL ; + }This seems very pessimistic, especially for the case of the registration only having a single segment, which I'd expect to be fairly common due to P2P bar mappings, huge pages or IOMMU coalescing. So at very least
Agree that PAGE_SIZE is too pessimistic, but I'd calculate it from the sg table on registration and use here. Not ideal but should cover most common cases like huge pages.
Quoted 2 lines
we'd want to special case that, but in an idea world the caller would be required to provide a useful
nr_segmentsfor the I/O.
Not sure we can do it in the straightforward way from the abstraction perspective, mapping are hidden from io_uring, and there could be multi-device files with differently shaped IOMMU mappings at some point. We'll surely need readv/writev support later, so maybe we can improve while doing that as it'd mean passing a {off,len} of some sort from io_uring.
Signature · 1 line
Pavel Begunkov
PATCH v4 10/14 io_uring/rsrc: introduce buf registration structure
In reply to Pavel BegunkovIn preparation to following changes, instead of passing an iovec for buffer registration introduce a new structure. It'll be moved to uapi later, but for now it's initialised early from a user provided iovec.
--- io_uring/rsrc.c | 47 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-)
io_uring/rsrc.c +32 -15
@@ -27,8 +27,13 @@ struct io_rsrc_update {u32 offset; }; +struct io_uring_regbuf_desc { +__u64 uaddr; +__u64 size; +}; +static struct io_rsrc_node *io_sqe_buffer_register (struct io_ring_ctx *ctx, -struct iovec *iov); +struct io_uring_regbuf_desc *desc);static int hpage_acct_ref (struct io_ring_ctx *ctx,struct page *hpage,bool *acct_new)
@@ -81,6 +86,15 @@ static bool hpage_acct_unref(struct io_ring_ctx *ctx, struct page *hpage)#define IO_CACHED_BVECS_SEGS 32 +static void io_iov_to_regbuf_desc (const struct iovec *iov, +struct io_uring_regbuf_desc *desc) +{ + *desc = (struct io_uring_regbuf_desc ) { + .uaddr = (u64 )(uintptr_t )iov->iov_base, + .size = iov->iov_len, + }; +} +int __io_account_mem (struct user_struct *user,unsigned long nr_pages) {unsigned long page_limit, cur_pages, new_pages;
@@ -381,6 +395,7 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx,return -EINVAL ;for (done =0 ; done < nr_args; done++) { +struct io_uring_regbuf_desc desc;struct io_rsrc_node *node;u64 tag =0 ;
@@ -394,7 +409,9 @@ static int __io_sqe_buffers_update(struct io_ring_ctx *ctx, err = -EFAULT ;break ; } - node =io_sqe_buffer_register (ctx, iov); + +io_iov_to_regbuf_desc (iov, &desc); + node =io_sqe_buffer_register (ctx, &desc);if (IS_ERR (node)) { err =PTR_ERR (node);break ;
@@ -853,26 +870,26 @@ bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, }static struct io_rsrc_node *io_sqe_buffer_register (struct io_ring_ctx *ctx, -struct iovec *iov) +struct io_uring_regbuf_desc *desc) { +unsigned long uaddr = (unsigned long )desc->uaddr; +size_t size = desc->size;struct io_mapped_ubuf *imu =NULL ;struct page **pages =NULL ;struct io_rsrc_node *node;unsigned long off; -size_t size;int ret, nr_pages, i;struct io_imu_folio_data data;bool coalesced = false; -if (!iov->iov_base) { -if (iov->iov_len) +if (!uaddr) { +if (size)return ERR_PTR (-EFAULT );/* remove the buffer without installing a new one */ return NULL ; } - ret =io_validate_user_buf_range ((unsigned long )iov->iov_base, - iov->iov_len); + ret =io_validate_user_buf_range (uaddr, size);if (ret)return ERR_PTR (ret);
@@ -881,8 +898,7 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,return ERR_PTR (-ENOMEM ); ret = -ENOMEM ; - pages =io_pin_pages ((unsigned long ) iov->iov_base, iov->iov_len, - &nr_pages); + pages =io_pin_pages (uaddr, size, &nr_pages);if (IS_ERR (pages)) { ret =PTR_ERR (pages); pages =NULL ;
@@ -904,10 +920,9 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,if (ret) goto done; - size = iov->iov_len;/* store original address for later verification */ - imu->ubuf = (unsigned long ) iov->iov_base; - imu->len = iov->iov_len; + imu->ubuf = uaddr; + imu->len = size; imu->folio_shift =PAGE_SHIFT ; imu->release = io_release_ubuf; imu->priv = imu;
@@ -917,7 +932,7 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx, imu->folio_shift = data.folio_shift;refcount_set (&imu->refs,1 ); - off = (unsigned long )iov->iov_base & ~PAGE_MASK ; + off = uaddr & ~PAGE_MASK ;if (coalesced) off += data.first_folio_page_idx <<PAGE_SHIFT ;
@@ -969,6 +984,7 @@ int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg,memset (iov,0 ,sizeof (*iov));for (i =0 ; i < nr_args; i++) { +struct io_uring_regbuf_desc desc;struct io_rsrc_node *node;u64 tag =0 ;
@@ -992,7 +1008,8 @@ int io_sqe_buffers_register(struct io_ring_ctx *ctx, void __user *arg, } } - node =io_sqe_buffer_register (ctx, iov); +io_iov_to_regbuf_desc (iov, &desc); + node =io_sqe_buffer_register (ctx, &desc);if (IS_ERR (node)) { ret =PTR_ERR (node);break ;
Signature · 1 line
2.54.0
PATCH v4 11/14 io_uring/rsrc: extend buffer update
In reply to Pavel BegunkovWe need to pass more information to buffer registration than we can fit into a single struct iovec. This patch allows users to optionally pass struct io_uring_regbuf_desc. Apart from having more space for future use cases, it also introduces registration types.
Currently, the type can be either of IO_REGBUF_TYPE_UADDR, which mirrors the iovec path, or IO_REGBUF_TYPE_EMPTY for leaving a buffer table slot empty. The next patch introduces a dmabuf backed type, and can be useful for other extensions like splicing a list of user addresses (i.e. iovec[]), interoperability with zcrx, kernel allocated memory like was brough up by Cristoph. Note, the type only represents a registration option, which is distinct from how io_uring internally stores it.
The flags field is not used yet but always useful to have, e.g. we can encode read-only / write-only restrictions using it.
--- include/uapi/linux/io_uring.h | 27 +++++++++++++- io_uring/rsrc.c | 69 ++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 27 deletions(-)
include/uapi/linux/io_uring.h +26 -1
@@ -790,13 +790,38 @@ struct io_uring_rsrc_update {struct io_uring_rsrc_update2 {__u32 offset; -__u32 resv; +__u32 flags;__aligned_u64 data;__aligned_u64 tags;__u32 nr;__u32 resv2; }; +/* struct io_uring_rsrc_update2::flags */ +enum io_uring_rsrc_reg_flags { +/* +* Use the extended descriptor format for buffer updates, +* see struct io_uring_regbuf_desc +*/ +IORING_RSRC_UPDATE_EXTENDED =1U <<1 , +}; + +/* Buffer registration type, passed in struct io_uring_regbuf_desc::type */ +enum io_uring_regbuf_type { +IO_REGBUF_TYPE_EMPTY , +IO_REGBUF_TYPE_UADDR , + + __IO_REGBUF_TYPE_MAX, +}; + +struct io_uring_regbuf_desc { +__u32 type;/* enum io_uring_regbuf_type */ +__u32 flags; +__u64 size; +__u64 uaddr; +__u64 __resv[7 ]; +}; +/* Skip updating fd indexes set to this value in the fd table */ #define IORING_REGISTER_FILES_SKIP (-2)
io_uring/rsrc.c +43 -26
151 lines not on this page · raw message
Signature · 1 line
2.54.0
Re: [PATCH v4 11/14] io_uring/rsrc: extend buffer update
io_uring/rsrc.c static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx, · line 900
Quoted 10 lines · Pavel Begunkov
io_uring/rsrc.c +4
-2@@ -882,8 +894,13 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,struct io_imu_folio_data data;bool coalesced = false; -if (!uaddr) { -if (size) +if (desc->type >= __IO_REGBUF_TYPE_MAX) +return ERR_PTR (-EINVAL ); +if (!mem_is_zero (&desc->__resv,sizeof (desc->__resv))) +return ERR_PTR (-EINVAL );
desc->flags isn't validated here, unlike __resv. nonzero flags should be rejected too?
Quoted 6 lines
io_uring/rsrc.c +3
-0+ +if (desc->type ==IO_REGBUF_TYPE_EMPTY ) { +if (uaddr || size)return ERR_PTR (-EFAULT );/* remove the buffer without installing a new one */ return NULL ;
Re: [PATCH v4 11/14] io_uring/rsrc: extend buffer update
Quoted 5 lines · Anuj Gupta/Anuj Gupta
Quoted 4 lines · Pavel Begunkov
io_uring/rsrc.c +0
-0@@ -882,8 +894,13 @@ static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx,struct io_imu_folio_data data;bool coalesced = false;
Quoted 9 lines
io_uring/rsrc.c static struct io_rsrc_node *io_sqe_buffer_register(struct io_ring_ctx *ctx, · line 900
Quoted 6 lines
io_uring/rsrc.c +4
-2-if (!uaddr) { -if (size) +if (desc->type >= __IO_REGBUF_TYPE_MAX) +return ERR_PTR (-EINVAL ); +if (!mem_is_zero (&desc->__resv,sizeof (desc->__resv))) +return ERR_PTR (-EINVAL );
desc->flagsisn't validated here, unlike__resv. nonzero flags should be rejected too?
good catch
Signature · 1 line
Pavel Begunkov
PATCH v4 12/14 io_uring/rsrc: add uncloneable regbuf flag
In reply to Pavel BegunkovIt's hard to implement cloning if the internal structure needs to be mutable and/or relies on other ring resources. In preparation to such buffer types, add a flag indicating that the buffer can't be cloned. It might be possible to add cloning in the future for them, but that would likely need reallocating the structure and reacquiring resources in case by case manner.
--- io_uring/rsrc.c | 5 +++++ io_uring/rsrc.h | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-)
io_uring/rsrc.c +5 -0
@@ -1381,6 +1381,11 @@ static int io_clone_buffers(struct io_ring_ctx *ctx, struct io_ring_ctx *src_ctxif (!src_node) { dst_node =NULL ; }else { +if (src_node->buf->flags &IO_REGBUF_F_UNCLONEABLE ) { +io_rsrc_data_free (ctx, &data); +return -ENOMEM ; + } + dst_node =io_rsrc_node_alloc (ctx,IORING_RSRC_BUFFER );if (!dst_node) {io_rsrc_data_free (ctx, &data);
io_uring/rsrc.h +2 -1
@@ -29,7 +29,8 @@ enum { };enum { -IO_REGBUF_F_KBUF =1 , +IO_REGBUF_F_KBUF =1 <<0 , +IO_REGBUF_F_UNCLONEABLE =1 <<1 , };struct io_mapped_ubuf {
Signature · 1 line
2.54.0
PATCH v4 13/14 io_uring/rsrc: add regbuf import flags
In reply to Pavel BegunkovWe'll have special registered buffer types that can't be used with all opcodes and need special handling. Add separate flags to control registered buffer import, which will be used to specify what kind of buffers the caller can handle.
--- io_uring/rsrc.c | 8 ++++---- io_uring/rsrc.h | 24 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-)
io_uring/rsrc.c +4 -4
@@ -1245,9 +1245,9 @@ inline struct io_rsrc_node *io_find_buf_node(struct io_kiocb *req,return NULL ; } -int io_import_reg_buf (struct io_kiocb *req,struct iov_iter *iter, +int __io_import_reg_buf (struct io_kiocb *req,struct iov_iter *iter,u64 buf_addr,size_t len,int ddir, -unsigned issue_flags) +unsigned issue_flags,unsigned import_flags) {struct io_rsrc_node *node;
@@ -1656,9 +1656,9 @@ static int io_kern_bvec_size(struct iovec *iov, unsigned nr_iovs,return 0 ; } -int io_import_reg_vec (int ddir,struct iov_iter *iter, +int __io_import_reg_vec (int ddir,struct iov_iter *iter,struct io_kiocb *req,struct iou_vec *vec, -unsigned nr_iovs,unsigned issue_flags) +unsigned nr_iovs,unsigned issue_flags,unsigned import_flags) {struct io_rsrc_node *node;struct io_mapped_ubuf *imu;
io_uring/rsrc.h +20 -4
@@ -65,12 +65,28 @@ int io_rsrc_data_alloc(struct io_rsrc_data *data, unsigned nr);struct io_rsrc_node *io_find_buf_node (struct io_kiocb *req,unsigned issue_flags); -int io_import_reg_buf (struct io_kiocb *req,struct iov_iter *iter, +int __io_import_reg_buf (struct io_kiocb *req,struct iov_iter *iter,u64 buf_addr,size_t len,int ddir, -unsigned issue_flags); -int io_import_reg_vec (int ddir,struct iov_iter *iter, +unsigned issue_flags,unsigned import_flags); +int __io_import_reg_vec (int ddir,struct iov_iter *iter,struct io_kiocb *req,struct iou_vec *vec, -unsigned nr_iovs,unsigned issue_flags); +unsigned nr_iovs,unsigned issue_flags, +unsigned import_flags); + +static inline int io_import_reg_buf (struct io_kiocb *req,struct iov_iter *iter, +u64 buf_addr,size_t len,int ddir, +unsigned issue_flags) +{ +return __io_import_reg_buf (req, iter, buf_addr, len, ddir, issue_flags,0 ); +} + +static inline int io_import_reg_vec (int ddir,struct iov_iter *iter, +struct io_kiocb *req,struct iou_vec *vec, +unsigned nr_iovs,unsigned issue_flags) +{ +return __io_import_reg_vec (ddir, iter, req, vec, nr_iovs, issue_flags,0 ); +} +int io_prep_reg_iovec (struct io_kiocb *req,struct iou_vec *iv,const struct iovec __user *uvec,size_t uvec_segs);
Signature · 1 line
2.54.0
PATCH v4 14/14 io_uring/rsrc: add dmabuf backed registered buffers
In reply to Pavel BegunkovImplement dmabuf backed registered buffers. To register them, the user should specify IO_REGBUF_TYPE_DMABUF for the regitration and pass the desired dmabuf fd and a file for which it should be registered.
From there, it can be used with io_uring read/write requests IORING_OP_{READ,WRITE}_FIXED) as normal. The requests should be issued against the file specified during registration, and otherwise they'll be failed. The user should also be prepared to handle spurious -EAGAIN by reissuing the request.
Internally, dmabuf registered buffers is an optin feature for io_uring request opcodes and they should pass a special flag on import to use it.
--- include/linux/io_uring_types.h | 5 + include/uapi/linux/io_uring.h | 6 +- io_uring/io_uring.c | 3 +- io_uring/rsrc.c | 168 ++++++++++++++++++++++++++++++++- io_uring/rsrc.h | 18 ++++ io_uring/rw.c | 6 +- 6 files changed, 198 insertions(+), 8 deletions(-)
include/linux/io_uring_types.h +5 -0
@@ -10,6 +10,7 @@struct iou_loop_params ;struct io_uring_bpf_ops ; +struct dma_buf_io_map ;enum {/*
@@ -594,6 +595,7 @@ enum {REQ_F_IMPORT_BUFFER_BIT ,REQ_F_SQE_COPIED_BIT ,REQ_F_IOPOLL_BIT , +REQ_F_DROP_DMABUF_BIT ,/* not a real bit, just to check we're not overflowing the space */ __REQ_F_LAST_BIT,
@@ -689,6 +691,8 @@ enum {REQ_F_SQE_COPIED =IO_REQ_FLAG (REQ_F_SQE_COPIED_BIT ),/* request must be iopolled to completion (set in ->issue()) */ REQ_F_IOPOLL =IO_REQ_FLAG (REQ_F_IOPOLL_BIT ), +/* there is a dma map attached to request that needs to be dropped */ + REQ_F_DROP_DMABUF = IO_REQ_FLAG(REQ_F_DROP_DMABUF_BIT), }; struct io_tw_req {
@@ -811,6 +815,7 @@ struct io_kiocb {/* custom credentials, valid IFF REQ_F_CREDS is set */ const struct cred *creds;struct io_wq_work work; +struct dma_buf_io_map *dmabuf_map;struct io_big_cqe {u64 extra1;
include/uapi/linux/io_uring.h +5 -1
@@ -810,6 +810,7 @@ enum io_uring_rsrc_reg_flags {enum io_uring_regbuf_type {IO_REGBUF_TYPE_EMPTY ,IO_REGBUF_TYPE_UADDR , +IO_REGBUF_TYPE_DMABUF , __IO_REGBUF_TYPE_MAX, };
@@ -819,7 +820,10 @@ struct io_uring_regbuf_desc {__u32 flags;__u64 size;__u64 uaddr; -__u64 __resv[7 ]; + +__s32 dmabuf_fd; +__s32 target_fd; +__u64 __resv[6 ]; };/* Skip updating fd indexes set to this value in the fd table */
io_uring/io_uring.c +2 -1
@@ -109,7 +109,7 @@#define IO_REQ_CLEAN_SLOW_FLAGS (REQ_F_REFCOUNT | IO_REQ_LINK_FLAGS | \ REQ_F_REISSUE | REQ_F_POLLED | \ - IO_REQ_CLEAN_FLAGS) + IO_REQ_CLEAN_FLAGS | REQ_F_DROP_DMABUF)#define IO_TCTX_REFS_CACHE_NR (1U << 10)
@@ -1125,6 +1125,7 @@ static void io_free_batch_list(struct io_ring_ctx *ctx,io_queue_next (req);if (unlikely (req->flags &IO_REQ_CLEAN_FLAGS ))io_clean_op (req); +io_req_drop_dmabuf (req); }io_put_file (req);io_req_put_rsrc_nodes (req);
io_uring/rsrc.c +164 -4
211 lines not on this page · raw message
io_uring/rsrc.h +18 -0
@@ -31,6 +31,11 @@ enum {enum {IO_REGBUF_F_KBUF =1 <<0 ,IO_REGBUF_F_UNCLONEABLE =1 <<1 , +IO_REGBUF_F_DMABUF =1 <<3 , +}; + +enum { +IO_REGBUF_IMPORT_ALLOW_DMABUF =1 <<1 , };struct io_mapped_ubuf {
@@ -173,4 +178,17 @@ static inline void io_alloc_cache_vec_kasan(struct iou_vec *iv) io_vec_free(iv); } +void io_drop_dmabuf_node (struct io_kiocb *req); + +static inline void io_req_drop_dmabuf (struct io_kiocb *req) +{ +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return ; +if (!(req->flags &REQ_F_DROP_DMABUF )) +return ; +if (WARN_ON_ONCE (!(req->flags &REQ_F_BUF_NODE ))) +return ; +io_drop_dmabuf_node (req); +} +#endif
io_uring/rw.c +4 -2
@@ -380,8 +380,8 @@ static int io_init_rw_fixed(struct io_kiocb *req, unsigned int issue_flags,if (io->bytes_done)return 0 ; - ret =io_import_reg_buf (req, &io->iter, rw->addr, rw->len, ddir, - issue_flags); + ret =__io_import_reg_buf (req, &io->iter, rw->addr, rw->len, ddir, + issue_flags,IO_REGBUF_IMPORT_ALLOW_DMABUF );iov_iter_save_state (&io->iter, &io->iter_state);return ret; }
@@ -591,6 +591,8 @@ static void io_complete_rw(struct kiocb *kiocb, long res) struct io_rw *rw = container_of(kiocb, struct io_rw, kiocb);struct io_kiocb *req =cmd_to_io_kiocb (rw); +io_req_drop_dmabuf (req); +__io_complete_rw_common (req, res);io_req_set_res (req,io_fixup_rw_res (req, res),0 ); req->io_task_work.func = io_req_rw_complete;
Signature · 1 line
2.54.0
Re: [PATCH v4 14/14] io_uring/rsrc: add dmabuf backed registered buffers
Quoted 41 lines · Pavel Begunkov
io_uring/rsrc.c +41
-0+static struct io_rsrc_node *io_register_dmabuf (struct io_ring_ctx *ctx, +struct io_uring_regbuf_desc *desc) +{ +struct io_rsrc_node *node =NULL ; +struct io_mapped_ubuf *imu =NULL ; +struct io_regbuf_dma *regbuf =NULL ; +struct file *target_file =NULL ; +struct dma_buf *dmabuf =NULL ; +int ret; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return ERR_PTR (-EOPNOTSUPP ); +if (ctx->flags &IORING_SETUP_IOPOLL ) +return ERR_PTR (-EOPNOTSUPP ); +if (desc->uaddr || desc->size) +return ERR_PTR (-EINVAL ); + + ret = -ENOMEM ; + node =io_rsrc_node_alloc (ctx,IORING_RSRC_BUFFER ); +if (!node) +return ERR_PTR (-ENOMEM ); + imu =io_alloc_imu (ctx,0 ); +if (!imu) + goto err; + regbuf =kzalloc (sizeof (*regbuf),GFP_KERNEL ); +if (!regbuf) + goto err; + + ret = -EBADF ; + target_file =fget (desc->target_fd); +if (!target_file) + goto err; + + dmabuf =dma_buf_get (desc->dmabuf_fd); +if (IS_ERR (dmabuf)) { + ret =PTR_ERR (dmabuf); + dmabuf =NULL ; + goto err; + } +if (dmabuf->size >SZ_1G ) { + ret = -EINVAL ;
io_uring/rsrc.c bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, · line 948
Quoted 10 lines
io_uring/rsrc.c +10
-0+ goto err; + } + + ret =dma_buf_io_ctx_create (target_file, ®buf->ctx, dmabuf, +DMA_BIDIRECTIONAL ); +if (ret) + goto err; + + regbuf->target_file = target_file; + imu->nr_bvecs =1 ;
There is no bvec backing a dmabuf imu - should this be 0?
Quoted 15 lines
io_uring/rsrc.c +15
-0+ imu->ubuf =0 ; + imu->len = dmabuf->size; + imu->folio_shift =0 ; + imu->release = io_release_reg_dmabuf; + imu->priv = regbuf; + imu->flags =IO_REGBUF_F_DMABUF ; + imu->dir =IO_IMU_DEST |IO_IMU_SOURCE ; +refcount_set (&imu->refs,1 ); + node->buf = imu; +dma_buf_put (dmabuf); +return node; +err: +kfree (regbuf); +if (imu) +io_free_imu (ctx, imu);
io_uring/rsrc.c bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, · line 973
Quoted 10 lines
io_uring/rsrc.c +10
-0+if (node) +io_cache_free (&ctx->node_cache, node); +if (target_file) +fput (target_file); +if (dmabuf) +dma_buf_put (dmabuf); +return ERR_PTR (ret); +} + +
nit: extra newline here
Quoted 10 lines
io_uring/rsrc.c +10
-0+static int io_import_dmabuf (struct io_kiocb *req, +int ddir,struct iov_iter *iter, +struct io_mapped_ubuf *imu, +size_t len,size_t offset, +unsigned issue_flags) +{ +struct io_regbuf_dma *db = imu->priv; +struct dma_buf_io_map *map; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER ))
io_uring/rsrc.c static int io_import_kbuf(int ddir, struct iov_iter *iter, · line 1303
Quoted 10 lines
io_uring/rsrc.c +10
-0+return -EOPNOTSUPP ; +if (!len) +return -EFAULT ; +if (req->file != db->target_file) +return -EBADF ; + +if (req->flags &REQ_F_DROP_DMABUF ) { + map = req->dmabuf_map; + goto init_iter; + }
O_DIRECT is checked only during registration; we should recheck (file->f_flags & O_DIRECT) per request.
Re: [PATCH v4 14/14] io_uring/rsrc: add dmabuf backed registered buffers
Quoted 46 lines · Anuj Gupta/Anuj Gupta
Quoted 45 lines · Pavel Begunkov
io_uring/rsrc.c +45
-0+static struct io_rsrc_node *io_register_dmabuf (struct io_ring_ctx *ctx, +struct io_uring_regbuf_desc *desc) +{ +struct io_rsrc_node *node =NULL ; +struct io_mapped_ubuf *imu =NULL ; +struct io_regbuf_dma *regbuf =NULL ; +struct file *target_file =NULL ; +struct dma_buf *dmabuf =NULL ; +int ret; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return ERR_PTR (-EOPNOTSUPP ); +if (ctx->flags &IORING_SETUP_IOPOLL ) +return ERR_PTR (-EOPNOTSUPP ); +if (desc->uaddr || desc->size) +return ERR_PTR (-EINVAL ); + + ret = -ENOMEM ; + node =io_rsrc_node_alloc (ctx,IORING_RSRC_BUFFER ); +if (!node) +return ERR_PTR (-ENOMEM ); + imu =io_alloc_imu (ctx,0 ); +if (!imu) + goto err; + regbuf =kzalloc (sizeof (*regbuf),GFP_KERNEL ); +if (!regbuf) + goto err; + + ret = -EBADF ; + target_file =fget (desc->target_fd); +if (!target_file) + goto err; + + dmabuf =dma_buf_get (desc->dmabuf_fd); +if (IS_ERR (dmabuf)) { + ret =PTR_ERR (dmabuf); + dmabuf =NULL ; + goto err; + } +if (dmabuf->size >SZ_1G ) { + ret = -EINVAL ; + goto err; + } + + ret = dma_buf_io_ctx_create(target_file, ®buf->ctx, dmabuf,
Quoted 8 lines
io_uring/rsrc.c bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, · line 948
Quoted 6 lines
io_uring/rsrc.c +6
-0+ DMA_BIDIRECTIONAL); +if (ret) + goto err; + + regbuf->target_file = target_file; + imu->nr_bvecs =1 ;There is no bvec backing a dmabuf imu - should this be 0?
Doesn't really matter as it's not used. It's 1 to say that it's just 1 contig segment, but I can just zero it to avoid confusion.
Quoted 44 lines
Quoted 16 lines
io_uring/rsrc.c +15
-0+ imu->ubuf =0 ; + imu->len = dmabuf->size; + imu->folio_shift =0 ; + imu->release = io_release_reg_dmabuf; + imu->priv = regbuf; + imu->flags =IO_REGBUF_F_DMABUF ; + imu->dir =IO_IMU_DEST |IO_IMU_SOURCE ; +refcount_set (&imu->refs,1 ); + node->buf = imu; +dma_buf_put (dmabuf); +return node; +err: +kfree (regbuf); +if (imu) +io_free_imu (ctx, imu);io_uring/rsrc.c bool io_check_coalesce_buffer(struct page **page_array, int nr_pages, · line 973
Quoted 10 lines
io_uring/rsrc.c +10
-0+if (node) +io_cache_free (&ctx->node_cache, node); +if (target_file) +fput (target_file); +if (dmabuf) +dma_buf_put (dmabuf); +return ERR_PTR (ret); +} + +nit: extra newline here
Quoted 15 lines
io_uring/rsrc.c +15
-0+static int io_import_dmabuf (struct io_kiocb *req, +int ddir,struct iov_iter *iter, +struct io_mapped_ubuf *imu, +size_t len,size_t offset, +unsigned issue_flags) +{ +struct io_regbuf_dma *db = imu->priv; +struct dma_buf_io_map *map; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return -EOPNOTSUPP ; +if (!len) +return -EFAULT ; +if (req->file != db->target_file) +return -EBADF ;
Quoted 8 lines
io_uring/rsrc.c static int io_import_kbuf(int ddir, struct iov_iter *iter, · line 1303
Quoted 5 lines
io_uring/rsrc.c +5
-0+ +if (req->flags &REQ_F_DROP_DMABUF ) { + map = req->dmabuf_map; + goto init_iter; + }
O_DIRECTis checked only during registration; we should recheck (file->f_flags&O_DIRECT) per request.
Nobody should be able to clear O_DIRECT, unless I missed something? And fwiw, io_uring wouldn't be the right place to do it.
Signature · 1 line
Pavel Begunkov
Re: [PATCH v4 14/14] io_uring/rsrc: add dmabuf backed registered buffers
Quoted 24 lines · Pavel Begunkov
Quoted 15 lines
Quoted 15 lines
io_uring/rsrc.c +15
-0+static int io_import_dmabuf (struct io_kiocb *req, +int ddir,struct iov_iter *iter, +struct io_mapped_ubuf *imu, +size_t len,size_t offset, +unsigned issue_flags) +{ +struct io_regbuf_dma *db = imu->priv; +struct dma_buf_io_map *map; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER )) +return -EOPNOTSUPP ; +if (!len) +return -EFAULT ; +if (req->file != db->target_file) +return -EBADF ;Quoted 8 lines
io_uring/rsrc.c static int io_import_kbuf(int ddir, struct iov_iter *iter, · line 1303
Quoted 5 lines
io_uring/rsrc.c +5
-0+ +if (req->flags &REQ_F_DROP_DMABUF ) { + map = req->dmabuf_map; + goto init_iter; + }
O_DIRECTis checked only during registration; we should recheck (file->f_flags&O_DIRECT) per request.
Quoted 3 lines
Nobody should be able to clear
O_DIRECT, unless I missed something? And fwiw,io_uringwouldn't be the right place to do it.
fcntl(fd, F_SETFL, flags & ~O_DIRECT) on an already registered, already open fd succeeds. The consequence is that subsequent I/Os switch to buffered I/O and cause silent data loss. Something on this path needs to reject this combination.
Quoted 4 lines
--
Pavel Begunkov
Re: [PATCH v4 14/14] io_uring/rsrc: add dmabuf backed registered buffers
Quoted 25 lines · Anuj gupta
Quoted 24 lines · Pavel Begunkov
Quoted 23 lines
Quoted 10 lines
io_uring/rsrc.c +10
-0+static int io_import_dmabuf (struct io_kiocb *req, +int ddir,struct iov_iter *iter, +struct io_mapped_ubuf *imu, +size_t len,size_t offset, +unsigned issue_flags) +{ +struct io_regbuf_dma *db = imu->priv; +struct dma_buf_io_map *map; + +if (!IS_ENABLED (CONFIG_DMA_SHARED_BUFFER ))io_uring/rsrc.c static int io_import_kbuf(int ddir, struct iov_iter *iter, · line 1303
Quoted 10 lines
io_uring/rsrc.c +10
-0+return -EOPNOTSUPP ; +if (!len) +return -EFAULT ; +if (req->file != db->target_file) +return -EBADF ; + +if (req->flags &REQ_F_DROP_DMABUF ) { + map = req->dmabuf_map; + goto init_iter; + }
O_DIRECTis checked only during registration; we should recheck (file->f_flags&O_DIRECT) per request.
Quoted 6 lines
Quoted 3 lines
Nobody should be able to clear
O_DIRECT, unless I missed something? And fwiw,io_uringwouldn't be the right place to do it.
fcntl(fd, F_SETFL, flags & ~O_DIRECT)on an already registered, already open fd succeeds. The consequence is that subsequent I/Os switch to
Very exciting.
Quoted 2 lines
buffered I/O and cause silent data loss. Something on this path needs to reject this combination.
Yeah, I'll fix it, thanks
Signature · 1 line
Pavel Begunkov
Christoph Hellwig
maintainerMight help to say this applies to Jens' for-next branch for those trying to apply the patches..
Quoted 2 lines · Christoph Hellwig
Might help to say this applies to Jens' for-next branch for those trying to apply the patches..
Ah yes, and there is a branch as well:
git: https://github.com/isilence/linux.git rw-dmabuf-v4
url: https://github.com/isilence/linux/tree/rw-dmabuf-v4
Signature · 1 line
Pavel Begunkov