For the devices which does not support copy, copy emulation is added.
It is required for in-kernel users like fabrics, where file descriptor is
not available and hence they can't use copy_file_range.
Copy-emulation is implemented by reading from source into memory and
writing to the corresponding destination asynchronously.
Also emulation is used, if copy offload fails or partially completes.

Signed-off-by: Nitesh Shetty <nj.she...@samsung.com>
Signed-off-by: Vincent Fu <vincent...@samsung.com>
Signed-off-by: Anuj Gupta <anuj2...@samsung.com>
---
 block/blk-lib.c        | 175 ++++++++++++++++++++++++++++++++++++++++-
 block/blk-map.c        |   4 +-
 include/linux/blkdev.h |   3 +
 3 files changed, 179 insertions(+), 3 deletions(-)

diff --git a/block/blk-lib.c b/block/blk-lib.c
index ed089e703cb1..ba32545eb8d5 100644
--- a/block/blk-lib.c
+++ b/block/blk-lib.c
@@ -295,6 +295,172 @@ static int __blkdev_copy_offload(struct block_device 
*bdev_in, loff_t pos_in,
        return blkdev_copy_wait_completion(cio);
 }
 
+static void *blkdev_copy_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;
+                       return buf;
+               }
+               /* retry half the requested size */
+               req_size >>= 1;
+       }
+
+       return NULL;
+}
+
+static void blkdev_copy_emulate_write_endio(struct bio *bio)
+{
+       struct copy_ctx *ctx = bio->bi_private;
+       struct cio *cio = ctx->cio;
+       sector_t clen;
+
+       if (bio->bi_status) {
+               clen = (bio->bi_iter.bi_sector << SECTOR_SHIFT) - cio->pos_out;
+               cio->comp_len = min_t(sector_t, clen, cio->comp_len);
+       }
+       kvfree(page_address(bio->bi_io_vec[0].bv_page));
+       bio_map_kern_endio(bio);
+       kfree(ctx);
+       if (atomic_dec_and_test(&cio->refcount)) {
+               if (cio->endio) {
+                       cio->endio(cio->private, cio->comp_len);
+                       kfree(cio);
+               } else
+                       blk_wake_io_task(cio->waiter);
+       }
+}
+
+static void blkdev_copy_emulate_read_endio(struct bio *read_bio)
+{
+       struct copy_ctx *ctx = read_bio->bi_private;
+       struct cio *cio = ctx->cio;
+       sector_t clen;
+
+       if (read_bio->bi_status) {
+               clen = (read_bio->bi_iter.bi_sector << SECTOR_SHIFT) -
+                                               cio->pos_in;
+               cio->comp_len = min_t(sector_t, clen, cio->comp_len);
+               __free_page(read_bio->bi_io_vec[0].bv_page);
+               bio_map_kern_endio(read_bio);
+               kfree(ctx);
+
+               if (atomic_dec_and_test(&cio->refcount)) {
+                       if (cio->endio) {
+                               cio->endio(cio->private, cio->comp_len);
+                               kfree(cio);
+                       } else
+                               blk_wake_io_task(cio->waiter);
+               }
+       }
+       schedule_work(&ctx->dispatch_work);
+       kfree(read_bio);
+}
+
+/*
+ * 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.
+ * Returns the length of bytes copied or error if encountered
+ */
+static int __blkdev_copy_emulate(struct block_device *bdev_in, loff_t pos_in,
+                     struct block_device *bdev_out, loff_t pos_out, size_t len,
+                     cio_iodone_t endio, void *private, gfp_t gfp_mask)
+{
+       struct request_queue *in = bdev_get_queue(bdev_in);
+       struct request_queue *out = bdev_get_queue(bdev_out);
+       struct bio *read_bio, *write_bio;
+       void *buf = NULL;
+       struct copy_ctx *ctx;
+       struct cio *cio;
+       sector_t buf_len, req_len, rem = 0;
+       sector_t max_src_hw_len = min_t(unsigned int,
+                       queue_max_hw_sectors(in),
+                       queue_max_segments(in) << (PAGE_SHIFT - SECTOR_SHIFT))
+                       << SECTOR_SHIFT;
+       sector_t max_dst_hw_len = min_t(unsigned int,
+               queue_max_hw_sectors(out),
+                       queue_max_segments(out) << (PAGE_SHIFT - SECTOR_SHIFT))
+                       << SECTOR_SHIFT;
+       sector_t max_hw_len = min_t(unsigned int,
+                       max_src_hw_len, max_dst_hw_len);
+
+       cio = kzalloc(sizeof(struct cio), GFP_KERNEL);
+       if (!cio)
+               return -ENOMEM;
+       atomic_set(&cio->refcount, 0);
+       cio->pos_in = pos_in;
+       cio->pos_out = pos_out;
+       cio->waiter = current;
+       cio->endio = endio;
+       cio->private = private;
+
+       for (rem = len; rem > 0; rem -= buf_len) {
+               req_len = min_t(int, max_hw_len, rem);
+
+               buf = blkdev_copy_alloc_buf(req_len, &buf_len, gfp_mask);
+               if (!buf)
+                       goto err_alloc_buf;
+
+               ctx = kzalloc(sizeof(struct copy_ctx), gfp_mask);
+               if (!ctx)
+                       goto err_ctx;
+
+               read_bio = bio_map_kern(in, buf, buf_len, gfp_mask);
+               if (IS_ERR(read_bio))
+                       goto err_read_bio;
+
+               write_bio = bio_map_kern(out, buf, buf_len, gfp_mask);
+               if (IS_ERR(write_bio))
+                       goto err_write_bio;
+
+               ctx->cio = cio;
+               ctx->write_bio = write_bio;
+               INIT_WORK(&ctx->dispatch_work, blkdev_copy_dispatch_work);
+
+               read_bio->bi_iter.bi_sector = pos_in >> SECTOR_SHIFT;
+               read_bio->bi_iter.bi_size = buf_len;
+               read_bio->bi_opf = REQ_OP_READ | REQ_SYNC;
+               bio_set_dev(read_bio, bdev_in);
+               read_bio->bi_end_io = blkdev_copy_emulate_read_endio;
+               read_bio->bi_private = ctx;
+
+               write_bio->bi_iter.bi_size = buf_len;
+               write_bio->bi_opf = REQ_OP_WRITE | REQ_SYNC;
+               bio_set_dev(write_bio, bdev_out);
+               write_bio->bi_end_io = blkdev_copy_emulate_write_endio;
+               write_bio->bi_iter.bi_sector = pos_out >> SECTOR_SHIFT;
+               write_bio->bi_private = ctx;
+
+               atomic_inc(&cio->refcount);
+               submit_bio(read_bio);
+
+               pos_in += buf_len;
+               pos_out += buf_len;
+       }
+
+       /* Wait for completion of all IO's*/
+       return blkdev_copy_wait_completion(cio);
+
+err_write_bio:
+       bio_put(read_bio);
+err_read_bio:
+       kfree(ctx);
+err_ctx:
+       kvfree(buf);
+err_alloc_buf:
+       cio->comp_len -= min_t(sector_t, cio->comp_len, len - rem);
+       if (!atomic_read(&cio->refcount))
+               return -ENOMEM;
+       /* Wait for submitted IOs to complete */
+       return blkdev_copy_wait_completion(cio);
+}
+
 static inline int blkdev_copy_sanity_check(struct block_device *bdev_in,
        loff_t pos_in, struct block_device *bdev_out, loff_t pos_out,
        size_t len)
