[PATCH 5/7] block: add emulation for simple copy

SelvaKumar S
2021-08-17T11:29:30Z

From: Nitesh Shetty <nj.shetty@samsung.com>

For the devices which does not support simple copy, copy emulation is
added. Also for stacked devices, copy is performed via emulation.

Copy-emulation is implemented by allocating maximum possible memory
less than or equal to total copy size. The source ranges are read
into memory by chaining bio for each source ranges and submitting them
async and the last bio waits for completion. After data is read, it is
written to the destination and the process is repeated till no source
ranges left.

bio_map_kern() is used to allocate bio and add pages of copy buffer to
bio. As bio->bi_private and bio->bi_end_io are needed for chaining the
bio and gets over-written, invalidate_kernel_vmap_range() for read is
called in the caller.

Signed-off-by: Nitesh Shetty <nj.shetty@samsung.com>
Signed-off-by: SelvaKumar S <selvakuma.s1@samsung.com>
---
block/blk-lib.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)

diff --git a/block/blk-lib.c b/block/blk-lib.c
index 7fee0ae95c44..d29c52b90dcf 100644
--- a/block/blk-lib.c
+++ b/block/blk-lib.c
@@ -333,6 +333,64 @@ int blk_copy_offload_scc(struct block_device *src_bdev, int nr_srcs,
return ret;
}

+int blk_submit_rw_buf(struct block_device *bdev, void *buf, sector_t buf_len,
+ sector_t sector, unsigned int op, gfp_t gfp_mask)
+{
+ struct request_queue *q = bdev_get_queue(bdev);
+ struct bio *bio, *parent = NULL;
+ sector_t max_hw_len = min_t(unsigned int, queue_max_hw_sectors(q),
+ queue_max_segments(q) << (PAGE_SHIFT - SECTOR_SHIFT));
+ sector_t len, remaining;
+ int ret;
+
+ for (remaining = buf_len; remaining > 0; remaining -= len) {
+ len = min_t(int, max_hw_len, remaining);
+retry:
+ bio = bio_map_kern(q, buf, len << SECTOR_SHIFT, gfp_mask);
+ if (IS_ERR(bio)) {
+ len >>= 1;
+ if (len)
+ goto retry;
+ return PTR_ERR(bio);
+ }
+
+ bio->bi_iter.bi_sector = sector;
+ bio->bi_opf = op;
+ bio_set_dev(bio, bdev);
+ bio->bi_end_io = NULL;
+ bio->bi_private = NULL;
+
+ if (parent) {
+ bio_chain(parent, bio);
+ submit_bio(parent);
+ }
+ parent = bio;
+ sector += len;
+ }
+ ret = submit_bio_wait(bio);
+ bio_put(bio);
+
+ return ret;
+}
+
+static void *blk_alloc_buf(sector_t req_size, sector_t *alloc_size, gfp_t gfp_mask)
+{
+ int min_size = PAGE_SIZE;
+ void *buf;
+
+ while (req_size >= min_size) {
+ buf = kvmalloc(req_size, gfp_mask);
+ if (buf) {
+ *alloc_size = (req_size >> SECTOR_SHIFT);
+ return buf;
+ }
+ /* retry half the requested size */
+ req_size >>= 1;
+ }
+
+ return NULL;
+}
+
static inline sector_t blk_copy_len(struct range_entry *rlist, int nr_srcs)
{
int i;
@@ -348,6 +406,46 @@ static inline sector_t blk_copy_len(struct range_entry *rlist, int nr_srcs)
return len;
}

+/*
+ * If native copy offload feature is absent, this function tries to emulate,
+ * by copying data from source to a temporary buffer and from buffer to
+ * destination device.
+ */
+static int blk_copy_emulate(struct block_device *src_bdev, int nr_srcs,
+ struct range_entry *rlist, struct block_device *dest_bdev,
+ sector_t dest, gfp_t gfp_mask)
+{
+ void *buf = NULL;
+ int ret, nr_i = 0;
+ sector_t src_blk, copy_len, buf_len, read_len, copied_len, remaining = 0;
+
+ copy_len = blk_copy_len(rlist, nr_srcs);
+ buf = blk_alloc_buf(copy_len << SECTOR_SHIFT, &buf_len, gfp_mask);
+ if (!buf)
+ return -ENOMEM;
+
+ for (copied_len = 0; copied_len < copy_len; copied_len += read_len) {
+ if (!remaining) {
+ src_blk = rlist[nr_i].src;
+ remaining = rlist[nr_i++].len;
+ }
+
+ read_len = min_t(sector_t, remaining, buf_len);
+ ret = blk_submit_rw_buf(src_bdev, buf, read_len, src_blk, REQ_OP_READ, gfp_mask);
+ if (ret)
+ goto out;
+ src_blk += read_len;
+ remaining -= read_len;
+ ret = blk_submit_rw_buf(dest_bdev, buf, read_len, dest + copied_len, REQ_OP_WRITE,
+ gfp_mask);
+ if (ret)
+ goto out;
+ }
+out:
+ kvfree(buf);
+ return ret;
+}
+
static inline bool blk_check_offload_scc(struct request_queue *src_q,
struct request_queue *dest_q)
{
@@ -398,6 +496,8 @@ int blkdev_issue_copy(struct block_device *src_bdev, int nr_srcs,

if (blk_check_offload_scc(src_q, dest_q))
ret = blk_copy_offload_scc(src_bdev, nr_srcs, src_rlist, dest_bdev, dest, gfp_mask);
+ else
+ ret = blk_copy_emulate(src_bdev, nr_srcs, src_rlist, dest_bdev, dest, gfp_mask);

return ret;
}
--
2.25.1