[PATCH v4 06/14] block: introduce dma map backed bio type
From: Pavel Begunkov
Date: Tue Jul 28 2026 - 17:36:36 EST
Premapped 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.
Suggested-by: Keith Busch <kbusch@xxxxxxxxxx>
Signed-off-by: Pavel Begunkov <asml.silence@xxxxxxxxx>
---
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(-)
diff --git a/block/bio.c b/block/bio.c
index 57c127aee116..cc2bb9183c1a 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -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);
diff --git a/block/blk-merge.c b/block/blk-merge.c
index 258a726071d1..6f0cabfe25ef 100644
--- a/block/blk-merge.c
+++ b/block/blk-merge.c
@@ -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;
diff --git a/block/fops.c b/block/fops.c
index 15783a6180de..37d491f45015 100644
--- a/block/fops.c
+++ b/block/fops.c
@@ -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))
diff --git a/include/linux/bio.h b/include/linux/bio.h
index dea9f567379d..c053e9444514 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -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);
diff --git a/include/linux/blk-mq.h b/include/linux/blk-mq.h
index af878597afb8..7c7504c84e09 100644
--- a/include/linux/blk-mq.h
+++ b/include/linux/blk-mq.h
@@ -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;
diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
index 8808ee76e73c..d605e31712d4 100644
--- a/include/linux/blk_types.h
+++ b/include/linux/blk_types.h
@@ -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.
*/
diff --git a/include/linux/bvec.h b/include/linux/bvec.h
index fc566ee1c1ff..b63914ff56e3 100644
--- a/include/linux/bvec.h
+++ b/include/linux/bvec.h
@@ -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);
--
2.54.0