@@ -342,9 +508,16 @@ int blkdev_issue_copy(struct block_device *bdev_in, loff_t 
pos_in,
        if (ret)
                return ret;
 
-       if (blk_queue_copy(q_in) && blk_queue_copy(q_out))
+       if (blk_queue_copy(q_in) && blk_queue_copy(q_out)) {
                ret = __blkdev_copy_offload(bdev_in, pos_in, bdev_out, pos_out,
                           len, endio, private, gfp_mask);
+               if (ret < 0)
+                       ret = 0;
+       }
+
+       if (ret != len)
+               ret = __blkdev_copy_emulate(bdev_in, pos_in + ret, bdev_out,
+                        pos_out + ret, len - ret, endio, private, gfp_mask);
 
        return ret;
 }
diff --git a/block/blk-map.c b/block/blk-map.c
index 04c55f1c492e..e79eb4d2e545 100644
--- a/block/blk-map.c
+++ b/block/blk-map.c
@@ -363,7 +363,7 @@ static void bio_invalidate_vmalloc_pages(struct bio *bio)
 #endif
 }
 
-static void bio_map_kern_endio(struct bio *bio)
+void bio_map_kern_endio(struct bio *bio)
 {
        bio_invalidate_vmalloc_pages(bio);
        bio_uninit(bio);
@@ -380,7 +380,7 @@ static void bio_map_kern_endio(struct bio *bio)
  *     Map the kernel address into a bio suitable for io to a block
  *     device. Returns an error pointer in case of error.
  */
-static struct bio *bio_map_kern(struct request_queue *q, void *data,
+struct bio *bio_map_kern(struct request_queue *q, void *data,
                unsigned int len, gfp_t gfp_mask)
 {
        unsigned long kaddr = (unsigned long)data;
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 6f2814ab4741..a95c26faa8b6 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -1054,6 +1054,9 @@ int blkdev_issue_secure_erase(struct block_device *bdev, 
sector_t sector,
 int blkdev_issue_copy(struct block_device *bdev_in, loff_t pos_in,
                      struct block_device *bdev_out, loff_t pos_out, size_t len,
                      cio_iodone_t end_io, void *private, gfp_t gfp_mask);
+struct bio *bio_map_kern(struct request_queue *q, void *data, unsigned int len,
+               gfp_t gfp_mask);
+void bio_map_kern_endio(struct bio *bio);
 
 #define BLKDEV_ZERO_NOUNMAP    (1 << 0)  /* do not free blocks */
 #define BLKDEV_ZERO_NOFALLBACK (1 << 1)  /* don't write explicit zeroes */
-- 
2.35.1.500.gb896f729e2

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel

Reply via email to