On Wed, Oct 14, 2026 at 12:02:02PM +0100, Pavel Begunkov wrote:
> Premapped buffers don't require a generic bio_vec since these have
> already been dma mapped. Repurpose the bi_io_vec space to store dmabuf
> maps as they are mutually exclusive.
>
> The bio splitting differs from the normal path because it's already
> pre-mapped and for the block layer it's just an offset into the dma-buf.
> The actual segmentation is only available to the importer driver and not
> the block layer, however, it doesn't contain alignment gaps and we don't
> have to check it. For the same reason we can't precisely split by the
> number of segments, but we use ->min_seg_shift stored in the map to
> calculate the minimum number of bytes a request consisting of
> lim->max_segments full segments can cover and split by that. It's
> stricter and can add extra splitting. E.g. for a {4K, 4G} segmentation,
> the min segment size is 4K, and we'll split it into bios of
> (4K * lim->max_segments) bytes each, but it should be good enough for
> now to cover the most popular use cases.
>
> Suggested-by: Keith Busch <[email protected]>
> Signed-off-by: Pavel Begunkov <[email protected]>
> ---
> block/bio.c | 15 +++++++++--
> block/blk-merge.c | 52 +++++++++++++++++++++++++++++++++++++++
> block/blk-mq.c | 6 +++++
> block/fops.c | 2 +-
> include/linux/bio.h | 9 ++++---
> include/linux/blk-mq.h | 7 ++++++
> include/linux/blk_types.h | 14 ++++++++++-
> include/linux/bvec.h | 3 ++-
> 8 files changed, 99 insertions(+), 9 deletions(-)
>
> diff --git a/block/bio.c b/block/bio.c
> index b48091c7663f..1e0d9714c541 100644
> --- a/block/bio.c
> +++ b/block/bio.c
> @@ -881,7 +881,11 @@ static int __bio_clone(struct bio *bio, struct bio
> *bio_src, gfp_t gfp)
> bio->bi_write_stream = bio_src->bi_write_stream;
> bio->bi_bvec_gap_bit = bio_src->bi_bvec_gap_bit;
> 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 &&
> @@ -1204,16 +1208,23 @@ EXPORT_SYMBOL_GPL(__bio_release_pages);
>
> bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter)
> {
> - if (!iov_iter_is_bvec(iter))
> + if (!iov_iter_is_bvec(iter) && !iov_iter_is_dmabuf_map(iter))
> return false;
>
> WARN_ON_ONCE(bio->bi_max_vecs);
>
> + static_assert(offsetof(struct bio, bi_io_vec) ==
> + offsetof(struct bio, bi_dmabuf_map));
> + static_assert(offsetof(struct iov_iter, bvec) ==
> + offsetof(struct iov_iter, dmabuf_map));
> +
> 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);
> + if (iov_iter_is_dmabuf_map(iter))
> + bio->bi_opf |= REQ_NOMERGE | REQ_DMABUF;
> return true;
> }
>
> diff --git a/block/blk-merge.c b/block/blk-merge.c
> index 258a726071d1..501ddc01c449 100644
> --- a/block/blk-merge.c
> +++ b/block/blk-merge.c
> @@ -9,6 +9,7 @@
> #include <linux/blk-integrity.h>
> #include <linux/part_stat.h>
> #include <linux/blk-cgroup.h>
> +#include <linux/dma-buf-io.h>
>
> #include <trace/events/block.h>
>
> @@ -319,6 +320,41 @@ static inline unsigned int bvec_seg_gap(struct bio_vec
> *bvprv,
> return bv->bv_offset | (bvprv->bv_offset + bvprv->bv_len);
> }
>
> +static inline int bio_split_io_at_dmabuf(struct bio *bio,
> + const struct queue_limits *lim, unsigned *segs,
> + unsigned max_bytes, unsigned len_align_mask,
> + unsigned start_align_mask)
> +{
> + unsigned bytes = min(bio->bi_iter.bi_size, max_bytes);
> + unsigned seg_shift = bio->bi_dmabuf_map->min_seg_shift;
> + unsigned offset = bio->bi_iter.bi_offset & ((1U << seg_shift) - 1);
> + u64 max_segs_bytes;
> +
> + /*
> + * dma-buf maps don't expose the underlying segmentation, but they're
> + * guaranteed to not have alignment gaps, we only need to check the
> + * start and length alignment.
> + */
> + if ((bio->bi_iter.bi_offset & start_align_mask) ||
> + (bio->bi_iter.bi_size & len_align_mask))
> + return -EINVAL;
> +
> + /* Presented as a single contiguous range into the dma-buf */
> + *segs = 1;
> +
> + /*
> + * Limit by the number of segments by using the minimal segment size.
> + * Any I/O consisting of N full segments should be able to cover at
> + * least N multiplied by the segment size. It's stricter than walking
> + * the segments and might cause extra splitting.
> + */
> + max_segs_bytes = (u64)lim->max_segments << seg_shift;
> + bytes = min_t(u64, bytes, max_segs_bytes - offset);
> + if (bytes != bio->bi_iter.bi_size)
> + return bytes;
> + return 0;
> +}
> +
> /**
> * bio_split_io_at - check if and where to split a bio
> * @bio: [in] bio to be split
> @@ -346,6 +382,19 @@ 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)) {
> + int ret;
> +
> + ret = bio_split_io_at_dmabuf(bio, lim, &nsegs, max_bytes,
> + len_align_mask, start_align_mask);
> + if (ret < 0)
> + return ret;
> + if (!ret)
> + goto out;
> + bytes = ret;
> + goto split;
> + }
> +
> bio_for_each_bvec(bv, bio, iter) {
> if (bv.bv_offset & start_align_mask ||
> bv.bv_len & len_align_mask)
> @@ -376,6 +425,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;
> @@ -493,6 +543,8 @@ unsigned int blk_recalc_rq_segments(struct request *rq)
>
> if (!rq->bio)
> return 0;
> + if (op_is_dmabuf(rq->bio->bi_opf))
> + return 1;
>
> switch (bio_op(rq->bio)) {
> case REQ_OP_DISCARD:
> diff --git a/block/blk-mq.c b/block/blk-mq.c
> index e929f94b25c7..1b7f8cbbda8b 100644
> --- a/block/blk-mq.c
> +++ b/block/blk-mq.c
> @@ -3150,6 +3150,12 @@ void blk_mq_submit_bio(struct bio *bio)
> goto queue_exit;
> }
>
> + if (unlikely(op_is_dmabuf(bio->bi_opf) &&
> + bdev_has_integrity_csum(bio->bi_bdev))) {
> + bio_endio_status(bio, BLK_STS_NOTSUPP);
Wrong indentation, conditions are never indented the same level as
the following code block, even with the weirdest preferences.
/me note the patch from Anuj on the list actually got this right..
Otherwise looks good:
Reviewed-by: Christoph Hellwig <[email protected]>