lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1601643362-7370-2-git-send-email-sergei.shtepa@veeam.com>
Date:   Fri, 2 Oct 2020 15:56:02 +0300
From:   Sergei Shtepa <sergei.shtepa@...am.com>
To:     <axboe@...nel.dk>, <mchehab+huawei@...nel.org>,
        <davem@...emloft.net>, <robh@...nel.org>, <koct9i@...il.com>,
        <damien.lemoal@....com>, <jack@...e.cz>, <ming.lei@...hat.com>,
        <steve@....org>, <linux-kernel@...r.kernel.org>,
        <linux-kbuild@...r.kernel.org>, <linux-block@...r.kernel.org>
CC:     Sergei Shtepa <sergei.shtepa@...am.com>
Subject: [PATCH 1/1] blk-snap - Block snapshot module This module implements snapshot and changed block tracking functionality. It is intended to create backup copies of any block devices without usage of device-mapper.

blk-filter - Block layer filter API
Block layer filter API provides a callback to intercept bio-requests.

Signed-off-by: Sergei Shtepa <sergei.shtepa@...am.com>
---
 MAINTAINERS                                 |  14 +
 block/Kconfig                               |  11 +
 block/Makefile                              |   1 +
 block/blk-core.c                            |  11 +-
 block/blk-filter-internal.h                 |  34 +
 block/blk-filter.c                          | 288 ++++++
 block/genhd.c                               |  24 +
 drivers/block/Kconfig                       |   6 +-
 drivers/block/Makefile                      |   3 +-
 drivers/block/blk-snap/Kconfig              |  24 +
 drivers/block/blk-snap/Makefile             |  29 +
 drivers/block/blk-snap/big_buffer.c         | 193 ++++
 drivers/block/blk-snap/big_buffer.h         |  24 +
 drivers/block/blk-snap/blk-snap-ctl.h       | 190 ++++
 drivers/block/blk-snap/blk_deferred.c       | 566 +++++++++++
 drivers/block/blk-snap/blk_deferred.h       |  67 ++
 drivers/block/blk-snap/blk_descr_file.c     |  82 ++
 drivers/block/blk-snap/blk_descr_file.h     |  26 +
 drivers/block/blk-snap/blk_descr_mem.c      |  66 ++
 drivers/block/blk-snap/blk_descr_mem.h      |  14 +
 drivers/block/blk-snap/blk_descr_multidev.c |  86 ++
 drivers/block/blk-snap/blk_descr_multidev.h |  25 +
 drivers/block/blk-snap/blk_descr_pool.c     | 190 ++++
 drivers/block/blk-snap/blk_descr_pool.h     |  38 +
 drivers/block/blk-snap/blk_redirect.c       | 507 ++++++++++
 drivers/block/blk-snap/blk_redirect.h       |  73 ++
 drivers/block/blk-snap/blk_util.c           |  33 +
 drivers/block/blk-snap/blk_util.h           |  10 +
 drivers/block/blk-snap/cbt_map.c            | 210 +++++
 drivers/block/blk-snap/cbt_map.h            |  62 ++
 drivers/block/blk-snap/common.h             |  29 +
 drivers/block/blk-snap/ctrl_fops.c          | 693 ++++++++++++++
 drivers/block/blk-snap/ctrl_fops.h          |  19 +
 drivers/block/blk-snap/ctrl_pipe.c          | 562 +++++++++++
 drivers/block/blk-snap/ctrl_pipe.h          |  34 +
 drivers/block/blk-snap/ctrl_sysfs.c         |  73 ++
 drivers/block/blk-snap/ctrl_sysfs.h         |   5 +
 drivers/block/blk-snap/defer_io.c           | 393 ++++++++
 drivers/block/blk-snap/defer_io.h           |  39 +
 drivers/block/blk-snap/filter.c             |  72 ++
 drivers/block/blk-snap/filter.h             |   7 +
 drivers/block/blk-snap/main.c               |  83 ++
 drivers/block/blk-snap/params.c             |  57 ++
 drivers/block/blk-snap/params.h             |  29 +
 drivers/block/blk-snap/rangevector.c        |  85 ++
 drivers/block/blk-snap/rangevector.h        |  31 +
 drivers/block/blk-snap/snapimage.c          | 982 ++++++++++++++++++++
 drivers/block/blk-snap/snapimage.h          |  16 +
 drivers/block/blk-snap/snapshot.c           | 228 +++++
 drivers/block/blk-snap/snapshot.h           |  17 +
 drivers/block/blk-snap/snapstore.c          | 929 ++++++++++++++++++
 drivers/block/blk-snap/snapstore.h          |  68 ++
 drivers/block/blk-snap/snapstore_device.c   | 532 +++++++++++
 drivers/block/blk-snap/snapstore_device.h   |  63 ++
 drivers/block/blk-snap/snapstore_file.c     |  52 ++
 drivers/block/blk-snap/snapstore_file.h     |  15 +
 drivers/block/blk-snap/snapstore_mem.c      |  89 ++
 drivers/block/blk-snap/snapstore_mem.h      |  20 +
 drivers/block/blk-snap/snapstore_multidev.c | 118 +++
 drivers/block/blk-snap/snapstore_multidev.h |  22 +
 drivers/block/blk-snap/tracker.c            | 521 +++++++++++
 drivers/block/blk-snap/tracker.h            |  50 +
 drivers/block/blk-snap/tracking.c           | 260 ++++++
 drivers/block/blk-snap/tracking.h           |  12 +
 drivers/block/blk-snap/version.h            |   7 +
 include/linux/blk-filter.h                  |  41 +
 include/linux/genhd.h                       |   3 +-
 67 files changed, 9157 insertions(+), 6 deletions(-)
 create mode 100644 block/blk-filter-internal.h
 create mode 100644 block/blk-filter.c
 create mode 100644 drivers/block/blk-snap/Kconfig
 create mode 100644 drivers/block/blk-snap/Makefile
 create mode 100644 drivers/block/blk-snap/big_buffer.c
 create mode 100644 drivers/block/blk-snap/big_buffer.h
 create mode 100644 drivers/block/blk-snap/blk-snap-ctl.h
 create mode 100644 drivers/block/blk-snap/blk_deferred.c
 create mode 100644 drivers/block/blk-snap/blk_deferred.h
 create mode 100644 drivers/block/blk-snap/blk_descr_file.c
 create mode 100644 drivers/block/blk-snap/blk_descr_file.h
 create mode 100644 drivers/block/blk-snap/blk_descr_mem.c
 create mode 100644 drivers/block/blk-snap/blk_descr_mem.h
 create mode 100644 drivers/block/blk-snap/blk_descr_multidev.c
 create mode 100644 drivers/block/blk-snap/blk_descr_multidev.h
 create mode 100644 drivers/block/blk-snap/blk_descr_pool.c
 create mode 100644 drivers/block/blk-snap/blk_descr_pool.h
 create mode 100644 drivers/block/blk-snap/blk_redirect.c
 create mode 100644 drivers/block/blk-snap/blk_redirect.h
 create mode 100644 drivers/block/blk-snap/blk_util.c
 create mode 100644 drivers/block/blk-snap/blk_util.h
 create mode 100644 drivers/block/blk-snap/cbt_map.c
 create mode 100644 drivers/block/blk-snap/cbt_map.h
 create mode 100644 drivers/block/blk-snap/common.h
 create mode 100644 drivers/block/blk-snap/ctrl_fops.c
 create mode 100644 drivers/block/blk-snap/ctrl_fops.h
 create mode 100644 drivers/block/blk-snap/ctrl_pipe.c
 create mode 100644 drivers/block/blk-snap/ctrl_pipe.h
 create mode 100644 drivers/block/blk-snap/ctrl_sysfs.c
 create mode 100644 drivers/block/blk-snap/ctrl_sysfs.h
 create mode 100644 drivers/block/blk-snap/defer_io.c
 create mode 100644 drivers/block/blk-snap/defer_io.h
 create mode 100644 drivers/block/blk-snap/filter.c
 create mode 100644 drivers/block/blk-snap/filter.h
 create mode 100644 drivers/block/blk-snap/main.c
 create mode 100644 drivers/block/blk-snap/params.c
 create mode 100644 drivers/block/blk-snap/params.h
 create mode 100644 drivers/block/blk-snap/rangevector.c
 create mode 100644 drivers/block/blk-snap/rangevector.h
 create mode 100644 drivers/block/blk-snap/snapimage.c
 create mode 100644 drivers/block/blk-snap/snapimage.h
 create mode 100644 drivers/block/blk-snap/snapshot.c
 create mode 100644 drivers/block/blk-snap/snapshot.h
 create mode 100644 drivers/block/blk-snap/snapstore.c
 create mode 100644 drivers/block/blk-snap/snapstore.h
 create mode 100644 drivers/block/blk-snap/snapstore_device.c
 create mode 100644 drivers/block/blk-snap/snapstore_device.h
 create mode 100644 drivers/block/blk-snap/snapstore_file.c
 create mode 100644 drivers/block/blk-snap/snapstore_file.h
 create mode 100644 drivers/block/blk-snap/snapstore_mem.c
 create mode 100644 drivers/block/blk-snap/snapstore_mem.h
 create mode 100644 drivers/block/blk-snap/snapstore_multidev.c
 create mode 100644 drivers/block/blk-snap/snapstore_multidev.h
 create mode 100644 drivers/block/blk-snap/tracker.c
 create mode 100644 drivers/block/blk-snap/tracker.h
 create mode 100644 drivers/block/blk-snap/tracking.c
 create mode 100644 drivers/block/blk-snap/tracking.h
 create mode 100644 drivers/block/blk-snap/version.h
 create mode 100644 include/linux/blk-filter.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 190c7fa2ea01..a235e620fc71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3208,6 +3208,20 @@ F:	include/linux/blk*
 F:	kernel/trace/blktrace.c
 F:	lib/sbitmap.c
 
+BLOCK LAYER FILTER API
+M:	Sergei Shtepa <sergei.shtepa@...am.com>
+L:	linux-block@...r.kernel.org
+S:	Maintained
+F:	block/blk-filter.c
+F:	block/blk-filter-internal.h
+F:	include/linux/blk-filter.h
+
+BLOCK SNAPSHOT DRIVER
+M:	Sergei Shtepa <sergei.shtepa@...am.com>
+L:	linux-block@...r.kernel.org
+S:	Maintained
+F:	drivers/block/blk-snap/*
+
 BLOCK2MTD DRIVER
 M:	Joern Engel <joern@...ybastard.org>
 L:	linux-mtd@...ts.infradead.org
diff --git a/block/Kconfig b/block/Kconfig
index bbad5e8bbffe..a308801b4376 100644
--- a/block/Kconfig
+++ b/block/Kconfig
@@ -204,6 +204,17 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
 	  by falling back to the kernel crypto API when inline
 	  encryption hardware is not present.
 
+config BLK_FILTER
+	bool "Enable support for block layer filters"
+	default y
+	depends on MODULES
+	help
+	  Enabling this lets third-party kernel modules intercept
+	  bio requests for any block device. This allows them to implement
+	  changed block tracking and snapshots without any reconfiguration of
+	  the existing setup. For example, this option allows snapshotting of
+	  a block device without adding it to LVM.
+
 menu "Partition Types"
 
 source "block/partitions/Kconfig"
diff --git a/block/Makefile b/block/Makefile
index 8d841f5f986f..b8ee50b8e031 100644
--- a/block/Makefile
+++ b/block/Makefile
@@ -38,3 +38,4 @@ obj-$(CONFIG_BLK_SED_OPAL)	+= sed-opal.o
 obj-$(CONFIG_BLK_PM)		+= blk-pm.o
 obj-$(CONFIG_BLK_INLINE_ENCRYPTION)	+= keyslot-manager.o blk-crypto.o
 obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK)	+= blk-crypto-fallback.o
+obj-$(CONFIG_BLK_FILTER)	+= blk-filter.o
diff --git a/block/blk-core.c b/block/blk-core.c
index 10c08ac50697..d75e3866341b 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -50,6 +50,7 @@
 #include "blk-mq-sched.h"
 #include "blk-pm.h"
 #include "blk-rq-qos.h"
+#include "blk-filter-internal.h"
 
 struct dentry *blk_debugfs_root;
 
@@ -1274,13 +1275,19 @@ blk_qc_t submit_bio(struct bio *bio)
 		blk_qc_t ret;
 
 		psi_memstall_enter(&pflags);
-		ret = submit_bio_noacct(bio);
+		if (IS_ENABLED(CONFIG_BLK_FILTER))
+			ret = blk_filter_submit_bio(bio);
+		else
+			ret = submit_bio_noacct(bio);
 		psi_memstall_leave(&pflags);
 
 		return ret;
 	}
 
-	return submit_bio_noacct(bio);
+	if (IS_ENABLED(CONFIG_BLK_FILTER))
+		return blk_filter_submit_bio(bio);
+	else
+		return submit_bio_noacct(bio);
 }
 EXPORT_SYMBOL(submit_bio);
 
diff --git a/block/blk-filter-internal.h b/block/blk-filter-internal.h
new file mode 100644
index 000000000000..942066f3fecb
--- /dev/null
+++ b/block/blk-filter-internal.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ *
+ * Block device filters internal declarations
+ */
+
+#ifndef BLK_FILTER_INTERNAL_H
+#define BLK_FILTER_INTERNAL_H
+
+#ifdef CONFIG_BLK_FILTER
+#include <linux/blk-filter.h>
+
+void blk_filter_disk_add(struct gendisk *disk);
+
+void blk_filter_disk_del(struct gendisk *disk);
+
+void blk_filter_disk_release(struct gendisk *disk);
+
+blk_qc_t blk_filter_submit_bio(struct bio *bio);
+
+#else /* CONFIG_BLK_FILTER */
+
+static inline void blk_filter_disk_add(struct gendisk *disk) { }
+
+static inline void blk_filter_disk_del(struct gendisk *disk) { }
+
+static inline void blk_filter_disk_release(struct gendisk *disk) { }
+
+static inline blk_qc_t blk_filter_submit_bio(struct bio *bio) { return 0; }
+
+#endif /* CONFIG_BLK_FILTER */
+
+#endif
diff --git a/block/blk-filter.c b/block/blk-filter.c
new file mode 100644
index 000000000000..edb30f342a3d
--- /dev/null
+++ b/block/blk-filter.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/genhd.h>
+#include <linux/bio.h>
+#include <linux/blkdev.h>
+#include "blk-filter-internal.h"
+#include <linux/rwsem.h>
+
+struct blk_filter_ctx {
+	struct blk_filter *filter;
+	/*
+	 * Reserved for extension
+	 */
+};
+
+DECLARE_RWSEM(blk_filter_ctx_list_lock);
+struct blk_filter_ctx *blk_filter_ctx_list[BLK_FILTER_ALTITUDE_MAX] = { 0 };
+
+static inline struct blk_filter_ctx *_get_ctx(size_t altitude)
+{
+	return blk_filter_ctx_list[altitude-1];
+}
+
+static inline void _set_ctx(size_t altitude, struct blk_filter_ctx *ctx)
+{
+	blk_filter_ctx_list[altitude-1] = ctx;
+}
+
+static struct blk_filter_ctx *_blk_ctx_new(struct blk_filter *filter)
+{
+	struct blk_filter_ctx *ctx = kzalloc(sizeof(struct blk_filter_ctx), GFP_KERNEL);
+
+	if (!ctx)
+		return ctx;
+
+	ctx->filter = filter;
+
+	return ctx;
+}
+
+static int _blk_ctx_link(struct blk_filter_ctx *ctx, size_t altitude)
+{
+	int result = 0;
+
+	if (altitude > BLK_FILTER_ALTITUDE_MAX)
+		return -ENOENT;
+
+	down_write(&blk_filter_ctx_list_lock);
+
+	if (_get_ctx(altitude))
+		result = -EEXIST;
+	else
+		_set_ctx(altitude, ctx);
+
+	up_write(&blk_filter_ctx_list_lock);
+
+	return result;
+}
+
+static int _blk_ctx_unlink(struct blk_filter_ctx *ctx)
+{
+	int result = -EEXIST;
+	size_t altitude = BLK_FILTER_ALTITUDE_MIN;
+
+	down_write(&blk_filter_ctx_list_lock);
+
+	for (; altitude <= BLK_FILTER_ALTITUDE_MAX; ++altitude) {
+		if (_get_ctx(altitude) && (_get_ctx(altitude) == ctx)) {
+			_set_ctx(altitude, NULL);
+			result = 0;
+			break;
+		}
+	}
+
+	up_write(&blk_filter_ctx_list_lock);
+
+	return result;
+}
+
+/**
+ * blk_filter_disk_add() - Notify filters when a new disk is added.
+ * @disk: The new disk.
+ */
+void blk_filter_disk_add(struct gendisk *disk)
+{
+	size_t altitude = BLK_FILTER_ALTITUDE_MIN;
+
+	pr_warn("blk-filter: add disk [%s].\n", disk->disk_name);
+
+	down_read(&blk_filter_ctx_list_lock);
+
+	for (; altitude <= BLK_FILTER_ALTITUDE_MAX; ++altitude) {
+		struct blk_filter_ctx *ctx = _get_ctx(altitude);
+
+		if (ctx && ctx->filter->ops && ctx->filter->ops->disk_add)
+			ctx->filter->ops->disk_add(disk);
+	}
+
+	up_read(&blk_filter_ctx_list_lock);
+}
+
+/**
+ * blk_filter_disk_del() - Notify filters when the disk is deleted.
+ * @disk: The disk to delete.
+ */
+void blk_filter_disk_del(struct gendisk *disk)
+{
+	size_t altitude = BLK_FILTER_ALTITUDE_MIN;
+
+	pr_warn("blk-filter: del disk [%s].\n", disk->disk_name);
+
+	down_read(&blk_filter_ctx_list_lock);
+
+	for (; altitude <= BLK_FILTER_ALTITUDE_MAX; ++altitude) {
+		struct blk_filter_ctx *ctx = _get_ctx(altitude);
+
+		if (ctx && ctx->filter->ops && ctx->filter->ops->disk_del)
+			ctx->filter->ops->disk_del(disk);
+	}
+
+	up_read(&blk_filter_ctx_list_lock);
+}
+
+/**
+ * blk_filter_disk_release() - Notify filters when the disk is released.
+ * @disk: The disk to release.
+ */
+void blk_filter_disk_release(struct gendisk *disk)
+{
+	size_t altitude = BLK_FILTER_ALTITUDE_MAX;
+
+	pr_warn("blk-filter: release disk [%s].\n", disk->disk_name);
+
+	down_read(&blk_filter_ctx_list_lock);
+
+	for (; altitude <= BLK_FILTER_ALTITUDE_MIN; --altitude) {
+		struct blk_filter_ctx *ctx = _get_ctx(altitude);
+
+		if (ctx && ctx->filter->ops && ctx->filter->ops->disk_release)
+			ctx->filter->ops->disk_release(disk);
+	}
+
+	up_read(&blk_filter_ctx_list_lock);
+}
+
+/**
+ * blk_filter_submit_bio_altitude() - Send bio for porcessing to specific filter.
+ * @altitude: The filter altitude.
+ * @bio: The new bio for block I/O layer.
+ *
+ * Return: Bio submitting result, like for submit_bio function.
+ */
+blk_qc_t blk_filter_submit_bio_altitude(size_t altitude, struct bio *bio)
+{
+	blk_qc_t ret;
+	bool bypass = true;
+
+	down_read(&blk_filter_ctx_list_lock);
+	while (altitude >= BLK_FILTER_ALTITUDE_MIN) {
+		struct blk_filter_ctx *ctx = _get_ctx(altitude);
+
+		if (ctx && ctx->filter->ops && ctx->filter->ops->submit_bio) {
+			ret = ctx->filter->ops->submit_bio(bio);
+			bypass = false;
+			break;
+		}
+		--altitude;
+	}
+	up_read(&blk_filter_ctx_list_lock);
+
+	if (bypass)
+		ret = submit_bio_noacct(bio);
+
+	return ret;
+}
+
+/**
+ * blk_filter_submit_bio() - Send new bio to filters for processing.
+ * @bio: The new bio for block I/O layer.
+ *
+ * Return: Bio submitting result, like for submit_bio function.
+ */
+blk_qc_t blk_filter_submit_bio(struct bio *bio)
+{
+	return blk_filter_submit_bio_altitude(BLK_FILTER_ALTITUDE_MAX, bio);
+}
+
+/**
+ * blk_filter_register() - Create new block I/O layer filter.
+ * @filter: The filter description structure.
+ *
+ * Return: Zero if the filter was registered successfully or an error code if it failed.
+ */
+int blk_filter_register(struct blk_filter *filter)
+{
+	int result = 0;
+	struct blk_filter_ctx *ctx;
+
+	pr_warn("blk-filter: register filter [%s].\n", filter->name);
+
+	ctx = _blk_ctx_new(filter);
+	if (!ctx)
+		return -ENOMEM;
+
+	result = _blk_ctx_link(ctx, filter->altitude);
+	if (result)
+		goto failed;
+
+	filter->blk_filter_ctx = (void *)ctx;
+	return 0;
+
+failed:
+	kfree(ctx);
+	return result;
+}
+EXPORT_SYMBOL(blk_filter_register);
+
+/**
+ * blk_filter_unregister() - Remove existing block I/O layer filter.
+ * @filter: The filter description structure.
+ *
+ * Return: Zero if the filter was removed successfully or an error code if it failed.
+ */
+int blk_filter_unregister(struct blk_filter *filter)
+{
+	int result = 0;
+	struct blk_filter_ctx *ctx;
+
+	pr_warn("blk-filter: unregister filter [%s].\n", filter->name);
+
+	ctx = (struct blk_filter_ctx *)filter->blk_filter_ctx;
+
+	result = _blk_ctx_unlink(ctx);
+	if (result == 0)
+		kfree(ctx);
+
+	return result;
+}
+EXPORT_SYMBOL(blk_filter_unregister);
+
+/**
+ * blk_filter_check_altitude() - Checking that altitude is free.
+ * @altitude: The filter description structure.
+ *
+ * Return: NULL if the altitude is free or the name of the module registered at this altitude.
+ */
+const char *blk_filter_check_altitude(size_t altitude)
+{
+	struct blk_filter_ctx *ctx = _get_ctx(altitude);
+
+	if (!ctx)
+		return NULL;
+
+	return ctx->filter->name;
+}
+EXPORT_SYMBOL(blk_filter_check_altitude);
+
+static void _attach_fn(struct gendisk *disk, void *_ctx)
+{
+	struct blk_filter *filter = (struct blk_filter *)_ctx;
+
+	if (filter->ops && filter->ops->disk_add)
+		filter->ops->disk_add(disk);
+}
+
+/**
+ * blk_filter_attach_disks() - Enumerate all existing disks and call disk_add callback for each.
+ * @filter: The filter description structure.
+ *
+ * Return: Zero if the existing disks was attached successfully or an error code if it failed.
+ */
+int blk_filter_attach_disks(struct blk_filter *filter)
+{
+	return disk_enumerate(_attach_fn, filter);
+}
+EXPORT_SYMBOL(blk_filter_attach_disks);
+
+/**
+ * blk_filter_submit_bio_next() - Send a bio to the lower filters for processing.
+ * @bio: The bio for block I/O layer.
+ *
+ * Return: Bio submitting result, like for submit_bio function.
+ */
+blk_qc_t blk_filter_submit_bio_next(struct blk_filter *filter, struct bio *bio)
+{
+	return blk_filter_submit_bio_altitude(filter->altitude-1, bio);
+}
+EXPORT_SYMBOL(blk_filter_submit_bio_next);
diff --git a/block/genhd.c b/block/genhd.c
index 99c64641c314..c5604415e772 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -25,6 +25,7 @@
 #include <linux/badblocks.h>
 
 #include "blk.h"
+#include "blk-filter-internal.h"
 
 static DEFINE_MUTEX(block_class_lock);
 static struct kobject *block_depr;
@@ -837,6 +838,7 @@ static void __device_add_disk(struct device *parent, struct gendisk *disk,
 	 */
 	WARN_ON_ONCE(!blk_get_queue(disk->queue));
 
+	blk_filter_disk_add(disk);
 	disk_add_events(disk);
 	blk_integrity_add(disk);
 }
@@ -900,6 +902,7 @@ void del_gendisk(struct gendisk *disk)
 
 	might_sleep();
 
+	blk_filter_disk_del(disk);
 	blk_integrity_del(disk);
 	disk_del_events(disk);
 
@@ -1562,6 +1565,7 @@ static void disk_release(struct device *dev)
 
 	might_sleep();
 
+	blk_filter_disk_release(disk);
 	blk_free_devt(dev->devt);
 	disk_release_events(disk);
 	kfree(disk->random);
@@ -2339,3 +2343,23 @@ static void disk_release_events(struct gendisk *disk)
 	WARN_ON_ONCE(disk->ev && disk->ev->block != 1);
 	kfree(disk->ev);
 }
+
+int disk_enumerate(void (*fn)(struct gendisk *disk, void *ctx), void *ctx)
+{
+	struct class_dev_iter *iter;
+	struct device *dev;
+
+	iter = kmalloc(sizeof(*iter), GFP_KERNEL);
+	if (!iter)
+		return -ENOMEM;
+
+	class_dev_iter_init(iter, &block_class, NULL, &disk_type);
+	dev = class_dev_iter_next(iter);
+	while (dev) {
+		fn(dev_to_disk(dev), ctx);
+		dev = class_dev_iter_next(iter);
+	};
+
+	kfree(iter);
+	return 0;
+}
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index ecceaaa1a66f..1c8bebd66afa 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -235,7 +235,7 @@ config BLK_DEV_CRYPTOLOOP
 	select CRYPTO_CBC
 	depends on BLK_DEV_LOOP
 	help
-	  Say Y here if you want to be able to use the ciphers that are 
+	  Say Y here if you want to be able to use the ciphers that are
 	  provided by the CryptoAPI as loop transformation. This might be
 	  used as hard disk encryption.
 
@@ -287,7 +287,7 @@ config BLK_DEV_SX8
 	tristate "Promise SATA SX8 support"
 	depends on PCI
 	help
-	  Saying Y or M here will enable support for the 
+	  Saying Y or M here will enable support for the
 	  Promise SATA SX8 controllers.
 
 	  Use devices /dev/sx8/$N and /dev/sx8/$Np$M.
@@ -460,4 +460,6 @@ config BLK_DEV_RSXX
 
 source "drivers/block/rnbd/Kconfig"
 
+source "drivers/block/blk-snap/Kconfig"
+
 endif # BLK_DEV
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index e1f63117ee94..128d44b47e7f 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -4,7 +4,7 @@
 #
 # 12 June 2000, Christoph Hellwig <hch@...radead.org>
 # Rewritten to use lists instead of if-statements.
-# 
+#
 
 # needed for trace events
 ccflags-y				+= -I$(src)
@@ -50,3 +50,4 @@ null_blk-$(CONFIG_BLK_DEV_ZONED) += null_blk_zoned.o
 
 skd-y		:= skd_main.o
 swim_mod-y	:= swim.o swim_asm.o
+obj-$(CONFIG_BLK_SNAP) += blk-snap/
diff --git a/drivers/block/blk-snap/Kconfig b/drivers/block/blk-snap/Kconfig
new file mode 100644
index 000000000000..7a2db99a80dd
--- /dev/null
+++ b/drivers/block/blk-snap/Kconfig
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# blk-snap block io layer filter module configuration
+#
+#
+#select BLK_FILTER
+
+config BLK_SNAP
+	tristate "Block device snapshot filter"
+	depends on BLK_FILTER
+	help
+
+	  Allow to create snapshots and track block changes for a block
+	  devices. Designed for creating backups for any block devices
+	  (without device mapper). Snapshots are temporary and are released
+	  then backup is completed. Change block tracking allows you to
+	  create incremental or differential backups.
+
+config BLK_SNAP_SNAPSTORE_MULTIDEV
+	bool "Multi device snapstore configuration support"
+	depends on BLK_SNAP
+	help
+
+	  Allow to create snapstore on multiple block devices.
diff --git a/drivers/block/blk-snap/Makefile b/drivers/block/blk-snap/Makefile
new file mode 100644
index 000000000000..35f4178ee845
--- /dev/null
+++ b/drivers/block/blk-snap/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+blk-snap-y += blk_deferred.o
+blk-snap-y += blk_descr_file.o
+blk-snap-y += blk_descr_mem.o
+blk-snap-y += blk_descr_multidev.o
+blk-snap-y += blk_descr_pool.o
+blk-snap-y += blk_redirect.o
+blk-snap-y += blk_util.o
+blk-snap-y += cbt_map.o
+blk-snap-y += ctrl_fops.o
+blk-snap-y += ctrl_pipe.o
+blk-snap-y += ctrl_sysfs.o
+blk-snap-y += defer_io.o
+blk-snap-y += filter.o
+blk-snap-y += main.o
+blk-snap-y += params.o
+blk-snap-y += big_buffer.o
+blk-snap-y += rangevector.o
+blk-snap-y += snapimage.o
+blk-snap-y += snapshot.o
+blk-snap-y += snapstore.o
+blk-snap-y += snapstore_device.o
+blk-snap-y += snapstore_file.o
+blk-snap-y += snapstore_mem.o
+blk-snap-y += snapstore_multidev.o
+blk-snap-y += tracker.o
+blk-snap-y += tracking.o
+
+obj-$(CONFIG_BLK_SNAP)	 += blk-snap.o
diff --git a/drivers/block/blk-snap/big_buffer.c b/drivers/block/blk-snap/big_buffer.c
new file mode 100644
index 000000000000..c0a75255a807
--- /dev/null
+++ b/drivers/block/blk-snap/big_buffer.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include <linux/mm.h>
+#include "big_buffer.h"
+
+static inline size_t page_count_calc(size_t buffer_size)
+{
+	size_t page_count = buffer_size / PAGE_SIZE;
+
+	if (buffer_size & (PAGE_SIZE - 1))
+		page_count += 1;
+	return page_count;
+}
+
+struct big_buffer *big_buffer_alloc(size_t buffer_size, int gfp_opt)
+{
+	int res = SUCCESS;
+	struct big_buffer *bbuff;
+	size_t count;
+	size_t inx;
+
+	count = page_count_calc(buffer_size);
+
+	bbuff = kzalloc(sizeof(struct big_buffer) + count * sizeof(void *), gfp_opt);
+	if (bbuff == NULL)
+		return NULL;
+
+	bbuff->pg_cnt = count;
+	for (inx = 0; inx < bbuff->pg_cnt; ++inx) {
+		struct page *pg = alloc_page(gfp_opt);
+
+		if (!pg) {
+			res = -ENOMEM;
+			break;
+		}
+		bbuff->pg[inx] = page_address(pg);
+	}
+
+	if (res != SUCCESS) {
+		big_buffer_free(bbuff);
+		return NULL;
+	}
+
+	return bbuff;
+}
+
+void big_buffer_free(struct big_buffer *bbuff)
+{
+	size_t inx;
+	size_t count = bbuff->pg_cnt;
+
+	if (bbuff == NULL)
+		return;
+
+	for (inx = 0; inx < count; ++inx)
+		if (bbuff->pg[inx] != NULL)
+			free_page((unsigned long)bbuff->pg[inx]);
+
+	kfree(bbuff);
+}
+
+size_t big_buffer_copy_to_user(char __user *dst_user, size_t offset, struct big_buffer *bbuff,
+			       size_t length)
+{
+	size_t left_data_length;
+	int page_inx = offset / PAGE_SIZE;
+	size_t processed_len = 0;
+	size_t unordered = offset & (PAGE_SIZE - 1);
+
+	if (unordered != 0) { //first
+		size_t page_len = min_t(size_t, (PAGE_SIZE - unordered), length);
+
+		left_data_length = copy_to_user(dst_user + processed_len,
+						bbuff->pg[page_inx] + unordered, page_len);
+		if (left_data_length != 0) {
+			pr_err("Failed to copy data from big_buffer to user buffer\n");
+			return processed_len;
+		}
+
+		++page_inx;
+		processed_len += page_len;
+	}
+
+	while ((processed_len < length) && (page_inx < bbuff->pg_cnt)) {
+		size_t page_len = min_t(size_t, PAGE_SIZE, (length - processed_len));
+
+		left_data_length =
+			copy_to_user(dst_user + processed_len, bbuff->pg[page_inx], page_len);
+		if (left_data_length != 0) {
+			pr_err("Failed to copy data from big_buffer to user buffer\n");
+			break;
+		}
+
+		++page_inx;
+		processed_len += page_len;
+	}
+
+	return processed_len;
+}
+
+size_t big_buffer_copy_from_user(const char __user *src_user, size_t offset,
+				 struct big_buffer *bbuff, size_t length)
+{
+	size_t left_data_length;
+	int page_inx = offset / PAGE_SIZE;
+	size_t processed_len = 0;
+	size_t unordered = offset & (PAGE_SIZE - 1);
+
+	if (unordered != 0) { //first
+		size_t page_len = min_t(size_t, (PAGE_SIZE - unordered), length);
+
+		left_data_length = copy_from_user(bbuff->pg[page_inx] + unordered,
+						  src_user + processed_len, page_len);
+		if (left_data_length != 0) {
+			pr_err("Failed to copy data from user buffer to big_buffer\n");
+			return processed_len;
+		}
+
+		++page_inx;
+		processed_len += page_len;
+	}
+
+	while ((processed_len < length) && (page_inx < bbuff->pg_cnt)) {
+		size_t page_len = min_t(size_t, PAGE_SIZE, (length - processed_len));
+
+		left_data_length =
+			copy_from_user(bbuff->pg[page_inx], src_user + processed_len, page_len);
+		if (left_data_length != 0) {
+			pr_err("Failed to copy data from user buffer to big_buffer\n");
+			break;
+		}
+
+		++page_inx;
+		processed_len += page_len;
+	}
+
+	return processed_len;
+}
+
+void *big_buffer_get_element(struct big_buffer *bbuff, size_t index, size_t sizeof_element)
+{
+	size_t elements_in_page = PAGE_SIZE / sizeof_element;
+	size_t pg_inx = index / elements_in_page;
+	size_t pg_ofs = (index - (pg_inx * elements_in_page)) * sizeof_element;
+
+	if (pg_inx >= bbuff->pg_cnt)
+		return NULL;
+
+	return bbuff->pg[pg_inx] + pg_ofs;
+}
+
+void big_buffer_memset(struct big_buffer *bbuff, int value)
+{
+	size_t inx;
+
+	for (inx = 0; inx < bbuff->pg_cnt; ++inx)
+		memset(bbuff->pg[inx], value, PAGE_SIZE);
+}
+
+void big_buffer_memcpy(struct big_buffer *dst, struct big_buffer *src)
+{
+	size_t inx;
+	size_t count = min_t(size_t, dst->pg_cnt, src->pg_cnt);
+
+	for (inx = 0; inx < count; ++inx)
+		memcpy(dst->pg[inx], src->pg[inx], PAGE_SIZE);
+}
+
+int big_buffer_byte_get(struct big_buffer *bbuff, size_t inx, u8 *value)
+{
+	size_t page_inx = inx >> PAGE_SHIFT;
+	size_t byte_pos = inx & (PAGE_SIZE - 1);
+
+	if (page_inx >= bbuff->pg_cnt)
+		return -ENODATA;
+
+	*value = bbuff->pg[page_inx][byte_pos];
+
+	return SUCCESS;
+}
+
+int big_buffer_byte_set(struct big_buffer *bbuff, size_t inx, u8 value)
+{
+	size_t page_inx = inx >> PAGE_SHIFT;
+	size_t byte_pos = inx & (PAGE_SIZE - 1);
+
+	if (page_inx >= bbuff->pg_cnt)
+		return -ENODATA;
+
+	bbuff->pg[page_inx][byte_pos] = value;
+
+	return SUCCESS;
+}
diff --git a/drivers/block/blk-snap/big_buffer.h b/drivers/block/blk-snap/big_buffer.h
new file mode 100644
index 000000000000..f38ab5288b05
--- /dev/null
+++ b/drivers/block/blk-snap/big_buffer.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+struct big_buffer {
+	size_t pg_cnt;
+	u8 *pg[0];
+};
+
+struct big_buffer *big_buffer_alloc(size_t count, int gfp_opt);
+void big_buffer_free(struct big_buffer *bbuff);
+
+size_t big_buffer_copy_to_user(char __user *dst_user_buffer, size_t offset,
+			       struct big_buffer *bbuff, size_t length);
+size_t big_buffer_copy_from_user(const char __user *src_user_buffer, size_t offset,
+				 struct big_buffer *bbuff, size_t length);
+
+void *big_buffer_get_element(struct big_buffer *bbuff, size_t index, size_t sizeof_element);
+
+void big_buffer_memset(struct big_buffer *bbuff, int value);
+void big_buffer_memcpy(struct big_buffer *dst, struct big_buffer *src);
+
+//byte access
+int big_buffer_byte_get(struct big_buffer *bbuff, size_t inx, u8 *value);
+int big_buffer_byte_set(struct big_buffer *bbuff, size_t inx, u8 value);
diff --git a/drivers/block/blk-snap/blk-snap-ctl.h b/drivers/block/blk-snap/blk-snap-ctl.h
new file mode 100644
index 000000000000..d66f43573320
--- /dev/null
+++ b/drivers/block/blk-snap/blk-snap-ctl.h
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#define MODULE_NAME "blk-snap"
+#define SNAP_IMAGE_NAME "blk-snap-image"
+
+#define SUCCESS 0
+
+#define MAX_TRACKING_DEVICE_COUNT 256
+
+#define BLK_SNAP 0x56730000 // 'sV' <-> "Vs"
+
+#pragma pack(push, 1)
+//////////////////////////////////////////////////////////////////////////
+// version
+
+#define BLK_SNAP_COMPATIBILITY_SNAPSTORE 0x0000000000000001ull /* rudiment */
+//#define BLK_SNAP_COMPATIBILITY_BTRFS	 0x0000000000000002ull /* rudiment */
+#define BLK_SNAP_COMPATIBILITY_MULTIDEV 0x0000000000000004ull
+
+struct ioctl_compatibility_flags_s {
+	unsigned long long flags;
+};
+#define IOCTL_COMPATIBILITY_FLAGS _IOW(BLK_SNAP, 0, struct ioctl_compatibility_flags_s)
+
+struct ioctl_getversion_s {
+	unsigned short major;
+	unsigned short minor;
+	unsigned short revision;
+	unsigned short build;
+};
+#define IOCTL_GETVERSION _IOW(BLK_SNAP, 1, struct ioctl_getversion_s)
+
+//////////////////////////////////////////////////////////////////////////
+// tracking
+struct ioctl_dev_id_s {
+	int major;
+	int minor;
+};
+#define IOCTL_TRACKING_ADD _IOW(BLK_SNAP, 2, struct ioctl_dev_id_s)
+
+#define IOCTL_TRACKING_REMOVE _IOW(BLK_SNAP, 3, struct ioctl_dev_id_s)
+
+struct cbt_info_s {
+	struct ioctl_dev_id_s dev_id;
+	unsigned long long dev_capacity;
+	unsigned int cbt_map_size;
+	unsigned char snap_number;
+	unsigned char generationId[16];
+};
+struct ioctl_tracking_collect_s {
+	unsigned int count;
+	union {
+		struct cbt_info_s *p_cbt_info;
+		unsigned long long ull_cbt_info;
+	};
+};
+#define IOCTL_TRACKING_COLLECT _IOW(BLK_SNAP, 4, struct ioctl_tracking_collect_s)
+
+#define IOCTL_TRACKING_BLOCK_SIZE _IOW(BLK_SNAP, 5, unsigned int)
+
+struct ioctl_tracking_read_cbt_bitmap_s {
+	struct ioctl_dev_id_s dev_id;
+	unsigned int offset;
+	unsigned int length;
+	union {
+		unsigned char *buff;
+		unsigned long long ull_buff;
+	};
+};
+#define IOCTL_TRACKING_READ_CBT_BITMAP _IOR(BLK_SNAP, 6, struct ioctl_tracking_read_cbt_bitmap_s)
+
+struct block_range_s {
+	unsigned long long ofs; //sectors
+	unsigned long long cnt; //sectors
+};
+
+struct ioctl_tracking_mark_dirty_blocks_s {
+	struct ioctl_dev_id_s image_dev_id;
+	unsigned int count;
+	union {
+		struct block_range_s *p_dirty_blocks;
+		unsigned long long ull_dirty_blocks;
+	};
+};
+#define IOCTL_TRACKING_MARK_DIRTY_BLOCKS                                                           \
+	_IOR(BLK_SNAP, 7, struct ioctl_tracking_mark_dirty_blocks_s)
+//////////////////////////////////////////////////////////////////////////
+// snapshot
+
+struct ioctl_snapshot_create_s {
+	unsigned long long snapshot_id;
+	unsigned int count;
+	union {
+		struct ioctl_dev_id_s *p_dev_id;
+		unsigned long long ull_dev_id;
+	};
+};
+#define IOCTL_SNAPSHOT_CREATE _IOW(BLK_SNAP, 0x10, struct ioctl_snapshot_create_s)
+
+#define IOCTL_SNAPSHOT_DESTROY _IOR(BLK_SNAP, 0x11, unsigned long long)
+
+struct ioctl_snapshot_errno_s {
+	struct ioctl_dev_id_s dev_id;
+	int err_code;
+};
+#define IOCTL_SNAPSHOT_ERRNO _IOW(BLK_SNAP, 0x12, struct ioctl_snapshot_errno_s)
+
+struct ioctl_range_s {
+	unsigned long long left;
+	unsigned long long right;
+};
+
+//////////////////////////////////////////////////////////////////////////
+// snapstore
+struct ioctl_snapstore_create_s {
+	unsigned char id[16];
+	struct ioctl_dev_id_s snapstore_dev_id;
+	unsigned int count;
+	union {
+		struct ioctl_dev_id_s *p_dev_id;
+		unsigned long long ull_dev_id;
+	};
+};
+#define IOCTL_SNAPSTORE_CREATE _IOR(BLK_SNAP, 0x28, struct ioctl_snapstore_create_s)
+
+struct ioctl_snapstore_file_add_s {
+	unsigned char id[16];
+	unsigned int range_count;
+	union {
+		struct ioctl_range_s *ranges;
+		unsigned long long ull_ranges;
+	};
+};
+#define IOCTL_SNAPSTORE_FILE _IOR(BLK_SNAP, 0x29, struct ioctl_snapstore_file_add_s)
+
+struct ioctl_snapstore_memory_limit_s {
+	unsigned char id[16];
+	unsigned long long size;
+};
+#define IOCTL_SNAPSTORE_MEMORY _IOR(BLK_SNAP, 0x2A, struct ioctl_snapstore_memory_limit_s)
+
+struct ioctl_snapstore_cleanup_s {
+	unsigned char id[16];
+	unsigned long long filled_bytes;
+};
+#define IOCTL_SNAPSTORE_CLEANUP _IOW(BLK_SNAP, 0x2B, struct ioctl_snapstore_cleanup_s)
+
+struct ioctl_snapstore_file_add_multidev_s {
+	unsigned char id[16];
+	struct ioctl_dev_id_s dev_id;
+	unsigned int range_count;
+	union {
+		struct ioctl_range_s *ranges;
+		unsigned long long ull_ranges;
+	};
+};
+#define IOCTL_SNAPSTORE_FILE_MULTIDEV                                                              \
+	_IOR(BLK_SNAP, 0x2C, struct ioctl_snapstore_file_add_multidev_s)
+//////////////////////////////////////////////////////////////////////////
+// collect snapshot images
+
+struct image_info_s {
+	struct ioctl_dev_id_s original_dev_id;
+	struct ioctl_dev_id_s snapshot_dev_id;
+};
+
+struct ioctl_collect_snapshot_images_s {
+	int count; //
+	union {
+		struct image_info_s *p_image_info;
+		unsigned long long ull_image_info;
+	};
+};
+#define IOCTL_COLLECT_SNAPSHOT_IMAGES _IOW(BLK_SNAP, 0x30, struct ioctl_collect_snapshot_images_s)
+
+#pragma pack(pop)
+
+// commands for character device interface
+#define BLK_SNAP_CHARCMD_UNDEFINED 0x00
+#define BLK_SNAP_CHARCMD_ACKNOWLEDGE 0x01
+#define BLK_SNAP_CHARCMD_INVALID 0xFF
+// to module commands
+#define BLK_SNAP_CHARCMD_INITIATE 0x21
+#define BLK_SNAP_CHARCMD_NEXT_PORTION 0x22
+#define BLK_SNAP_CHARCMD_NEXT_PORTION_MULTIDEV 0x23
+// from module commands
+#define BLK_SNAP_CHARCMD_HALFFILL 0x41
+#define BLK_SNAP_CHARCMD_OVERFLOW 0x42
+#define BLK_SNAP_CHARCMD_TERMINATE 0x43
diff --git a/drivers/block/blk-snap/blk_deferred.c b/drivers/block/blk-snap/blk_deferred.c
new file mode 100644
index 000000000000..031b71ac1da4
--- /dev/null
+++ b/drivers/block/blk-snap/blk_deferred.c
@@ -0,0 +1,566 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-deferred"
+#include "common.h"
+
+#include "blk_deferred.h"
+#include "blk_util.h"
+#include "snapstore.h"
+#include "params.h"
+
+struct bio_set blk_deferred_bioset = { 0 };
+
+struct dio_bio_complete {
+	struct blk_deferred_request *dio_req;
+	sector_t bio_sect_len;
+};
+
+struct dio_deadlocked_list {
+	struct list_head link;
+
+	struct blk_deferred_request *dio_req;
+};
+
+LIST_HEAD(dio_deadlocked_list);
+DEFINE_RWLOCK(dio_deadlocked_list_lock);
+
+atomic64_t dio_alloc_count = ATOMIC64_INIT(0);
+atomic64_t dio_free_count = ATOMIC64_INIT(0);
+
+void blk_deferred_done(void)
+{
+	struct dio_deadlocked_list *dio_locked;
+
+	do {
+		dio_locked = NULL;
+
+		write_lock(&dio_deadlocked_list_lock);
+		if (!list_empty(&dio_deadlocked_list)) {
+			dio_locked = list_entry(dio_deadlocked_list.next,
+						struct dio_deadlocked_list, link);
+
+			list_del(&dio_locked->link);
+		}
+		write_unlock(&dio_deadlocked_list_lock);
+
+		if (dio_locked) {
+			if (dio_locked->dio_req->sect_len ==
+			    atomic64_read(&dio_locked->dio_req->sect_processed))
+				blk_deferred_request_free(dio_locked->dio_req);
+			else
+				pr_err("Locked defer IO is still in memory\n");
+
+			kfree(dio_locked);
+		}
+	} while (dio_locked);
+}
+
+void blk_deferred_request_deadlocked(struct blk_deferred_request *dio_req)
+{
+	struct dio_deadlocked_list *dio_locked =
+		kzalloc(sizeof(struct dio_deadlocked_list), GFP_KERNEL);
+
+	dio_locked->dio_req = dio_req;
+
+	write_lock(&dio_deadlocked_list_lock);
+	list_add_tail(&dio_locked->link, &dio_deadlocked_list);
+	write_unlock(&dio_deadlocked_list_lock);
+
+	pr_warn("Deadlock with defer IO\n");
+}
+
+void blk_deferred_free(struct blk_deferred_io *dio)
+{
+	size_t inx = 0;
+
+	if (dio->page_array != NULL) {
+		while (dio->page_array[inx] != NULL) {
+			__free_page(dio->page_array[inx]);
+			dio->page_array[inx] = NULL;
+
+			++inx;
+		}
+
+		kfree(dio->page_array);
+		dio->page_array = NULL;
+	}
+	kfree(dio);
+}
+
+struct blk_deferred_io *blk_deferred_alloc(unsigned long block_index,
+					   union blk_descr_unify blk_descr)
+{
+	size_t inx;
+	size_t page_count;
+	struct blk_deferred_io *dio = kmalloc(sizeof(struct blk_deferred_io), GFP_NOIO);
+
+	if (dio == NULL)
+		return NULL;
+
+	INIT_LIST_HEAD(&dio->link);
+
+	dio->blk_descr = blk_descr;
+	dio->blk_index = block_index;
+
+	dio->sect.ofs = block_index << snapstore_block_shift();
+	dio->sect.cnt = snapstore_block_size();
+
+	page_count = snapstore_block_size() / (PAGE_SIZE / SECTOR_SIZE);
+	/*
+	 * empty pointer on the end
+	 */
+	dio->page_array = kzalloc((page_count + 1) * sizeof(struct page *), GFP_NOIO);
+	if (dio->page_array == NULL) {
+		blk_deferred_free(dio);
+		return NULL;
+	}
+
+	for (inx = 0; inx < page_count; inx++) {
+		dio->page_array[inx] = alloc_page(GFP_NOIO);
+		if (dio->page_array[inx] == NULL) {
+			pr_err("Failed to allocate page\n");
+			blk_deferred_free(dio);
+			return NULL;
+		}
+	}
+
+	return dio;
+}
+
+int blk_deferred_bioset_create(void)
+{
+	return bioset_init(&blk_deferred_bioset, 64, sizeof(struct dio_bio_complete),
+			   BIOSET_NEED_BVECS | BIOSET_NEED_RESCUER);
+}
+
+void blk_deferred_bioset_free(void)
+{
+	bioset_exit(&blk_deferred_bioset);
+}
+
+struct bio *_blk_deferred_bio_alloc(int nr_iovecs)
+{
+	struct bio *new_bio = bio_alloc_bioset(GFP_NOIO, nr_iovecs, &blk_deferred_bioset);
+
+	if (new_bio == NULL)
+		return NULL;
+
+	new_bio->bi_end_io = blk_deferred_bio_endio;
+	new_bio->bi_private = ((void *)new_bio) - sizeof(struct dio_bio_complete);
+
+	return new_bio;
+}
+
+static void blk_deferred_complete(struct blk_deferred_request *dio_req, sector_t portion_sect_cnt,
+				  int result)
+{
+	atomic64_add(portion_sect_cnt, &dio_req->sect_processed);
+
+	if (dio_req->sect_len == atomic64_read(&dio_req->sect_processed))
+		complete(&dio_req->complete);
+
+	if (result != SUCCESS) {
+		dio_req->result = result;
+		pr_err("Failed to process defer IO request. errno=%d\n", result);
+	}
+}
+
+void blk_deferred_bio_endio(struct bio *bio)
+{
+	int local_err;
+	struct dio_bio_complete *complete_param = (struct dio_bio_complete *)bio->bi_private;
+
+	if (complete_param == NULL) {
+		//bio already complete
+	} else {
+		if (bio->bi_status != BLK_STS_OK)
+			local_err = -EIO;
+		else
+			local_err = SUCCESS;
+
+		blk_deferred_complete(complete_param->dio_req, complete_param->bio_sect_len,
+				      local_err);
+		bio->bi_private = NULL;
+	}
+
+	bio_put(bio);
+}
+
+static inline size_t _page_count_calculate(sector_t size_sector)
+{
+	size_t page_count = size_sector / (PAGE_SIZE / SECTOR_SIZE);
+
+	if (unlikely(size_sector & ((PAGE_SIZE / SECTOR_SIZE) - 1)))
+		page_count += 1;
+
+	return page_count;
+}
+
+sector_t _blk_deferred_submit_pages(struct block_device *blk_dev,
+				    struct blk_deferred_request *dio_req, int direction,
+				    sector_t arr_ofs, struct page **page_array, sector_t ofs_sector,
+				    sector_t size_sector)
+{
+	struct bio *bio = NULL;
+	int nr_iovecs;
+	int page_inx = arr_ofs >> (PAGE_SHIFT - SECTOR_SHIFT);
+	sector_t process_sect = 0;
+
+	nr_iovecs = _page_count_calculate(size_sector);
+
+	while (NULL == (bio = _blk_deferred_bio_alloc(nr_iovecs))) {
+		size_sector = (size_sector >> 1) & ~((PAGE_SIZE / SECTOR_SIZE) - 1);
+		if (size_sector == 0)
+			return 0;
+
+		nr_iovecs = _page_count_calculate(size_sector);
+	}
+
+	bio_set_dev(bio, blk_dev);
+
+	if (direction == READ)
+		bio_set_op_attrs(bio, REQ_OP_READ, 0);
+	else
+		bio_set_op_attrs(bio, REQ_OP_WRITE, 0);
+
+	bio->bi_iter.bi_sector = ofs_sector;
+
+	{ //add first
+		sector_t unordered = arr_ofs & ((PAGE_SIZE / SECTOR_SIZE) - 1);
+		sector_t bvec_len_sect =
+			min_t(sector_t, ((PAGE_SIZE / SECTOR_SIZE) - unordered), size_sector);
+		struct page *page = page_array[page_inx];
+		unsigned int len = (unsigned int)from_sectors(bvec_len_sect);
+		unsigned int offset = (unsigned int)from_sectors(unordered);
+
+		if (unlikely(page == NULL)) {
+			pr_err("NULL found in page array");
+			bio_put(bio);
+			return 0;
+		}
+		if (unlikely(bio_add_page(bio, page, len, offset) != len)) {
+			bio_put(bio);
+			return 0;
+		}
+		++page_inx;
+		process_sect += bvec_len_sect;
+	}
+
+	while (process_sect < size_sector) {
+		sector_t bvec_len_sect =
+			min_t(sector_t, (PAGE_SIZE / SECTOR_SIZE), (size_sector - process_sect));
+		struct page *page = page_array[page_inx];
+		unsigned int len = (unsigned int)from_sectors(bvec_len_sect);
+
+
+		if (unlikely(page == NULL)) {
+			pr_err("NULL found in page array");
+			break;
+		}
+		if (unlikely(bio_add_page(bio, page, len, 0) != len))
+			break;
+
+		++page_inx;
+		process_sect += bvec_len_sect;
+	}
+
+	((struct dio_bio_complete *)bio->bi_private)->dio_req = dio_req;
+	((struct dio_bio_complete *)bio->bi_private)->bio_sect_len = process_sect;
+
+	submit_bio(bio);
+
+	return process_sect;
+}
+
+sector_t blk_deferred_submit_pages(struct block_device *blk_dev,
+				   struct blk_deferred_request *dio_req, int direction,
+				   sector_t arr_ofs, struct page **page_array, sector_t ofs_sector,
+				   sector_t size_sector)
+{
+	sector_t process_sect = 0;
+
+	do {
+		sector_t portion_sect = _blk_deferred_submit_pages(
+			blk_dev, dio_req, direction, arr_ofs + process_sect, page_array,
+			ofs_sector + process_sect, size_sector - process_sect);
+		if (portion_sect == 0) {
+			pr_err("Failed to submit defer IO pages. Only [%lld] sectors processed\n",
+			       process_sect);
+			break;
+		}
+		process_sect += portion_sect;
+	} while (process_sect < size_sector);
+
+	return process_sect;
+}
+
+struct blk_deferred_request *blk_deferred_request_new(void)
+{
+	struct blk_deferred_request *dio_req = NULL;
+
+	dio_req = kzalloc(sizeof(struct blk_deferred_request), GFP_NOIO);
+	if (dio_req == NULL)
+		return NULL;
+
+	INIT_LIST_HEAD(&dio_req->dios);
+
+	dio_req->result = SUCCESS;
+	atomic64_set(&dio_req->sect_processed, 0);
+	dio_req->sect_len = 0;
+	init_completion(&dio_req->complete);
+
+	return dio_req;
+}
+
+bool blk_deferred_request_already_added(struct blk_deferred_request *dio_req,
+					unsigned long block_index)
+{
+	bool result = false;
+	struct list_head *_list_head;
+
+	if (list_empty(&dio_req->dios))
+		return result;
+
+	list_for_each(_list_head, &dio_req->dios) {
+		struct blk_deferred_io *dio = list_entry(_list_head, struct blk_deferred_io, link);
+
+		if (dio->blk_index == block_index) {
+			result = true;
+			break;
+		}
+	}
+
+	return result;
+}
+
+int blk_deferred_request_add(struct blk_deferred_request *dio_req, struct blk_deferred_io *dio)
+{
+	list_add_tail(&dio->link, &dio_req->dios);
+	dio_req->sect_len += dio->sect.cnt;
+
+	return SUCCESS;
+}
+
+void blk_deferred_request_free(struct blk_deferred_request *dio_req)
+{
+	if (dio_req != NULL) {
+		while (!list_empty(&dio_req->dios)) {
+			struct blk_deferred_io *dio =
+				list_entry(dio_req->dios.next, struct blk_deferred_io, link);
+
+			list_del(&dio->link);
+
+			blk_deferred_free(dio);
+		}
+		kfree(dio_req);
+	}
+}
+
+void blk_deferred_request_waiting_skip(struct blk_deferred_request *dio_req)
+{
+	init_completion(&dio_req->complete);
+	atomic64_set(&dio_req->sect_processed, 0);
+}
+
+int blk_deferred_request_wait(struct blk_deferred_request *dio_req)
+{
+	u64 start_jiffies = get_jiffies_64();
+	u64 current_jiffies;
+
+	while (wait_for_completion_timeout(&dio_req->complete, (HZ * 1)) == 0) {
+		current_jiffies = get_jiffies_64();
+		if (jiffies_to_msecs(current_jiffies - start_jiffies) > 60 * 1000) {
+			pr_warn("Defer IO request timeout\n");
+			return -EDEADLK;
+		}
+	}
+
+	return dio_req->result;
+}
+
+int blk_deferred_request_read_original(struct block_device *original_blk_dev,
+				       struct blk_deferred_request *dio_copy_req)
+{
+	int res = -ENODATA;
+	struct list_head *_list_head;
+
+	blk_deferred_request_waiting_skip(dio_copy_req);
+
+	if (list_empty(&dio_copy_req->dios))
+		return res;
+
+	list_for_each(_list_head, &dio_copy_req->dios) {
+		struct blk_deferred_io *dio = list_entry(_list_head, struct blk_deferred_io, link);
+
+		sector_t ofs = dio->sect.ofs;
+		sector_t cnt = dio->sect.cnt;
+
+		if (cnt != blk_deferred_submit_pages(original_blk_dev, dio_copy_req, READ, 0,
+						     dio->page_array, ofs, cnt)) {
+			pr_err("Failed to submit reading defer IO request. offset=%lld\n",
+			       dio->sect.ofs);
+			res = -EIO;
+			break;
+		}
+		res = SUCCESS;
+	}
+
+	if (res == SUCCESS)
+		res = blk_deferred_request_wait(dio_copy_req);
+
+	return res;
+}
+
+
+static int _store_file(struct block_device *blk_dev, struct blk_deferred_request *dio_copy_req,
+		       struct blk_descr_file *blk_descr, struct page **page_array)
+{
+	struct list_head *_rangelist_head;
+	sector_t page_array_ofs = 0;
+
+	if (unlikely(list_empty(&blk_descr->rangelist))) {
+		pr_err("Invalid block descriptor");
+		return -EINVAL;
+	}
+	list_for_each(_rangelist_head, &blk_descr->rangelist) {
+		struct blk_range_link *range_link;
+		sector_t process_sect;
+
+		range_link = list_entry(_rangelist_head, struct blk_range_link, link);
+		process_sect = blk_deferred_submit_pages(blk_dev, dio_copy_req, WRITE,
+							 page_array_ofs, page_array,
+							 range_link->rg.ofs, range_link->rg.cnt);
+		if (range_link->rg.cnt != process_sect) {
+			pr_err("Failed to submit defer IO request for storing\n");
+			return -EIO;
+		}
+		page_array_ofs += range_link->rg.cnt;
+	}
+	return SUCCESS;
+}
+
+int blk_deferred_request_store_file(struct block_device *blk_dev,
+				    struct blk_deferred_request *dio_copy_req)
+{
+	struct list_head *_dio_list_head;
+
+	blk_deferred_request_waiting_skip(dio_copy_req);
+
+	if (unlikely(list_empty(&dio_copy_req->dios))) {
+		pr_err("Invalid deferred io request");
+		return -EINVAL;
+	}
+	list_for_each(_dio_list_head, &dio_copy_req->dios) {
+		int res;
+		struct blk_deferred_io *dio;
+
+		dio = list_entry(_dio_list_head, struct blk_deferred_io, link);
+		res = _store_file(blk_dev, dio_copy_req, dio->blk_descr.file, dio->page_array);
+		if (res != SUCCESS)
+			return res;
+	}
+
+	return blk_deferred_request_wait(dio_copy_req);
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+
+static int _store_multidev(struct blk_deferred_request *dio_copy_req,
+			   struct blk_descr_multidev *blk_descr, struct page **page_array)
+{
+	struct list_head *_ranges_list_head;
+	sector_t page_array_ofs = 0;
+
+	if (unlikely(list_empty(&blk_descr->rangelist))) {
+		pr_err("Invalid block descriptor");
+		return -EINVAL;
+	}
+	list_for_each(_ranges_list_head, &blk_descr->rangelist) {
+		sector_t process_sect;
+		struct blk_range_link_ex *range_link;
+
+		range_link = list_entry(_ranges_list_head, struct blk_range_link_ex, link);
+		process_sect = blk_deferred_submit_pages(range_link->blk_dev, dio_copy_req, WRITE,
+							 page_array_ofs, page_array,
+							 range_link->rg.ofs, range_link->rg.cnt);
+		if (range_link->rg.cnt != process_sect) {
+			pr_err("Failed to submit defer IO request for storing\n");
+			return -EIO;
+		}
+
+		page_array_ofs += range_link->rg.cnt;
+	}
+
+	return SUCCESS;
+}
+
+int blk_deferred_request_store_multidev(struct blk_deferred_request *dio_copy_req)
+{
+	struct list_head *_dio_list_head;
+
+	blk_deferred_request_waiting_skip(dio_copy_req);
+
+	if (unlikely(list_empty(&dio_copy_req->dios))) {
+		pr_err("Invalid deferred io request");
+		return -EINVAL;
+	}
+	list_for_each(_dio_list_head, &dio_copy_req->dios) {
+		int res;
+		struct blk_deferred_io *dio;
+
+		dio = list_entry(_dio_list_head, struct blk_deferred_io, link);
+		res = _store_multidev(dio_copy_req, dio->blk_descr.multidev, dio->page_array);
+		if (res != SUCCESS)
+			return res;
+	}
+
+	return blk_deferred_request_wait(dio_copy_req);
+}
+#endif
+
+static size_t _store_pages(void *dst, struct page **page_array, size_t length)
+{
+	size_t page_inx = 0;
+	size_t processed_len = 0;
+
+	while ((processed_len < length) && (page_array[page_inx] != NULL)) {
+		void *src;
+		size_t page_len = min_t(size_t, PAGE_SIZE, (length - processed_len));
+
+		src = page_address(page_array[page_inx]);
+		memcpy(dst + processed_len, src, page_len);
+
+		++page_inx;
+		processed_len += page_len;
+	}
+
+	return processed_len;
+}
+
+int blk_deferred_request_store_mem(struct blk_deferred_request *dio_copy_req)
+{
+	int res = SUCCESS;
+	sector_t processed = 0;
+
+	if (!list_empty(&dio_copy_req->dios)) {
+		struct list_head *_list_head;
+
+		list_for_each(_list_head, &dio_copy_req->dios) {
+			size_t length;
+			size_t portion;
+			struct blk_deferred_io *dio;
+
+			dio = list_entry(_list_head, struct blk_deferred_io, link);
+			length = snapstore_block_size() * SECTOR_SIZE;
+
+			portion = _store_pages(dio->blk_descr.mem->buff, dio->page_array, length);
+			if (unlikely(portion != length)) {
+				res = -EIO;
+				break;
+			}
+			processed += (sector_t)to_sectors(portion);
+		}
+	}
+
+	blk_deferred_complete(dio_copy_req, processed, res);
+	return res;
+}
diff --git a/drivers/block/blk-snap/blk_deferred.h b/drivers/block/blk-snap/blk_deferred.h
new file mode 100644
index 000000000000..3c516a835c25
--- /dev/null
+++ b/drivers/block/blk-snap/blk_deferred.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include "blk_descr_file.h"
+#include "blk_descr_mem.h"
+#include "blk_descr_multidev.h"
+
+#define DEFER_IO_DIO_REQUEST_LENGTH 250
+#define DEFER_IO_DIO_REQUEST_SECTORS_COUNT (10 * 1024 * 1024 / SECTOR_SIZE)
+
+struct blk_deferred_io {
+	struct list_head link;
+
+	unsigned long blk_index;
+	union blk_descr_unify blk_descr;
+
+	struct blk_range sect;
+
+	struct page **page_array; //null pointer on tail
+};
+
+struct blk_deferred_request {
+	struct completion complete;
+	sector_t sect_len;
+	atomic64_t sect_processed;
+	int result;
+
+	struct list_head dios;
+};
+
+void blk_deferred_done(void);
+
+struct blk_deferred_io *blk_deferred_alloc(unsigned long block_index,
+					   union blk_descr_unify blk_descr);
+void blk_deferred_free(struct blk_deferred_io *dio);
+
+void blk_deferred_bio_endio(struct bio *bio);
+
+sector_t blk_deferred_submit_pages(struct block_device *blk_dev,
+				   struct blk_deferred_request *dio_req, int direction,
+				   sector_t arr_ofs, struct page **page_array, sector_t ofs_sector,
+				   sector_t size_sector);
+
+struct blk_deferred_request *blk_deferred_request_new(void);
+
+bool blk_deferred_request_already_added(struct blk_deferred_request *dio_req,
+					unsigned long block_index);
+
+int blk_deferred_request_add(struct blk_deferred_request *dio_req, struct blk_deferred_io *dio);
+void blk_deferred_request_free(struct blk_deferred_request *dio_req);
+void blk_deferred_request_deadlocked(struct blk_deferred_request *dio_req);
+
+void blk_deferred_request_waiting_skip(struct blk_deferred_request *dio_req);
+int blk_deferred_request_wait(struct blk_deferred_request *dio_req);
+
+int blk_deferred_bioset_create(void);
+void blk_deferred_bioset_free(void);
+
+int blk_deferred_request_read_original(struct block_device *original_blk_dev,
+				       struct blk_deferred_request *dio_copy_req);
+
+int blk_deferred_request_store_file(struct block_device *blk_dev,
+				    struct blk_deferred_request *dio_copy_req);
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+int blk_deferred_request_store_multidev(struct blk_deferred_request *dio_copy_req);
+#endif
+int blk_deferred_request_store_mem(struct blk_deferred_request *dio_copy_req);
diff --git a/drivers/block/blk-snap/blk_descr_file.c b/drivers/block/blk-snap/blk_descr_file.c
new file mode 100644
index 000000000000..fca298d35744
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_file.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-blk_descr"
+#include "common.h"
+
+#include "blk_descr_file.h"
+
+static inline void list_assign(struct list_head *dst, struct list_head *src)
+{
+	dst->next = src->next;
+	dst->prev = src->prev;
+
+	src->next->prev = dst;
+	src->prev->next = dst;
+}
+
+static inline void blk_descr_file_init(struct blk_descr_file *blk_descr,
+				       struct list_head *rangelist)
+{
+	list_assign(&blk_descr->rangelist, rangelist);
+}
+
+static inline void blk_descr_file_done(struct blk_descr_file *blk_descr)
+{
+	struct blk_range_link *range_link;
+
+	while (!list_empty(&blk_descr->rangelist)) {
+		range_link = list_entry(blk_descr->rangelist.next, struct blk_range_link, link);
+
+		list_del(&range_link->link);
+		kfree(range_link);
+	}
+}
+
+void blk_descr_file_pool_init(struct blk_descr_pool *pool)
+{
+	blk_descr_pool_init(pool, 0);
+}
+
+void _blk_descr_file_cleanup(void *descr_array, size_t count)
+{
+	size_t inx;
+	struct blk_descr_file *file_blocks = descr_array;
+
+	for (inx = 0; inx < count; ++inx)
+		blk_descr_file_done(file_blocks + inx);
+}
+
+void blk_descr_file_pool_done(struct blk_descr_pool *pool)
+{
+	blk_descr_pool_done(pool, _blk_descr_file_cleanup);
+}
+
+static union blk_descr_unify _blk_descr_file_allocate(void *descr_array, size_t index, void *arg)
+{
+	union blk_descr_unify blk_descr;
+	struct blk_descr_file *file_blocks = descr_array;
+
+	blk_descr.file = &file_blocks[index];
+
+	blk_descr_file_init(blk_descr.file, (struct list_head *)arg);
+
+	return blk_descr;
+}
+
+int blk_descr_file_pool_add(struct blk_descr_pool *pool, struct list_head *rangelist)
+{
+	union blk_descr_unify blk_descr;
+
+	blk_descr = blk_descr_pool_alloc(pool, sizeof(struct blk_descr_file),
+					 _blk_descr_file_allocate, (void *)rangelist);
+	if (blk_descr.ptr == NULL) {
+		pr_err("Failed to allocate block descriptor\n");
+		return -ENOMEM;
+	}
+
+	return SUCCESS;
+}
+
+union blk_descr_unify blk_descr_file_pool_take(struct blk_descr_pool *pool)
+{
+	return blk_descr_pool_take(pool, sizeof(struct blk_descr_file));
+}
diff --git a/drivers/block/blk-snap/blk_descr_file.h b/drivers/block/blk-snap/blk_descr_file.h
new file mode 100644
index 000000000000..0e9a5e3efdea
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_file.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include "blk_descr_pool.h"
+
+struct blk_descr_file {
+	struct list_head rangelist;
+};
+
+struct blk_range_link {
+	struct list_head link;
+	struct blk_range rg;
+};
+
+void blk_descr_file_pool_init(struct blk_descr_pool *pool);
+void blk_descr_file_pool_done(struct blk_descr_pool *pool);
+
+/*
+ * allocate new empty block in pool
+ */
+int blk_descr_file_pool_add(struct blk_descr_pool *pool, struct list_head *rangelist);
+
+/*
+ * take empty block from pool
+ */
+union blk_descr_unify blk_descr_file_pool_take(struct blk_descr_pool *pool);
diff --git a/drivers/block/blk-snap/blk_descr_mem.c b/drivers/block/blk-snap/blk_descr_mem.c
new file mode 100644
index 000000000000..cd326ac150b6
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_mem.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-blk_descr"
+#include "common.h"
+#include "blk_descr_mem.h"
+
+#define SECTION "blk_descr "
+
+static inline void blk_descr_mem_init(struct blk_descr_mem *blk_descr, void *ptr)
+{
+	blk_descr->buff = ptr;
+}
+
+static inline void blk_descr_mem_done(struct blk_descr_mem *blk_descr)
+{
+	blk_descr->buff = NULL;
+}
+
+void blk_descr_mem_pool_init(struct blk_descr_pool *pool, size_t available_blocks)
+{
+	blk_descr_pool_init(pool, available_blocks);
+}
+
+void blk_descr_mem_cleanup(void *descr_array, size_t count)
+{
+	size_t inx;
+	struct blk_descr_mem *mem_blocks = descr_array;
+
+	for (inx = 0; inx < count; ++inx)
+		blk_descr_mem_done(mem_blocks + inx);
+}
+
+void blk_descr_mem_pool_done(struct blk_descr_pool *pool)
+{
+	blk_descr_pool_done(pool, blk_descr_mem_cleanup);
+}
+
+static union blk_descr_unify blk_descr_mem_alloc(void *descr_array, size_t index, void *arg)
+{
+	union blk_descr_unify blk_descr;
+	struct blk_descr_mem *mem_blocks = descr_array;
+
+	blk_descr.mem = &mem_blocks[index];
+
+	blk_descr_mem_init(blk_descr.mem, (void *)arg);
+
+	return blk_descr;
+}
+
+int blk_descr_mem_pool_add(struct blk_descr_pool *pool, void *buffer)
+{
+	union blk_descr_unify blk_descr;
+
+	blk_descr = blk_descr_pool_alloc(pool, sizeof(struct blk_descr_mem),
+					 blk_descr_mem_alloc, buffer);
+	if (blk_descr.ptr == NULL) {
+		pr_err("Failed to allocate block descriptor\n");
+		return -ENOMEM;
+	}
+
+	return SUCCESS;
+}
+
+union blk_descr_unify blk_descr_mem_pool_take(struct blk_descr_pool *pool)
+{
+	return blk_descr_pool_take(pool, sizeof(struct blk_descr_mem));
+}
diff --git a/drivers/block/blk-snap/blk_descr_mem.h b/drivers/block/blk-snap/blk_descr_mem.h
new file mode 100644
index 000000000000..43e8de76c07c
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_mem.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include "blk_descr_pool.h"
+
+struct blk_descr_mem {
+	void *buff; //pointer to snapstore block in memory
+};
+
+void blk_descr_mem_pool_init(struct blk_descr_pool *pool, size_t available_blocks);
+void blk_descr_mem_pool_done(struct blk_descr_pool *pool);
+
+int blk_descr_mem_pool_add(struct blk_descr_pool *pool, void *buffer);
+union blk_descr_unify blk_descr_mem_pool_take(struct blk_descr_pool *pool);
diff --git a/drivers/block/blk-snap/blk_descr_multidev.c b/drivers/block/blk-snap/blk_descr_multidev.c
new file mode 100644
index 000000000000..cf5e0ed6f781
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_multidev.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-blk_descr"
+#include "common.h"
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+#include "blk_descr_multidev.h"
+
+static inline void list_assign(struct list_head *dst, struct list_head *src)
+{
+	dst->next = src->next;
+	dst->prev = src->prev;
+
+	src->next->prev = dst;
+	src->prev->next = dst;
+}
+
+static inline void blk_descr_multidev_init(struct blk_descr_multidev *blk_descr,
+					   struct list_head *rangelist)
+{
+	list_assign(&blk_descr->rangelist, rangelist);
+}
+
+static inline void blk_descr_multidev_done(struct blk_descr_multidev *blk_descr)
+{
+	struct blk_range_link_ex *rangelist;
+
+	while (!list_empty(&blk_descr->rangelist)) {
+		rangelist = list_entry(blk_descr->rangelist.next,
+				       struct blk_range_link_ex, link);
+
+		list_del(&rangelist->link);
+		kfree(rangelist);
+	}
+}
+
+void blk_descr_multidev_pool_init(struct blk_descr_pool *pool)
+{
+	blk_descr_pool_init(pool, 0);
+}
+
+static void blk_descr_multidev_cleanup(void *descr_array, size_t count)
+{
+	size_t inx;
+	struct blk_descr_multidev *descr_multidev = descr_array;
+
+	for (inx = 0; inx < count; ++inx)
+		blk_descr_multidev_done(descr_multidev + inx);
+}
+
+void blk_descr_multidev_pool_done(struct blk_descr_pool *pool)
+{
+	blk_descr_pool_done(pool, blk_descr_multidev_cleanup);
+}
+
+static union blk_descr_unify blk_descr_multidev_allocate(void *descr_array, size_t index, void *arg)
+{
+	union blk_descr_unify blk_descr;
+	struct blk_descr_multidev *multidev_blocks = descr_array;
+
+	blk_descr.multidev = &multidev_blocks[index];
+
+	blk_descr_multidev_init(blk_descr.multidev, (struct list_head *)arg);
+
+	return blk_descr;
+}
+
+int blk_descr_multidev_pool_add(struct blk_descr_pool *pool, struct list_head *rangelist)
+{
+	union blk_descr_unify blk_descr;
+
+	blk_descr = blk_descr_pool_alloc(pool, sizeof(struct blk_descr_multidev),
+					 blk_descr_multidev_allocate, (void *)rangelist);
+	if (blk_descr.ptr == NULL) {
+		pr_err("Failed to allocate block descriptor\n");
+		return -ENOMEM;
+	}
+
+	return SUCCESS;
+}
+
+union blk_descr_unify blk_descr_multidev_pool_take(struct blk_descr_pool *pool)
+{
+	return blk_descr_pool_take(pool, sizeof(struct blk_descr_multidev));
+}
+
+#endif //CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
diff --git a/drivers/block/blk-snap/blk_descr_multidev.h b/drivers/block/blk-snap/blk_descr_multidev.h
new file mode 100644
index 000000000000..0145b0d78b10
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_multidev.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+
+#include "blk_descr_pool.h"
+
+struct blk_descr_multidev {
+	struct list_head rangelist;
+};
+
+struct blk_range_link_ex {
+	struct list_head link;
+	struct blk_range rg;
+	struct block_device *blk_dev;
+};
+
+void blk_descr_multidev_pool_init(struct blk_descr_pool *pool);
+void blk_descr_multidev_pool_done(struct blk_descr_pool *pool);
+
+int blk_descr_multidev_pool_add(struct blk_descr_pool *pool,
+				struct list_head *rangelist); //allocate new empty block
+union blk_descr_unify blk_descr_multidev_pool_take(struct blk_descr_pool *pool); //take empty
+
+#endif //CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
diff --git a/drivers/block/blk-snap/blk_descr_pool.c b/drivers/block/blk-snap/blk_descr_pool.c
new file mode 100644
index 000000000000..3d5b355b7b37
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_pool.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-blk_descr"
+#include "common.h"
+#include "blk_descr_pool.h"
+#include "params.h"
+
+struct pool_el {
+	struct list_head link;
+
+	size_t used_cnt; // used blocks
+	size_t capacity; // blocks array capacity
+
+	u8 descr_array[0];
+};
+
+static void *kmalloc_huge(size_t max_size, size_t min_size, gfp_t flags, size_t *p_allocated_size)
+{
+	void *ptr = NULL;
+
+	do {
+		ptr = kmalloc(max_size, flags | __GFP_NOWARN | __GFP_RETRY_MAYFAIL);
+
+		if (ptr != NULL) {
+			*p_allocated_size = max_size;
+			return ptr;
+		}
+		pr_err("Failed to allocate buffer size=%lu\n", max_size);
+		max_size = max_size >> 1;
+	} while (max_size >= min_size);
+
+	pr_err("Failed to allocate buffer.");
+	return NULL;
+}
+
+static struct pool_el *pool_el_alloc(size_t blk_descr_size)
+{
+	size_t el_size;
+	struct pool_el *el;
+
+	el = kmalloc_huge(8 * PAGE_SIZE, PAGE_SIZE, GFP_NOIO, &el_size);
+	if (el == NULL)
+		return NULL;
+
+	el->capacity = (el_size - sizeof(struct pool_el)) / blk_descr_size;
+	el->used_cnt = 0;
+
+	INIT_LIST_HEAD(&el->link);
+
+	return el;
+}
+
+static void _pool_el_free(struct pool_el *el)
+{
+	if (el != NULL)
+		kfree(el);
+}
+
+void blk_descr_pool_init(struct blk_descr_pool *pool, size_t available_blocks)
+{
+	mutex_init(&pool->lock);
+
+	INIT_LIST_HEAD(&pool->head);
+
+	pool->blocks_cnt = 0;
+
+	pool->total_cnt = available_blocks;
+	pool->take_cnt = 0;
+}
+
+void blk_descr_pool_done(struct blk_descr_pool *pool,
+			 void (*blocks_cleanup_cb)(void *descr_array, size_t count))
+{
+	mutex_lock(&pool->lock);
+	while (!list_empty(&pool->head)) {
+		struct pool_el *el;
+
+		el = list_entry(pool->head.next, struct pool_el, link);
+		if (el == NULL)
+			break;
+
+		list_del(&el->link);
+		--pool->blocks_cnt;
+
+		pool->total_cnt -= el->used_cnt;
+
+		blocks_cleanup_cb(el->descr_array, el->used_cnt);
+
+		_pool_el_free(el);
+	}
+	mutex_unlock(&pool->lock);
+}
+
+union blk_descr_unify blk_descr_pool_alloc(
+	struct blk_descr_pool *pool, size_t blk_descr_size,
+	union blk_descr_unify (*block_alloc_cb)(void *descr_array, size_t index, void *arg),
+	void *arg)
+{
+	union blk_descr_unify blk_descr = { NULL };
+
+	mutex_lock(&pool->lock);
+	do {
+		struct pool_el *el = NULL;
+
+		if (!list_empty(&pool->head)) {
+			el = list_entry(pool->head.prev, struct pool_el, link);
+			if (el->used_cnt == el->capacity)
+				el = NULL;
+		}
+
+		if (el == NULL) {
+			el = pool_el_alloc(blk_descr_size);
+			if (el == NULL)
+				break;
+
+			list_add_tail(&el->link, &pool->head);
+
+			++pool->blocks_cnt;
+		}
+
+		blk_descr = block_alloc_cb(el->descr_array, el->used_cnt, arg);
+
+		++el->used_cnt;
+		++pool->total_cnt;
+
+	} while (false);
+	mutex_unlock(&pool->lock);
+
+	return blk_descr;
+}
+
+static union blk_descr_unify __blk_descr_pool_at(struct blk_descr_pool *pool, size_t blk_descr_size,
+						 size_t index)
+{
+	union blk_descr_unify bkl_descr = { NULL };
+	size_t curr_inx = 0;
+	struct pool_el *el;
+	struct list_head *_list_head;
+
+	if (list_empty(&(pool)->head))
+		return bkl_descr;
+
+	list_for_each(_list_head, &(pool)->head) {
+		el = list_entry(_list_head, struct pool_el, link);
+
+		if ((index >= curr_inx) && (index < (curr_inx + el->used_cnt))) {
+			bkl_descr.ptr = el->descr_array + (index - curr_inx) * blk_descr_size;
+			break;
+		}
+		curr_inx += el->used_cnt;
+	}
+
+	return bkl_descr;
+}
+
+union blk_descr_unify blk_descr_pool_take(struct blk_descr_pool *pool, size_t blk_descr_size)
+{
+	union blk_descr_unify result = { NULL };
+
+	mutex_lock(&pool->lock);
+	do {
+		if (pool->take_cnt >= pool->total_cnt) {
+			pr_err("Unable to get block descriptor: ");
+			pr_err("not enough descriptors, already took %ld, total %ld\n",
+			       pool->take_cnt, pool->total_cnt);
+			break;
+		}
+
+		result = __blk_descr_pool_at(pool, blk_descr_size, pool->take_cnt);
+		if (result.ptr == NULL) {
+			pr_err("Unable to get block descriptor: ");
+			pr_err("not enough descriptors, already took %ld, total %ld\n",
+			       pool->take_cnt, pool->total_cnt);
+			break;
+		}
+
+		++pool->take_cnt;
+	} while (false);
+	mutex_unlock(&pool->lock);
+	return result;
+}
+
+bool blk_descr_pool_check_halffill(struct blk_descr_pool *pool, sector_t empty_limit,
+				   sector_t *fill_status)
+{
+	size_t empty_blocks = (pool->total_cnt - pool->take_cnt);
+
+	*fill_status = (sector_t)(pool->take_cnt) << snapstore_block_shift();
+
+	return (empty_blocks < (size_t)(empty_limit >> snapstore_block_shift()));
+}
diff --git a/drivers/block/blk-snap/blk_descr_pool.h b/drivers/block/blk-snap/blk_descr_pool.h
new file mode 100644
index 000000000000..32f8b8c4103e
--- /dev/null
+++ b/drivers/block/blk-snap/blk_descr_pool.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+struct blk_descr_mem;
+struct blk_descr_file;
+struct blk_descr_multidev;
+
+union blk_descr_unify {
+	void *ptr;
+	struct blk_descr_mem *mem;
+	struct blk_descr_file *file;
+	struct blk_descr_multidev *multidev;
+};
+
+struct blk_descr_pool {
+	struct list_head head;
+	struct mutex lock;
+
+	size_t blocks_cnt; // count of struct pool_el
+
+	size_t total_cnt;  // total count of block descriptors
+	size_t take_cnt;   // take count of block descriptors
+};
+
+void blk_descr_pool_init(struct blk_descr_pool *pool, size_t available_blocks);
+
+void blk_descr_pool_done(struct blk_descr_pool *pool,
+			 void (*blocks_cleanup_cb)(void *descr_array, size_t count));
+
+union blk_descr_unify blk_descr_pool_alloc(
+	struct blk_descr_pool *pool, size_t blk_descr_size,
+	union blk_descr_unify (*block_alloc_cb)(void *descr_array, size_t index, void *arg),
+	void *arg);
+
+union blk_descr_unify blk_descr_pool_take(struct blk_descr_pool *pool, size_t blk_descr_size);
+
+bool blk_descr_pool_check_halffill(struct blk_descr_pool *pool, sector_t empty_limit,
+				   sector_t *fill_status);
diff --git a/drivers/block/blk-snap/blk_redirect.c b/drivers/block/blk-snap/blk_redirect.c
new file mode 100644
index 000000000000..76e886eea94d
--- /dev/null
+++ b/drivers/block/blk-snap/blk_redirect.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-redirect"
+#include "common.h"
+#include "blk_util.h"
+#include "blk_redirect.h"
+
+#define bio_vec_sectors(bv) (bv.bv_len >> SECTOR_SHIFT)
+
+struct bio_set blk_redirect_bioset = { 0 };
+
+int blk_redirect_bioset_create(void)
+{
+	return bioset_init(&blk_redirect_bioset, 64, 0, BIOSET_NEED_BVECS | BIOSET_NEED_RESCUER);
+}
+
+void blk_redirect_bioset_free(void)
+{
+	bioset_exit(&blk_redirect_bioset);
+}
+
+void blk_redirect_bio_endio(struct bio *bb)
+{
+	struct blk_redirect_bio *rq_redir = (struct blk_redirect_bio *)bb->bi_private;
+
+	if (rq_redir != NULL) {
+		int err = SUCCESS;
+
+		if (bb->bi_status != BLK_STS_OK)
+			err = -EIO;
+
+		if (err != SUCCESS) {
+			pr_err("Failed to process redirect IO request. errno=%d\n", 0 - err);
+
+			if (rq_redir->err == SUCCESS)
+				rq_redir->err = err;
+		}
+
+		if (atomic64_dec_and_test(&rq_redir->bio_count))
+			blk_redirect_complete(rq_redir, rq_redir->err);
+	}
+	bio_put(bb);
+}
+
+struct bio *_blk_dev_redirect_bio_alloc(int nr_iovecs, void *bi_private)
+{
+	struct bio *new_bio;
+
+	new_bio = bio_alloc_bioset(GFP_NOIO, nr_iovecs, &blk_redirect_bioset);
+	if (new_bio == NULL)
+		return NULL;
+
+	new_bio->bi_end_io = blk_redirect_bio_endio;
+	new_bio->bi_private = bi_private;
+
+	return new_bio;
+}
+
+struct blk_redirect_bio_list *_redirect_bio_allocate_list(struct bio *new_bio)
+{
+	struct blk_redirect_bio_list *next;
+
+	next = kzalloc(sizeof(struct blk_redirect_bio_list), GFP_NOIO);
+	if (next == NULL)
+		return NULL;
+
+	next->next = NULL;
+	next->this = new_bio;
+
+	return next;
+}
+
+int bio_endio_list_push(struct blk_redirect_bio *rq_redir, struct bio *new_bio)
+{
+	struct blk_redirect_bio_list *head;
+
+	if (rq_redir->bio_list_head == NULL) {
+		//list is empty, add first bio
+		rq_redir->bio_list_head = _redirect_bio_allocate_list(new_bio);
+		if (rq_redir->bio_list_head == NULL)
+			return -ENOMEM;
+		return SUCCESS;
+	}
+
+	// seek end of list
+	head = rq_redir->bio_list_head;
+	while (head->next != NULL)
+		head = head->next;
+
+	//append new bio to the end of list
+	head->next = _redirect_bio_allocate_list(new_bio);
+	if (head->next == NULL)
+		return -ENOMEM;
+
+	return SUCCESS;
+}
+
+void bio_endio_list_cleanup(struct blk_redirect_bio_list *curr)
+{
+	while (curr != NULL) {
+		struct blk_redirect_bio_list *next;
+
+		next = curr->next;
+		kfree(curr);
+		curr = next;
+	}
+}
+
+static unsigned int get_max_sect(struct block_device *blk_dev)
+{
+	struct request_queue *q = bdev_get_queue(blk_dev);
+
+	return min((unsigned int)(BIO_MAX_PAGES << (PAGE_SHIFT - SECTOR_SHIFT)),
+		   q->limits.max_sectors);
+}
+
+static int _blk_dev_redirect_part_fast(struct blk_redirect_bio *rq_redir, int direction,
+				       struct block_device *blk_dev, sector_t target_pos,
+				       sector_t rq_ofs, sector_t rq_count)
+{
+	__label__ _fail_out;
+	__label__ _reprocess_bv;
+
+	int res = SUCCESS;
+
+	struct bio_vec bvec;
+	struct bvec_iter iter;
+
+	struct bio *new_bio = NULL;
+
+	sector_t sect_ofs = 0;
+	sector_t processed_sectors = 0;
+	int nr_iovecs;
+	struct blk_redirect_bio_list *bio_endio_rec;
+
+	nr_iovecs = get_max_sect(blk_dev) >> (PAGE_SHIFT - SECTOR_SHIFT);
+
+	bio_for_each_segment(bvec, rq_redir->bio, iter) {
+		sector_t bvec_ofs;
+		sector_t bvec_sectors;
+
+		unsigned int len;
+		unsigned int offset;
+
+		if ((sect_ofs + bio_vec_sectors(bvec)) <= rq_ofs) {
+			sect_ofs += bio_vec_sectors(bvec);
+			continue;
+		}
+		if (sect_ofs >= (rq_ofs + rq_count))
+			break;
+
+		bvec_ofs = 0;
+		if (sect_ofs < rq_ofs)
+			bvec_ofs = rq_ofs - sect_ofs;
+
+		bvec_sectors = bio_vec_sectors(bvec) - bvec_ofs;
+		if (bvec_sectors > (rq_count - processed_sectors))
+			bvec_sectors = rq_count - processed_sectors;
+
+		if (bvec_sectors == 0) {
+			res = -EIO;
+			goto _fail_out;
+		}
+
+_reprocess_bv:
+		if (new_bio == NULL) {
+			new_bio = _blk_dev_redirect_bio_alloc(nr_iovecs, rq_redir);
+			while (new_bio == NULL) {
+				pr_err("Unable to allocate new bio for redirect IO.\n");
+				res = -ENOMEM;
+				goto _fail_out;
+			}
+
+			bio_set_dev(new_bio, blk_dev);
+
+			if (direction == READ)
+				bio_set_op_attrs(new_bio, REQ_OP_READ, 0);
+
+			if (direction == WRITE)
+				bio_set_op_attrs(new_bio, REQ_OP_WRITE, 0);
+
+			new_bio->bi_iter.bi_sector = target_pos + processed_sectors;
+		}
+
+		len = (unsigned int)from_sectors(bvec_sectors);
+		offset = bvec.bv_offset + (unsigned int)from_sectors(bvec_ofs);
+		if (unlikely(bio_add_page(new_bio, bvec.bv_page, len, offset) != len)) {
+			if (bio_sectors(new_bio) == 0) {
+				res = -EIO;
+				goto _fail_out;
+			}
+
+			res = bio_endio_list_push(rq_redir, new_bio);
+			if (res != SUCCESS) {
+				pr_err("Failed to add bio into bio_endio_list\n");
+				goto _fail_out;
+			}
+
+			atomic64_inc(&rq_redir->bio_count);
+			new_bio = NULL;
+
+			goto _reprocess_bv;
+		}
+		processed_sectors += bvec_sectors;
+
+		sect_ofs += bio_vec_sectors(bvec);
+	}
+
+	if (new_bio != NULL) {
+		res = bio_endio_list_push(rq_redir, new_bio);
+		if (res != SUCCESS) {
+			pr_err("Failed to add bio into bio_endio_list\n");
+			goto _fail_out;
+		}
+
+		atomic64_inc(&rq_redir->bio_count);
+		new_bio = NULL;
+	}
+
+	return SUCCESS;
+
+_fail_out:
+	bio_endio_rec = rq_redir->bio_list_head;
+	while (bio_endio_rec != NULL) {
+		if (bio_endio_rec->this != NULL)
+			bio_put(bio_endio_rec->this);
+
+		bio_endio_rec = bio_endio_rec->next;
+	}
+
+	bio_endio_list_cleanup(bio_endio_rec);
+
+	pr_err("Failed to process part of redirect IO request. rq_ofs=%lld, rq_count=%lld\n",
+	       rq_ofs, rq_count);
+	return res;
+}
+
+int blk_dev_redirect_part(struct blk_redirect_bio *rq_redir, int direction,
+			  struct block_device *blk_dev, sector_t target_pos, sector_t rq_ofs,
+			  sector_t rq_count)
+{
+	struct request_queue *q = bdev_get_queue(blk_dev);
+	sector_t logical_block_size_mask =
+		(sector_t)((q->limits.logical_block_size >> SECTOR_SHIFT) - 1);
+
+	if (likely(logical_block_size_mask == 0))
+		return _blk_dev_redirect_part_fast(rq_redir, direction, blk_dev, target_pos, rq_ofs,
+						   rq_count);
+
+	if (likely((0 == (target_pos & logical_block_size_mask)) &&
+		   (0 == (rq_count & logical_block_size_mask))))
+		return _blk_dev_redirect_part_fast(rq_redir, direction, blk_dev, target_pos, rq_ofs,
+						   rq_count);
+
+	return -EFAULT;
+}
+
+void blk_dev_redirect_submit(struct blk_redirect_bio *rq_redir)
+{
+	struct blk_redirect_bio_list *head;
+	struct blk_redirect_bio_list *curr;
+
+	head = curr = rq_redir->bio_list_head;
+	rq_redir->bio_list_head = NULL;
+
+	while (curr != NULL) {
+		submit_bio(curr->this);
+
+		curr = curr->next;
+	}
+
+	bio_endio_list_cleanup(head);
+}
+
+int blk_dev_redirect_memcpy_part(struct blk_redirect_bio *rq_redir, int direction, void *buff,
+				 sector_t rq_ofs, sector_t rq_count)
+{
+	struct bio_vec bvec;
+	struct bvec_iter iter;
+
+	sector_t sect_ofs = 0;
+	sector_t processed_sectors = 0;
+
+	bio_for_each_segment(bvec, rq_redir->bio, iter) {
+		void *mem;
+		sector_t bvec_ofs;
+		sector_t bvec_sectors;
+
+		if ((sect_ofs + bio_vec_sectors(bvec)) <= rq_ofs) {
+			sect_ofs += bio_vec_sectors(bvec);
+			continue;
+		}
+
+		if (sect_ofs >= (rq_ofs + rq_count))
+			break;
+
+		bvec_ofs = 0;
+		if (sect_ofs < rq_ofs)
+			bvec_ofs = rq_ofs - sect_ofs;
+
+		bvec_sectors = bio_vec_sectors(bvec) - bvec_ofs;
+		if (bvec_sectors > (rq_count - processed_sectors))
+			bvec_sectors = rq_count - processed_sectors;
+
+		mem = kmap_atomic(bvec.bv_page);
+		if (direction == READ) {
+			memcpy(mem + bvec.bv_offset + (unsigned int)from_sectors(bvec_ofs),
+			       buff + (unsigned int)from_sectors(processed_sectors),
+			       (unsigned int)from_sectors(bvec_sectors));
+		} else {
+			memcpy(buff + (unsigned int)from_sectors(processed_sectors),
+			       mem + bvec.bv_offset + (unsigned int)from_sectors(bvec_ofs),
+			       (unsigned int)from_sectors(bvec_sectors));
+		}
+		kunmap_atomic(mem);
+
+		processed_sectors += bvec_sectors;
+
+		sect_ofs += bio_vec_sectors(bvec);
+	}
+
+	return SUCCESS;
+}
+
+int blk_dev_redirect_zeroed_part(struct blk_redirect_bio *rq_redir, sector_t rq_ofs,
+				 sector_t rq_count)
+{
+	struct bio_vec bvec;
+	struct bvec_iter iter;
+
+	sector_t sect_ofs = 0;
+	sector_t processed_sectors = 0;
+
+	bio_for_each_segment(bvec, rq_redir->bio, iter) {
+		void *mem;
+		sector_t bvec_ofs;
+		sector_t bvec_sectors;
+
+		if ((sect_ofs + bio_vec_sectors(bvec)) <= rq_ofs) {
+			sect_ofs += bio_vec_sectors(bvec);
+			continue;
+		}
+
+		if (sect_ofs >= (rq_ofs + rq_count))
+			break;
+
+		bvec_ofs = 0;
+		if (sect_ofs < rq_ofs)
+			bvec_ofs = rq_ofs - sect_ofs;
+
+		bvec_sectors = bio_vec_sectors(bvec) - bvec_ofs;
+		if (bvec_sectors > (rq_count - processed_sectors))
+			bvec_sectors = rq_count - processed_sectors;
+
+		mem = kmap_atomic(bvec.bv_page);
+		memset(mem + bvec.bv_offset + (unsigned int)from_sectors(bvec_ofs), 0,
+		       (unsigned int)from_sectors(bvec_sectors));
+		kunmap_atomic(mem);
+
+		processed_sectors += bvec_sectors;
+
+		sect_ofs += bio_vec_sectors(bvec);
+	}
+
+	return SUCCESS;
+}
+
+int blk_dev_redirect_read_zeroed(struct blk_redirect_bio *rq_redir, struct block_device *blk_dev,
+				 sector_t rq_pos, sector_t blk_ofs_start, sector_t blk_ofs_count,
+				 struct rangevector *zero_sectors)
+{
+	int res = SUCCESS;
+	struct blk_range_tree_node *range_node;
+
+	sector_t ofs = 0;
+
+	sector_t from = rq_pos + blk_ofs_start;
+	sector_t to = rq_pos + blk_ofs_start + blk_ofs_count - 1;
+
+	down_read(&zero_sectors->lock);
+	range_node = blk_range_rb_iter_first(&zero_sectors->root, from, to);
+	while (range_node) {
+		struct blk_range *zero_range = &range_node->range;
+		sector_t current_portion;
+
+		if (zero_range->ofs > rq_pos + blk_ofs_start + ofs) {
+			sector_t pre_zero_cnt = zero_range->ofs - (rq_pos + blk_ofs_start + ofs);
+
+			res = blk_dev_redirect_part(rq_redir, READ, blk_dev,
+						    rq_pos + blk_ofs_start + ofs,
+						    blk_ofs_start + ofs, pre_zero_cnt);
+			if (res != SUCCESS)
+				break;
+
+			ofs += pre_zero_cnt;
+		}
+
+		current_portion = min_t(sector_t, zero_range->cnt, blk_ofs_count - ofs);
+
+		res = blk_dev_redirect_zeroed_part(rq_redir, blk_ofs_start + ofs, current_portion);
+		if (res != SUCCESS)
+			break;
+
+		ofs += current_portion;
+
+		range_node = blk_range_rb_iter_next(range_node, from, to);
+	}
+	up_read(&zero_sectors->lock);
+
+	if (res == SUCCESS)
+		if ((blk_ofs_count - ofs) > 0)
+			res = blk_dev_redirect_part(rq_redir, READ, blk_dev,
+						    rq_pos + blk_ofs_start + ofs,
+						    blk_ofs_start + ofs, blk_ofs_count - ofs);
+
+	return res;
+}
+void blk_redirect_complete(struct blk_redirect_bio *rq_redir, int res)
+{
+	rq_redir->complete_cb(rq_redir->complete_param, rq_redir->bio, res);
+	redirect_bio_queue_free(rq_redir);
+}
+
+void redirect_bio_queue_init(struct redirect_bio_queue *queue)
+{
+	INIT_LIST_HEAD(&queue->list);
+
+	spin_lock_init(&queue->lock);
+
+	atomic_set(&queue->in_queue_cnt, 0);
+	atomic_set(&queue->alloc_cnt, 0);
+
+	atomic_set(&queue->active_state, true);
+}
+
+struct blk_redirect_bio *redirect_bio_queue_new(struct redirect_bio_queue *queue)
+{
+	struct blk_redirect_bio *rq_redir = kzalloc(sizeof(struct blk_redirect_bio), GFP_NOIO);
+
+	if (rq_redir == NULL)
+		return NULL;
+
+	atomic_inc(&queue->alloc_cnt);
+
+	INIT_LIST_HEAD(&rq_redir->link);
+	rq_redir->queue = queue;
+
+	return rq_redir;
+}
+
+void redirect_bio_queue_free(struct blk_redirect_bio *rq_redir)
+{
+	if (rq_redir) {
+		if (rq_redir->queue)
+			atomic_dec(&rq_redir->queue->alloc_cnt);
+
+		kfree(rq_redir);
+	}
+}
+
+int redirect_bio_queue_push_back(struct redirect_bio_queue *queue,
+				 struct blk_redirect_bio *rq_redir)
+{
+	int res = SUCCESS;
+
+	spin_lock(&queue->lock);
+
+	if (atomic_read(&queue->active_state)) {
+		INIT_LIST_HEAD(&rq_redir->link);
+		list_add_tail(&rq_redir->link, &queue->list);
+		atomic_inc(&queue->in_queue_cnt);
+	} else
+		res = -EACCES;
+
+	spin_unlock(&queue->lock);
+	return res;
+}
+
+struct blk_redirect_bio *redirect_bio_queue_get_first(struct redirect_bio_queue *queue)
+{
+	struct blk_redirect_bio *rq_redir = NULL;
+
+	spin_lock(&queue->lock);
+
+	if (!list_empty(&queue->list)) {
+		rq_redir = list_entry(queue->list.next, struct blk_redirect_bio, link);
+		list_del(&rq_redir->link);
+		atomic_dec(&queue->in_queue_cnt);
+	}
+
+	spin_unlock(&queue->lock);
+
+	return rq_redir;
+}
+
+bool redirect_bio_queue_active(struct redirect_bio_queue *queue, bool state)
+{
+	bool prev_state;
+
+	spin_lock(&queue->lock);
+
+	prev_state = atomic_read(&queue->active_state);
+	atomic_set(&queue->active_state, state);
+
+	spin_unlock(&queue->lock);
+
+	return prev_state;
+}
diff --git a/drivers/block/blk-snap/blk_redirect.h b/drivers/block/blk-snap/blk_redirect.h
new file mode 100644
index 000000000000..aae23e78ebe2
--- /dev/null
+++ b/drivers/block/blk-snap/blk_redirect.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include "rangevector.h"
+
+int blk_redirect_bioset_create(void);
+void blk_redirect_bioset_free(void);
+
+void blk_redirect_bio_endio(struct bio *bb);
+
+struct blk_redirect_bio_list {
+	struct blk_redirect_bio_list *next;
+	struct bio *this;
+};
+
+struct redirect_bio_queue {
+	struct list_head list;
+	spinlock_t lock;
+
+	atomic_t active_state;
+	atomic_t in_queue_cnt;
+	atomic_t alloc_cnt;
+};
+
+struct blk_redirect_bio {
+	struct list_head link;
+	struct redirect_bio_queue *queue;
+
+	struct bio *bio;
+	int err;
+	struct blk_redirect_bio_list *bio_list_head; //list of created bios
+	atomic64_t bio_count;
+
+	void *complete_param;
+	void (*complete_cb)(void *complete_param, struct bio *rq, int err);
+};
+
+int blk_dev_redirect_part(struct blk_redirect_bio *rq_redir, int direction,
+			  struct block_device *blk_dev, sector_t target_pos, sector_t rq_ofs,
+			  sector_t rq_count);
+
+void blk_dev_redirect_submit(struct blk_redirect_bio *rq_redir);
+
+int blk_dev_redirect_memcpy_part(struct blk_redirect_bio *rq_redir, int direction, void *src_buff,
+				 sector_t rq_ofs, sector_t rq_count);
+
+int blk_dev_redirect_zeroed_part(struct blk_redirect_bio *rq_redir, sector_t rq_ofs,
+				 sector_t rq_count);
+
+int blk_dev_redirect_read_zeroed(struct blk_redirect_bio *rq_redir, struct block_device *blk_dev,
+				 sector_t rq_pos, sector_t blk_ofs_start, sector_t blk_ofs_count,
+				 struct rangevector *zero_sectors);
+
+void blk_redirect_complete(struct blk_redirect_bio *rq_redir, int res);
+
+void redirect_bio_queue_init(struct redirect_bio_queue *queue);
+
+struct blk_redirect_bio *redirect_bio_queue_new(struct redirect_bio_queue *queue);
+
+void redirect_bio_queue_free(struct blk_redirect_bio *rq_redir);
+
+int redirect_bio_queue_push_back(struct redirect_bio_queue *queue,
+				 struct blk_redirect_bio *rq_redir);
+
+struct blk_redirect_bio *redirect_bio_queue_get_first(struct redirect_bio_queue *queue);
+
+bool redirect_bio_queue_active(struct redirect_bio_queue *queue, bool state);
+
+#define redirect_bio_queue_empty(queue) (atomic_read(&(queue).in_queue_cnt) == 0)
+
+#define redirect_bio_queue_unactive(queue)                                                         \
+	((atomic_read(&((queue).active_state)) == false) &&                                        \
+	 (atomic_read(&((queue).alloc_cnt)) == 0))
diff --git a/drivers/block/blk-snap/blk_util.c b/drivers/block/blk-snap/blk_util.c
new file mode 100644
index 000000000000..57db70b86516
--- /dev/null
+++ b/drivers/block/blk-snap/blk_util.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include "blk_util.h"
+
+int blk_dev_open(dev_t dev_id, struct block_device **p_blk_dev)
+{
+	int result = SUCCESS;
+	struct block_device *blk_dev;
+	int refCount;
+
+	blk_dev = bdget(dev_id);
+	if (blk_dev == NULL) {
+		pr_err("Unable to open device [%d:%d]: bdget return NULL\n", MAJOR(dev_id),
+		       MINOR(dev_id));
+		return -ENODEV;
+	}
+
+	refCount = blkdev_get(blk_dev, FMODE_READ | FMODE_WRITE, NULL);
+	if (refCount < 0) {
+		pr_err("Unable to open device [%d:%d]: blkdev_get return error code %d\n",
+		       MAJOR(dev_id), MINOR(dev_id), 0 - refCount);
+		result = refCount;
+	}
+
+	if (result == SUCCESS)
+		*p_blk_dev = blk_dev;
+	return result;
+}
+
+void blk_dev_close(struct block_device *blk_dev)
+{
+	blkdev_put(blk_dev, FMODE_READ);
+}
diff --git a/drivers/block/blk-snap/blk_util.h b/drivers/block/blk-snap/blk_util.h
new file mode 100644
index 000000000000..0e74de059479
--- /dev/null
+++ b/drivers/block/blk-snap/blk_util.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+int blk_dev_open(dev_t dev_id, struct block_device **p_blk_dev);
+void blk_dev_close(struct block_device *blk_dev);
+
+static inline sector_t blk_dev_get_capacity(struct block_device *blk_dev)
+{
+	return blk_dev->bd_part->nr_sects;
+};
diff --git a/drivers/block/blk-snap/cbt_map.c b/drivers/block/blk-snap/cbt_map.c
new file mode 100644
index 000000000000..793c7720a436
--- /dev/null
+++ b/drivers/block/blk-snap/cbt_map.c
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-cbt_map"
+#include "common.h"
+#include "cbt_map.h"
+
+int cbt_map_allocate(struct cbt_map *cbt_map, unsigned int cbt_sect_in_block_degree,
+		     sector_t device_capacity)
+{
+	sector_t size_mod;
+
+	cbt_map->sect_in_block_degree = cbt_sect_in_block_degree;
+	cbt_map->device_capacity = device_capacity;
+	cbt_map->map_size = (device_capacity >> (sector_t)cbt_sect_in_block_degree);
+
+	pr_info("Allocate CBT map of %lu\n", cbt_map->map_size);
+
+	size_mod = (device_capacity & ((sector_t)(1 << cbt_sect_in_block_degree) - 1));
+	if (size_mod)
+		cbt_map->map_size++;
+
+	cbt_map->read_map = big_buffer_alloc(cbt_map->map_size, GFP_KERNEL);
+	if (cbt_map->read_map != NULL)
+		big_buffer_memset(cbt_map->read_map, 0);
+
+	cbt_map->write_map = big_buffer_alloc(cbt_map->map_size, GFP_KERNEL);
+	if (cbt_map->write_map != NULL)
+		big_buffer_memset(cbt_map->write_map, 0);
+
+	if ((cbt_map->read_map == NULL) || (cbt_map->write_map == NULL)) {
+		pr_err("Cannot allocate CBT map. map_size=%lu\n", cbt_map->map_size);
+		return -ENOMEM;
+	}
+
+	cbt_map->snap_number_previous = 0;
+	cbt_map->snap_number_active = 1;
+	generate_random_uuid(cbt_map->generationId.b);
+	cbt_map->active = true;
+
+	cbt_map->state_changed_sectors = 0;
+	cbt_map->state_dirty_sectors = 0;
+
+	return SUCCESS;
+}
+
+void cbt_map_deallocate(struct cbt_map *cbt_map)
+{
+	if (cbt_map->read_map != NULL) {
+		big_buffer_free(cbt_map->read_map);
+		cbt_map->read_map = NULL;
+	}
+
+	if (cbt_map->write_map != NULL) {
+		big_buffer_free(cbt_map->write_map);
+		cbt_map->write_map = NULL;
+	}
+
+	cbt_map->active = false;
+}
+
+static void cbt_map_destroy(struct cbt_map *cbt_map)
+{
+	pr_info("CBT map destroy\n");
+	if (cbt_map != NULL) {
+		cbt_map_deallocate(cbt_map);
+
+		kfree(cbt_map);
+	}
+}
+
+struct cbt_map *cbt_map_create(unsigned int cbt_sect_in_block_degree, sector_t device_capacity)
+{
+	struct cbt_map *cbt_map = NULL;
+
+	pr_info("CBT map create\n");
+
+	cbt_map = kzalloc(sizeof(struct cbt_map), GFP_KERNEL);
+	if (cbt_map == NULL)
+		return NULL;
+
+	if (cbt_map_allocate(cbt_map, cbt_sect_in_block_degree, device_capacity) != SUCCESS) {
+		cbt_map_destroy(cbt_map);
+		return NULL;
+	}
+
+	spin_lock_init(&cbt_map->locker);
+	init_rwsem(&cbt_map->rw_lock);
+	kref_init(&cbt_map->refcount);
+
+	return cbt_map;
+}
+
+void cbt_map_destroy_cb(struct kref *kref)
+{
+	cbt_map_destroy(container_of(kref, struct cbt_map, refcount));
+}
+
+struct cbt_map *cbt_map_get_resource(struct cbt_map *cbt_map)
+{
+	if (cbt_map)
+		kref_get(&cbt_map->refcount);
+
+	return cbt_map;
+}
+
+void cbt_map_put_resource(struct cbt_map *cbt_map)
+{
+	if (cbt_map)
+		kref_put(&cbt_map->refcount, cbt_map_destroy_cb);
+}
+
+void cbt_map_switch(struct cbt_map *cbt_map)
+{
+	pr_info("CBT map switch\n");
+	spin_lock(&cbt_map->locker);
+
+	big_buffer_memcpy(cbt_map->read_map, cbt_map->write_map);
+
+	cbt_map->snap_number_previous = cbt_map->snap_number_active;
+	++cbt_map->snap_number_active;
+	if (cbt_map->snap_number_active == 256) {
+		cbt_map->snap_number_active = 1;
+
+		big_buffer_memset(cbt_map->write_map, 0);
+
+		generate_random_uuid(cbt_map->generationId.b);
+
+		pr_info("CBT reset\n");
+	}
+	spin_unlock(&cbt_map->locker);
+}
+
+int _cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, sector_t sector_cnt,
+		 u8 snap_number, struct big_buffer *map)
+{
+	int res = SUCCESS;
+	size_t cbt_block;
+	size_t cbt_block_first = (size_t)(sector_start >> cbt_map->sect_in_block_degree);
+	size_t cbt_block_last = (size_t)((sector_start + sector_cnt - 1) >>
+					 cbt_map->sect_in_block_degree); //inclusive
+
+	for (cbt_block = cbt_block_first; cbt_block <= cbt_block_last; ++cbt_block) {
+		if (cbt_block < cbt_map->map_size) {
+			u8 num;
+
+			res = big_buffer_byte_get(map, cbt_block, &num);
+			if (res == SUCCESS)
+				if (num < snap_number)
+					res = big_buffer_byte_set(map, cbt_block, snap_number);
+		} else
+			res = -EINVAL;
+
+		if (res != SUCCESS) {
+			pr_err("Block index is too large. #%ld was demanded, map size %ld\n",
+			       cbt_block, cbt_map->map_size);
+			break;
+		}
+	}
+	return res;
+}
+
+int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, sector_t sector_cnt)
+{
+	int res = SUCCESS;
+
+	spin_lock(&cbt_map->locker);
+
+	res = _cbt_map_set(cbt_map, sector_start, sector_cnt, (u8)cbt_map->snap_number_active,
+			   cbt_map->write_map);
+	cbt_map->state_changed_sectors += sector_cnt;
+
+	spin_unlock(&cbt_map->locker);
+	return res;
+}
+
+int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, sector_t sector_cnt)
+{
+	int res = SUCCESS;
+
+	spin_lock(&cbt_map->locker);
+
+	res = _cbt_map_set(cbt_map, sector_start, sector_cnt,
+			   (u8)cbt_map->snap_number_active, cbt_map->write_map);
+	if (res == SUCCESS)
+		res = _cbt_map_set(cbt_map, sector_start, sector_cnt,
+				   (u8)cbt_map->snap_number_previous, cbt_map->read_map);
+	cbt_map->state_dirty_sectors += sector_cnt;
+
+	spin_unlock(&cbt_map->locker);
+	return res;
+}
+
+size_t cbt_map_read_to_user(struct cbt_map *cbt_map, void __user *user_buff, size_t offset,
+			    size_t size)
+{
+	size_t readed = 0;
+	size_t left_size;
+	size_t real_size = min((cbt_map->map_size - offset), size);
+
+	left_size = real_size -
+		    big_buffer_copy_to_user(user_buff, offset, cbt_map->read_map, real_size);
+
+	if (left_size == 0)
+		readed = real_size;
+	else {
+		pr_err("Not all CBT data was read. Left [%ld] bytes\n", left_size);
+		readed = real_size - left_size;
+	}
+
+	return readed;
+}
diff --git a/drivers/block/blk-snap/cbt_map.h b/drivers/block/blk-snap/cbt_map.h
new file mode 100644
index 000000000000..cb52b09531fe
--- /dev/null
+++ b/drivers/block/blk-snap/cbt_map.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include "big_buffer.h"
+#include <linux/kref.h>
+#include <linux/uuid.h>
+
+struct cbt_map {
+	struct kref refcount;
+
+	spinlock_t locker;
+
+	size_t sect_in_block_degree;
+	sector_t device_capacity;
+	size_t map_size;
+
+	struct big_buffer *read_map;
+	struct big_buffer *write_map;
+
+	unsigned long snap_number_active;
+	unsigned long snap_number_previous;
+	uuid_t generationId;
+
+	bool active;
+
+	struct rw_semaphore rw_lock;
+
+	sector_t state_changed_sectors;
+	sector_t state_dirty_sectors;
+};
+
+struct cbt_map *cbt_map_create(unsigned int cbt_sect_in_block_degree, sector_t device_capacity);
+
+struct cbt_map *cbt_map_get_resource(struct cbt_map *cbt_map);
+void cbt_map_put_resource(struct cbt_map *cbt_map);
+
+void cbt_map_switch(struct cbt_map *cbt_map);
+int cbt_map_set(struct cbt_map *cbt_map, sector_t sector_start, sector_t sector_cnt);
+int cbt_map_set_both(struct cbt_map *cbt_map, sector_t sector_start, sector_t sector_cnt);
+
+size_t cbt_map_read_to_user(struct cbt_map *cbt_map, void __user *user_buffer, size_t offset,
+			    size_t size);
+
+static inline void cbt_map_read_lock(struct cbt_map *cbt_map)
+{
+	down_read(&cbt_map->rw_lock);
+};
+
+static inline void cbt_map_read_unlock(struct cbt_map *cbt_map)
+{
+	up_read(&cbt_map->rw_lock);
+};
+
+static inline void cbt_map_write_lock(struct cbt_map *cbt_map)
+{
+	down_write(&cbt_map->rw_lock);
+};
+
+static inline void cbt_map_write_unlock(struct cbt_map *cbt_map)
+{
+	up_write(&cbt_map->rw_lock);
+};
diff --git a/drivers/block/blk-snap/common.h b/drivers/block/blk-snap/common.h
new file mode 100644
index 000000000000..f080446ad1f6
--- /dev/null
+++ b/drivers/block/blk-snap/common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#ifndef BLK_SNAP_SECTION
+#define BLK_SNAP_SECTION ""
+#endif
+#define pr_fmt(fmt) KBUILD_MODNAME BLK_SNAP_SECTION ": " fmt
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/blkdev.h>
+
+#define from_sectors(_sectors) (_sectors << SECTOR_SHIFT)
+#define to_sectors(_byte_size) (_byte_size >> SECTOR_SHIFT)
+
+struct blk_range {
+	sector_t ofs;
+	blkcnt_t cnt;
+};
+
+#ifndef SUCCESS
+#define SUCCESS 0
+#endif
diff --git a/drivers/block/blk-snap/ctrl_fops.c b/drivers/block/blk-snap/ctrl_fops.c
new file mode 100644
index 000000000000..09d8de207931
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_fops.c
@@ -0,0 +1,693 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-ctrl"
+#include "common.h"
+#include "blk-snap-ctl.h"
+#include "ctrl_fops.h"
+#include "version.h"
+#include "tracking.h"
+#include "snapshot.h"
+#include "snapstore.h"
+#include "snapimage.h"
+#include "tracker.h"
+#include "blk_deferred.h"
+#include "big_buffer.h"
+#include "params.h"
+
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+
+int blk_snap_major; //zero by default
+
+const struct file_operations ctrl_fops = { .owner = THIS_MODULE,
+					   .read = ctrl_read,
+					   .write = ctrl_write,
+					   .open = ctrl_open,
+					   .release = ctrl_release,
+					   .poll = ctrl_poll,
+					   .unlocked_ioctl = ctrl_unlocked_ioctl };
+
+atomic_t dev_open_cnt = ATOMIC_INIT(0);
+
+const struct ioctl_getversion_s version = { .major = FILEVER_MAJOR,
+					    .minor = FILEVER_MINOR,
+					    .revision = FILEVER_REVISION,
+					    .build = 0 };
+
+int get_blk_snap_major(void)
+{
+	return blk_snap_major;
+}
+
+int ctrl_init(void)
+{
+	int ret;
+
+	ret = register_chrdev(0, MODULE_NAME, &ctrl_fops);
+	if (ret < 0) {
+		pr_err("Failed to register a character device. errno=%d\n", blk_snap_major);
+		return ret;
+	}
+
+	blk_snap_major = ret;
+	pr_info("Module major [%d]\n", blk_snap_major);
+	return SUCCESS;
+}
+
+void ctrl_done(void)
+{
+	unregister_chrdev(blk_snap_major, MODULE_NAME);
+	ctrl_pipe_done();
+}
+
+ssize_t ctrl_read(struct file *fl, char __user *buffer, size_t length, loff_t *offset)
+{
+	ssize_t bytes_read = 0;
+	struct ctrl_pipe *pipe = (struct ctrl_pipe *)fl->private_data;
+
+	if (pipe == NULL) {
+		pr_err("Unable to read from pipe: invalid pipe pointer\n");
+		return -EINVAL;
+	}
+
+	bytes_read = ctrl_pipe_read(pipe, buffer, length);
+	if (bytes_read == 0)
+		if (fl->f_flags & O_NONBLOCK)
+			bytes_read = -EAGAIN;
+
+	return bytes_read;
+}
+
+ssize_t ctrl_write(struct file *fl, const char __user *buffer, size_t length, loff_t *offset)
+{
+	struct ctrl_pipe *pipe = (struct ctrl_pipe *)fl->private_data;
+
+	if (pipe == NULL) {
+		pr_err("Unable to write into pipe: invalid pipe pointer\n");
+		return -EINVAL;
+	}
+
+	return ctrl_pipe_write(pipe, buffer, length);
+}
+
+unsigned int ctrl_poll(struct file *fl, struct poll_table_struct *wait)
+{
+	struct ctrl_pipe *pipe = (struct ctrl_pipe *)fl->private_data;
+
+	if (pipe == NULL) {
+		pr_err("Unable to poll pipe: invalid pipe pointer\n");
+		return -EINVAL;
+	}
+
+	return ctrl_pipe_poll(pipe);
+}
+
+int ctrl_open(struct inode *inode, struct file *fl)
+{
+	fl->f_pos = 0;
+
+	if (false == try_module_get(THIS_MODULE))
+		return -EINVAL;
+
+	fl->private_data = (void *)ctrl_pipe_new();
+	if (fl->private_data == NULL) {
+		pr_err("Failed to open ctrl file\n");
+		return -ENOMEM;
+	}
+
+	atomic_inc(&dev_open_cnt);
+
+	return SUCCESS;
+}
+
+int ctrl_release(struct inode *inode, struct file *fl)
+{
+	int result = SUCCESS;
+
+	if (atomic_read(&dev_open_cnt) > 0) {
+		module_put(THIS_MODULE);
+		ctrl_pipe_put_resource((struct ctrl_pipe *)fl->private_data);
+
+		atomic_dec(&dev_open_cnt);
+	} else {
+		pr_err("Unable to close ctrl file: the file is already closed\n");
+		result = -EALREADY;
+	}
+
+	return result;
+}
+
+int ioctl_compatibility_flags(unsigned long arg)
+{
+	unsigned long len;
+	struct ioctl_compatibility_flags_s param;
+
+	param.flags = 0;
+	param.flags |= BLK_SNAP_COMPATIBILITY_SNAPSTORE;
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	param.flags |= BLK_SNAP_COMPATIBILITY_MULTIDEV;
+#endif
+	len = copy_to_user((void *)arg, &param, sizeof(struct ioctl_compatibility_flags_s));
+	if (len != 0) {
+		pr_err("Unable to get compatibility flags: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	return SUCCESS;
+}
+
+int ioctl_get_version(unsigned long arg)
+{
+	unsigned long len;
+
+	pr_info("Get version\n");
+
+	len = copy_to_user((void *)arg, &version, sizeof(struct ioctl_getversion_s));
+	if (len != 0) {
+		pr_err("Unable to get version: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	return SUCCESS;
+}
+
+int ioctl_tracking_add(unsigned long arg)
+{
+	unsigned long len;
+	struct ioctl_dev_id_s dev;
+
+	len = copy_from_user(&dev, (void *)arg, sizeof(struct ioctl_dev_id_s));
+	if (len != 0) {
+		pr_err("Unable to add device under tracking: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	return tracking_add(MKDEV(dev.major, dev.minor), get_change_tracking_block_size_pow(),
+			    0ull);
+}
+
+int ioctl_tracking_remove(unsigned long arg)
+{
+	struct ioctl_dev_id_s dev;
+
+	if (copy_from_user(&dev, (void *)arg, sizeof(struct ioctl_dev_id_s)) != 0) {
+		pr_err("Unable to remove device from tracking: invalid user buffer\n");
+		return -ENODATA;
+	}
+	return tracking_remove(MKDEV(dev.major, dev.minor));
+	;
+}
+
+int ioctl_tracking_collect(unsigned long arg)
+{
+	unsigned long len;
+	int res;
+	struct ioctl_tracking_collect_s get;
+
+	pr_info("Collecting tracking devices:\n");
+
+	len = copy_from_user(&get, (void *)arg, sizeof(struct ioctl_tracking_collect_s));
+	if (len  != 0) {
+		pr_err("Unable to collect tracking devices: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	if (get.p_cbt_info == NULL) {
+		res = tracking_collect(0x7FFFffff, NULL, &get.count);
+		if (res == SUCCESS) {
+			len = copy_to_user((void *)arg, (void *)&get,
+					   sizeof(struct ioctl_tracking_collect_s));
+			if (len != 0) {
+				pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n");
+				res = -ENODATA;
+			}
+		} else {
+			pr_err("Failed to execute tracking_collect. errno=%d\n", res);
+		}
+	} else {
+		struct cbt_info_s *p_cbt_info = NULL;
+
+		p_cbt_info = kcalloc(get.count, sizeof(struct cbt_info_s), GFP_KERNEL);
+		if (p_cbt_info == NULL)
+			return -ENOMEM;
+
+		do {
+			res = tracking_collect(get.count, p_cbt_info, &get.count);
+			if (res != SUCCESS) {
+				pr_err("Failed to execute tracking_collect. errno=%d\n", res);
+				break;
+			}
+			len = copy_to_user(get.p_cbt_info, p_cbt_info,
+					      get.count * sizeof(struct cbt_info_s));
+			if (len != 0) {
+				pr_err("Unable to collect tracking devices: invalid user buffer for CBT info\n");
+				res = -ENODATA;
+				break;
+			}
+
+			len = copy_to_user((void *)arg, (void *)&get,
+					   sizeof(struct ioctl_tracking_collect_s));
+			if (len != 0) {
+				pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n");
+				res = -ENODATA;
+				break;
+			}
+
+		} while (false);
+
+		kfree(p_cbt_info);
+		p_cbt_info = NULL;
+	}
+	return res;
+}
+
+int ioctl_tracking_block_size(unsigned long arg)
+{
+	unsigned long len;
+	unsigned int blk_sz = change_tracking_block_size();
+
+	len = copy_to_user((void *)arg, &blk_sz, sizeof(unsigned int));
+	if (len != 0) {
+		pr_err("Unable to get tracking block size: invalid user buffer for arguments\n");
+		return -ENODATA;
+	}
+	return SUCCESS;
+}
+
+int ioctl_tracking_read_cbt_map(unsigned long arg)
+{
+	dev_t dev_id;
+	unsigned long len;
+	struct ioctl_tracking_read_cbt_bitmap_s readbitmap;
+
+	len = copy_from_user(&readbitmap, (void *)arg,
+				sizeof(struct ioctl_tracking_read_cbt_bitmap_s));
+	if (len != 0) {
+		pr_err("Unable to read CBT map: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	dev_id = MKDEV(readbitmap.dev_id.major, readbitmap.dev_id.minor);
+	return tracking_read_cbt_bitmap(dev_id, readbitmap.offset, readbitmap.length,
+					(void *)readbitmap.buff);
+}
+
+int ioctl_tracking_mark_dirty_blocks(unsigned long arg)
+{
+	unsigned long len;
+	struct ioctl_tracking_mark_dirty_blocks_s param;
+	struct block_range_s *p_dirty_blocks;
+	size_t buffer_size;
+	int result = SUCCESS;
+
+	len = copy_from_user(&param, (void *)arg,
+			     sizeof(struct ioctl_tracking_mark_dirty_blocks_s));
+	if (len != 0) {
+		pr_err("Unable to mark dirty blocks: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	buffer_size = param.count * sizeof(struct block_range_s);
+	p_dirty_blocks = kzalloc(buffer_size, GFP_KERNEL);
+	if (p_dirty_blocks == NULL) {
+		pr_err("Unable to mark dirty blocks: cannot allocate [%ld] bytes\n", buffer_size);
+		return -ENOMEM;
+	}
+
+	do {
+		dev_t image_dev_id;
+
+		len = copy_from_user(p_dirty_blocks, (void *)param.p_dirty_blocks, buffer_size);
+		if (len != 0) {
+			pr_err("Unable to mark dirty blocks: invalid user buffer\n");
+			result = -ENODATA;
+			break;
+		}
+
+		image_dev_id = MKDEV(param.image_dev_id.major, param.image_dev_id.minor);
+		result = snapimage_mark_dirty_blocks(image_dev_id, p_dirty_blocks, param.count);
+	} while (false);
+	kfree(p_dirty_blocks);
+
+	return result;
+}
+
+int ioctl_snapshot_create(unsigned long arg)
+{
+	unsigned long len;
+	size_t dev_id_buffer_size;
+	int status;
+	struct ioctl_snapshot_create_s param;
+	struct ioctl_dev_id_s *pk_dev_id = NULL;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_snapshot_create_s));
+	if (len != 0) {
+		pr_err("Unable to create snapshot: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	dev_id_buffer_size = sizeof(struct ioctl_dev_id_s) * param.count;
+	pk_dev_id = kzalloc(dev_id_buffer_size, GFP_KERNEL);
+	if (pk_dev_id == NULL) {
+		pr_err("Unable to create snapshot: cannot allocate [%ld] bytes\n",
+		       dev_id_buffer_size);
+		return -ENOMEM;
+	}
+
+	do {
+		size_t dev_buffer_size;
+		dev_t *p_dev = NULL;
+		int inx = 0;
+
+		len = copy_from_user(pk_dev_id, (void *)param.p_dev_id,
+				     param.count * sizeof(struct ioctl_dev_id_s));
+		if (len != 0) {
+			pr_err("Unable to create snapshot: invalid user buffer for parameters\n");
+			status = -ENODATA;
+			break;
+		}
+
+		dev_buffer_size = sizeof(dev_t) * param.count;
+		p_dev = kzalloc(dev_buffer_size, GFP_KERNEL);
+		if (p_dev == NULL) {
+			pr_err("Unable to create snapshot: cannot allocate [%ld] bytes\n",
+			       dev_buffer_size);
+			status = -ENOMEM;
+			break;
+		}
+
+		for (inx = 0; inx < param.count; ++inx)
+			p_dev[inx] = MKDEV(pk_dev_id[inx].major, pk_dev_id[inx].minor);
+
+		status = snapshot_create(p_dev, param.count, get_change_tracking_block_size_pow(),
+					 &param.snapshot_id);
+
+		kfree(p_dev);
+		p_dev = NULL;
+
+	} while (false);
+	kfree(pk_dev_id);
+	pk_dev_id = NULL;
+
+	if (status == SUCCESS) {
+		len = copy_to_user((void *)arg, &param, sizeof(struct ioctl_snapshot_create_s));
+		if (len != 0) {
+			pr_err("Unable to create snapshot: invalid user buffer\n");
+			status = -ENODATA;
+		}
+	}
+
+	return status;
+}
+
+int ioctl_snapshot_destroy(unsigned long arg)
+{
+	unsigned long len;
+	unsigned long long param;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(unsigned long long));
+	if (len != 0) {
+		pr_err("Unable to destroy snapshot: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	return snapshot_destroy(param);
+}
+
+static inline dev_t _snapstore_dev(struct ioctl_dev_id_s *dev_id)
+{
+	if ((dev_id->major == 0) && (dev_id->minor == 0))
+		return 0; //memory snapstore
+
+	if ((dev_id->major == -1) && (dev_id->minor == -1))
+		return 0xFFFFffff; //multidevice snapstore
+
+	return MKDEV(dev_id->major, dev_id->minor);
+}
+
+int ioctl_snapstore_create(unsigned long arg)
+{
+	unsigned long len;
+	int res = SUCCESS;
+	struct ioctl_snapstore_create_s param;
+	size_t inx = 0;
+	dev_t *dev_id_set = NULL;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_snapstore_create_s));
+	if (len != 0) {
+		pr_err("Unable to create snapstore: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	dev_id_set = kcalloc(param.count, sizeof(dev_t), GFP_KERNEL);
+	if (dev_id_set == NULL)
+		return -ENOMEM;
+
+	for (inx = 0; inx < param.count; ++inx) {
+		struct ioctl_dev_id_s dev_id;
+
+		len = copy_from_user(&dev_id, param.p_dev_id + inx, sizeof(struct ioctl_dev_id_s));
+		if (len != 0) {
+			pr_err("Unable to create snapstore: ");
+			pr_err("invalid user buffer for parameters\n");
+
+			res = -ENODATA;
+			break;
+		}
+
+		dev_id_set[inx] = MKDEV(dev_id.major, dev_id.minor);
+	}
+
+	if (res == SUCCESS)
+		res = snapstore_create((uuid_t *)param.id, _snapstore_dev(&param.snapstore_dev_id),
+				       dev_id_set, (size_t)param.count);
+
+	kfree(dev_id_set);
+
+	return res;
+}
+
+int ioctl_snapstore_file(unsigned long arg)
+{
+	unsigned long len;
+	int res = SUCCESS;
+	struct ioctl_snapstore_file_add_s param;
+	struct big_buffer *ranges = NULL;
+	size_t ranges_buffer_size;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_snapstore_file_add_s));
+	if (len != 0) {
+		pr_err("Unable to add file to snapstore: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	ranges_buffer_size = sizeof(struct ioctl_range_s) * param.range_count;
+
+	ranges = big_buffer_alloc(ranges_buffer_size, GFP_KERNEL);
+	if (ranges == NULL) {
+		pr_err("Unable to add file to snapstore: cannot allocate [%ld] bytes\n",
+		       ranges_buffer_size);
+		return -ENOMEM;
+	}
+
+	if (big_buffer_copy_from_user((void *)param.ranges, 0, ranges, ranges_buffer_size)
+		!= ranges_buffer_size) {
+
+		pr_err("Unable to add file to snapstore: invalid user buffer for parameters\n");
+		res = -ENODATA;
+	} else
+		res = snapstore_add_file((uuid_t *)(param.id), ranges, (size_t)param.range_count);
+
+	big_buffer_free(ranges);
+
+	return res;
+}
+
+int ioctl_snapstore_memory(unsigned long arg)
+{
+	unsigned long len;
+	int res = SUCCESS;
+	struct ioctl_snapstore_memory_limit_s param;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_snapstore_memory_limit_s));
+	if (len != 0) {
+		pr_err("Unable to add memory block to snapstore: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	res = snapstore_add_memory((uuid_t *)param.id, param.size);
+
+	return res;
+}
+int ioctl_snapstore_cleanup(unsigned long arg)
+{
+	unsigned long len;
+	int res = SUCCESS;
+	struct ioctl_snapstore_cleanup_s param;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_snapstore_cleanup_s));
+	if (len != 0) {
+		pr_err("Unable to perform snapstore cleanup: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	pr_err("id=%pUB\n", (uuid_t *)param.id);
+	res = snapstore_cleanup((uuid_t *)param.id, &param.filled_bytes);
+
+	if (res == SUCCESS) {
+		if (0 !=
+		    copy_to_user((void *)arg, &param, sizeof(struct ioctl_snapstore_cleanup_s))) {
+			pr_err("Unable to perform snapstore cleanup: invalid user buffer\n");
+			res = -ENODATA;
+		}
+	}
+
+	return res;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+int ioctl_snapstore_file_multidev(unsigned long arg)
+{
+	unsigned long len;
+	int res = SUCCESS;
+	struct ioctl_snapstore_file_add_multidev_s param;
+	struct big_buffer *ranges = NULL; //struct ioctl_range_s* ranges = NULL;
+	size_t ranges_buffer_size;
+
+	len = copy_from_user(&param, (void *)arg,
+				sizeof(struct ioctl_snapstore_file_add_multidev_s));
+	if (len != 0) {
+		pr_err("Unable to add file to multidev snapstore: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	ranges_buffer_size = sizeof(struct ioctl_range_s) * param.range_count;
+
+	ranges = big_buffer_alloc(ranges_buffer_size, GFP_KERNEL);
+	if (ranges == NULL) {
+		pr_err("Unable to add file to multidev snapstore: cannot allocate [%ld] bytes\n",
+		       ranges_buffer_size);
+		return -ENOMEM;
+	}
+
+	do {
+		uuid_t *id = (uuid_t *)(param.id);
+		dev_t snapstore_device = MKDEV(param.dev_id.major, param.dev_id.minor);
+		size_t ranges_cnt = (size_t)param.range_count;
+
+		if (ranges_buffer_size != big_buffer_copy_from_user((void *)param.ranges, 0, ranges,
+								    ranges_buffer_size)) {
+			pr_err("Unable to add file to snapstore: invalid user buffer for parameters\n");
+			res = -ENODATA;
+			break;
+		}
+
+		res = snapstore_add_multidev(id, snapstore_device, ranges, ranges_cnt);
+	} while (false);
+	big_buffer_free(ranges);
+
+	return res;
+}
+
+#endif
+//////////////////////////////////////////////////////////////////////////
+
+/*
+ * Snapshot get errno for device
+ */
+int ioctl_snapshot_errno(unsigned long arg)
+{
+	unsigned long len;
+	int res;
+	struct ioctl_snapshot_errno_s param;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_dev_id_s));
+	if (len != 0) {
+		pr_err("Unable failed to get snapstore error code: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	res = snapstore_device_errno(MKDEV(param.dev_id.major, param.dev_id.minor),
+				     &param.err_code);
+
+	if (res != SUCCESS)
+		return res;
+
+	len = copy_to_user((void *)arg, &param, sizeof(struct ioctl_snapshot_errno_s));
+	if (len != 0) {
+		pr_err("Unable to get snapstore error code: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	return SUCCESS;
+}
+
+int ioctl_collect_snapimages(unsigned long arg)
+{
+	unsigned long len;
+	int status = SUCCESS;
+	struct ioctl_collect_snapshot_images_s param;
+
+	len = copy_from_user(&param, (void *)arg, sizeof(struct ioctl_collect_snapshot_images_s));
+	if (len != 0) {
+		pr_err("Unable to collect snapshot images: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	status = snapimage_collect_images(param.count, param.p_image_info, &param.count);
+
+	len = copy_to_user((void *)arg, &param, sizeof(struct ioctl_collect_snapshot_images_s));
+	if (len != 0) {
+		pr_err("Unable to collect snapshot images: invalid user buffer\n");
+		return -ENODATA;
+	}
+
+	return status;
+}
+
+struct blk_snap_ioctl_table {
+	unsigned int cmd;
+	int (*fn)(unsigned long arg);
+};
+
+static struct blk_snap_ioctl_table blk_snap_ioctl_table[] = {
+	{ (IOCTL_COMPATIBILITY_FLAGS), ioctl_compatibility_flags },
+	{ (IOCTL_GETVERSION), ioctl_get_version },
+
+	{ (IOCTL_TRACKING_ADD), ioctl_tracking_add },
+	{ (IOCTL_TRACKING_REMOVE), ioctl_tracking_remove },
+	{ (IOCTL_TRACKING_COLLECT), ioctl_tracking_collect },
+	{ (IOCTL_TRACKING_BLOCK_SIZE), ioctl_tracking_block_size },
+	{ (IOCTL_TRACKING_READ_CBT_BITMAP), ioctl_tracking_read_cbt_map },
+	{ (IOCTL_TRACKING_MARK_DIRTY_BLOCKS), ioctl_tracking_mark_dirty_blocks },
+
+	{ (IOCTL_SNAPSHOT_CREATE), ioctl_snapshot_create },
+	{ (IOCTL_SNAPSHOT_DESTROY), ioctl_snapshot_destroy },
+	{ (IOCTL_SNAPSHOT_ERRNO), ioctl_snapshot_errno },
+
+	{ (IOCTL_SNAPSTORE_CREATE), ioctl_snapstore_create },
+	{ (IOCTL_SNAPSTORE_FILE), ioctl_snapstore_file },
+	{ (IOCTL_SNAPSTORE_MEMORY), ioctl_snapstore_memory },
+	{ (IOCTL_SNAPSTORE_CLEANUP), ioctl_snapstore_cleanup },
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	{ (IOCTL_SNAPSTORE_FILE_MULTIDEV), ioctl_snapstore_file_multidev },
+#endif
+	{ (IOCTL_COLLECT_SNAPSHOT_IMAGES), ioctl_collect_snapimages },
+	{ 0, NULL }
+};
+
+long ctrl_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	long status = -ENOTTY;
+	size_t inx = 0;
+
+	while (blk_snap_ioctl_table[inx].cmd != 0) {
+		if (blk_snap_ioctl_table[inx].cmd == cmd) {
+			status = blk_snap_ioctl_table[inx].fn(arg);
+			break;
+		}
+		++inx;
+	}
+
+	return status;
+}
diff --git a/drivers/block/blk-snap/ctrl_fops.h b/drivers/block/blk-snap/ctrl_fops.h
new file mode 100644
index 000000000000..98072b61aa96
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_fops.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include <linux/fs.h>
+
+int get_blk_snap_major(void);
+
+int ctrl_init(void);
+void ctrl_done(void);
+
+int ctrl_open(struct inode *inode, struct file *file);
+int ctrl_release(struct inode *inode, struct file *file);
+
+ssize_t ctrl_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset);
+ssize_t ctrl_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset);
+
+unsigned int ctrl_poll(struct file *filp, struct poll_table_struct *wait);
+
+long ctrl_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
diff --git a/drivers/block/blk-snap/ctrl_pipe.c b/drivers/block/blk-snap/ctrl_pipe.c
new file mode 100644
index 000000000000..5ea6ff4365d6
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_pipe.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-ctrl"
+#include "common.h"
+#include "ctrl_pipe.h"
+#include "version.h"
+#include "blk-snap-ctl.h"
+#include "snapstore.h"
+#include "big_buffer.h"
+
+#include <linux/poll.h>
+#include <linux/uuid.h>
+
+#define CMD_TO_USER_FIFO_SIZE 1024
+
+LIST_HEAD(ctl_pipes);
+DECLARE_RWSEM(ctl_pipes_lock);
+
+
+static void ctrl_pipe_push_request(struct ctrl_pipe *pipe, unsigned int *cmd, size_t cmd_len)
+{
+	kfifo_in_spinlocked(&pipe->cmd_to_user, cmd, (cmd_len * sizeof(unsigned int)),
+			    &pipe->cmd_to_user_lock);
+
+	wake_up(&pipe->readq);
+}
+
+static void ctrl_pipe_request_acknowledge(struct ctrl_pipe *pipe, unsigned int result)
+{
+	unsigned int cmd[2];
+
+	cmd[0] = BLK_SNAP_CHARCMD_ACKNOWLEDGE;
+	cmd[1] = result;
+
+	ctrl_pipe_push_request(pipe, cmd, 2);
+}
+
+static inline dev_t _snapstore_dev(struct ioctl_dev_id_s *dev_id)
+{
+	if ((dev_id->major == 0) && (dev_id->minor == 0))
+		return 0; //memory snapstore
+
+	if ((dev_id->major == -1) && (dev_id->minor == -1))
+		return 0xFFFFffff; //multidevice snapstore
+
+	return MKDEV(dev_id->major, dev_id->minor);
+}
+
+static ssize_t ctrl_pipe_command_initiate(struct ctrl_pipe *pipe, const char __user *buffer,
+					  size_t length)
+{
+	unsigned long len;
+	int result = SUCCESS;
+	ssize_t processed = 0;
+	char *kernel_buffer;
+
+	kernel_buffer = kmalloc(length, GFP_KERNEL);
+	if (kernel_buffer == NULL)
+		return -ENOMEM;
+
+	len = copy_from_user(kernel_buffer, buffer, length);
+	if (len != 0) {
+		kfree(kernel_buffer);
+		pr_err("Unable to write to pipe: invalid user buffer\n");
+		return -EINVAL;
+	}
+
+	do {
+		u64 stretch_empty_limit;
+		unsigned int dev_id_list_length;
+		uuid_t *unique_id;
+		struct ioctl_dev_id_s *snapstore_dev_id;
+		struct ioctl_dev_id_s *dev_id_list;
+
+		//get snapstore uuid
+		if ((length - processed) < 16) {
+			pr_err("Unable to get snapstore uuid: invalid ctrl pipe initiate command. length=%lu\n",
+			       length);
+			break;
+		}
+		unique_id = (uuid_t *)(kernel_buffer + processed);
+		processed += 16;
+
+		//get snapstore empty limit
+		if ((length - processed) < sizeof(u64)) {
+			pr_err("Unable to get stretch snapstore limit: invalid ctrl pipe initiate command. length=%lu\n",
+			       length);
+			break;
+		}
+		stretch_empty_limit = *(u64 *)(kernel_buffer + processed);
+		processed += sizeof(u64);
+
+		//get snapstore device id
+		if ((length - processed) < sizeof(struct ioctl_dev_id_s)) {
+			pr_err("Unable to get snapstore device id: invalid ctrl pipe initiate command. length=%lu\n",
+			       length);
+			break;
+		}
+		snapstore_dev_id = (struct ioctl_dev_id_s *)(kernel_buffer + processed);
+		processed += sizeof(struct ioctl_dev_id_s);
+
+		//get device id list length
+		if ((length - processed) < 4) {
+			pr_err("Unable to get device id list length: ivalid ctrl pipe initiate command. length=%lu\n",
+			       length);
+			break;
+		}
+		dev_id_list_length = *(unsigned int *)(kernel_buffer + processed);
+		processed += sizeof(unsigned int);
+
+		//get devices id list
+		if ((length - processed) < (dev_id_list_length * sizeof(struct ioctl_dev_id_s))) {
+			pr_err("Unable to get all devices from device id list: invalid ctrl pipe initiate command. length=%lu\n",
+			       length);
+			break;
+		}
+		dev_id_list = (struct ioctl_dev_id_s *)(kernel_buffer + processed);
+		processed += (dev_id_list_length * sizeof(struct ioctl_dev_id_s));
+
+		{
+			size_t inx;
+			dev_t *dev_set;
+			size_t dev_id_set_length = (size_t)dev_id_list_length;
+
+			dev_set = kcalloc(dev_id_set_length, sizeof(dev_t), GFP_KERNEL);
+			if (dev_set == NULL) {
+				result = -ENOMEM;
+				break;
+			}
+
+			for (inx = 0; inx < dev_id_set_length; ++inx)
+				dev_set[inx] =
+					MKDEV(dev_id_list[inx].major, dev_id_list[inx].minor);
+
+			result = snapstore_create(unique_id, _snapstore_dev(snapstore_dev_id),
+						  dev_set, dev_id_set_length);
+			kfree(dev_set);
+			if (result != SUCCESS) {
+				pr_err("Failed to create snapstore\n");
+				break;
+			}
+
+			result = snapstore_stretch_initiate(
+				unique_id, pipe, (sector_t)to_sectors(stretch_empty_limit));
+			if (result != SUCCESS) {
+				pr_err("Failed to initiate stretch snapstore %pUB\n", unique_id);
+				break;
+			}
+		}
+	} while (false);
+	kfree(kernel_buffer);
+	ctrl_pipe_request_acknowledge(pipe, result);
+
+	if (result == SUCCESS)
+		return processed;
+	return result;
+}
+
+static ssize_t ctrl_pipe_command_next_portion(struct ctrl_pipe *pipe, const char __user *buffer,
+					      size_t length)
+{
+	unsigned long len;
+	int result = SUCCESS;
+	ssize_t processed = 0;
+	struct big_buffer *ranges = NULL;
+
+	do {
+		uuid_t unique_id;
+		unsigned int ranges_length;
+		size_t ranges_buffer_size;
+
+		//get snapstore id
+		if ((length - processed) < 16) {
+			pr_err("Unable to get snapstore id: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n",
+			       length);
+			break;
+		}
+		len = copy_from_user(&unique_id, buffer + processed, sizeof(uuid_t));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += 16;
+
+		//get ranges length
+		if ((length - processed) < 4) {
+			pr_err("Unable to get device id list length: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n",
+			       length);
+			break;
+		}
+		len = copy_from_user(&ranges_length, buffer + processed, sizeof(unsigned int));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += sizeof(unsigned int);
+
+		ranges_buffer_size = ranges_length * sizeof(struct ioctl_range_s);
+
+		// ranges
+		if ((length - processed) < (ranges_buffer_size)) {
+			pr_err("Unable to get all ranges: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n",
+			       length);
+			break;
+		}
+		ranges = big_buffer_alloc(ranges_buffer_size, GFP_KERNEL);
+		if (ranges == NULL) {
+			pr_err("Unable to allocate page array buffer: ");
+			pr_err("failed to process next portion command\n");
+			processed = -ENOMEM;
+			break;
+		}
+		if (ranges_buffer_size !=
+		    big_buffer_copy_from_user(buffer + processed, 0, ranges, ranges_buffer_size)) {
+			pr_err("Unable to process next portion command: ");
+			pr_err("invalid user buffer for parameters\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += ranges_buffer_size;
+
+		{
+			result = snapstore_add_file(&unique_id, ranges, ranges_length);
+
+			if (result != SUCCESS) {
+				pr_err("Failed to add file to snapstore\n");
+				result = -ENODEV;
+				break;
+			}
+		}
+	} while (false);
+	if (ranges)
+		big_buffer_free(ranges);
+
+	if (result == SUCCESS)
+		return processed;
+	return result;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+static ssize_t ctrl_pipe_command_next_portion_multidev(struct ctrl_pipe *pipe,
+						       const char __user *buffer, size_t length)
+{
+	unsigned long len;
+	int result = SUCCESS;
+	ssize_t processed = 0;
+	struct big_buffer *ranges = NULL;
+
+	do {
+		uuid_t unique_id;
+		int snapstore_major;
+		int snapstore_minor;
+		unsigned int ranges_length;
+		size_t ranges_buffer_size;
+
+		//get snapstore id
+		if ((length - processed) < 16) {
+			pr_err("Unable to get snapstore id: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n",
+			       length);
+			break;
+		}
+		len = copy_from_user(&unique_id, buffer + processed, sizeof(uuid_t));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += 16;
+
+		//get device id
+		if ((length - processed) < 8) {
+			pr_err("Unable to get device id list length: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n", length);
+			break;
+		}
+		len = copy_from_user(&snapstore_major, buffer + processed, sizeof(unsigned int));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += sizeof(unsigned int);
+
+		len = copy_from_user(&snapstore_minor, buffer + processed, sizeof(unsigned int));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += sizeof(unsigned int);
+
+		//get ranges length
+		if ((length - processed) < 4) {
+			pr_err("Unable to get device id list length: ");
+			pr_err("invalid ctrl pipe next portion command. length=%lu\n",
+			       length);
+			break;
+		}
+		len = copy_from_user(&ranges_length, buffer + processed, sizeof(unsigned int));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += sizeof(unsigned int);
+
+		ranges_buffer_size = ranges_length * sizeof(struct ioctl_range_s);
+
+		// ranges
+		if ((length - processed) < (ranges_buffer_size)) {
+			pr_err("Unable to get all ranges: ");
+			pr_err("invalid ctrl pipe next portion command.  length=%lu\n",
+			       length);
+			break;
+		}
+		ranges = big_buffer_alloc(ranges_buffer_size, GFP_KERNEL);
+		if (ranges == NULL) {
+			pr_err("Unable to process next portion command: ");
+			pr_err("failed to allocate page array buffer\n");
+			processed = -ENOMEM;
+			break;
+		}
+		if (ranges_buffer_size !=
+		    big_buffer_copy_from_user(buffer + processed, 0, ranges, ranges_buffer_size)) {
+			pr_err("Unable to process next portion command: ");
+			pr_err("invalid user buffer from parameters\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += ranges_buffer_size;
+
+		{
+			result = snapstore_add_multidev(&unique_id,
+							MKDEV(snapstore_major, snapstore_minor),
+							ranges, ranges_length);
+
+			if (result != SUCCESS) {
+				pr_err("Failed to add file to snapstore\n");
+				result = -ENODEV;
+				break;
+			}
+		}
+	} while (false);
+	if (ranges)
+		big_buffer_free(ranges);
+
+	if (result == SUCCESS)
+		return processed;
+
+	return result;
+}
+#endif
+
+static void ctrl_pipe_release_cb(struct kref *kref)
+{
+	struct ctrl_pipe *pipe = container_of(kref, struct ctrl_pipe, refcount);
+
+	down_write(&ctl_pipes_lock);
+	list_del(&pipe->link);
+	up_write(&ctl_pipes_lock);
+
+	kfifo_free(&pipe->cmd_to_user);
+
+	kfree(pipe);
+}
+
+struct ctrl_pipe *ctrl_pipe_get_resource(struct ctrl_pipe *pipe)
+{
+	if (pipe)
+		kref_get(&pipe->refcount);
+
+	return pipe;
+}
+
+void ctrl_pipe_put_resource(struct ctrl_pipe *pipe)
+{
+	if (pipe)
+		kref_put(&pipe->refcount, ctrl_pipe_release_cb);
+}
+
+void ctrl_pipe_done(void)
+{
+	bool is_empty;
+
+	pr_err("Ctrl pipes - done\n");
+
+	down_write(&ctl_pipes_lock);
+	is_empty = list_empty(&ctl_pipes);
+	up_write(&ctl_pipes_lock);
+
+	if (!is_empty)
+		pr_err("Unable to perform ctrl pipes cleanup: container is not empty\n");
+}
+
+struct ctrl_pipe *ctrl_pipe_new(void)
+{
+	int ret;
+	struct ctrl_pipe *pipe;
+
+	pipe = kzalloc(sizeof(struct ctrl_pipe), GFP_KERNEL);
+	if (pipe == NULL)
+		return NULL;
+
+	INIT_LIST_HEAD(&pipe->link);
+
+	ret = kfifo_alloc(&pipe->cmd_to_user, CMD_TO_USER_FIFO_SIZE, GFP_KERNEL);
+	if (ret) {
+		pr_err("Failed to allocate fifo. errno=%d.\n", ret);
+		kfree(pipe);
+		return NULL;
+	}
+	spin_lock_init(&pipe->cmd_to_user_lock);
+
+	kref_init(&pipe->refcount);
+
+	init_waitqueue_head(&pipe->readq);
+
+	down_write(&ctl_pipes_lock);
+	list_add_tail(&pipe->link, &ctl_pipes);
+	up_write(&ctl_pipes_lock);
+
+	return pipe;
+}
+
+ssize_t ctrl_pipe_read(struct ctrl_pipe *pipe, char __user *buffer, size_t length)
+{
+	int ret;
+	unsigned int processed = 0;
+
+	if (kfifo_is_empty_spinlocked(&pipe->cmd_to_user, &pipe->cmd_to_user_lock)) {
+		//nothing to read
+		ret = wait_event_interruptible(pipe->readq,
+					       !kfifo_is_empty_spinlocked(&pipe->cmd_to_user,
+									&pipe->cmd_to_user_lock));
+		if (ret) {
+			pr_err("Unable to wait for pipe read queue: interrupt signal was received\n");
+			return -ERESTARTSYS;
+		}
+	}
+
+	ret = kfifo_to_user(&pipe->cmd_to_user, buffer, length, &processed);
+	if (ret) {
+		pr_err("Failed to read command from ctrl pipe\n");
+		return ret;
+	}
+
+	return (ssize_t)processed;
+}
+
+ssize_t ctrl_pipe_write(struct ctrl_pipe *pipe, const char __user *buffer, size_t length)
+{
+	ssize_t processed = 0;
+
+	do {
+		unsigned long len;
+		unsigned int command;
+
+		if ((length - processed) < 4) {
+			pr_err("Unable to write command to ctrl pipe: invalid command length=%lu\n",
+			       length);
+			break;
+		}
+		len = copy_from_user(&command, buffer + processed, sizeof(unsigned int));
+		if (len != 0) {
+			pr_err("Unable to write to pipe: invalid user buffer\n");
+			processed = -EINVAL;
+			break;
+		}
+		processed += sizeof(unsigned int);
+		//+4
+		switch (command) {
+		case BLK_SNAP_CHARCMD_INITIATE: {
+			ssize_t res = ctrl_pipe_command_initiate(pipe, buffer + processed,
+								 length - processed);
+			if (res >= 0)
+				processed += res;
+			else
+				processed = res;
+		} break;
+		case BLK_SNAP_CHARCMD_NEXT_PORTION: {
+			ssize_t res = ctrl_pipe_command_next_portion(pipe, buffer + processed,
+								     length - processed);
+			if (res >= 0)
+				processed += res;
+			else
+				processed = res;
+		} break;
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+		case BLK_SNAP_CHARCMD_NEXT_PORTION_MULTIDEV: {
+			ssize_t res = ctrl_pipe_command_next_portion_multidev(
+				pipe, buffer + processed, length - processed);
+			if (res >= 0)
+				processed += res;
+			else
+				processed = res;
+		} break;
+#endif
+		default:
+			pr_err("Ctrl pipe write error: invalid command [0x%x] received\n", command);
+			break;
+		}
+	} while (false);
+	return processed;
+}
+
+unsigned int ctrl_pipe_poll(struct ctrl_pipe *pipe)
+{
+	unsigned int mask = 0;
+
+	if (!kfifo_is_empty_spinlocked(&pipe->cmd_to_user, &pipe->cmd_to_user_lock))
+		mask |= (POLLIN | POLLRDNORM); /* readable */
+
+	mask |= (POLLOUT | POLLWRNORM); /* writable */
+
+	return mask;
+}
+
+void ctrl_pipe_request_halffill(struct ctrl_pipe *pipe, unsigned long long filled_status)
+{
+	unsigned int cmd[3];
+
+	pr_err("Snapstore is half-full\n");
+
+	cmd[0] = (unsigned int)BLK_SNAP_CHARCMD_HALFFILL;
+	cmd[1] = (unsigned int)(filled_status & 0xFFFFffff); //lo
+	cmd[2] = (unsigned int)(filled_status >> 32);
+
+	ctrl_pipe_push_request(pipe, cmd, 3);
+}
+
+void ctrl_pipe_request_overflow(struct ctrl_pipe *pipe, unsigned int error_code,
+				unsigned long long filled_status)
+{
+	unsigned int cmd[4];
+
+	pr_err("Snapstore overflow\n");
+
+	cmd[0] = (unsigned int)BLK_SNAP_CHARCMD_OVERFLOW;
+	cmd[1] = error_code;
+	cmd[2] = (unsigned int)(filled_status & 0xFFFFffff); //lo
+	cmd[3] = (unsigned int)(filled_status >> 32);
+
+	ctrl_pipe_push_request(pipe, cmd, 4);
+}
+
+void ctrl_pipe_request_terminate(struct ctrl_pipe *pipe, unsigned long long filled_status)
+{
+	unsigned int cmd[3];
+
+	pr_err("Snapstore termination\n");
+
+	cmd[0] = (unsigned int)BLK_SNAP_CHARCMD_TERMINATE;
+	cmd[1] = (unsigned int)(filled_status & 0xFFFFffff); //lo
+	cmd[2] = (unsigned int)(filled_status >> 32);
+
+	ctrl_pipe_push_request(pipe, cmd, 3);
+}
diff --git a/drivers/block/blk-snap/ctrl_pipe.h b/drivers/block/blk-snap/ctrl_pipe.h
new file mode 100644
index 000000000000..1aa1099eec25
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_pipe.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include <linux/kref.h>
+#include <linux/wait.h>
+#include <linux/kfifo.h>
+
+struct ctrl_pipe {
+	struct list_head link;
+
+	struct kref refcount;
+
+	wait_queue_head_t readq;
+
+	struct kfifo cmd_to_user;
+	spinlock_t cmd_to_user_lock;
+};
+
+struct ctrl_pipe *ctrl_pipe_get_resource(struct ctrl_pipe *pipe);
+void ctrl_pipe_put_resource(struct ctrl_pipe *pipe);
+
+void ctrl_pipe_done(void);
+
+struct ctrl_pipe *ctrl_pipe_new(void);
+
+ssize_t ctrl_pipe_read(struct ctrl_pipe *pipe, char __user *buffer, size_t length);
+ssize_t ctrl_pipe_write(struct ctrl_pipe *pipe, const char __user *buffer, size_t length);
+
+unsigned int ctrl_pipe_poll(struct ctrl_pipe *pipe);
+
+void ctrl_pipe_request_halffill(struct ctrl_pipe *pipe, unsigned long long filled_status);
+void ctrl_pipe_request_overflow(struct ctrl_pipe *pipe, unsigned int error_code,
+				unsigned long long filled_status);
+void ctrl_pipe_request_terminate(struct ctrl_pipe *pipe, unsigned long long filled_status);
diff --git a/drivers/block/blk-snap/ctrl_sysfs.c b/drivers/block/blk-snap/ctrl_sysfs.c
new file mode 100644
index 000000000000..4ec78e85b510
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_sysfs.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-ctrl"
+#include "common.h"
+#include "ctrl_sysfs.h"
+#include "ctrl_fops.h"
+#include "blk-snap-ctl.h"
+
+#include <linux/blkdev.h>
+#include <linux/sysfs.h>
+
+static ssize_t major_show(struct class *class, struct class_attribute *attr, char *buf)
+{
+	sprintf(buf, "%d", get_blk_snap_major());
+	return strlen(buf);
+}
+
+CLASS_ATTR_RO(major); // declare class_attr_major
+static struct class *blk_snap_class;
+
+static struct device *blk_snap_device;
+
+int ctrl_sysfs_init(void)
+{
+	struct device *dev;
+	int res;
+
+	blk_snap_class = class_create(THIS_MODULE, MODULE_NAME);
+	if (IS_ERR(blk_snap_class)) {
+		res = PTR_ERR(blk_snap_class);
+
+		pr_err("Bad class create. errno=%d\n", 0 - res);
+		return res;
+	}
+
+	pr_info("Create 'major' sysfs attribute\n");
+	res = class_create_file(blk_snap_class, &class_attr_major);
+	if (res != SUCCESS) {
+		pr_err("Failed to create 'major' sysfs file\n");
+
+		class_destroy(blk_snap_class);
+		blk_snap_class = NULL;
+		return res;
+	}
+
+	dev = device_create(blk_snap_class, NULL, MKDEV(get_blk_snap_major(), 0), NULL,
+			    MODULE_NAME);
+	if (IS_ERR(dev)) {
+		res = PTR_ERR(dev);
+		pr_err("Failed to create device, errno=%d\n", res);
+
+		class_remove_file(blk_snap_class, &class_attr_major);
+		class_destroy(blk_snap_class);
+		blk_snap_class = NULL;
+		return res;
+	}
+
+	blk_snap_device = dev;
+	return res;
+}
+
+void ctrl_sysfs_done(void)
+{
+	if (blk_snap_device) {
+		device_unregister(blk_snap_device);
+		blk_snap_device = NULL;
+	}
+
+	if (blk_snap_class != NULL) {
+		class_remove_file(blk_snap_class, &class_attr_major);
+		class_destroy(blk_snap_class);
+		blk_snap_class = NULL;
+	}
+}
diff --git a/drivers/block/blk-snap/ctrl_sysfs.h b/drivers/block/blk-snap/ctrl_sysfs.h
new file mode 100644
index 000000000000..27a2a4d3da4c
--- /dev/null
+++ b/drivers/block/blk-snap/ctrl_sysfs.h
@@ -0,0 +1,5 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+int ctrl_sysfs_init(void);
+void ctrl_sysfs_done(void);
diff --git a/drivers/block/blk-snap/defer_io.c b/drivers/block/blk-snap/defer_io.c
new file mode 100644
index 000000000000..3290fa326fc7
--- /dev/null
+++ b/drivers/block/blk-snap/defer_io.c
@@ -0,0 +1,393 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-defer_io"
+#include "common.h"
+#include "defer_io.h"
+#include "blk_deferred.h"
+#include "tracker.h"
+#include "blk_util.h"
+#include "filter.h"
+
+#include <linux/kthread.h>
+
+#define BLK_IMAGE_THROTTLE_TIMEOUT (1 * HZ) //delay 1 sec
+//#define BLK_IMAGE_THROTTLE_TIMEOUT ( HZ/1000 * 10 )	//delay 10 ms
+
+
+
+struct defer_io_orig_rq {
+	struct list_head link;
+	struct defer_io_queue *queue;
+
+	struct bio *bio;
+	struct tracker *tracker;
+};
+
+static inline void defer_io_queue_init(struct defer_io_queue *queue)
+{
+	INIT_LIST_HEAD(&queue->list);
+
+	spin_lock_init(&queue->lock);
+
+	atomic_set(&queue->in_queue_cnt, 0);
+	atomic_set(&queue->active_state, true);
+}
+
+static inline struct defer_io_orig_rq *defer_io_queue_new(struct defer_io_queue *queue)
+{
+	struct defer_io_orig_rq *dio_rq = kzalloc(sizeof(struct defer_io_orig_rq), GFP_NOIO);
+
+	if (dio_rq == NULL)
+		return NULL;
+
+	INIT_LIST_HEAD(&dio_rq->link);
+	dio_rq->queue = queue;
+
+	return dio_rq;
+}
+
+static inline void defer_io_queue_free(struct defer_io_orig_rq *dio_rq)
+{
+	kfree(dio_rq);
+}
+
+static int defer_io_queue_push_back(struct defer_io_queue *queue, struct defer_io_orig_rq *dio_rq)
+{
+	int res = SUCCESS;
+
+	spin_lock(&queue->lock);
+
+	if (atomic_read(&queue->active_state)) {
+		INIT_LIST_HEAD(&dio_rq->link);
+
+		list_add_tail(&dio_rq->link, &queue->list);
+		atomic_inc(&queue->in_queue_cnt);
+	} else
+		res = -EACCES;
+
+	spin_unlock(&queue->lock);
+	return res;
+}
+
+static struct defer_io_orig_rq *defer_io_queue_get_first(struct defer_io_queue *queue)
+{
+	struct defer_io_orig_rq *dio_rq = NULL;
+
+	spin_lock(&queue->lock);
+
+	if (!list_empty(&queue->list)) {
+		dio_rq = list_entry(queue->list.next, struct defer_io_orig_rq, link);
+		list_del(&dio_rq->link);
+		atomic_dec(&queue->in_queue_cnt);
+	}
+
+	spin_unlock(&queue->lock);
+
+	return dio_rq;
+}
+
+static bool defer_io_queue_active(struct defer_io_queue *queue, bool state)
+{
+	bool prev_state;
+
+	spin_lock(&queue->lock);
+
+	prev_state = atomic_read(&queue->active_state);
+	atomic_set(&queue->active_state, state);
+
+	spin_unlock(&queue->lock);
+
+	return prev_state;
+}
+
+#define defer_io_queue_empty(queue) (atomic_read(&(queue).in_queue_cnt) == 0)
+
+static void _defer_io_finish(struct defer_io *defer_io, struct defer_io_queue *queue_in_progress)
+{
+	while (!defer_io_queue_empty(*queue_in_progress)) {
+		struct tracker *tracker = NULL;
+		bool cbt_locked = false;
+		bool is_write_bio;
+		sector_t sectCount = 0;
+
+		struct defer_io_orig_rq *orig_req = defer_io_queue_get_first(queue_in_progress);
+
+		is_write_bio = bio_data_dir(orig_req->bio) && bio_has_data(orig_req->bio);
+
+		if (orig_req->tracker && is_write_bio) {
+			tracker = orig_req->tracker;
+			cbt_locked = tracker_cbt_bitmap_lock(tracker);
+			if (cbt_locked) {
+				sectCount = bio_sectors(orig_req->bio);
+				tracker_cbt_bitmap_set(tracker, orig_req->bio->bi_iter.bi_sector,
+						       sectCount);
+			}
+		}
+
+		bio_put(orig_req->bio);
+		filter_submit_original_bio(orig_req->bio);
+
+		if (cbt_locked)
+			tracker_cbt_bitmap_unlock(tracker);
+
+		defer_io_queue_free(orig_req);
+	}
+}
+
+static int _defer_io_copy_prepare(struct defer_io *defer_io,
+				  struct defer_io_queue *queue_in_process,
+				  struct blk_deferred_request **dio_copy_req)
+{
+	int res = SUCCESS;
+	int dios_count = 0;
+	sector_t dios_sectors_count = 0;
+
+	//fill copy_request set
+	while (!defer_io_queue_empty(defer_io->dio_queue) &&
+	       (dios_count < DEFER_IO_DIO_REQUEST_LENGTH) &&
+	       (dios_sectors_count < DEFER_IO_DIO_REQUEST_SECTORS_COUNT)) {
+		struct defer_io_orig_rq *dio_orig_req =
+			(struct defer_io_orig_rq *)defer_io_queue_get_first(&defer_io->dio_queue);
+		atomic_dec(&defer_io->queue_filling_count);
+
+		defer_io_queue_push_back(queue_in_process, dio_orig_req);
+
+		if (!kthread_should_stop() &&
+		    !snapstore_device_is_corrupted(defer_io->snapstore_device)) {
+			if (bio_data_dir(dio_orig_req->bio) && bio_has_data(dio_orig_req->bio)) {
+				struct blk_range copy_range;
+
+				copy_range.ofs = dio_orig_req->bio->bi_iter.bi_sector;
+				copy_range.cnt = bio_sectors(dio_orig_req->bio);
+				res = snapstore_device_prepare_requests(defer_io->snapstore_device,
+									&copy_range, dio_copy_req);
+				if (res != SUCCESS) {
+					pr_err("Unable to execute Copy On Write algorithm: failed to add ranges to copy to snapstore request. errno=%d\n",
+					       res);
+					break;
+				}
+
+				dios_sectors_count += copy_range.cnt;
+			}
+		}
+		++dios_count;
+	}
+	return res;
+}
+
+static int defer_io_work_thread(void *p)
+{
+	struct defer_io_queue queue_in_process = { 0 };
+	struct defer_io *defer_io = NULL;
+
+	//set_user_nice( current, -20 ); //MIN_NICE
+	defer_io_queue_init(&queue_in_process);
+
+	defer_io = defer_io_get_resource((struct defer_io *)p);
+	pr_info("Defer IO thread for original device [%d:%d] started\n",
+		MAJOR(defer_io->original_dev_id), MINOR(defer_io->original_dev_id));
+
+	while (!kthread_should_stop() || !defer_io_queue_empty(defer_io->dio_queue)) {
+		if (defer_io_queue_empty(defer_io->dio_queue)) {
+			int res = wait_event_interruptible_timeout(
+				defer_io->queue_add_event,
+				(!defer_io_queue_empty(defer_io->dio_queue)),
+				BLK_IMAGE_THROTTLE_TIMEOUT);
+			if (-ERESTARTSYS == res)
+				pr_err("Signal received in defer IO thread. Waiting for completion with code ERESTARTSYS\n");
+		}
+
+		if (!defer_io_queue_empty(defer_io->dio_queue)) {
+			int dio_copy_result = SUCCESS;
+			struct blk_deferred_request *dio_copy_req = NULL;
+
+			mutex_lock(&defer_io->snapstore_device->store_block_map_locker);
+			do {
+				dio_copy_result = _defer_io_copy_prepare(
+					defer_io, &queue_in_process, &dio_copy_req);
+				if (dio_copy_result != SUCCESS) {
+					pr_err("Unable to process defer IO request: failed to prepare copy request. erro=%d\n",
+					       dio_copy_result);
+					break;
+				}
+				if (dio_copy_req == NULL)
+					break; //nothing to copy
+
+				dio_copy_result = blk_deferred_request_read_original(
+					defer_io->original_blk_dev, dio_copy_req);
+				if (dio_copy_result != SUCCESS) {
+					pr_err("Unable to process defer IO request: failed to read data to copy request. errno=%d\n",
+					       dio_copy_result);
+					break;
+				}
+				dio_copy_result = snapstore_device_store(defer_io->snapstore_device,
+									 dio_copy_req);
+				if (dio_copy_result != SUCCESS) {
+					pr_err("Unable to process defer IO request: failed to write data from copy request. errno=%d\n",
+					       dio_copy_result);
+					break;
+				}
+
+			} while (false);
+			_defer_io_finish(defer_io, &queue_in_process);
+			mutex_unlock(&defer_io->snapstore_device->store_block_map_locker);
+
+			if (dio_copy_req) {
+				if (dio_copy_result == -EDEADLK)
+					blk_deferred_request_deadlocked(dio_copy_req);
+				else
+					blk_deferred_request_free(dio_copy_req);
+			}
+		}
+
+		//wake up snapimage if defer io queue empty
+		if (defer_io_queue_empty(defer_io->dio_queue))
+			wake_up_interruptible(&defer_io->queue_throttle_waiter);
+	}
+	defer_io_queue_active(&defer_io->dio_queue, false);
+
+	//waiting for all sent request complete
+	_defer_io_finish(defer_io, &defer_io->dio_queue);
+
+	pr_info("Defer IO thread for original device [%d:%d] completed\n",
+		MAJOR(defer_io->original_dev_id), MINOR(defer_io->original_dev_id));
+	defer_io_put_resource(defer_io);
+	return SUCCESS;
+}
+
+static void _defer_io_destroy(struct defer_io *defer_io)
+{
+	if (defer_io == NULL)
+		return;
+
+	if (defer_io->dio_thread)
+		defer_io_stop(defer_io);
+
+	if (defer_io->snapstore_device)
+		snapstore_device_put_resource(defer_io->snapstore_device);
+
+	kfree(defer_io);
+	pr_info("Defer IO processor was destroyed\n");
+}
+
+static void defer_io_destroy_cb(struct kref *kref)
+{
+	_defer_io_destroy(container_of(kref, struct defer_io, refcount));
+}
+
+struct defer_io *defer_io_get_resource(struct defer_io *defer_io)
+{
+	if (defer_io)
+		kref_get(&defer_io->refcount);
+
+	return defer_io;
+}
+
+void defer_io_put_resource(struct defer_io *defer_io)
+{
+	if (defer_io)
+		kref_put(&defer_io->refcount, defer_io_destroy_cb);
+}
+
+int defer_io_create(dev_t dev_id, struct block_device *blk_dev, struct defer_io **pp_defer_io)
+{
+	int res = SUCCESS;
+	struct defer_io *defer_io = NULL;
+	struct snapstore_device *snapstore_device;
+
+	pr_info("Defer IO processor was created for device [%d:%d]\n", MAJOR(dev_id),
+		MINOR(dev_id));
+
+	defer_io = kzalloc(sizeof(struct defer_io), GFP_KERNEL);
+	if (defer_io == NULL)
+		return -ENOMEM;
+
+	snapstore_device = snapstore_device_find_by_dev_id(dev_id);
+	if (snapstore_device == NULL) {
+		pr_err("Unable to create defer IO processor: failed to initialize snapshot data for device [%d:%d]\n",
+		       MAJOR(dev_id), MINOR(dev_id));
+
+		kfree(defer_io);
+		return -ENODATA;
+	}
+
+	defer_io->snapstore_device = snapstore_device_get_resource(snapstore_device);
+	defer_io->original_dev_id = dev_id;
+	defer_io->original_blk_dev = blk_dev;
+
+	kref_init(&defer_io->refcount);
+
+	defer_io_queue_init(&defer_io->dio_queue);
+
+	init_waitqueue_head(&defer_io->queue_add_event);
+
+	atomic_set(&defer_io->queue_filling_count, 0);
+
+	init_waitqueue_head(&defer_io->queue_throttle_waiter);
+
+	defer_io->dio_thread = kthread_create(defer_io_work_thread, (void *)defer_io,
+					      "blksnapdeferio%d:%d", MAJOR(dev_id), MINOR(dev_id));
+	if (IS_ERR(defer_io->dio_thread)) {
+		res = PTR_ERR(defer_io->dio_thread);
+		pr_err("Unable to create defer IO processor: failed to create thread. errno=%d\n",
+		       res);
+
+		_defer_io_destroy(defer_io);
+		defer_io = NULL;
+		*pp_defer_io = NULL;
+
+		return res;
+	}
+
+	wake_up_process(defer_io->dio_thread);
+
+	*pp_defer_io = defer_io;
+	pr_info("Defer IO processor was created\n");
+
+	return SUCCESS;
+}
+
+int defer_io_stop(struct defer_io *defer_io)
+{
+	int res = SUCCESS;
+
+	pr_info("Defer IO thread for the device stopped [%d:%d]\n",
+		MAJOR(defer_io->original_dev_id), MINOR(defer_io->original_dev_id));
+
+	if (defer_io->dio_thread != NULL) {
+		struct task_struct *dio_thread = defer_io->dio_thread;
+
+		defer_io->dio_thread = NULL;
+		res = kthread_stop(dio_thread); //stopping and waiting.
+		if (res != SUCCESS)
+			pr_err("Failed to stop defer IO thread. errno=%d\n", res);
+	}
+
+	return res;
+}
+
+int defer_io_redirect_bio(struct defer_io *defer_io, struct bio *bio, void *tracker)
+{
+	struct defer_io_orig_rq *dio_orig_req;
+
+	if (snapstore_device_is_corrupted(defer_io->snapstore_device))
+		return -ENODATA;
+
+	dio_orig_req = defer_io_queue_new(&defer_io->dio_queue);
+	if (dio_orig_req == NULL)
+		return -ENOMEM;
+
+	bio_get(dio_orig_req->bio = bio);
+
+	dio_orig_req->tracker = (struct tracker *)tracker;
+
+	if (defer_io_queue_push_back(&defer_io->dio_queue, dio_orig_req) != SUCCESS) {
+		defer_io_queue_free(dio_orig_req);
+		return -EFAULT;
+	}
+
+	atomic_inc(&defer_io->queue_filling_count);
+
+	wake_up_interruptible(&defer_io->queue_add_event);
+
+	return SUCCESS;
+}
diff --git a/drivers/block/blk-snap/defer_io.h b/drivers/block/blk-snap/defer_io.h
new file mode 100644
index 000000000000..27c3bb03241f
--- /dev/null
+++ b/drivers/block/blk-snap/defer_io.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+#include <linux/kref.h>
+#include "snapstore_device.h"
+
+struct defer_io_queue {
+	struct list_head list;
+	spinlock_t lock;
+
+	atomic_t active_state;
+	atomic_t in_queue_cnt;
+};
+
+struct defer_io {
+	struct kref refcount;
+
+	wait_queue_head_t queue_add_event;
+
+	atomic_t queue_filling_count;
+	wait_queue_head_t queue_throttle_waiter;
+
+	dev_t original_dev_id;
+	struct block_device *original_blk_dev;
+
+	struct snapstore_device *snapstore_device;
+
+	struct task_struct *dio_thread;
+
+	struct defer_io_queue dio_queue;
+};
+
+int defer_io_create(dev_t dev_id, struct block_device *blk_dev, struct defer_io **pp_defer_io);
+int defer_io_stop(struct defer_io *defer_io);
+
+struct defer_io *defer_io_get_resource(struct defer_io *defer_io);
+void defer_io_put_resource(struct defer_io *defer_io);
+
+int defer_io_redirect_bio(struct defer_io *defer_io, struct bio *bio, void *tracker);
diff --git a/drivers/block/blk-snap/filter.c b/drivers/block/blk-snap/filter.c
new file mode 100644
index 000000000000..4e225a2a04c2
--- /dev/null
+++ b/drivers/block/blk-snap/filter.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include "filter.h"
+#include "tracking.h"
+
+#include <linux/blk-filter.h>
+
+#define BLK_SNAP_DEFAULT_ALTITUDE BLK_FILTER_ALTITUDE_MIN
+
+static void filter_disk_add(struct gendisk *disk)
+{
+	pr_info("new disk [%s] in system\n", disk->disk_name);
+}
+static void filter_disk_del(struct gendisk *disk)
+{
+	pr_info("del disk [%s] from system\n", disk->disk_name);
+}
+static void filter_disk_release(struct gendisk *disk)
+{
+	pr_info("release disk [%s] from system\n", disk->disk_name);
+}
+
+static blk_qc_t filter_submit_bio(struct bio *bio)
+{
+	blk_qc_t result;
+
+	if (tracking_submit_bio(bio, &result))
+		return result;
+
+	return filter_submit_original_bio(bio);
+}
+
+const struct blk_filter_ops filter_ops = { .disk_add = filter_disk_add,
+					   .disk_del = filter_disk_del,
+					   .disk_release = filter_disk_release,
+					   .submit_bio = filter_submit_bio };
+
+struct blk_filter filter = { .name = MODULE_NAME,
+			     .ops = &filter_ops,
+			     .altitude = BLK_SNAP_DEFAULT_ALTITUDE,
+			     .blk_filter_ctx = NULL };
+
+blk_qc_t filter_submit_original_bio(struct bio *bio)
+{
+	return blk_filter_submit_bio_next(&filter, bio);
+}
+
+int filter_init(void)
+{
+	const char *exist_filter;
+	int result;
+
+	result = blk_filter_register(&filter);
+	if (result != SUCCESS) {
+		pr_err("Failed to register block io layer filter\n");
+
+		exist_filter = blk_filter_check_altitude(filter.altitude);
+		if (exist_filter)
+			pr_err("Block io layer filter [%s] already exist on altitude [%ld]\n",
+			       exist_filter, filter.altitude);
+	}
+
+	return result;
+}
+
+void filter_done(void)
+{
+	int result = blk_filter_unregister(&filter);
+
+	if (unlikely(result != SUCCESS))
+		pr_err("Failed to unregister block filter");
+}
diff --git a/drivers/block/blk-snap/filter.h b/drivers/block/blk-snap/filter.h
new file mode 100644
index 000000000000..fe5efe1bca69
--- /dev/null
+++ b/drivers/block/blk-snap/filter.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+blk_qc_t filter_submit_original_bio(struct bio *bio);
+
+int filter_init(void);
+void filter_done(void);
diff --git a/drivers/block/blk-snap/main.c b/drivers/block/blk-snap/main.c
new file mode 100644
index 000000000000..e510500fc479
--- /dev/null
+++ b/drivers/block/blk-snap/main.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include "version.h"
+#include "blk-snap-ctl.h"
+#include "params.h"
+#include "ctrl_fops.h"
+#include "ctrl_pipe.h"
+#include "ctrl_sysfs.h"
+#include "snapimage.h"
+#include "snapstore.h"
+#include "snapstore_device.h"
+#include "snapshot.h"
+#include "tracker.h"
+#include "filter.h"
+#include <linux/module.h>
+
+int __init blk_snap_init(void)
+{
+	int result = SUCCESS;
+
+	pr_info("Loading\n");
+
+	params_check();
+
+	result = ctrl_init();
+	if (result != SUCCESS)
+		return result;
+
+	result = blk_redirect_bioset_create();
+	if (result != SUCCESS)
+		return result;
+
+	result = blk_deferred_bioset_create();
+	if (result != SUCCESS)
+		return result;
+
+	result = snapimage_init();
+	if (result != SUCCESS)
+		return result;
+
+	result = ctrl_sysfs_init();
+	if (result != SUCCESS)
+		return result;
+
+	result = filter_init();
+	if (result != SUCCESS)
+		return result;
+
+	return result;
+}
+
+void __exit blk_snap_exit(void)
+{
+	pr_info("Unloading module\n");
+
+	ctrl_sysfs_done();
+
+	snapshot_done();
+
+	snapstore_device_done();
+	snapstore_done();
+
+	tracker_done();
+
+	filter_done();
+
+	snapimage_done();
+
+	blk_deferred_bioset_free();
+	blk_deferred_done();
+
+	blk_redirect_bioset_free();
+
+	ctrl_done();
+}
+
+module_init(blk_snap_init);
+module_exit(blk_snap_exit);
+
+MODULE_DESCRIPTION("Block Layer Snapshot Kernel Module");
+MODULE_VERSION(FILEVER_STR);
+MODULE_AUTHOR("Veeam Software Group GmbH");
+MODULE_LICENSE("GPL");
diff --git a/drivers/block/blk-snap/params.c b/drivers/block/blk-snap/params.c
new file mode 100644
index 000000000000..368d5e90523c
--- /dev/null
+++ b/drivers/block/blk-snap/params.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include <linux/module.h>
+
+int snapstore_block_size_pow = 14;
+int change_tracking_block_size_pow = 18;
+
+int get_snapstore_block_size_pow(void)
+{
+	return snapstore_block_size_pow;
+}
+
+int inc_snapstore_block_size_pow(void)
+{
+	if (snapstore_block_size_pow > 30)
+		return -EFAULT;
+
+	++snapstore_block_size_pow;
+	return SUCCESS;
+}
+
+int get_change_tracking_block_size_pow(void)
+{
+	return change_tracking_block_size_pow;
+}
+
+void params_check(void)
+{
+	pr_info("snapstore_block_size_pow: %d\n", snapstore_block_size_pow);
+	pr_info("change_tracking_block_size_pow: %d\n", change_tracking_block_size_pow);
+
+	if (snapstore_block_size_pow > 23) {
+		snapstore_block_size_pow = 23;
+		pr_info("Limited snapstore_block_size_pow: %d\n", snapstore_block_size_pow);
+	} else if (snapstore_block_size_pow < 12) {
+		snapstore_block_size_pow = 12;
+		pr_info("Limited snapstore_block_size_pow: %d\n", snapstore_block_size_pow);
+	}
+
+	if (change_tracking_block_size_pow > 23) {
+		change_tracking_block_size_pow = 23;
+		pr_info("Limited change_tracking_block_size_pow: %d\n",
+			change_tracking_block_size_pow);
+	} else if (change_tracking_block_size_pow < 12) {
+		change_tracking_block_size_pow = 12;
+		pr_info("Limited change_tracking_block_size_pow: %d\n",
+			change_tracking_block_size_pow);
+	}
+}
+
+module_param_named(snapstore_block_size_pow, snapstore_block_size_pow, int, 0644);
+MODULE_PARM_DESC(snapstore_block_size_pow,
+		 "Snapstore block size binary pow. 20 for 1MiB block size");
+
+module_param_named(change_tracking_block_size_pow, change_tracking_block_size_pow, int, 0644);
+MODULE_PARM_DESC(change_tracking_block_size_pow,
+		 "Change-tracking block size binary pow. 18 for 256 KiB block size");
diff --git a/drivers/block/blk-snap/params.h b/drivers/block/blk-snap/params.h
new file mode 100644
index 000000000000..570a053da5fc
--- /dev/null
+++ b/drivers/block/blk-snap/params.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#pragma once
+
+int get_snapstore_block_size_pow(void);
+int inc_snapstore_block_size_pow(void);
+
+static inline sector_t snapstore_block_shift(void)
+{
+	return get_snapstore_block_size_pow() - SECTOR_SHIFT;
+};
+
+static inline sector_t snapstore_block_size(void)
+{
+	return 1 << snapstore_block_shift();
+};
+
+static inline sector_t snapstore_block_mask(void)
+{
+	return snapstore_block_size() - 1;
+};
+
+int get_change_tracking_block_size_pow(void);
+
+static inline unsigned int change_tracking_block_size(void)
+{
+	return 1 << get_change_tracking_block_size_pow();
+};
+
+void params_check(void);
diff --git a/drivers/block/blk-snap/rangevector.c b/drivers/block/blk-snap/rangevector.c
new file mode 100644
index 000000000000..49fe4589b6f7
--- /dev/null
+++ b/drivers/block/blk-snap/rangevector.c
@@ -0,0 +1,85 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "common.h"
+#include "rangevector.h"
+
+#define SECTION "ranges	"
+
+static inline sector_t range_node_start(struct blk_range_tree_node *range_node)
+{
+	return range_node->range.ofs;
+}
+
+static inline sector_t range_node_last(struct blk_range_tree_node *range_node)
+{
+	return range_node->range.ofs + range_node->range.cnt - 1;
+}
+
+#ifndef INTERVAL_TREE_DEFINE
+#pragma message("INTERVAL_TREE_DEFINE is undefined")
+#endif
+INTERVAL_TREE_DEFINE(struct blk_range_tree_node, _node, sector_t, _subtree_last,
+		     range_node_start, range_node_last, static inline, _blk_range_rb)
+
+void blk_range_rb_insert(struct blk_range_tree_node *node, struct rb_root_cached *root)
+{
+	_blk_range_rb_insert(node, root);
+}
+
+void blk_range_rb_remove(struct blk_range_tree_node *node, struct rb_root_cached *root)
+{
+	_blk_range_rb_remove(node, root);
+}
+
+struct blk_range_tree_node *blk_range_rb_iter_first(struct rb_root_cached *root, sector_t start,
+						    sector_t last)
+{
+	return _blk_range_rb_iter_first(root, start, last);
+}
+
+struct blk_range_tree_node *blk_range_rb_iter_next(struct blk_range_tree_node *node, sector_t start,
+						   sector_t last)
+{
+	return _blk_range_rb_iter_next(node, start, last);
+}
+
+void rangevector_init(struct rangevector *rangevector)
+{
+	init_rwsem(&rangevector->lock);
+
+	rangevector->root = RB_ROOT_CACHED;
+}
+
+void rangevector_done(struct rangevector *rangevector)
+{
+	struct rb_node *rb_node = NULL;
+
+	down_write(&rangevector->lock);
+	rb_node = rb_first_cached(&rangevector->root);
+	while (rb_node) {
+		struct blk_range_tree_node *range_node = (struct blk_range_tree_node *)
+			rb_node; //container_of(rb_node, struct blk_range_tree_node, node);
+
+		blk_range_rb_remove(range_node, &rangevector->root);
+		kfree(range_node);
+
+		rb_node = rb_first_cached(&rangevector->root);
+	}
+	up_write(&rangevector->lock);
+}
+
+int rangevector_add(struct rangevector *rangevector, struct blk_range *rg)
+{
+	struct blk_range_tree_node *range_node;
+
+	range_node = kzalloc(sizeof(struct blk_range_tree_node), GFP_KERNEL);
+	if (range_node)
+		return -ENOMEM;
+
+	range_node->range = *rg;
+
+	down_write(&rangevector->lock);
+	blk_range_rb_insert(range_node, &rangevector->root);
+	up_write(&rangevector->lock);
+
+	return SUCCESS;
+}
diff --git a/drivers/block/blk-snap/rangevector.h b/drivers/block/blk-snap/rangevector.h
new file mode 100644
index 000000000000..5ff439423178
--- /dev/null
+++ b/drivers/block/blk-snap/rangevector.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include <linux/interval_tree_generic.h>
+
+struct blk_range_tree_node {
+	struct rb_node _node;
+	struct blk_range range;
+	sector_t _subtree_last;
+};
+
+void blk_range_rb_insert(struct blk_range_tree_node *node, struct rb_root_cached *root);
+
+void blk_range_rb_remove(struct blk_range_tree_node *node, struct rb_root_cached *root);
+
+struct blk_range_tree_node *blk_range_rb_iter_first(struct rb_root_cached *root, sector_t start,
+						    sector_t last);
+
+struct blk_range_tree_node *blk_range_rb_iter_next(struct blk_range_tree_node *node, sector_t start,
+						   sector_t last);
+
+struct rangevector {
+	struct rb_root_cached root;
+	struct rw_semaphore lock;
+};
+
+void rangevector_init(struct rangevector *rangevector);
+
+void rangevector_done(struct rangevector *rangevector);
+
+int rangevector_add(struct rangevector *rangevector, struct blk_range *rg);
diff --git a/drivers/block/blk-snap/snapimage.c b/drivers/block/blk-snap/snapimage.c
new file mode 100644
index 000000000000..de9cd48fd117
--- /dev/null
+++ b/drivers/block/blk-snap/snapimage.c
@@ -0,0 +1,982 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapimage"
+#include "common.h"
+#include "snapimage.h"
+#include "blk_util.h"
+#include "defer_io.h"
+#include "cbt_map.h"
+#include "tracker.h"
+
+#include <asm/div64.h>
+#include <linux/cdrom.h>
+#include <linux/blk-mq.h>
+#include <linux/hdreg.h>
+#include <linux/kthread.h>
+
+#define SNAPIMAGE_MAX_DEVICES 2048
+
+int snapimage_major;
+unsigned long *snapimage_minors;
+DEFINE_SPINLOCK(snapimage_minors_lock);
+
+LIST_HEAD(snap_images);
+DECLARE_RWSEM(snap_images_lock);
+
+DECLARE_RWSEM(snap_image_destroy_lock);
+
+struct snapimage {
+	struct list_head link;
+
+	sector_t capacity;
+	dev_t original_dev;
+
+	struct defer_io *defer_io;
+	struct cbt_map *cbt_map;
+
+	dev_t image_dev;
+
+	struct request_queue *queue;
+	struct gendisk *disk;
+
+	atomic_t own_cnt;
+
+	struct redirect_bio_queue image_queue;
+
+	struct task_struct *rq_processor;
+
+	wait_queue_head_t rq_proc_event;
+	wait_queue_head_t rq_complete_event;
+
+	struct mutex open_locker;
+	struct block_device *open_bdev;
+
+	size_t open_cnt;
+};
+
+int _snapimage_open(struct block_device *bdev, fmode_t mode)
+{
+	int res = SUCCESS;
+
+	if (bdev->bd_disk == NULL) {
+		pr_err("Unable to open snapshot image: bd_disk is NULL. Device [%d:%d]\n",
+		       MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev));
+		pr_err("Block device object %p\n", bdev);
+		return -ENODEV;
+	}
+
+	down_read(&snap_image_destroy_lock);
+	do {
+		struct snapimage *image = bdev->bd_disk->private_data;
+
+		if (image == NULL) {
+			pr_err("Unable to open snapshot image: private data is not initialized. Block device object %p\n",
+			       bdev);
+			res = -ENODEV;
+			break;
+		}
+
+		mutex_lock(&image->open_locker);
+		{
+			if (image->open_cnt == 0)
+				image->open_bdev = bdev;
+
+			image->open_cnt++;
+		}
+		mutex_unlock(&image->open_locker);
+	} while (false);
+	up_read(&snap_image_destroy_lock);
+	return res;
+}
+
+static inline uint64_t do_div_inline(uint64_t division, uint32_t divisor)
+{
+	do_div(division, divisor);
+	return division;
+}
+
+int _snapimage_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+{
+	int res = SUCCESS;
+	sector_t quotient;
+
+	down_read(&snap_image_destroy_lock);
+	do {
+		struct snapimage *image = bdev->bd_disk->private_data;
+
+		if (image == NULL) {
+			pr_err("Unable to open snapshot image: private data is not initialized. Block device object %p\n",
+			       bdev);
+			res = -ENODEV;
+			break;
+		}
+
+		pr_info("Getting geo for snapshot image device [%d:%d]\n", MAJOR(image->image_dev),
+			MINOR(image->image_dev));
+
+		geo->start = 0;
+		if (image->capacity > 63) {
+			geo->sectors = 63;
+			quotient = do_div_inline(image->capacity + (63 - 1), 63);
+
+			if (quotient > 255ULL) {
+				geo->heads = 255;
+				geo->cylinders =
+					(unsigned short)do_div_inline(quotient + (255 - 1), 255);
+			} else {
+				geo->heads = (unsigned char)quotient;
+				geo->cylinders = 1;
+			}
+		} else {
+			geo->sectors = (unsigned char)image->capacity;
+			geo->cylinders = 1;
+			geo->heads = 1;
+		}
+
+		pr_info("Image device geo: capacity=%lld, heads=%d, cylinders=%d, sectors=%d\n",
+			image->capacity, geo->heads, geo->cylinders, geo->sectors);
+	} while (false);
+	up_read(&snap_image_destroy_lock);
+
+	return res;
+}
+
+void _snapimage_close(struct gendisk *disk, fmode_t mode)
+{
+	if (disk->private_data != NULL) {
+		down_read(&snap_image_destroy_lock);
+		do {
+			struct snapimage *image = disk->private_data;
+
+			mutex_lock(&image->open_locker);
+			{
+				if (image->open_cnt > 0)
+					image->open_cnt--;
+
+				if (image->open_cnt == 0)
+					image->open_bdev = NULL;
+			}
+			mutex_unlock(&image->open_locker);
+		} while (false);
+		up_read(&snap_image_destroy_lock);
+	} else
+		pr_err("Unable to close snapshot image: private data is not initialized\n");
+}
+
+int _snapimage_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
+{
+	int res = -ENOTTY;
+
+	down_read(&snap_image_destroy_lock);
+	{
+		struct snapimage *image = bdev->bd_disk->private_data;
+
+		switch (cmd) {
+			/*
+			 * The only command we need to interpret is HDIO_GETGEO, since
+			 * we can't partition the drive otherwise.  We have no real
+			 * geometry, of course, so make something up.
+			 */
+		case HDIO_GETGEO: {
+			unsigned long len;
+			struct hd_geometry geo;
+
+			res = _snapimage_getgeo(bdev, &geo);
+
+			len = copy_to_user((void *)arg, &geo, sizeof(geo));
+			if (len != 0)
+				res = -EFAULT;
+			else
+				res = SUCCESS;
+		} break;
+		case CDROM_GET_CAPABILITY: //0x5331  / * get capabilities * /
+		{
+			struct gendisk *disk = bdev->bd_disk;
+
+			if (bdev->bd_disk && (disk->flags & GENHD_FL_CD))
+				res = SUCCESS;
+			else
+				res = -EINVAL;
+		} break;
+
+		default:
+			pr_info("Snapshot image ioctl receive unsupported command\n");
+			pr_info("Device [%d:%d], command 0x%x, arg 0x%lx\n",
+				MAJOR(image->image_dev), MINOR(image->image_dev), cmd, arg);
+
+			res = -ENOTTY; /* unknown command */
+		}
+	}
+	up_read(&snap_image_destroy_lock);
+	return res;
+}
+
+blk_qc_t _snapimage_submit_bio(struct bio *bio);
+
+const struct block_device_operations snapimage_ops = {
+	.owner = THIS_MODULE,
+	.submit_bio = _snapimage_submit_bio,
+	.open = _snapimage_open,
+	.ioctl = _snapimage_ioctl,
+	.release = _snapimage_close,
+};
+
+static inline int _snapimage_request_read(struct snapimage *image,
+					  struct blk_redirect_bio *rq_redir)
+{
+	struct snapstore_device *snapstore_device = image->defer_io->snapstore_device;
+
+	return snapstore_device_read(snapstore_device, rq_redir);
+}
+
+int _snapimage_request_write(struct snapimage *image, struct blk_redirect_bio *rq_redir)
+{
+	struct snapstore_device *snapstore_device;
+	struct cbt_map *cbt_map;
+	int res = SUCCESS;
+
+	if (unlikely((image->defer_io == NULL) || (image->cbt_map == NULL))) {
+		pr_err("Invalid snapshot image structure");
+		return -EINVAL;
+	}
+
+
+	snapstore_device = image->defer_io->snapstore_device;
+	cbt_map = image->cbt_map;
+
+	if (snapstore_device_is_corrupted(snapstore_device))
+		return -ENODATA;
+
+	if (!bio_has_data(rq_redir->bio)) {
+		pr_warn("Snapshot image receive empty block IO. flags=%u\n",
+			rq_redir->bio->bi_flags);
+
+		blk_redirect_complete(rq_redir, SUCCESS);
+		return SUCCESS;
+	}
+
+	if (cbt_map != NULL) {
+		sector_t ofs = rq_redir->bio->bi_iter.bi_sector;
+		sector_t cnt = bio_sectors(rq_redir->bio);
+
+		res = cbt_map_set_both(cbt_map, ofs, cnt);
+		if (res != SUCCESS)
+			pr_err("Unable to write data to snapshot image: failed to set CBT map. errno=%d\n",
+			       res);
+	}
+
+	res = snapstore_device_write(snapstore_device, rq_redir);
+
+	if (res != SUCCESS) {
+		pr_err("Failed to write data to snapshot image\n");
+		return res;
+	}
+
+	return res;
+}
+
+void _snapimage_processing(struct snapimage *image)
+{
+	int res = SUCCESS;
+	struct blk_redirect_bio *rq_redir;
+
+	rq_redir = redirect_bio_queue_get_first(&image->image_queue);
+
+	if (bio_data_dir(rq_redir->bio) == READ) {
+		res = _snapimage_request_read(image, rq_redir);
+		if (res != SUCCESS)
+			pr_err("Failed to read data from snapshot image. errno=%d\n", res);
+
+	} else {
+		res = _snapimage_request_write(image, rq_redir);
+		if (res != SUCCESS)
+			pr_err("Failed to write data to snapshot image. errno=%d\n", res);
+	}
+
+	if (res != SUCCESS)
+		blk_redirect_complete(rq_redir, res);
+}
+
+int snapimage_processor_waiting(struct snapimage *image)
+{
+	int res = SUCCESS;
+
+	if (redirect_bio_queue_empty(image->image_queue)) {
+		res = wait_event_interruptible_timeout(
+			image->rq_proc_event,
+			(!redirect_bio_queue_empty(image->image_queue) || kthread_should_stop()),
+			5 * HZ);
+		if (res > 0)
+			res = SUCCESS;
+		else if (res == 0)
+			res = -ETIME;
+	}
+	return res;
+}
+
+int snapimage_processor_thread(void *data)
+{
+	struct snapimage *image = data;
+
+	pr_info("Snapshot image thread for device [%d:%d] start\n", MAJOR(image->image_dev),
+		MINOR(image->image_dev));
+
+	add_disk(image->disk);
+
+	//priority
+	set_user_nice(current, -20); //MIN_NICE
+
+	while (!kthread_should_stop()) {
+		int res = snapimage_processor_waiting(image);
+
+		if (res == SUCCESS) {
+			if (!redirect_bio_queue_empty(image->image_queue))
+				_snapimage_processing(image);
+		} else if (res != -ETIME) {
+			pr_err("Failed to wait snapshot image thread queue. errno=%d\n", res);
+			return res;
+		}
+		schedule();
+	}
+	pr_info("Snapshot image disk delete\n");
+	del_gendisk(image->disk);
+
+	while (!redirect_bio_queue_empty(image->image_queue))
+		_snapimage_processing(image);
+
+	pr_info("Snapshot image thread for device [%d:%d] complete", MAJOR(image->image_dev),
+		MINOR(image->image_dev));
+	return 0;
+}
+
+static inline void _snapimage_bio_complete(struct bio *bio, int err)
+{
+	if (err == SUCCESS)
+		bio->bi_status = BLK_STS_OK;
+	else
+		bio->bi_status = BLK_STS_IOERR;
+
+	bio_endio(bio);
+}
+
+void _snapimage_bio_complete_cb(void *complete_param, struct bio *bio, int err)
+{
+	struct snapimage *image = (struct snapimage *)complete_param;
+
+	_snapimage_bio_complete(bio, err);
+
+	if (redirect_bio_queue_unactive(image->image_queue))
+		wake_up_interruptible(&image->rq_complete_event);
+
+	atomic_dec(&image->own_cnt);
+}
+
+int _snapimage_throttling(struct defer_io *defer_io)
+{
+	return wait_event_interruptible(defer_io->queue_throttle_waiter,
+					redirect_bio_queue_empty(defer_io->dio_queue));
+}
+
+blk_qc_t _snapimage_submit_bio(struct bio *bio)
+{
+	blk_qc_t result = SUCCESS;
+	struct request_queue *q = bio->bi_disk->queue;
+	struct snapimage *image = q->queuedata;
+
+	if (unlikely(blk_mq_queue_stopped(q))) {
+		pr_info("Failed to make snapshot image request. Queue already is not active.");
+		pr_info("Queue flags=%lx\n", q->queue_flags);
+
+		_snapimage_bio_complete(bio, -ENODEV);
+
+		return result;
+	}
+
+	atomic_inc(&image->own_cnt);
+	do {
+		int res;
+		struct blk_redirect_bio *rq_redir;
+
+		if (false == atomic_read(&(image->image_queue.active_state))) {
+			_snapimage_bio_complete(bio, -ENODEV);
+			break;
+		}
+
+		if (snapstore_device_is_corrupted(image->defer_io->snapstore_device)) {
+			_snapimage_bio_complete(bio, -ENODATA);
+			break;
+		}
+
+		res = _snapimage_throttling(image->defer_io);
+		if (res != SUCCESS) {
+			pr_err("Failed to throttle snapshot image device. errno=%d\n", res);
+			_snapimage_bio_complete(bio, res);
+			break;
+		}
+
+		rq_redir = redirect_bio_queue_new(&image->image_queue);
+		if (rq_redir == NULL) {
+			pr_err("Unable to make snapshot image request: failed to allocate redirect bio structure\n");
+			_snapimage_bio_complete(bio, -ENOMEM);
+			break;
+		}
+		rq_redir->bio = bio;
+		rq_redir->complete_cb = _snapimage_bio_complete_cb;
+		rq_redir->complete_param = (void *)image;
+		atomic_inc(&image->own_cnt);
+
+		res = redirect_bio_queue_push_back(&image->image_queue, rq_redir);
+		if (res == SUCCESS)
+			wake_up(&image->rq_proc_event);
+		else {
+			redirect_bio_queue_free(rq_redir);
+			_snapimage_bio_complete(bio, -EIO);
+
+			if (redirect_bio_queue_unactive(image->image_queue))
+				wake_up_interruptible(&image->rq_complete_event);
+		}
+
+	} while (false);
+	atomic_dec(&image->own_cnt);
+
+	return result;
+}
+
+struct blk_dev_info {
+	size_t blk_size;
+	sector_t start_sect;
+	sector_t count_sect;
+
+	unsigned int io_min;
+	unsigned int physical_block_size;
+	unsigned short logical_block_size;
+};
+
+static int _blk_dev_get_info(struct block_device *blk_dev, struct blk_dev_info *pdev_info)
+{
+	sector_t SectorStart;
+	sector_t SectorsCapacity;
+
+	if (blk_dev->bd_part)
+		SectorsCapacity = blk_dev->bd_part->nr_sects;
+	else if (blk_dev->bd_disk)
+		SectorsCapacity = get_capacity(blk_dev->bd_disk);
+	else
+		return -EINVAL;
+
+	SectorStart = get_start_sect(blk_dev);
+
+	pdev_info->physical_block_size = blk_dev->bd_disk->queue->limits.physical_block_size;
+	pdev_info->logical_block_size = blk_dev->bd_disk->queue->limits.logical_block_size;
+	pdev_info->io_min = blk_dev->bd_disk->queue->limits.io_min;
+
+	pdev_info->blk_size = block_size(blk_dev);
+	pdev_info->start_sect = SectorStart;
+	pdev_info->count_sect = SectorsCapacity;
+	return SUCCESS;
+}
+
+static int blk_dev_get_info(dev_t dev_id, struct blk_dev_info *pdev_info)
+{
+	int result = SUCCESS;
+	struct block_device *blk_dev;
+
+	result = blk_dev_open(dev_id, &blk_dev);
+	if (result != SUCCESS) {
+		pr_err("Failed to open device [%d:%d]\n", MAJOR(dev_id), MINOR(dev_id));
+		return result;
+	}
+
+	result = _blk_dev_get_info(blk_dev, pdev_info);
+	if (result != SUCCESS)
+		pr_err("Failed to identify block device [%d:%d]\n", MAJOR(dev_id), MINOR(dev_id));
+
+	blk_dev_close(blk_dev);
+
+	return result;
+}
+
+static inline void _snapimage_free(struct snapimage *image)
+{
+	defer_io_put_resource(image->defer_io);
+	cbt_map_put_resource(image->cbt_map);
+	image->defer_io = NULL;
+}
+
+static void _snapimage_stop(struct snapimage *image)
+{
+	if (image->rq_processor != NULL) {
+		if (redirect_bio_queue_active(&image->image_queue, false)) {
+			struct request_queue *q = image->queue;
+
+			pr_info("Snapshot image request processing stop\n");
+
+			if (!blk_queue_stopped(q)) {
+				blk_sync_queue(q);
+				blk_mq_stop_hw_queues(q);
+			}
+		}
+
+		pr_info("Snapshot image thread stop\n");
+		kthread_stop(image->rq_processor);
+		image->rq_processor = NULL;
+
+		while (!redirect_bio_queue_unactive(image->image_queue))
+			wait_event_interruptible(image->rq_complete_event,
+						 redirect_bio_queue_unactive(image->image_queue));
+	}
+}
+
+static void _snapimage_destroy(struct snapimage *image)
+{
+	if (image->rq_processor != NULL)
+		_snapimage_stop(image);
+
+	if (image->queue) {
+		pr_info("Snapshot image queue cleanup\n");
+		blk_cleanup_queue(image->queue);
+		image->queue = NULL;
+	}
+
+	if (image->disk != NULL) {
+		struct gendisk *disk;
+
+		disk = image->disk;
+		image->disk = NULL;
+
+		pr_info("Snapshot image disk structure release\n");
+
+		disk->private_data = NULL;
+		put_disk(disk);
+	}
+
+	spin_lock(&snapimage_minors_lock);
+	bitmap_clear(snapimage_minors, MINOR(image->image_dev), 1u);
+	spin_unlock(&snapimage_minors_lock);
+}
+
+int snapimage_create(dev_t original_dev)
+{
+	int res = SUCCESS;
+	struct tracker *tracker = NULL;
+	struct snapimage *image = NULL;
+	struct gendisk *disk = NULL;
+	int minor;
+	struct blk_dev_info original_dev_info;
+
+	pr_info("Create snapshot image for device [%d:%d]\n", MAJOR(original_dev),
+		MINOR(original_dev));
+
+	res = blk_dev_get_info(original_dev, &original_dev_info);
+	if (res != SUCCESS) {
+		pr_err("Failed to obtain original device info\n");
+		return res;
+	}
+
+	res = tracker_find_by_dev_id(original_dev, &tracker);
+	if (res != SUCCESS) {
+		pr_err("Unable to create snapshot image: cannot find tracker for device [%d:%d]\n",
+		       MAJOR(original_dev), MINOR(original_dev));
+		return res;
+	}
+
+	image = kzalloc(sizeof(struct snapimage), GFP_KERNEL);
+	if (image == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&image->link);
+
+	do {
+		spin_lock(&snapimage_minors_lock);
+		minor = bitmap_find_free_region(snapimage_minors, SNAPIMAGE_MAX_DEVICES, 0);
+		spin_unlock(&snapimage_minors_lock);
+
+		if (minor < SUCCESS) {
+			pr_err("Failed to allocate minor for snapshot image device. errno=%d\n",
+			       0 - minor);
+			break;
+		}
+
+		image->rq_processor = NULL;
+
+		image->capacity = original_dev_info.count_sect;
+
+		image->defer_io = defer_io_get_resource(tracker->defer_io);
+		image->cbt_map = cbt_map_get_resource(tracker->cbt_map);
+		image->original_dev = original_dev;
+
+		image->image_dev = MKDEV(snapimage_major, minor);
+		pr_info("Snapshot image device id [%d:%d]\n", MAJOR(image->image_dev),
+			MINOR(image->image_dev));
+
+		atomic_set(&image->own_cnt, 0);
+
+		mutex_init(&image->open_locker);
+		image->open_bdev = NULL;
+		image->open_cnt = 0;
+
+		image->queue = blk_alloc_queue(NUMA_NO_NODE);
+		if (image->queue == NULL) {
+			res = -ENOMEM;
+			break;
+		}
+		image->queue->queuedata = image;
+
+		blk_queue_max_segment_size(image->queue, 1024 * PAGE_SIZE);
+
+		{
+			unsigned int physical_block_size = original_dev_info.physical_block_size;
+			unsigned short logical_block_size = original_dev_info.logical_block_size;
+
+			pr_info("Snapshot image physical block size %d\n", physical_block_size);
+			pr_info("Snapshot image logical block size %d\n", logical_block_size);
+
+			blk_queue_physical_block_size(image->queue, physical_block_size);
+			blk_queue_logical_block_size(image->queue, logical_block_size);
+		}
+		disk = alloc_disk(1); //only one partition on disk
+		if (disk == NULL) {
+			pr_err("Failed to allocate disk for snapshot image device\n");
+			res = -ENOMEM;
+			break;
+		}
+		image->disk = disk;
+
+		if (snprintf(disk->disk_name, DISK_NAME_LEN, "%s%d", SNAP_IMAGE_NAME, minor) < 0) {
+			pr_err("Unable to set disk name for snapshot image device: invalid minor %d\n",
+			       minor);
+			res = -EINVAL;
+			break;
+		}
+
+		pr_info("Snapshot image disk name [%s]", disk->disk_name);
+
+		disk->flags |= GENHD_FL_NO_PART_SCAN;
+		disk->flags |= GENHD_FL_REMOVABLE;
+
+		disk->major = snapimage_major;
+		disk->minors = 1; // one disk have only one partition.
+		disk->first_minor = minor;
+
+		disk->private_data = image;
+
+		disk->fops = &snapimage_ops;
+		disk->queue = image->queue;
+
+		set_capacity(disk, image->capacity);
+		pr_info("Snapshot image device capacity %lld bytes",
+			(u64)from_sectors(image->capacity));
+
+		//res = -ENOMEM;
+		redirect_bio_queue_init(&image->image_queue);
+
+		{
+			struct task_struct *task =
+				kthread_create(snapimage_processor_thread, image, disk->disk_name);
+			if (IS_ERR(task)) {
+				res = PTR_ERR(task);
+				pr_err("Failed to create request processing thread for snapshot image device. errno=%d\n",
+				       res);
+				break;
+			}
+			image->rq_processor = task;
+		}
+		init_waitqueue_head(&image->rq_complete_event);
+
+		init_waitqueue_head(&image->rq_proc_event);
+		wake_up_process(image->rq_processor);
+	} while (false);
+
+	if (res == SUCCESS) {
+		down_write(&snap_images_lock);
+		list_add_tail(&image->link, &snap_images);
+		up_write(&snap_images_lock);
+	} else {
+		_snapimage_destroy(image);
+		_snapimage_free(image);
+
+		kfree(image);
+		image = NULL;
+	}
+	return res;
+}
+
+static struct snapimage *snapimage_find(dev_t original_dev)
+{
+	struct snapimage *image = NULL;
+
+	down_read(&snap_images_lock);
+	if (!list_empty(&snap_images)) {
+		struct list_head *_list_head;
+
+		list_for_each(_list_head, &snap_images) {
+			struct snapimage *_image = list_entry(_list_head, struct snapimage, link);
+
+			if (_image->original_dev == original_dev) {
+				image = _image;
+				break;
+			}
+		}
+	}
+	up_read(&snap_images_lock);
+
+	return image;
+}
+
+void snapimage_stop(dev_t original_dev)
+{
+	struct snapimage *image;
+
+	pr_info("Snapshot image processing stop for original device [%d:%d]\n", MAJOR(original_dev),
+		MINOR(original_dev));
+
+	down_read(&snap_image_destroy_lock);
+
+	image = snapimage_find(original_dev);
+	if (image != NULL)
+		_snapimage_stop(image);
+	else
+		pr_err("Snapshot image [%d:%d] not found\n", MAJOR(original_dev),
+		       MINOR(original_dev));
+
+	up_read(&snap_image_destroy_lock);
+}
+
+void snapimage_destroy(dev_t original_dev)
+{
+	struct snapimage *image = NULL;
+
+	pr_info("Destroy snapshot image for device [%d:%d]\n", MAJOR(original_dev),
+		MINOR(original_dev));
+
+	down_write(&snap_images_lock);
+	if (!list_empty(&snap_images)) {
+		struct list_head *_list_head;
+
+		list_for_each(_list_head, &snap_images) {
+			struct snapimage *_image = list_entry(_list_head, struct snapimage, link);
+
+			if (_image->original_dev == original_dev) {
+				image = _image;
+				list_del(&image->link);
+				break;
+			}
+		}
+	}
+	up_write(&snap_images_lock);
+
+	if (image != NULL) {
+		down_write(&snap_image_destroy_lock);
+
+		_snapimage_destroy(image);
+		_snapimage_free(image);
+
+		kfree(image);
+		image = NULL;
+
+		up_write(&snap_image_destroy_lock);
+	} else
+		pr_err("Snapshot image [%d:%d] not found\n", MAJOR(original_dev),
+		       MINOR(original_dev));
+}
+
+void snapimage_destroy_for(dev_t *p_dev, int count)
+{
+	int inx = 0;
+
+	for (; inx < count; ++inx)
+		snapimage_destroy(p_dev[inx]);
+}
+
+int snapimage_create_for(dev_t *p_dev, int count)
+{
+	int res = SUCCESS;
+	int inx = 0;
+
+	for (; inx < count; ++inx) {
+		res = snapimage_create(p_dev[inx]);
+		if (res != SUCCESS) {
+			pr_err("Failed to create snapshot image for original device [%d:%d]\n",
+			       MAJOR(p_dev[inx]), MINOR(p_dev[inx]));
+			break;
+		}
+	}
+	if (res != SUCCESS)
+		if (inx > 0)
+			snapimage_destroy_for(p_dev, inx - 1);
+	return res;
+}
+
+int snapimage_init(void)
+{
+	int res = SUCCESS;
+
+	res = register_blkdev(snapimage_major, SNAP_IMAGE_NAME);
+	if (res >= SUCCESS) {
+		snapimage_major = res;
+		pr_info("Snapshot image block device major %d was registered\n", snapimage_major);
+		res = SUCCESS;
+
+		spin_lock(&snapimage_minors_lock);
+		snapimage_minors = bitmap_zalloc(SNAPIMAGE_MAX_DEVICES, GFP_KERNEL);
+		spin_unlock(&snapimage_minors_lock);
+
+		if (snapimage_minors == NULL)
+			pr_err("Failed to initialize bitmap of minors\n");
+	} else
+		pr_err("Failed to register snapshot image block device. errno=%d\n", res);
+
+	return res;
+}
+
+void snapimage_done(void)
+{
+	down_write(&snap_image_destroy_lock);
+	while (true) {
+		struct snapimage *image = NULL;
+
+		down_write(&snap_images_lock);
+		if (!list_empty(&snap_images)) {
+			image = list_entry(snap_images.next, struct snapimage, link);
+
+			list_del(&image->link);
+		}
+		up_write(&snap_images_lock);
+
+		if (image == NULL)
+			break;
+
+		pr_err("Snapshot image for device was unexpectedly removed [%d:%d]\n",
+		       MAJOR(image->original_dev), MINOR(image->original_dev));
+
+		_snapimage_destroy(image);
+		_snapimage_free(image);
+
+		kfree(image);
+		image = NULL;
+	}
+
+	spin_lock(&snapimage_minors_lock);
+	bitmap_free(snapimage_minors);
+	snapimage_minors = NULL;
+	spin_unlock(&snapimage_minors_lock);
+
+	if (!list_empty(&snap_images))
+		pr_err("Failed to release snapshot images container\n");
+
+	unregister_blkdev(snapimage_major, SNAP_IMAGE_NAME);
+	pr_info("Snapshot image block device [%d] was unregistered\n", snapimage_major);
+
+	up_write(&snap_image_destroy_lock);
+}
+
+int snapimage_collect_images(int count, struct image_info_s *p_user_image_info, int *p_real_count)
+{
+	int res = SUCCESS;
+	int real_count = 0;
+
+	down_read(&snap_images_lock);
+	if (!list_empty(&snap_images)) {
+		struct list_head *_list_head;
+
+		list_for_each(_list_head, &snap_images)
+			real_count++;
+	}
+	up_read(&snap_images_lock);
+	*p_real_count = real_count;
+
+	if (count < real_count)
+		res = -ENODATA;
+
+	real_count = min(count, real_count);
+	if (real_count > 0) {
+		unsigned long len;
+		struct image_info_s *p_kernel_image_info = NULL;
+		size_t buff_size;
+
+		buff_size = sizeof(struct image_info_s) * real_count;
+		p_kernel_image_info = kzalloc(buff_size, GFP_KERNEL);
+		if (p_kernel_image_info == NULL) {
+			pr_err("Unable to collect snapshot images: not enough memory. size=%lu\n",
+			       buff_size);
+			return res = -ENOMEM;
+		}
+
+		down_read(&snap_image_destroy_lock);
+		down_read(&snap_images_lock);
+
+		if (!list_empty(&snap_images)) {
+			size_t inx = 0;
+			struct list_head *_list_head;
+
+			list_for_each(_list_head, &snap_images) {
+				struct snapimage *img =
+					list_entry(_list_head, struct snapimage, link);
+
+				real_count++;
+
+				p_kernel_image_info[inx].original_dev_id.major =
+					MAJOR(img->original_dev);
+				p_kernel_image_info[inx].original_dev_id.minor =
+					MINOR(img->original_dev);
+
+				p_kernel_image_info[inx].snapshot_dev_id.major =
+					MAJOR(img->image_dev);
+				p_kernel_image_info[inx].snapshot_dev_id.minor =
+					MINOR(img->image_dev);
+
+				++inx;
+				if (inx > real_count)
+					break;
+			}
+		}
+
+		up_read(&snap_images_lock);
+		up_read(&snap_image_destroy_lock);
+
+		len = copy_to_user(p_user_image_info, p_kernel_image_info, buff_size);
+		if (len != 0) {
+			pr_err("Unable to collect snapshot images: failed to copy data to user buffer\n");
+			res = -ENODATA;
+		}
+
+		kfree(p_kernel_image_info);
+	}
+
+	return res;
+}
+
+int snapimage_mark_dirty_blocks(dev_t image_dev_id, struct block_range_s *block_ranges,
+				unsigned int count)
+{
+	size_t inx = 0;
+	int res = SUCCESS;
+
+	pr_info("Marking [%d] dirty blocks for image device [%d:%d]\n", count, MAJOR(image_dev_id),
+		MINOR(image_dev_id));
+
+	down_read(&snap_image_destroy_lock);
+	do {
+		struct snapimage *image = snapimage_find(image_dev_id);
+
+		if (image == NULL) {
+			pr_err("Cannot find device [%d:%d]\n", MAJOR(image_dev_id),
+			       MINOR(image_dev_id));
+			res = -ENODEV;
+			break;
+		}
+
+		for (inx = 0; inx < count; ++inx) {
+			sector_t ofs = (sector_t)block_ranges[inx].ofs;
+			sector_t cnt = (sector_t)block_ranges[inx].cnt;
+
+			res = cbt_map_set_both(image->cbt_map, ofs, cnt);
+			if (res != SUCCESS) {
+				pr_err("Failed to set CBT table. errno=%d\n", res);
+				break;
+			}
+		}
+	} while (false);
+	up_read(&snap_image_destroy_lock);
+
+	return res;
+}
diff --git a/drivers/block/blk-snap/snapimage.h b/drivers/block/blk-snap/snapimage.h
new file mode 100644
index 000000000000..67995c321496
--- /dev/null
+++ b/drivers/block/blk-snap/snapimage.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "blk-snap-ctl.h"
+
+int snapimage_init(void);
+void snapimage_done(void);
+int snapimage_create_for(dev_t *p_dev, int count);
+
+void snapimage_stop(dev_t original_dev);
+void snapimage_destroy(dev_t original_dev);
+
+int snapimage_collect_images(int count, struct image_info_s *p_user_image_info, int *p_real_count);
+
+int snapimage_mark_dirty_blocks(dev_t image_dev_id, struct block_range_s *block_ranges,
+				unsigned int count);
diff --git a/drivers/block/blk-snap/snapshot.c b/drivers/block/blk-snap/snapshot.c
new file mode 100644
index 000000000000..937930e483f4
--- /dev/null
+++ b/drivers/block/blk-snap/snapshot.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapshot"
+#include "common.h"
+#include "snapshot.h"
+#include "tracker.h"
+#include "snapimage.h"
+#include "tracking.h"
+
+LIST_HEAD(snapshots);
+DECLARE_RWSEM(snapshots_lock);
+
+
+static int _snapshot_remove_device(dev_t dev_id)
+{
+	int result;
+	struct tracker *tracker = NULL;
+
+	result = tracker_find_by_dev_id(dev_id, &tracker);
+	if (result != SUCCESS) {
+		if (result == -ENODEV)
+			pr_err("Cannot find device by device id=[%d:%d]\n", MAJOR(dev_id),
+			       MINOR(dev_id));
+		else
+			pr_err("Failed to find device by device id=[%d:%d]\n", MAJOR(dev_id),
+			       MINOR(dev_id));
+		return SUCCESS;
+	}
+
+	if (result != SUCCESS)
+		return result;
+
+	tracker_snapshot_id_set(tracker, 0ull);
+
+	pr_info("Device [%d:%d] successfully removed from snapshot\n", MAJOR(dev_id),
+		MINOR(dev_id));
+	return SUCCESS;
+}
+
+static void _snapshot_cleanup(struct snapshot *snapshot)
+{
+	int inx;
+
+	for (inx = 0; inx < snapshot->dev_id_set_size; ++inx) {
+
+		if (_snapshot_remove_device(snapshot->dev_id_set[inx]) != SUCCESS)
+			pr_err("Failed to remove device [%d:%d] from snapshot\n",
+			       MAJOR(snapshot->dev_id_set[inx]), MINOR(snapshot->dev_id_set[inx]));
+	}
+
+	if (snapshot->dev_id_set != NULL)
+		kfree(snapshot->dev_id_set);
+	kfree(snapshot);
+}
+
+static void _snapshot_destroy(struct snapshot *snapshot)
+{
+	int result = SUCCESS;
+	size_t inx;
+
+	for (inx = 0; inx < snapshot->dev_id_set_size; ++inx)
+		snapimage_stop(snapshot->dev_id_set[inx]);
+
+	pr_info("Release snapshot [0x%llx]\n", snapshot->id);
+
+	result = tracker_release_snapshot(snapshot->dev_id_set, snapshot->dev_id_set_size);
+	if (result != SUCCESS)
+		pr_err("Failed to release snapshot [0x%llx]\n", snapshot->id);
+
+	for (inx = 0; inx < snapshot->dev_id_set_size; ++inx)
+		snapimage_destroy(snapshot->dev_id_set[inx]);
+
+	_snapshot_cleanup(snapshot);
+}
+
+
+static int _snapshot_new(dev_t *p_dev, int count, struct snapshot **pp_snapshot)
+{
+	struct snapshot *p_snapshot = NULL;
+	dev_t *snap_set = NULL;
+
+	p_snapshot = kzalloc(sizeof(struct snapshot), GFP_KERNEL);
+	if (p_snapshot == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&p_snapshot->link);
+
+	p_snapshot->id = (unsigned long)(p_snapshot);
+
+	snap_set = kcalloc(count, sizeof(dev_t), GFP_KERNEL);
+	if (snap_set == NULL) {
+		kfree(p_snapshot);
+
+		pr_err("Unable to create snapshot: faile to allocate memory for snapshot map\n");
+		return -ENOMEM;
+	}
+	memcpy(snap_set, p_dev, sizeof(dev_t) * count);
+
+	p_snapshot->dev_id_set_size = count;
+	p_snapshot->dev_id_set = snap_set;
+
+	down_write(&snapshots_lock);
+	list_add_tail(&snapshots, &p_snapshot->link);
+	up_write(&snapshots_lock);
+
+	*pp_snapshot = p_snapshot;
+
+	return SUCCESS;
+}
+
+void snapshot_done(void)
+{
+	struct snapshot *snap;
+
+	pr_info("Removing all snapshots\n");
+	do {
+		snap = NULL;
+		down_write(&snapshots_lock);
+		if (!list_empty(&snapshots)) {
+			struct snapshot *snap = list_entry(snapshots.next, struct snapshot, link);
+
+			list_del(&snap->link);
+		}
+		up_write(&snapshots_lock);
+
+		if (snap)
+			_snapshot_destroy(snap);
+
+	} while (snap);
+}
+
+int snapshot_create(dev_t *dev_id_set, unsigned int dev_id_set_size,
+		    unsigned int cbt_block_size_degree, unsigned long long *psnapshot_id)
+{
+	struct snapshot *snapshot = NULL;
+	int result = SUCCESS;
+	unsigned int inx;
+
+	pr_info("Create snapshot for devices:\n");
+	for (inx = 0; inx < dev_id_set_size; ++inx)
+		pr_info("\t%d:%d\n", MAJOR(dev_id_set[inx]), MINOR(dev_id_set[inx]));
+
+	result = _snapshot_new(dev_id_set, dev_id_set_size, &snapshot);
+	if (result != SUCCESS) {
+		pr_err("Unable to create snapshot: failed to allocate snapshot structure\n");
+		return result;
+	}
+
+	do {
+		result = -ENODEV;
+		for (inx = 0; inx < snapshot->dev_id_set_size; ++inx) {
+			dev_t dev_id = snapshot->dev_id_set[inx];
+
+			result = tracking_add(dev_id, cbt_block_size_degree, snapshot->id);
+			if (result == -EALREADY)
+				result = SUCCESS;
+			else if (result != SUCCESS) {
+				pr_err("Unable to create snapshot\n");
+				pr_err("Failed to add device [%d:%d] to snapshot tracking\n",
+				       MAJOR(dev_id), MINOR(dev_id));
+				break;
+			}
+		}
+		if (result != SUCCESS)
+			break;
+
+		result = tracker_capture_snapshot(snapshot->dev_id_set, snapshot->dev_id_set_size);
+		if (result != SUCCESS) {
+			pr_err("Unable to create snapshot: failed to capture snapshot [0x%llx]\n",
+			       snapshot->id);
+			break;
+		}
+
+		result = snapimage_create_for(snapshot->dev_id_set, snapshot->dev_id_set_size);
+		if (result != SUCCESS) {
+			pr_err("Unable to create snapshot\n");
+			pr_err("Failed to create snapshot image devices\n");
+
+			tracker_release_snapshot(snapshot->dev_id_set, snapshot->dev_id_set_size);
+			break;
+		}
+
+		*psnapshot_id = snapshot->id;
+		pr_info("Snapshot [0x%llx] was created\n", snapshot->id);
+	} while (false);
+
+	if (result != SUCCESS) {
+		pr_info("Snapshot [0x%llx] cleanup\n", snapshot->id);
+
+		down_write(&snapshots_lock);
+		list_del(&snapshot->link);
+		up_write(&snapshots_lock);
+
+		_snapshot_cleanup(snapshot);
+	}
+	return result;
+}
+
+int snapshot_destroy(unsigned long long snapshot_id)
+{
+	struct snapshot *snapshot = NULL;
+
+	pr_info("Destroy snapshot [0x%llx]\n", snapshot_id);
+
+	down_read(&snapshots_lock);
+	if (!list_empty(&snapshots)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &snapshots) {
+			struct snapshot *_snap = list_entry(_head, struct snapshot, link);
+
+			if (_snap->id == snapshot_id) {
+				snapshot = _snap;
+				list_del(&snapshot->link);
+				break;
+			}
+		}
+	}
+	up_read(&snapshots_lock);
+
+	if (snapshot == NULL) {
+		pr_err("Unable to destroy snapshot [0x%llx]: cannot find snapshot by id\n",
+		       snapshot_id);
+		return -ENODEV;
+	}
+
+	_snapshot_destroy(snapshot);
+	return SUCCESS;
+}
diff --git a/drivers/block/blk-snap/snapshot.h b/drivers/block/blk-snap/snapshot.h
new file mode 100644
index 000000000000..de180483eeab
--- /dev/null
+++ b/drivers/block/blk-snap/snapshot.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+struct snapshot {
+	struct list_head link;
+	unsigned long long id;
+
+	dev_t *dev_id_set; //array of assigned devices
+	int dev_id_set_size;
+};
+
+void snapshot_done(void);
+
+int snapshot_create(dev_t *dev_id_set, unsigned int dev_id_set_size,
+		    unsigned int cbt_block_size_degree, unsigned long long *psnapshot_id);
+
+int snapshot_destroy(unsigned long long snapshot_id);
diff --git a/drivers/block/blk-snap/snapstore.c b/drivers/block/blk-snap/snapstore.c
new file mode 100644
index 000000000000..1a54bd00eee0
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore.c
@@ -0,0 +1,929 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapstore"
+#include "common.h"
+#include "snapstore.h"
+#include "snapstore_device.h"
+#include "big_buffer.h"
+#include "params.h"
+
+LIST_HEAD(snapstores);
+DECLARE_RWSEM(snapstores_lock);
+
+bool _snapstore_check_halffill(struct snapstore *snapstore, sector_t *fill_status)
+{
+	struct blk_descr_pool *pool = NULL;
+
+	if (snapstore->file)
+		pool = &snapstore->file->pool;
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	else if (snapstore->multidev)
+		pool = &snapstore->multidev->pool;
+#endif
+	else if (snapstore->mem)
+		pool = &snapstore->mem->pool;
+
+	if (pool)
+		return blk_descr_pool_check_halffill(pool, snapstore->empty_limit, fill_status);
+
+	return false;
+}
+
+void _snapstore_destroy(struct snapstore *snapstore)
+{
+	sector_t fill_status;
+
+	pr_info("Destroy snapstore with id %pUB\n", &snapstore->id);
+
+	_snapstore_check_halffill(snapstore, &fill_status);
+
+	down_write(&snapstores_lock);
+	list_del(&snapstore->link);
+	up_write(&snapstores_lock);
+
+	if (snapstore->mem != NULL)
+		snapstore_mem_destroy(snapstore->mem);
+	if (snapstore->multidev != NULL)
+		snapstore_multidev_destroy(snapstore->multidev);
+	if (snapstore->file != NULL)
+		snapstore_file_destroy(snapstore->file);
+
+	if (snapstore->ctrl_pipe) {
+		struct ctrl_pipe *pipe;
+
+		pipe = snapstore->ctrl_pipe;
+		snapstore->ctrl_pipe = NULL;
+
+		ctrl_pipe_request_terminate(pipe, fill_status);
+
+		ctrl_pipe_put_resource(pipe);
+	}
+
+	kfree(snapstore);
+}
+
+static void _snapstore_destroy_cb(struct kref *kref)
+{
+	struct snapstore *snapstore = container_of(kref, struct snapstore, refcount);
+
+	_snapstore_destroy(snapstore);
+}
+
+struct snapstore *snapstore_get(struct snapstore *snapstore)
+{
+	if (snapstore)
+		kref_get(&snapstore->refcount);
+
+	return snapstore;
+}
+
+void snapstore_put(struct snapstore *snapstore)
+{
+	if (snapstore)
+		kref_put(&snapstore->refcount, _snapstore_destroy_cb);
+}
+
+void snapstore_done(void)
+{
+	bool is_empty;
+
+	down_read(&snapstores_lock);
+	is_empty = list_empty(&snapstores);
+	up_read(&snapstores_lock);
+
+	if (!is_empty)
+		pr_err("Unable to perform snapstore cleanup: container is not empty\n");
+}
+
+int snapstore_create(uuid_t *id, dev_t snapstore_dev_id, dev_t *dev_id_set,
+		     size_t dev_id_set_length)
+{
+	int res = SUCCESS;
+	size_t dev_id_inx;
+	struct snapstore *snapstore = NULL;
+
+	if (dev_id_set_length == 0)
+		return -EINVAL;
+
+	snapstore = kzalloc(sizeof(struct snapstore), GFP_KERNEL);
+	if (snapstore == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&snapstore->link);
+	uuid_copy(&snapstore->id, id);
+
+	pr_info("Create snapstore with id %pUB\n", &snapstore->id);
+
+	snapstore->mem = NULL;
+	snapstore->multidev = NULL;
+	snapstore->file = NULL;
+
+	snapstore->ctrl_pipe = NULL;
+	snapstore->empty_limit = (sector_t)(64 * (1024 * 1024 / SECTOR_SIZE)); //by default value
+	snapstore->halffilled = false;
+	snapstore->overflowed = false;
+
+	if (snapstore_dev_id == 0)
+		pr_info("Memory snapstore create\n");
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	else if (snapstore_dev_id == 0xFFFFffff) {
+		struct snapstore_multidev *multidev = NULL;
+
+		res = snapstore_multidev_create(&multidev);
+		if (res != SUCCESS) {
+			kfree(snapstore);
+
+			pr_err("Failed to create multidevice snapstore %pUB\n", id);
+			return res;
+		}
+		snapstore->multidev = multidev;
+	}
+#endif
+	else {
+		struct snapstore_file *file = NULL;
+
+		res = snapstore_file_create(snapstore_dev_id, &file);
+		if (res != SUCCESS) {
+			kfree(snapstore);
+
+			pr_err("Failed to create snapstore file for snapstore %pUB\n", id);
+			return res;
+		}
+		snapstore->file = file;
+	}
+
+	down_write(&snapstores_lock);
+	list_add_tail(&snapstores, &snapstore->link);
+	up_write(&snapstores_lock);
+
+	kref_init(&snapstore->refcount);
+
+	for (dev_id_inx = 0; dev_id_inx < dev_id_set_length; ++dev_id_inx) {
+		res = snapstore_device_create(dev_id_set[dev_id_inx], snapstore);
+		if (res != SUCCESS)
+			break;
+	}
+
+	if (res != SUCCESS)
+		snapstore_device_cleanup(id);
+
+	snapstore_put(snapstore);
+	return res;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+int snapstore_create_multidev(uuid_t *id, dev_t *dev_id_set, size_t dev_id_set_length)
+{
+	int res = SUCCESS;
+	size_t dev_id_inx;
+	struct snapstore *snapstore = NULL;
+	struct snapstore_multidev *multidev = NULL;
+
+	if (dev_id_set_length == 0)
+		return -EINVAL;
+
+	snapstore = kzalloc(sizeof(struct snapstore), GFP_KERNEL);
+	if (snapstore == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&snapstore->link);
+
+	uuid_copy(&snapstore->id, id);
+
+	pr_info("Create snapstore with id %pUB\n", &snapstore->id);
+
+	snapstore->mem = NULL;
+	snapstore->file = NULL;
+	snapstore->multidev = NULL;
+
+	snapstore->ctrl_pipe = NULL;
+	snapstore->empty_limit = (sector_t)(64 * (1024 * 1024 / SECTOR_SIZE)); //by default value
+	snapstore->halffilled = false;
+	snapstore->overflowed = false;
+
+	res = snapstore_multidev_create(&multidev);
+	if (res != SUCCESS) {
+		kfree(snapstore);
+
+		pr_err("Failed to create snapstore file for snapstore %pUB\n", id);
+		return res;
+	}
+	snapstore->multidev = multidev;
+
+	down_write(&snapstores_lock);
+	list_add_tail(&snapstore->link, &snapstores);
+	up_write(&snapstores_lock);
+
+	kref_init(&snapstore->refcount);
+
+	for (dev_id_inx = 0; dev_id_inx < dev_id_set_length; ++dev_id_inx) {
+		res = snapstore_device_create(dev_id_set[dev_id_inx], snapstore);
+		if (res != SUCCESS)
+			break;
+	}
+
+	if (res != SUCCESS)
+		snapstore_device_cleanup(id);
+
+	snapstore_put(snapstore);
+	return res;
+}
+#endif
+
+int snapstore_cleanup(uuid_t *id, u64 *filled_bytes)
+{
+	int res;
+	sector_t filled;
+
+	res = snapstore_check_halffill(id, &filled);
+	if (res == SUCCESS) {
+		*filled_bytes = (u64)from_sectors(filled);
+
+		pr_info("Snapstore fill size: %lld MiB\n", (*filled_bytes >> 20));
+	} else {
+		*filled_bytes = -1;
+		pr_err("Failed to obtain snapstore data filled size\n");
+	}
+
+	return snapstore_device_cleanup(id);
+}
+
+struct snapstore *_snapstore_find(uuid_t *id)
+{
+	struct snapstore *result = NULL;
+
+	down_read(&snapstores_lock);
+	if (!list_empty(&snapstores)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &snapstores) {
+			struct snapstore *snapstore = list_entry(_head, struct snapstore, link);
+
+			if (uuid_equal(&snapstore->id, id)) {
+				result = snapstore;
+				break;
+			}
+		}
+	}
+	up_read(&snapstores_lock);
+
+	return result;
+}
+
+int snapstore_stretch_initiate(uuid_t *unique_id, struct ctrl_pipe *ctrl_pipe, sector_t empty_limit)
+{
+	struct snapstore *snapstore;
+
+	snapstore = _snapstore_find(unique_id);
+	if (snapstore == NULL) {
+		pr_err("Unable to initiate stretch snapstore: ");
+		pr_err("cannot find snapstore by uuid %pUB\n", unique_id);
+		return -ENODATA;
+	}
+
+	snapstore->ctrl_pipe = ctrl_pipe_get_resource(ctrl_pipe);
+	snapstore->empty_limit = empty_limit;
+
+	return SUCCESS;
+}
+
+int snapstore_add_memory(uuid_t *id, unsigned long long sz)
+{
+	int res = SUCCESS;
+	struct snapstore *snapstore = NULL;
+	size_t available_blocks = (size_t)(sz >> (snapstore_block_shift() + SECTOR_SHIFT));
+	size_t current_block = 0;
+
+	pr_info("Adding %lld bytes to the snapstore\n", sz);
+
+	snapstore = _snapstore_find(id);
+	if (snapstore == NULL) {
+		pr_err("Unable to add memory block to the snapstore: ");
+		pr_err("cannot found snapstore by id %pUB\n", id);
+		return -ENODATA;
+	}
+
+	if (snapstore->file != NULL) {
+		pr_err("Unable to add memory block to the snapstore: ");
+		pr_err("snapstore file is already created\n");
+		return -EINVAL;
+	}
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	if (snapstore->multidev != NULL) {
+		pr_err("Unable to add memory block to the snapstore: ");
+		pr_err("snapstore multidevice is already created\n");
+		return -EINVAL;
+	}
+#endif
+	if (snapstore->mem != NULL) {
+		pr_err("Unable to add memory block to the snapstore: ");
+		pr_err("snapstore memory buffer is already created\n");
+		return -EINVAL;
+	}
+
+	snapstore->mem = snapstore_mem_create(available_blocks);
+	for (current_block = 0; current_block < available_blocks; ++current_block) {
+		void *buffer = snapstore_mem_get_block(snapstore->mem);
+
+		if (buffer == NULL) {
+			pr_err("Unable to add memory block to the snapstore: ");
+			pr_err("not enough memory\n");
+			res = -ENOMEM;
+			break;
+		}
+
+		res = blk_descr_mem_pool_add(&snapstore->mem->pool, buffer);
+		if (res != SUCCESS) {
+			pr_err("Unable to add memory block to the snapstore: ");
+			pr_err("failed to initialize new block\n");
+			break;
+		}
+	}
+	if (res != SUCCESS) {
+		snapstore_mem_destroy(snapstore->mem);
+		snapstore->mem = NULL;
+	}
+
+	return res;
+}
+
+int rangelist_add(struct list_head *rglist, struct blk_range *rg)
+{
+	struct blk_range_link *range_link;
+
+	range_link = kzalloc(sizeof(struct blk_range_link), GFP_KERNEL);
+	if (range_link == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&range_link->link);
+
+	range_link->rg.ofs = rg->ofs;
+	range_link->rg.cnt = rg->cnt;
+
+	list_add_tail(&range_link->link, rglist);
+
+	return SUCCESS;
+}
+
+int snapstore_add_file(uuid_t *id, struct big_buffer *ranges, size_t ranges_cnt)
+{
+	int res = SUCCESS;
+	struct snapstore *snapstore = NULL;
+	struct snapstore_device *snapstore_device = NULL;
+	sector_t current_blk_size = 0;
+	LIST_HEAD(blk_rangelist);
+	size_t inx;
+
+	pr_info("Snapstore add %ld ranges\n", ranges_cnt);
+
+	if ((ranges_cnt == 0) || (ranges == NULL))
+		return -EINVAL;
+
+	snapstore = _snapstore_find(id);
+	if (snapstore == NULL) {
+		pr_err("Unable to add file to snapstore: ");
+		pr_err("cannot find snapstore by id %pUB\n", id);
+		return -ENODATA;
+	}
+
+	if (snapstore->file == NULL) {
+		pr_err("Unable to add file to snapstore: ");
+		pr_err("snapstore file was not initialized\n");
+		return -EFAULT;
+	}
+
+	snapstore_device =
+		snapstore_device_find_by_dev_id(snapstore->file->blk_dev_id); //for zeroed
+
+	for (inx = 0; inx < ranges_cnt; ++inx) {
+		size_t blocks_count = 0;
+		sector_t range_offset = 0;
+
+		struct blk_range range;
+		struct ioctl_range_s *ioctl_range;
+
+		ioctl_range = big_buffer_get_element(ranges, inx, sizeof(struct ioctl_range_s));
+		if (ioctl_range == NULL) {
+			pr_err("Invalid count of ranges\n");
+			res = -ENODATA;
+			break;
+		}
+
+		range.ofs = (sector_t)to_sectors(ioctl_range->left);
+		range.cnt = (blkcnt_t)to_sectors(ioctl_range->right) - range.ofs;
+
+		while (range_offset < range.cnt) {
+			struct blk_range rg;
+
+			rg.ofs = range.ofs + range_offset;
+			rg.cnt = min_t(sector_t, (range.cnt - range_offset),
+				       (snapstore_block_size() - current_blk_size));
+
+			range_offset += rg.cnt;
+
+			res = rangelist_add(&blk_rangelist, &rg);
+			if (res != SUCCESS) {
+				pr_err("Unable to add file to snapstore: ");
+				pr_err("cannot add range to rangelist\n");
+				break;
+			}
+
+			//zero sectors logic
+			if (snapstore_device != NULL) {
+				res = rangevector_add(&snapstore_device->zero_sectors, &rg);
+				if (res != SUCCESS) {
+					pr_err("Unable to add file to snapstore: ");
+					pr_err("cannot add range to zero_sectors tree\n");
+					break;
+				}
+			}
+
+			current_blk_size += rg.cnt;
+
+			if (current_blk_size == snapstore_block_size()) { //allocate  block
+				res = blk_descr_file_pool_add(&snapstore->file->pool,
+							      &blk_rangelist);
+				if (res != SUCCESS) {
+					pr_err("Unable to add file to snapstore: ");
+					pr_err("cannot initialize new block\n");
+					break;
+				}
+
+				snapstore->halffilled = false;
+
+				current_blk_size = 0;
+				INIT_LIST_HEAD(&blk_rangelist); //renew list
+				++blocks_count;
+			}
+		}
+		if (res != SUCCESS)
+			break;
+	}
+
+	if ((res == SUCCESS) && (current_blk_size != 0))
+		pr_warn("Snapstore portion was not ordered by Copy-on-Write block size\n");
+
+	return res;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+static int rangelist_ex_add(struct list_head *list, struct blk_range *rg,
+			    struct block_device *blk_dev)
+{
+	struct blk_range_link_ex *range_link =
+		kzalloc(sizeof(struct blk_range_link_ex), GFP_KERNEL);
+	if (range_link == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&range_link->link);
+
+	range_link->rg.ofs = rg->ofs;
+	range_link->rg.cnt = rg->cnt;
+	range_link->blk_dev = blk_dev;
+
+	list_add_tail(&range_link->link, list);
+
+	return SUCCESS;
+}
+
+int snapstore_add_multidev(uuid_t *id, dev_t dev_id, struct big_buffer *ranges, size_t ranges_cnt)
+{
+	int res = SUCCESS;
+	struct snapstore *snapstore = NULL;
+	sector_t current_blk_size = 0;
+	size_t inx;
+	LIST_HEAD(blk_rangelist);
+
+	pr_info("Snapstore add %ld ranges for device [%d:%d]\n", ranges_cnt, MAJOR(dev_id),
+		MINOR(dev_id));
+
+	if ((ranges_cnt == 0) || (ranges == NULL))
+		return -EINVAL;
+
+	snapstore = _snapstore_find(id);
+	if (snapstore == NULL) {
+		pr_err("Unable to add file to multidevice snapstore: ");
+		pr_err("cannot find snapstore by id %pUB\n", id);
+		return -ENODATA;
+	}
+
+	if (snapstore->multidev == NULL) {
+		pr_err("Unable to add file to multidevice snapstore: ");
+		pr_err("it was not initialized\n");
+		return -EFAULT;
+	}
+
+	for (inx = 0; inx < ranges_cnt; ++inx) {
+		size_t blocks_count = 0;
+		sector_t range_offset = 0;
+		struct blk_range range;
+		struct ioctl_range_s *data;
+
+		data = big_buffer_get_element(ranges, inx, sizeof(struct ioctl_range_s));
+		if (data == NULL) {
+			pr_err("Invalid count of ranges\n");
+			res = -ENODATA;
+			break;
+		}
+
+		range.ofs = (sector_t)to_sectors(data->left);
+		range.cnt = (blkcnt_t)to_sectors(data->right) - range.ofs;
+
+		while (range_offset < range.cnt) {
+			struct blk_range rg;
+			struct block_device *blk_dev = NULL;
+
+			rg.ofs = range.ofs + range_offset;
+			rg.cnt = min_t(sector_t,
+				       range.cnt - range_offset,
+				       snapstore_block_size() - current_blk_size);
+
+			range_offset += rg.cnt;
+
+			blk_dev = snapstore_multidev_get_device(snapstore->multidev, dev_id);
+			if (blk_dev == NULL) {
+				pr_err("Cannot find or open device [%d:%d] for multidevice snapstore\n",
+				       MAJOR(dev_id), MINOR(dev_id));
+				res = -ENODEV;
+				break;
+			}
+
+			res = rangelist_ex_add(&blk_rangelist, &rg, blk_dev);
+			if (res != SUCCESS) {
+				pr_err("Unable to add file to multidevice snapstore: ");
+				pr_err("failed to add range to rangelist\n");
+				break;
+			}
+
+			/*
+			 * zero sectors logic is not implemented for multidevice snapstore
+			 */
+
+			current_blk_size += rg.cnt;
+
+			if (current_blk_size == snapstore_block_size()) { //allocate  block
+				res = blk_descr_multidev_pool_add(&snapstore->multidev->pool,
+								  &blk_rangelist);
+				if (res != SUCCESS) {
+					pr_err("Unable to add file to multidevice snapstore: ");
+					pr_err("failed to initialize new block\n");
+					break;
+				}
+
+				snapstore->halffilled = false;
+
+				current_blk_size = 0;
+				INIT_LIST_HEAD(&blk_rangelist);
+				++blocks_count;
+			}
+		}
+		if (res != SUCCESS)
+			break;
+	}
+
+	if ((res == SUCCESS) && (current_blk_size != 0))
+		pr_warn("Snapstore portion was not ordered by Copy-on-Write block size\n");
+
+	return res;
+}
+#endif
+
+void snapstore_order_border(struct blk_range *in, struct blk_range *out)
+{
+	struct blk_range unorder;
+
+	unorder.ofs = in->ofs & snapstore_block_mask();
+	out->ofs = in->ofs & ~snapstore_block_mask();
+	out->cnt = in->cnt + unorder.ofs;
+
+	unorder.cnt = out->cnt & snapstore_block_mask();
+	if (unorder.cnt != 0)
+		out->cnt += (snapstore_block_size() - unorder.cnt);
+}
+
+union blk_descr_unify snapstore_get_empty_block(struct snapstore *snapstore)
+{
+	union blk_descr_unify result = { NULL };
+
+	if (snapstore->overflowed)
+		return result;
+
+	if (snapstore->file != NULL)
+		result = blk_descr_file_pool_take(&snapstore->file->pool);
+	else if (snapstore->multidev != NULL)
+		result = blk_descr_multidev_pool_take(&snapstore->multidev->pool);
+	else if (snapstore->mem != NULL)
+		result = blk_descr_mem_pool_take(&snapstore->mem->pool);
+
+	if (result.ptr == NULL) {
+		if (snapstore->ctrl_pipe) {
+			sector_t fill_status;
+
+			_snapstore_check_halffill(snapstore, &fill_status);
+			ctrl_pipe_request_overflow(snapstore->ctrl_pipe, -EINVAL,
+						   (u64)from_sectors(fill_status));
+		}
+		snapstore->overflowed = true;
+	}
+
+	return result;
+}
+
+int snapstore_check_halffill(uuid_t *unique_id, sector_t *fill_status)
+{
+	struct snapstore *snapstore;
+
+	snapstore = _snapstore_find(unique_id);
+	if (snapstore == NULL) {
+		pr_err("Cannot find snapstore by uuid %pUB\n", unique_id);
+		return -ENODATA;
+	}
+
+	_snapstore_check_halffill(snapstore, fill_status);
+
+	return SUCCESS;
+}
+
+int snapstore_request_store(struct snapstore *snapstore, struct blk_deferred_request *dio_copy_req)
+{
+	int res = SUCCESS;
+
+	if (snapstore->ctrl_pipe) {
+		if (!snapstore->halffilled) {
+			sector_t fill_status = 0;
+
+			if (_snapstore_check_halffill(snapstore, &fill_status)) {
+				snapstore->halffilled = true;
+				ctrl_pipe_request_halffill(snapstore->ctrl_pipe,
+							   (u64)from_sectors(fill_status));
+			}
+		}
+	}
+
+	if (snapstore->file)
+		res = blk_deferred_request_store_file(snapstore->file->blk_dev, dio_copy_req);
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	else if (snapstore->multidev)
+		res = blk_deferred_request_store_multidev(dio_copy_req);
+#endif
+	else if (snapstore->mem)
+		res = blk_deferred_request_store_mem(dio_copy_req);
+	else
+		res = -EINVAL;
+
+	return res;
+}
+
+static int _snapstore_redirect_read_file(struct blk_redirect_bio *rq_redir,
+					 struct block_device *snapstore_blk_dev,
+					 struct blk_descr_file *file,
+					 sector_t block_ofs,
+					 sector_t rq_ofs, sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t current_ofs = 0;
+	struct list_head *_list_head;
+
+	if (unlikely(list_empty(&file->rangelist))) {
+		pr_err("Invalid file block descriptor");
+		return -EINVAL;
+	}
+
+	list_for_each(_list_head, &file->rangelist) {
+		struct blk_range_link *range_link;
+
+		range_link = list_entry(_list_head, struct blk_range_link, link);
+		if (current_ofs >= rq_count)
+			break;
+
+		if (range_link->rg.cnt > block_ofs) {
+			sector_t pos = range_link->rg.ofs + block_ofs;
+			sector_t len = min_t(sector_t,
+					     range_link->rg.cnt - block_ofs,
+					     rq_count - current_ofs);
+
+			res = blk_dev_redirect_part(rq_redir, READ, snapstore_blk_dev, pos,
+						    rq_ofs + current_ofs, len);
+			if (res != SUCCESS) {
+				pr_err("Failed to read from snapstore file. Sector #%lld\n",
+				       pos);
+				break;
+			}
+
+			current_ofs += len;
+			block_ofs = 0;
+		} else
+			block_ofs -= range_link->rg.cnt;
+	}
+
+	if (res != SUCCESS)
+		pr_err("Failed to read from file snapstore\n");
+	return res;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+static int _snapstore_redirect_read_multidev(struct blk_redirect_bio *rq_redir,
+					      struct blk_descr_multidev *multidev,
+					      sector_t block_ofs,
+					      sector_t rq_ofs, sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t current_ofs = 0;
+	struct list_head *_list_head;
+
+	if (unlikely(list_empty(&multidev->rangelist))) {
+		pr_err("Invalid multidev block descriptor");
+		return -EINVAL;
+	}
+
+	list_for_each(_list_head, &multidev->rangelist) {
+		struct blk_range_link_ex *range_link =
+			list_entry(_list_head, struct blk_range_link_ex, link);
+
+		if (current_ofs >= rq_count)
+			break;
+
+		if (range_link->rg.cnt > block_ofs) {
+			sector_t pos = range_link->rg.ofs + block_ofs;
+			sector_t len = min_t(sector_t,
+					     range_link->rg.cnt - block_ofs,
+					     rq_count - current_ofs);
+
+			res = blk_dev_redirect_part(rq_redir, READ, range_link->blk_dev, pos,
+						    rq_ofs + current_ofs, len);
+
+			if (res != SUCCESS) {
+				pr_err("Failed to read from snapstore file. Sector #%lld\n", pos);
+				break;
+			}
+
+			current_ofs += len;
+			block_ofs = 0;
+		} else
+			block_ofs -= range_link->rg.cnt;
+	}
+
+	if (res != SUCCESS)
+		pr_err("Failed to read from multidev snapstore\n");
+	return res;
+}
+#endif
+
+int snapstore_redirect_read(struct blk_redirect_bio *rq_redir, struct snapstore *snapstore,
+			    union blk_descr_unify blk_descr, sector_t target_pos, sector_t rq_ofs,
+			    sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t block_ofs = target_pos & snapstore_block_mask();
+
+	if (snapstore->file)
+		res = _snapstore_redirect_read_file(rq_redir, snapstore->file->blk_dev,
+						    blk_descr.file, block_ofs, rq_ofs, rq_count);
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	else if (snapstore->multidev)
+		res = _snapstore_redirect_read_multidev(rq_redir, blk_descr.multidev, block_ofs,
+							rq_ofs, rq_count);
+#endif
+	else if (snapstore->mem) {
+		res = blk_dev_redirect_memcpy_part(
+			rq_redir, READ, blk_descr.mem->buff + (size_t)from_sectors(block_ofs),
+			rq_ofs, rq_count);
+
+		if (res != SUCCESS)
+			pr_err("Failed to read from snapstore memory\n");
+	} else
+		res = -EINVAL;
+
+	if (res != SUCCESS)
+		pr_err("Failed to read from snapstore. Offset %lld sector\n", target_pos);
+	return res;
+}
+
+static int _snapstore_redirect_write_file(struct blk_redirect_bio *rq_redir,
+					  struct block_device *snapstore_blk_dev,
+					  struct blk_descr_file *file,
+					  sector_t block_ofs,
+					  sector_t rq_ofs, sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t current_ofs = 0;
+	struct list_head *_list_head;
+
+	if (unlikely(list_empty(&file->rangelist))) {
+		pr_err("Invalid file block descriptor");
+		return -EINVAL;
+	}
+
+	list_for_each(_list_head, &file->rangelist) {
+		struct blk_range_link *range_link;
+
+		range_link = list_entry(_list_head, struct blk_range_link, link);
+		if (current_ofs >= rq_count)
+			break;
+
+		if (range_link->rg.cnt > block_ofs) {
+			sector_t pos = range_link->rg.ofs + block_ofs;
+			sector_t len = min_t(sector_t,
+					     range_link->rg.cnt - block_ofs,
+					     rq_count - current_ofs);
+
+			res = blk_dev_redirect_part(rq_redir, WRITE, snapstore_blk_dev, pos,
+						    rq_ofs + current_ofs, len);
+
+			if (res != SUCCESS) {
+				pr_err("Failed to write to snapstore file. Sector #%lld\n",
+				       pos);
+				break;
+			}
+
+			current_ofs += len;
+			block_ofs = 0;
+		} else
+			block_ofs -= range_link->rg.cnt;
+	}
+	if (res != SUCCESS)
+		pr_err("Failed to write to file snapstore\n");
+	return res;
+}
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+static int _snapstore_redirect_write_multidev(struct blk_redirect_bio *rq_redir,
+					      struct blk_descr_multidev *multidev,
+					      sector_t block_ofs,
+					      sector_t rq_ofs, sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t current_ofs = 0;
+	struct list_head *_list_head;
+
+	if (unlikely(list_empty(&multidev->rangelist))) {
+		pr_err("Invalid multidev block descriptor");
+		return -EINVAL;
+	}
+
+	list_for_each(_list_head, &multidev->rangelist) {
+		struct blk_range_link_ex *range_link;
+
+		range_link = list_entry(_list_head, struct blk_range_link_ex, link);
+		if (current_ofs >= rq_count)
+			break;
+
+		if (range_link->rg.cnt > block_ofs) {
+			sector_t pos = range_link->rg.ofs + block_ofs;
+			sector_t len = min_t(sector_t,
+					     range_link->rg.cnt - block_ofs,
+					     rq_count - current_ofs);
+
+			res = blk_dev_redirect_part(rq_redir, WRITE, range_link->blk_dev, pos,
+						    rq_ofs + current_ofs, len);
+
+			if (res != SUCCESS) {
+				pr_err("Failed to write to snapstore file. Sector #%lld\n",
+				       pos);
+				break;
+			}
+
+			current_ofs += len;
+			block_ofs = 0;
+		} else
+			block_ofs -= range_link->rg.cnt;
+	}
+
+	if (res != SUCCESS)
+		pr_err("Failed to write to multidevice snapstore\n");
+	return res;
+}
+#endif
+
+int snapstore_redirect_write(struct blk_redirect_bio *rq_redir, struct snapstore *snapstore,
+			     union blk_descr_unify blk_descr, sector_t target_pos, sector_t rq_ofs,
+			     sector_t rq_count)
+{
+	int res = SUCCESS;
+	sector_t block_ofs = target_pos & snapstore_block_mask();
+
+	if (snapstore->file)
+		res = _snapstore_redirect_write_file(rq_redir, snapstore->file->blk_dev,
+						     blk_descr.file, block_ofs, rq_ofs, rq_count);
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	else if (snapstore->multidev)
+		res = _snapstore_redirect_write_multidev(rq_redir, blk_descr.multidev,
+							 block_ofs, rq_ofs, rq_count);
+#endif
+	else if (snapstore->mem) {
+		res = blk_dev_redirect_memcpy_part(
+			rq_redir, WRITE, blk_descr.mem->buff + (size_t)from_sectors(block_ofs),
+			rq_ofs, rq_count);
+
+		if (res != SUCCESS)
+			pr_err("Failed to write to memory snapstore\n");
+	} else {
+		pr_err("Unable to write to snapstore: invalid type of snapstore device\n");
+		res = -EINVAL;
+	}
+
+	if (res != SUCCESS)
+		pr_err("Failed to write to snapstore. Offset %lld sector\n", target_pos);
+	return res;
+}
diff --git a/drivers/block/blk-snap/snapstore.h b/drivers/block/blk-snap/snapstore.h
new file mode 100644
index 000000000000..db34ad2e2c58
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include <linux/uuid.h>
+#include <linux/kref.h>
+#include "blk-snap-ctl.h"
+#include "rangevector.h"
+#include "snapstore_mem.h"
+#include "snapstore_file.h"
+#include "snapstore_multidev.h"
+#include "blk_redirect.h"
+#include "ctrl_pipe.h"
+#include "big_buffer.h"
+
+struct snapstore {
+	struct list_head link;
+	struct kref refcount;
+
+	uuid_t id;
+
+	struct snapstore_mem *mem;
+	struct snapstore_file *file;
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+	struct snapstore_multidev *multidev;
+#endif
+
+	struct ctrl_pipe *ctrl_pipe;
+	sector_t empty_limit;
+
+	bool halffilled;
+	bool overflowed;
+};
+
+void snapstore_done(void);
+
+int snapstore_create(uuid_t *id, dev_t snapstore_dev_id, dev_t *dev_id_set,
+		     size_t dev_id_set_length);
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+int snapstore_create_multidev(uuid_t *id, dev_t *dev_id_set, size_t dev_id_set_length);
+#endif
+int snapstore_cleanup(uuid_t *id, u64 *filled_bytes);
+
+struct snapstore *snapstore_get(struct snapstore *snapstore);
+void snapstore_put(struct snapstore *snapstore);
+
+int snapstore_stretch_initiate(uuid_t *unique_id, struct ctrl_pipe *ctrl_pipe,
+			       sector_t empty_limit);
+
+int snapstore_add_memory(uuid_t *id, unsigned long long sz);
+int snapstore_add_file(uuid_t *id, struct big_buffer *ranges, size_t ranges_cnt);
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+int snapstore_add_multidev(uuid_t *id, dev_t dev_id, struct big_buffer *ranges, size_t ranges_cnt);
+#endif
+
+void snapstore_order_border(struct blk_range *in, struct blk_range *out);
+
+union blk_descr_unify snapstore_get_empty_block(struct snapstore *snapstore);
+
+int snapstore_request_store(struct snapstore *snapstore, struct blk_deferred_request *dio_copy_req);
+
+int snapstore_redirect_read(struct blk_redirect_bio *rq_redir, struct snapstore *snapstore,
+			    union blk_descr_unify blk_descr, sector_t target_pos, sector_t rq_ofs,
+			    sector_t rq_count);
+int snapstore_redirect_write(struct blk_redirect_bio *rq_redir, struct snapstore *snapstore,
+			     union blk_descr_unify blk_descr, sector_t target_pos, sector_t rq_ofs,
+			     sector_t rq_count);
+
+int snapstore_check_halffill(uuid_t *unique_id, sector_t *fill_status);
diff --git a/drivers/block/blk-snap/snapstore_device.c b/drivers/block/blk-snap/snapstore_device.c
new file mode 100644
index 000000000000..6fdeebacce22
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_device.c
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapstore"
+#include "common.h"
+#include "snapstore_device.h"
+#include "snapstore.h"
+#include "params.h"
+#include "blk_util.h"
+
+LIST_HEAD(snapstore_devices);
+DECLARE_RWSEM(snapstore_devices_lock);
+
+static inline void _snapstore_device_descr_write_lock(struct snapstore_device *snapstore_device)
+{
+	mutex_lock(&snapstore_device->store_block_map_locker);
+}
+static inline void _snapstore_device_descr_write_unlock(struct snapstore_device *snapstore_device)
+{
+	mutex_unlock(&snapstore_device->store_block_map_locker);
+}
+
+void snapstore_device_done(void)
+{
+	struct snapstore_device *snapstore_device = NULL;
+
+	do {
+		down_write(&snapstore_devices_lock);
+		if (!list_empty(&snapstore_devices)) {
+			snapstore_device =
+				list_entry(snapstore_devices.next, struct snapstore_device, link);
+			list_del(&snapstore_device->link);
+		}
+		up_write(&snapstore_devices_lock);
+
+		if (snapstore_device)
+			snapstore_device_put_resource(snapstore_device);
+	} while (snapstore_device);
+}
+
+struct snapstore_device *snapstore_device_find_by_dev_id(dev_t dev_id)
+{
+	struct snapstore_device *result = NULL;
+
+	down_read(&snapstore_devices_lock);
+	if (!list_empty(&snapstore_devices)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &snapstore_devices) {
+			struct snapstore_device *snapstore_device =
+				list_entry(_head, struct snapstore_device, link);
+
+			if (dev_id == snapstore_device->dev_id) {
+				result = snapstore_device;
+				break;
+			}
+		}
+	}
+	up_read(&snapstore_devices_lock);
+
+	return result;
+}
+
+struct snapstore_device *_snapstore_device_get_by_snapstore_id(uuid_t *id)
+{
+	struct snapstore_device *result = NULL;
+
+	down_write(&snapstore_devices_lock);
+	if (!list_empty(&snapstore_devices)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &snapstore_devices) {
+			struct snapstore_device *snapstore_device =
+				list_entry(_head, struct snapstore_device, link);
+
+			if (uuid_equal(id, &snapstore_device->snapstore->id)) {
+				result = snapstore_device;
+				list_del(&snapstore_device->link);
+				break;
+			}
+		}
+	}
+	up_write(&snapstore_devices_lock);
+
+	return result;
+}
+
+static void _snapstore_device_destroy(struct snapstore_device *snapstore_device)
+{
+	pr_info("Destroy snapstore device\n");
+
+	xa_destroy(&snapstore_device->store_block_map);
+
+	if (snapstore_device->orig_blk_dev != NULL)
+		blk_dev_close(snapstore_device->orig_blk_dev);
+
+	rangevector_done(&snapstore_device->zero_sectors);
+
+	if (snapstore_device->snapstore) {
+		pr_info("Snapstore uuid %pUB\n", &snapstore_device->snapstore->id);
+
+		snapstore_put(snapstore_device->snapstore);
+		snapstore_device->snapstore = NULL;
+	}
+
+	kfree(snapstore_device);
+}
+
+static void snapstore_device_free_cb(struct kref *kref)
+{
+	struct snapstore_device *snapstore_device =
+		container_of(kref, struct snapstore_device, refcount);
+
+	_snapstore_device_destroy(snapstore_device);
+}
+
+struct snapstore_device *snapstore_device_get_resource(struct snapstore_device *snapstore_device)
+{
+	if (snapstore_device)
+		kref_get(&snapstore_device->refcount);
+
+	return snapstore_device;
+};
+
+void snapstore_device_put_resource(struct snapstore_device *snapstore_device)
+{
+	if (snapstore_device)
+		kref_put(&snapstore_device->refcount, snapstore_device_free_cb);
+};
+
+int snapstore_device_cleanup(uuid_t *id)
+{
+	int result = SUCCESS;
+	struct snapstore_device *snapstore_device = NULL;
+
+	while (NULL != (snapstore_device = _snapstore_device_get_by_snapstore_id(id))) {
+		pr_info("Cleanup snapstore device for device [%d:%d]\n",
+			MAJOR(snapstore_device->dev_id), MINOR(snapstore_device->dev_id));
+
+		snapstore_device_put_resource(snapstore_device);
+	}
+	return result;
+}
+
+int snapstore_device_create(dev_t dev_id, struct snapstore *snapstore)
+{
+	int res = SUCCESS;
+	struct snapstore_device *snapstore_device =
+		kzalloc(sizeof(struct snapstore_device), GFP_KERNEL);
+
+	if (snapstore_device == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&snapstore_device->link);
+	snapstore_device->dev_id = dev_id;
+
+	res = blk_dev_open(dev_id, &snapstore_device->orig_blk_dev);
+	if (res != SUCCESS) {
+		kfree(snapstore_device);
+
+		pr_err("Unable to create snapstore device: failed to open original device [%d:%d]\n",
+		       MAJOR(dev_id), MINOR(dev_id));
+		return res;
+	}
+
+	kref_init(&snapstore_device->refcount);
+
+	snapstore_device->snapstore = NULL;
+	snapstore_device->err_code = SUCCESS;
+	snapstore_device->corrupted = false;
+	atomic_set(&snapstore_device->req_failed_cnt, 0);
+
+	mutex_init(&snapstore_device->store_block_map_locker);
+
+	rangevector_init(&snapstore_device->zero_sectors);
+
+	xa_init(&snapstore_device->store_block_map);
+
+	snapstore_device->snapstore = snapstore_get(snapstore);
+
+	down_write(&snapstore_devices_lock);
+	list_add_tail(&snapstore_device->link, &snapstore_devices);
+	up_write(&snapstore_devices_lock);
+
+	return SUCCESS;
+}
+
+int snapstore_device_add_request(struct snapstore_device *snapstore_device,
+				 unsigned long block_index,
+				 struct blk_deferred_request **dio_copy_req)
+{
+	int res = SUCCESS;
+	union blk_descr_unify blk_descr = { NULL };
+	struct blk_deferred_io *dio = NULL;
+	bool req_new = false;
+
+	blk_descr = snapstore_get_empty_block(snapstore_device->snapstore);
+	if (blk_descr.ptr == NULL) {
+		pr_err("Unable to add block to defer IO request: failed to allocate next block\n");
+		return -ENODATA;
+	}
+
+	res = xa_err(
+		xa_store(&snapstore_device->store_block_map, block_index, blk_descr.ptr, GFP_NOIO));
+	if (res != SUCCESS) {
+		pr_err("Unable to add block to defer IO request: failed to set block descriptor to descriptors array. errno=%d\n",
+		       res);
+		return res;
+	}
+
+	if (*dio_copy_req == NULL) {
+		*dio_copy_req = blk_deferred_request_new();
+		if (*dio_copy_req == NULL) {
+			pr_err("Unable to add block to defer IO request: failed to allocate defer IO request\n");
+			return -ENOMEM;
+		}
+		req_new = true;
+	}
+
+	do {
+		dio = blk_deferred_alloc(block_index, blk_descr);
+		if (dio == NULL) {
+			pr_err("Unabled to add block to defer IO request: failed to allocate defer IO\n");
+			res = -ENOMEM;
+			break;
+		}
+
+		res = blk_deferred_request_add(*dio_copy_req, dio);
+		if (res != SUCCESS)
+			pr_err("Unable to add block to defer IO request: failed to add defer IO to request\n");
+	} while (false);
+
+	if (res != SUCCESS) {
+		if (dio != NULL) {
+			blk_deferred_free(dio);
+			dio = NULL;
+		}
+		if (req_new) {
+			blk_deferred_request_free(*dio_copy_req);
+			*dio_copy_req = NULL;
+		}
+	}
+
+	return res;
+}
+
+int snapstore_device_prepare_requests(struct snapstore_device *snapstore_device,
+				      struct blk_range *copy_range,
+				      struct blk_deferred_request **dio_copy_req)
+{
+	int res = SUCCESS;
+	unsigned long inx = 0;
+	unsigned long first = (unsigned long)(copy_range->ofs >> snapstore_block_shift());
+	unsigned long last =
+		(unsigned long)((copy_range->ofs + copy_range->cnt - 1) >> snapstore_block_shift());
+
+	for (inx = first; inx <= last; inx++) {
+		if (xa_load(&snapstore_device->store_block_map, inx) == NULL) {
+			res = snapstore_device_add_request(snapstore_device, inx, dio_copy_req);
+			if (res != SUCCESS) {
+				pr_err("Failed to create copy defer IO request. errno=%d\n", res);
+				break;
+			}
+		}
+		/*
+		 * If xa_load() return not NULL, then block already stored.
+		 */
+	}
+	if (res != SUCCESS)
+		snapstore_device_set_corrupted(snapstore_device, res);
+
+	return res;
+}
+
+int snapstore_device_store(struct snapstore_device *snapstore_device,
+			   struct blk_deferred_request *dio_copy_req)
+{
+	int res;
+
+	res = snapstore_request_store(snapstore_device->snapstore, dio_copy_req);
+	if (res != SUCCESS)
+		snapstore_device_set_corrupted(snapstore_device, res);
+
+	return res;
+}
+
+int snapstore_device_read(struct snapstore_device *snapstore_device,
+			  struct blk_redirect_bio *rq_redir)
+{
+	int res = SUCCESS;
+
+	unsigned long block_index;
+	unsigned long block_index_last;
+	unsigned long block_index_first;
+
+	sector_t blk_ofs_start = 0; //device range start
+	sector_t blk_ofs_count = 0; //device range length
+
+	struct blk_range rq_range;
+	struct rangevector *zero_sectors = &snapstore_device->zero_sectors;
+
+	if (snapstore_device_is_corrupted(snapstore_device))
+		return -ENODATA;
+
+	rq_range.cnt = bio_sectors(rq_redir->bio);
+	rq_range.ofs = rq_redir->bio->bi_iter.bi_sector;
+
+	if (!bio_has_data(rq_redir->bio)) {
+		pr_warn("Empty bio was found during reading from snapstore device. flags=%u\n",
+			rq_redir->bio->bi_flags);
+
+		blk_redirect_complete(rq_redir, SUCCESS);
+		return SUCCESS;
+	}
+
+	block_index_first = (unsigned long)(rq_range.ofs >> snapstore_block_shift());
+	block_index_last =
+		(unsigned long)((rq_range.ofs + rq_range.cnt - 1) >> snapstore_block_shift());
+
+	_snapstore_device_descr_write_lock(snapstore_device);
+	for (block_index = block_index_first; block_index <= block_index_last; ++block_index) {
+		union blk_descr_unify blk_descr;
+
+		blk_ofs_count = min_t(sector_t,
+				      (((sector_t)(block_index + 1)) << snapstore_block_shift()) -
+					      (rq_range.ofs + blk_ofs_start),
+				      rq_range.cnt - blk_ofs_start);
+
+		blk_descr = (union blk_descr_unify)xa_load(&snapstore_device->store_block_map,
+							   block_index);
+		if (blk_descr.ptr) {
+			//push snapstore read
+			res = snapstore_redirect_read(rq_redir, snapstore_device->snapstore,
+						      blk_descr, rq_range.ofs + blk_ofs_start,
+						      blk_ofs_start, blk_ofs_count);
+			if (res != SUCCESS) {
+				pr_err("Failed to read from snapstore device\n");
+				break;
+			}
+		} else {
+			//device read with zeroing
+			if (zero_sectors)
+				res = blk_dev_redirect_read_zeroed(rq_redir,
+								   snapstore_device->orig_blk_dev,
+								   rq_range.ofs, blk_ofs_start,
+								   blk_ofs_count, zero_sectors);
+			else
+				res = blk_dev_redirect_part(rq_redir, READ,
+							    snapstore_device->orig_blk_dev,
+							    rq_range.ofs + blk_ofs_start,
+							    blk_ofs_start, blk_ofs_count);
+
+			if (res != SUCCESS) {
+				pr_err("Failed to redirect read request to the original device [%d:%d]\n",
+				       MAJOR(snapstore_device->dev_id),
+				       MINOR(snapstore_device->dev_id));
+				break;
+			}
+		}
+
+		blk_ofs_start += blk_ofs_count;
+	}
+
+	if (res == SUCCESS) {
+		if (atomic64_read(&rq_redir->bio_count) > 0ll) //async direct access needed
+			blk_dev_redirect_submit(rq_redir);
+		else
+			blk_redirect_complete(rq_redir, res);
+	} else {
+		pr_err("Failed to read from snapstore device. errno=%d\n", res);
+		pr_err("Position %lld sector, length %lld sectors\n", rq_range.ofs, rq_range.cnt);
+	}
+	_snapstore_device_descr_write_unlock(snapstore_device);
+
+	return res;
+}
+
+int _snapstore_device_copy_on_write(struct snapstore_device *snapstore_device,
+				    struct blk_range *rq_range)
+{
+	int res = SUCCESS;
+	struct blk_deferred_request *dio_copy_req = NULL;
+
+	mutex_lock(&snapstore_device->store_block_map_locker);
+	do {
+		res = snapstore_device_prepare_requests(snapstore_device, rq_range, &dio_copy_req);
+		if (res != SUCCESS) {
+			pr_err("Failed to create defer IO request for range. errno=%d\n", res);
+			break;
+		}
+
+		if (dio_copy_req == NULL)
+			break; //nothing to copy
+
+		res = blk_deferred_request_read_original(snapstore_device->orig_blk_dev,
+							 dio_copy_req);
+		if (res != SUCCESS) {
+			pr_err("Failed to read data from the original device. errno=%d\n", res);
+			break;
+		}
+
+		res = snapstore_device_store(snapstore_device, dio_copy_req);
+		if (res != SUCCESS) {
+			pr_err("Failed to write data to snapstore. errno=%d\n", res);
+			break;
+		}
+	} while (false);
+	mutex_unlock(&snapstore_device->store_block_map_locker);
+
+	if (dio_copy_req) {
+		if (res == -EDEADLK)
+			blk_deferred_request_deadlocked(dio_copy_req);
+		else
+			blk_deferred_request_free(dio_copy_req);
+	}
+
+	return res;
+}
+
+int snapstore_device_write(struct snapstore_device *snapstore_device,
+			   struct blk_redirect_bio *rq_redir)
+{
+	int res = SUCCESS;
+	unsigned long block_index;
+	unsigned long block_index_last;
+	unsigned long block_index_first;
+	sector_t blk_ofs_start = 0; //device range start
+	sector_t blk_ofs_count = 0; //device range length
+	struct blk_range rq_range;
+
+	if (snapstore_device_is_corrupted(snapstore_device))
+		return -ENODATA;
+
+	rq_range.cnt = bio_sectors(rq_redir->bio);
+	rq_range.ofs = rq_redir->bio->bi_iter.bi_sector;
+
+	if (!bio_has_data(rq_redir->bio)) {
+		pr_warn("Empty bio was found during reading from snapstore device. flags=%u\n",
+			rq_redir->bio->bi_flags);
+
+		blk_redirect_complete(rq_redir, SUCCESS);
+		return SUCCESS;
+	}
+
+	// do copy to snapstore previously
+	res = _snapstore_device_copy_on_write(snapstore_device, &rq_range);
+
+	block_index_first = (unsigned long)(rq_range.ofs >> snapstore_block_shift());
+	block_index_last =
+		(unsigned long)((rq_range.ofs + rq_range.cnt - 1) >> snapstore_block_shift());
+
+	_snapstore_device_descr_write_lock(snapstore_device);
+	for (block_index = block_index_first; block_index <= block_index_last; ++block_index) {
+		union blk_descr_unify blk_descr;
+
+		blk_ofs_count = min_t(sector_t,
+				      (((sector_t)(block_index + 1)) << snapstore_block_shift()) -
+					      (rq_range.ofs + blk_ofs_start),
+				      rq_range.cnt - blk_ofs_start);
+
+		blk_descr = (union blk_descr_unify)xa_load(&snapstore_device->store_block_map,
+							   block_index);
+		if (blk_descr.ptr == NULL) {
+			pr_err("Unable to write from snapstore device: invalid snapstore block descriptor\n");
+			res = -EIO;
+			break;
+		}
+
+		res = snapstore_redirect_write(rq_redir, snapstore_device->snapstore, blk_descr,
+					       rq_range.ofs + blk_ofs_start, blk_ofs_start,
+					       blk_ofs_count);
+		if (res != SUCCESS) {
+			pr_err("Unable to write from snapstore device: failed to redirect write request to snapstore\n");
+			break;
+		}
+
+		blk_ofs_start += blk_ofs_count;
+	}
+	if (res == SUCCESS) {
+		if (atomic64_read(&rq_redir->bio_count) > 0) { //async direct access needed
+			blk_dev_redirect_submit(rq_redir);
+		} else {
+			blk_redirect_complete(rq_redir, res);
+		}
+	} else {
+		pr_err("Failed to write from snapstore device. errno=%d\n", res);
+		pr_err("Position %lld sector, length %lld sectors\n", rq_range.ofs, rq_range.cnt);
+
+		snapstore_device_set_corrupted(snapstore_device, res);
+	}
+	_snapstore_device_descr_write_unlock(snapstore_device);
+	return res;
+}
+
+bool snapstore_device_is_corrupted(struct snapstore_device *snapstore_device)
+{
+	if (snapstore_device == NULL)
+		return true;
+
+	if (snapstore_device->corrupted) {
+		if (atomic_read(&snapstore_device->req_failed_cnt) == 0)
+			pr_err("Snapshot device is corrupted for [%d:%d]\n",
+			       MAJOR(snapstore_device->dev_id), MINOR(snapstore_device->dev_id));
+
+		atomic_inc(&snapstore_device->req_failed_cnt);
+		return true;
+	}
+
+	return false;
+}
+
+void snapstore_device_set_corrupted(struct snapstore_device *snapstore_device, int err_code)
+{
+	if (!snapstore_device->corrupted) {
+		atomic_set(&snapstore_device->req_failed_cnt, 0);
+		snapstore_device->corrupted = true;
+		snapstore_device->err_code = abs(err_code);
+
+		pr_err("Set snapshot device is corrupted for [%d:%d]\n",
+		       MAJOR(snapstore_device->dev_id), MINOR(snapstore_device->dev_id));
+	}
+}
+
+int snapstore_device_errno(dev_t dev_id, int *p_err_code)
+{
+	struct snapstore_device *snapstore_device;
+
+	snapstore_device = snapstore_device_find_by_dev_id(dev_id);
+	if (snapstore_device == NULL)
+		return -ENODATA;
+
+	*p_err_code = snapstore_device->err_code;
+	return SUCCESS;
+}
diff --git a/drivers/block/blk-snap/snapstore_device.h b/drivers/block/blk-snap/snapstore_device.h
new file mode 100644
index 000000000000..729b3c05ef70
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_device.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "rangevector.h"
+#include "blk_deferred.h"
+#include "blk_redirect.h"
+#include "snapstore.h"
+#include <linux/xarray.h>
+#include <linux/kref.h>
+
+struct snapstore_device {
+	struct list_head link;
+	struct kref refcount;
+
+	dev_t dev_id;
+	struct snapstore *snapstore;
+
+	struct block_device *orig_blk_dev;
+
+	struct xarray store_block_map; // map block index to read block offset
+	struct mutex store_block_map_locker;
+
+	struct rangevector zero_sectors;
+
+	atomic_t req_failed_cnt;
+	int err_code;
+	bool corrupted;
+};
+
+void snapstore_device_done(void);
+
+struct snapstore_device *snapstore_device_get_resource(struct snapstore_device *snapstore_device);
+void snapstore_device_put_resource(struct snapstore_device *snapstore_device);
+
+struct snapstore_device *snapstore_device_find_by_dev_id(dev_t dev_id);
+
+int snapstore_device_create(dev_t dev_id, struct snapstore *snapstore);
+
+int snapstore_device_cleanup(uuid_t *id);
+
+int snapstore_device_prepare_requests(struct snapstore_device *snapstore_device,
+				      struct blk_range *copy_range,
+				      struct blk_deferred_request **dio_copy_req);
+int snapstore_device_store(struct snapstore_device *snapstore_device,
+			   struct blk_deferred_request *dio_copy_req);
+
+int snapstore_device_read(struct snapstore_device *snapstore_device,
+			  struct blk_redirect_bio *rq_redir); //request from image
+int snapstore_device_write(struct snapstore_device *snapstore_device,
+			   struct blk_redirect_bio *rq_redir); //request from image
+
+bool snapstore_device_is_corrupted(struct snapstore_device *snapstore_device);
+void snapstore_device_set_corrupted(struct snapstore_device *snapstore_device, int err_code);
+int snapstore_device_errno(dev_t dev_id, int *p_err_code);
+
+static inline void _snapstore_device_descr_read_lock(struct snapstore_device *snapstore_device)
+{
+	mutex_lock(&snapstore_device->store_block_map_locker);
+}
+static inline void _snapstore_device_descr_read_unlock(struct snapstore_device *snapstore_device)
+{
+	mutex_unlock(&snapstore_device->store_block_map_locker);
+}
diff --git a/drivers/block/blk-snap/snapstore_file.c b/drivers/block/blk-snap/snapstore_file.c
new file mode 100644
index 000000000000..9c7830b037dc
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_file.c
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapstore"
+#include "common.h"
+#include "snapstore_file.h"
+#include "blk_util.h"
+
+int snapstore_file_create(dev_t dev_id, struct snapstore_file **pfile)
+{
+	int res = SUCCESS;
+	struct snapstore_file *file;
+
+	pr_err("Single device file snapstore was created on device [%d:%d]\n", MAJOR(dev_id),
+	       MINOR(dev_id));
+
+	file = kzalloc(sizeof(struct snapstore_file), GFP_KERNEL);
+	if (file == NULL)
+		return -ENOMEM;
+
+	res = blk_dev_open(dev_id, &file->blk_dev);
+	if (res != SUCCESS) {
+		kfree(file);
+		pr_err("Unable to create snapstore file: failed to open device [%d:%d]. errno=%d",
+		       MAJOR(dev_id), MINOR(dev_id), res);
+		return res;
+	}
+	{
+		struct request_queue *q = bdev_get_queue(file->blk_dev);
+
+		pr_err("snapstore device logical block size %d\n", q->limits.logical_block_size);
+		pr_err("snapstore device physical block size %d\n", q->limits.physical_block_size);
+	}
+
+	file->blk_dev_id = dev_id;
+	blk_descr_file_pool_init(&file->pool);
+
+	*pfile = file;
+	return res;
+}
+
+void snapstore_file_destroy(struct snapstore_file *file)
+{
+	if (file) {
+		blk_descr_file_pool_done(&file->pool);
+
+		if (file->blk_dev != NULL) {
+			blk_dev_close(file->blk_dev);
+			file->blk_dev = NULL;
+		}
+
+		kfree(file);
+	}
+}
diff --git a/drivers/block/blk-snap/snapstore_file.h b/drivers/block/blk-snap/snapstore_file.h
new file mode 100644
index 000000000000..effd9d888781
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_file.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "blk_deferred.h"
+
+struct snapstore_file {
+	dev_t blk_dev_id;
+	struct block_device *blk_dev;
+
+	struct blk_descr_pool pool;
+};
+
+int snapstore_file_create(dev_t dev_id, struct snapstore_file **pfile);
+
+void snapstore_file_destroy(struct snapstore_file *file);
diff --git a/drivers/block/blk-snap/snapstore_mem.c b/drivers/block/blk-snap/snapstore_mem.c
new file mode 100644
index 000000000000..4dba9267eb6d
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_mem.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapstore"
+#include "common.h"
+#include "snapstore_mem.h"
+#include "params.h"
+
+struct buffer_el {
+	struct list_head link;
+	void *buff;
+};
+
+struct snapstore_mem *snapstore_mem_create(size_t available_blocks)
+{
+	struct snapstore_mem *mem = kzalloc(sizeof(struct snapstore_mem), GFP_KERNEL);
+
+	if (mem == NULL)
+		return NULL;
+
+	blk_descr_mem_pool_init(&mem->pool, available_blocks);
+
+	mem->blocks_limit = available_blocks;
+
+	INIT_LIST_HEAD(&mem->blocks);
+	mutex_init(&mem->blocks_lock);
+
+	return mem;
+}
+
+void snapstore_mem_destroy(struct snapstore_mem *mem)
+{
+	struct buffer_el *buffer_el;
+
+	if (mem == NULL)
+		return;
+
+	do {
+		buffer_el = NULL;
+
+		mutex_lock(&mem->blocks_lock);
+		if (!list_empty(&mem->blocks)) {
+			buffer_el = list_entry(mem->blocks.next, struct buffer_el, link);
+
+			list_del(&buffer_el->link);
+		}
+		mutex_unlock(&mem->blocks_lock);
+
+		if (buffer_el) {
+			vfree(buffer_el->buff);
+			kfree(buffer_el);
+		}
+	} while (buffer_el);
+
+	blk_descr_mem_pool_done(&mem->pool);
+
+	kfree(mem);
+}
+
+void *snapstore_mem_get_block(struct snapstore_mem *mem)
+{
+	struct buffer_el *buffer_el;
+
+	if (mem->blocks_allocated >= mem->blocks_limit) {
+		pr_err("Unable to get block from snapstore in memory\n");
+		pr_err("Block limit is reached, allocated %ld, limit %ld\n", mem->blocks_allocated,
+		       mem->blocks_limit);
+		return NULL;
+	}
+
+	buffer_el = kzalloc(sizeof(struct buffer_el), GFP_KERNEL);
+	if (buffer_el == NULL)
+		return NULL;
+	INIT_LIST_HEAD(&buffer_el->link);
+
+	buffer_el->buff = __vmalloc(snapstore_block_size() * SECTOR_SIZE, GFP_NOIO);
+	if (buffer_el->buff == NULL) {
+		kfree(buffer_el);
+		return NULL;
+	}
+
+	++mem->blocks_allocated;
+	if (0 == (mem->blocks_allocated & 0x7F))
+		pr_info("%ld MiB was allocated\n", mem->blocks_allocated);
+
+	mutex_lock(&mem->blocks_lock);
+	list_add_tail(&buffer_el->link, &mem->blocks);
+	mutex_unlock(&mem->blocks_lock);
+
+	return buffer_el->buff;
+}
diff --git a/drivers/block/blk-snap/snapstore_mem.h b/drivers/block/blk-snap/snapstore_mem.h
new file mode 100644
index 000000000000..9044a6525966
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_mem.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#include "blk_descr_mem.h"
+
+struct snapstore_mem {
+	struct list_head blocks;
+	struct mutex blocks_lock;
+
+	size_t blocks_limit;
+	size_t blocks_allocated;
+
+	struct blk_descr_pool pool;
+};
+
+struct snapstore_mem *snapstore_mem_create(size_t available_blocks);
+
+void snapstore_mem_destroy(struct snapstore_mem *mem);
+
+void *snapstore_mem_get_block(struct snapstore_mem *mem);
diff --git a/drivers/block/blk-snap/snapstore_multidev.c b/drivers/block/blk-snap/snapstore_multidev.c
new file mode 100644
index 000000000000..bb6bfefa68d7
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_multidev.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-snapstore"
+#include "common.h"
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+
+#include "snapstore_multidev.h"
+#include "blk_util.h"
+
+struct multidev_el {
+	struct list_head link;
+
+	dev_t dev_id;
+	struct block_device *blk_dev;
+};
+
+int snapstore_multidev_create(struct snapstore_multidev **p_multidev)
+{
+	int res = SUCCESS;
+	struct snapstore_multidev *multidev;
+
+	pr_info("Multidevice file snapstore create\n");
+
+	multidev = kzalloc(sizeof(struct snapstore_multidev), GFP_KERNEL);
+	if (multidev == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&multidev->devicelist);
+	spin_lock_init(&multidev->devicelist_lock);
+
+	blk_descr_multidev_pool_init(&multidev->pool);
+
+	*p_multidev = multidev;
+	return res;
+}
+
+void snapstore_multidev_destroy(struct snapstore_multidev *multidev)
+{
+	struct multidev_el *el;
+
+	blk_descr_multidev_pool_done(&multidev->pool);
+
+	do {
+		el = NULL;
+		spin_lock(&multidev->devicelist_lock);
+		if (!list_empty(&multidev->devicelist)) {
+			el = list_entry(multidev->devicelist.next, struct multidev_el, link);
+
+			list_del(&el->link);
+		}
+		spin_unlock(&multidev->devicelist_lock);
+
+		if (el) {
+			blk_dev_close(el->blk_dev);
+
+			pr_info("Close device for multidevice snapstore [%d:%d]\n",
+				MAJOR(el->dev_id), MINOR(el->dev_id));
+
+			kfree(el);
+		}
+	} while (el);
+
+	kfree(multidev);
+}
+
+struct multidev_el *snapstore_multidev_find(struct snapstore_multidev *multidev, dev_t dev_id)
+{
+	struct multidev_el *el = NULL;
+
+	spin_lock(&multidev->devicelist_lock);
+	if (!list_empty(&multidev->devicelist)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &multidev->devicelist) {
+			struct multidev_el *_el = list_entry(_head, struct multidev_el, link);
+
+			if (_el->dev_id == dev_id) {
+				el = _el;
+				break;
+			}
+		}
+	}
+	spin_unlock(&multidev->devicelist_lock);
+
+	return el;
+}
+
+struct block_device *snapstore_multidev_get_device(struct snapstore_multidev *multidev,
+						   dev_t dev_id)
+{
+	int res;
+	struct block_device *blk_dev = NULL;
+	struct multidev_el *el = snapstore_multidev_find(multidev, dev_id);
+
+	if (el)
+		return el->blk_dev;
+
+	res = blk_dev_open(dev_id, &blk_dev);
+	if (res != SUCCESS) {
+		pr_err("Unable to add device to snapstore multidevice file\n");
+		pr_err("Failed to open [%d:%d]. errno=%d", MAJOR(dev_id), MINOR(dev_id), res);
+		return NULL;
+	}
+
+	el = kzalloc(sizeof(struct multidev_el), GFP_KERNEL);
+	INIT_LIST_HEAD(&el->link);
+
+	el->blk_dev = blk_dev;
+	el->dev_id = dev_id;
+
+	spin_lock(&multidev->devicelist_lock);
+	list_add_tail(&el->link, &multidev->devicelist);
+	spin_unlock(&multidev->devicelist_lock);
+
+	return el->blk_dev;
+}
+
+#endif
diff --git a/drivers/block/blk-snap/snapstore_multidev.h b/drivers/block/blk-snap/snapstore_multidev.h
new file mode 100644
index 000000000000..40c1c3a41b08
--- /dev/null
+++ b/drivers/block/blk-snap/snapstore_multidev.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#ifdef CONFIG_BLK_SNAP_SNAPSTORE_MULTIDEV
+
+#include "blk_deferred.h"
+#include "blk_descr_multidev.h"
+
+struct snapstore_multidev {
+	struct list_head devicelist; //for mapping device id to opened device struct pointer
+	spinlock_t devicelist_lock;
+
+	struct blk_descr_pool pool;
+};
+
+int snapstore_multidev_create(struct snapstore_multidev **p_file);
+
+void snapstore_multidev_destroy(struct snapstore_multidev *file);
+
+struct block_device *snapstore_multidev_get_device(struct snapstore_multidev *multidev,
+						   dev_t dev_id);
+#endif
diff --git a/drivers/block/blk-snap/tracker.c b/drivers/block/blk-snap/tracker.c
new file mode 100644
index 000000000000..d37e12ddcc5b
--- /dev/null
+++ b/drivers/block/blk-snap/tracker.c
@@ -0,0 +1,521 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-tracker"
+#include "common.h"
+#include "tracker.h"
+#include "blk_util.h"
+
+LIST_HEAD(trackers);
+DEFINE_RWLOCK(trackers_lock);
+
+void tracker_done(void)
+{
+	tracker_remove_all();
+}
+
+int tracker_find_by_queue(struct gendisk *disk, u8 partno, struct tracker **ptracker)
+{
+	int result = -ENODATA;
+
+	read_lock(&trackers_lock);
+	if (!list_empty(&trackers)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &trackers) {
+			struct tracker *_tracker = list_entry(_head, struct tracker, link);
+
+			if ((disk == _tracker->target_dev->bd_disk) &&
+			    (partno == _tracker->target_dev->bd_partno)) {
+				if (ptracker != NULL)
+					*ptracker = _tracker;
+
+				result = SUCCESS;
+				break;
+			}
+		}
+	}
+	read_unlock(&trackers_lock);
+
+	return result;
+}
+
+int tracker_find_by_dev_id(dev_t dev_id, struct tracker **ptracker)
+{
+	int result = -ENODATA;
+
+	read_lock(&trackers_lock);
+	if (!list_empty(&trackers)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &trackers) {
+			struct tracker *_tracker = list_entry(_head, struct tracker, link);
+
+			if (_tracker->original_dev_id == dev_id) {
+				if (ptracker != NULL)
+					*ptracker = _tracker;
+
+				result = SUCCESS;
+				break;
+			}
+		}
+	}
+	read_unlock(&trackers_lock);
+
+	return result;
+}
+
+int tracker_enum_cbt_info(int max_count, struct cbt_info_s *p_cbt_info, int *p_count)
+{
+	int result = SUCCESS;
+	int count = 0;
+
+	read_lock(&trackers_lock);
+	if (!list_empty(&trackers)) {
+		struct list_head *_head;
+
+		list_for_each(_head, &trackers) {
+			struct tracker *tracker = list_entry(_head, struct tracker, link);
+
+			if (count >= max_count) {
+				result = -ENOBUFS;
+				break; //don`t continue
+			}
+
+			if (p_cbt_info != NULL) {
+				p_cbt_info[count].dev_id.major = MAJOR(tracker->original_dev_id);
+				p_cbt_info[count].dev_id.minor = MINOR(tracker->original_dev_id);
+
+				if (tracker->cbt_map) {
+					p_cbt_info[count].cbt_map_size = tracker->cbt_map->map_size;
+					p_cbt_info[count].snap_number =
+						(unsigned char)
+							tracker->cbt_map->snap_number_previous;
+					uuid_copy((uuid_t *)(p_cbt_info[count].generationId),
+						  &tracker->cbt_map->generationId);
+				} else {
+					p_cbt_info[count].cbt_map_size = 0;
+					p_cbt_info[count].snap_number = 0;
+				}
+
+				p_cbt_info[count].dev_capacity = (u64)from_sectors(
+					blk_dev_get_capacity(tracker->target_dev));
+			}
+
+			++count;
+		}
+	}
+	read_unlock(&trackers_lock);
+
+	if (result == SUCCESS)
+		if (count == 0)
+			result = -ENODATA;
+
+	*p_count = count;
+	return result;
+}
+
+void tracker_cbt_start(struct tracker *tracker, unsigned long long snapshot_id)
+{
+	tracker_snapshot_id_set(tracker, snapshot_id);
+}
+
+static struct super_block *blk_thaw_bdev(dev_t dev_id, struct block_device *device,
+					 struct super_block *superblock)
+{
+	if (superblock == NULL)
+		return NULL;
+
+	if (thaw_bdev(device, superblock) == SUCCESS)
+		pr_info("Device [%d:%d] was unfrozen\n", MAJOR(dev_id), MINOR(dev_id));
+	else
+		pr_err("Failed to unfreeze device [%d:%d]\n", MAJOR(dev_id), MINOR(dev_id));
+
+	return NULL;
+}
+
+static int blk_freeze_bdev(dev_t dev_id, struct block_device *device,
+			   struct super_block **psuperblock)
+{
+	struct super_block *superblock;
+
+	if (device->bd_super == NULL) {
+		pr_warn("Unable to freeze device [%d:%d]: no superblock was found\n", MAJOR(dev_id),
+			MINOR(dev_id));
+		return SUCCESS;
+	}
+
+	superblock = freeze_bdev(device);
+	if (IS_ERR_OR_NULL(superblock)) {
+		int result;
+
+		pr_err("Failed to freeze device [%d:%d]\n", MAJOR(dev_id), MINOR(dev_id));
+
+		if (superblock == NULL)
+			result = -ENODEV;
+		else {
+			result = PTR_ERR(superblock);
+			pr_err("Error code: %d\n", result);
+		}
+		return result;
+	}
+
+	pr_info("Device [%d:%d] was frozen\n", MAJOR(dev_id), MINOR(dev_id));
+	*psuperblock = superblock;
+
+	return SUCCESS;
+}
+
+int tracker_create(unsigned long long snapshot_id, dev_t dev_id, unsigned int cbt_block_size_degree,
+		   struct cbt_map *cbt_map, struct tracker **ptracker)
+{
+	int result = SUCCESS;
+	struct tracker *tracker = NULL;
+
+	*ptracker = NULL;
+
+	tracker = kzalloc(sizeof(struct tracker), GFP_KERNEL);
+	if (tracker == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&tracker->link);
+
+	atomic_set(&tracker->is_captured, false);
+	tracker->is_unfreezable = false;
+	init_rwsem(&tracker->unfreezable_lock);
+
+	tracker->original_dev_id = dev_id;
+
+	result = blk_dev_open(tracker->original_dev_id, &tracker->target_dev);
+	if (result != SUCCESS) {
+		kfree(tracker);
+		return result;
+	}
+
+	write_lock(&trackers_lock);
+	list_add_tail(&tracker->link, &trackers);
+	write_unlock(&trackers_lock);
+
+	do {
+		struct super_block *superblock = NULL;
+
+		pr_info("Create tracker for device [%d:%d]. Capacity 0x%llx sectors\n",
+			MAJOR(tracker->original_dev_id), MINOR(tracker->original_dev_id),
+			(unsigned long long)blk_dev_get_capacity(tracker->target_dev));
+
+		if (cbt_map)
+			tracker->cbt_map = cbt_map_get_resource(cbt_map);
+		else {
+			tracker->cbt_map =
+				cbt_map_create(cbt_block_size_degree - SECTOR_SHIFT,
+					       blk_dev_get_capacity(tracker->target_dev));
+			if (tracker->cbt_map == NULL) {
+				result = -ENOMEM;
+				break;
+			}
+		}
+
+		tracker_cbt_start(tracker, snapshot_id);
+
+		result =
+			blk_freeze_bdev(tracker->original_dev_id, tracker->target_dev, &superblock);
+		if (result != SUCCESS) {
+			tracker->is_unfreezable = true;
+			break;
+		}
+
+		superblock =
+			blk_thaw_bdev(tracker->original_dev_id, tracker->target_dev, superblock);
+
+	} while (false);
+
+	if (result == SUCCESS) {
+		*ptracker = tracker;
+	} else {
+		int remove_status;
+
+		pr_err("Failed to create tracker for device [%d:%d]\n",
+		       MAJOR(tracker->original_dev_id), MINOR(tracker->original_dev_id));
+
+		remove_status = tracker_remove(tracker);
+		if ((remove_status == SUCCESS) || (remove_status == -ENODEV))
+			tracker = NULL;
+		else
+			pr_err("Failed to perfrom tracker cleanup. errno=%d\n",
+			       (0 - remove_status));
+	}
+
+	return result;
+}
+
+int _tracker_remove(struct tracker *tracker)
+{
+	int result = SUCCESS;
+
+	if (tracker->target_dev != NULL) {
+		struct super_block *superblock = NULL;
+
+		if (tracker->is_unfreezable)
+			down_write(&tracker->unfreezable_lock);
+		else
+			result = blk_freeze_bdev(tracker->original_dev_id, tracker->target_dev,
+						 &superblock);
+/*
+ *	!!! ToDo : remove from tracker must be in this place
+ */
+		if (tracker->is_unfreezable)
+			up_write(&tracker->unfreezable_lock);
+		else
+			superblock = blk_thaw_bdev(tracker->original_dev_id, tracker->target_dev,
+						   superblock);
+
+		blk_dev_close(tracker->target_dev);
+
+		tracker->target_dev = NULL;
+	} else
+		result = -ENODEV;
+
+	if (tracker->cbt_map != NULL) {
+		cbt_map_put_resource(tracker->cbt_map);
+		tracker->cbt_map = NULL;
+	}
+
+	return result;
+}
+
+int tracker_remove(struct tracker *tracker)
+{
+	int result = _tracker_remove(tracker);
+
+	write_lock(&trackers_lock);
+	list_del(&tracker->link);
+	write_unlock(&trackers_lock);
+
+	kfree(tracker);
+
+	return result;
+}
+
+void tracker_remove_all(void)
+{
+	struct tracker *tracker;
+
+	pr_info("Removing all devices from tracking\n");
+
+	do {
+		tracker = NULL;
+
+		write_lock(&trackers_lock);
+		if (!list_empty(&trackers)) {
+			tracker = list_entry(trackers.next, struct tracker, link);
+
+			list_del(&tracker->link);
+		}
+		write_unlock(&trackers_lock);
+
+		if (tracker) {
+			int status = _tracker_remove(tracker);
+
+			if (status != SUCCESS)
+				pr_err("Failed to remove device [%d:%d] from tracking. errno=%d\n",
+				       MAJOR(tracker->original_dev_id),
+				       MINOR(tracker->original_dev_id), 0 - status);
+
+			kfree(tracker);
+		}
+	} while (tracker);
+}
+
+void tracker_cbt_bitmap_set(struct tracker *tracker, sector_t sector, sector_t sector_cnt)
+{
+	if (tracker->cbt_map == NULL)
+		return;
+
+	if (tracker->cbt_map->device_capacity != blk_dev_get_capacity(tracker->target_dev)) {
+		pr_warn("Device resize detected\n");
+		tracker->cbt_map->active = false;
+		return;
+	}
+
+	if (cbt_map_set(tracker->cbt_map, sector, sector_cnt) != SUCCESS) { //cbt corrupt
+		pr_warn("CBT fault detected\n");
+		tracker->cbt_map->active = false;
+		return;
+	}
+}
+
+bool tracker_cbt_bitmap_lock(struct tracker *tracker)
+{
+	if (tracker->cbt_map == NULL)
+		return false;
+
+	cbt_map_read_lock(tracker->cbt_map);
+	if (!tracker->cbt_map->active) {
+		cbt_map_read_unlock(tracker->cbt_map);
+		return false;
+	}
+
+	return true;
+}
+
+void tracker_cbt_bitmap_unlock(struct tracker *tracker)
+{
+	if (tracker->cbt_map)
+		cbt_map_read_unlock(tracker->cbt_map);
+}
+
+int _tracker_capture_snapshot(struct tracker *tracker)
+{
+	int result = SUCCESS;
+
+	result = defer_io_create(tracker->original_dev_id, tracker->target_dev, &tracker->defer_io);
+	if (result != SUCCESS) {
+		pr_err("Failed to create defer IO processor\n");
+		return result;
+	}
+
+	atomic_set(&tracker->is_captured, true);
+
+	if (tracker->cbt_map != NULL) {
+		cbt_map_write_lock(tracker->cbt_map);
+		cbt_map_switch(tracker->cbt_map);
+		cbt_map_write_unlock(tracker->cbt_map);
+
+		pr_info("Snapshot captured for device [%d:%d]. New snap number %ld\n",
+			MAJOR(tracker->original_dev_id), MINOR(tracker->original_dev_id),
+			tracker->cbt_map->snap_number_active);
+	}
+
+	return result;
+}
+
+int tracker_capture_snapshot(dev_t *dev_id_set, int dev_id_set_size)
+{
+	int result = SUCCESS;
+	int inx = 0;
+
+	for (inx = 0; inx < dev_id_set_size; ++inx) {
+		struct super_block *superblock = NULL;
+		struct tracker *tracker = NULL;
+		dev_t dev_id = dev_id_set[inx];
+
+		result = tracker_find_by_dev_id(dev_id, &tracker);
+		if (result != SUCCESS) {
+			pr_err("Unable to capture snapshot: cannot find device [%d:%d]\n",
+			       MAJOR(dev_id), MINOR(dev_id));
+			break;
+		}
+
+		if (tracker->is_unfreezable)
+			down_write(&tracker->unfreezable_lock);
+		else
+			blk_freeze_bdev(tracker->original_dev_id, tracker->target_dev, &superblock);
+		{
+			result = _tracker_capture_snapshot(tracker);
+			if (result != SUCCESS)
+				pr_err("Failed to capture snapshot for device [%d:%d]\n",
+				       MAJOR(dev_id), MINOR(dev_id));
+		}
+		if (tracker->is_unfreezable)
+			up_write(&tracker->unfreezable_lock);
+		else
+			superblock = blk_thaw_bdev(tracker->original_dev_id, tracker->target_dev,
+						   superblock);
+	}
+	if (result != SUCCESS)
+		return result;
+
+	for (inx = 0; inx < dev_id_set_size; ++inx) {
+		struct tracker *p_tracker = NULL;
+		dev_t dev_id = dev_id_set[inx];
+
+		result = tracker_find_by_dev_id(dev_id, &p_tracker);
+		if (result != SUCCESS) {
+			pr_err("Unable to capture snapshot: cannot find device [%d:%d]\n",
+			       MAJOR(dev_id), MINOR(dev_id));
+			continue;
+		}
+
+		if (snapstore_device_is_corrupted(p_tracker->defer_io->snapstore_device)) {
+			pr_err("Unable to freeze devices [%d:%d]: snapshot data is corrupted\n",
+			       MAJOR(dev_id), MINOR(dev_id));
+			result = -EDEADLK;
+			break;
+		}
+	}
+
+	if (result != SUCCESS) {
+		int status;
+
+		pr_err("Failed to capture snapshot. errno=%d\n", result);
+
+		status = tracker_release_snapshot(dev_id_set, dev_id_set_size);
+		if (status != SUCCESS)
+			pr_err("Failed to perfrom snapshot cleanup. errno=%d\n", status);
+	}
+	return result;
+}
+
+int _tracker_release_snapshot(struct tracker *tracker)
+{
+	int result = SUCCESS;
+	struct super_block *superblock = NULL;
+	struct defer_io *defer_io = tracker->defer_io;
+
+	if (tracker->is_unfreezable)
+		down_write(&tracker->unfreezable_lock);
+	else
+		result =
+			blk_freeze_bdev(tracker->original_dev_id, tracker->target_dev, &superblock);
+	{ //locked region
+		atomic_set(&tracker->is_captured, false); //clear freeze flag
+
+		tracker->defer_io = NULL;
+	}
+	if (tracker->is_unfreezable)
+		up_write(&tracker->unfreezable_lock);
+	else
+		superblock =
+			blk_thaw_bdev(tracker->original_dev_id, tracker->target_dev, superblock);
+
+	defer_io_stop(defer_io);
+	defer_io_put_resource(defer_io);
+
+	return result;
+}
+
+int tracker_release_snapshot(dev_t *dev_id_set, int dev_id_set_size)
+{
+	int result = SUCCESS;
+	int inx = 0;
+
+	for (; inx < dev_id_set_size; ++inx) {
+		int status;
+		struct tracker *p_tracker = NULL;
+		dev_t dev = dev_id_set[inx];
+
+		status = tracker_find_by_dev_id(dev, &p_tracker);
+		if (status == SUCCESS) {
+			status = _tracker_release_snapshot(p_tracker);
+			if (status != SUCCESS) {
+				pr_err("Failed to release snapshot for device [%d:%d]\n",
+				       MAJOR(dev), MINOR(dev));
+
+				result = status;
+				break;
+			}
+		} else
+			pr_err("Unable to release snapshot: cannot find tracker for device [%d:%d]\n",
+			       MAJOR(dev), MINOR(dev));
+	}
+
+	return result;
+}
+
+void tracker_snapshot_id_set(struct tracker *tracker, unsigned long long snapshot_id)
+{
+	tracker->snapshot_id = snapshot_id;
+}
+
+unsigned long long tracker_snapshot_id_get(struct tracker *tracker)
+{
+	return tracker->snapshot_id;
+}
diff --git a/drivers/block/blk-snap/tracker.h b/drivers/block/blk-snap/tracker.h
new file mode 100644
index 000000000000..8332da418703
--- /dev/null
+++ b/drivers/block/blk-snap/tracker.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+#include "cbt_map.h"
+#include "defer_io.h"
+#include "blk-snap-ctl.h"
+#include "snapshot.h"
+
+struct tracker {
+	struct list_head link;
+
+	dev_t original_dev_id;
+
+	struct block_device *target_dev;
+
+	struct cbt_map *cbt_map;
+
+	atomic_t is_captured;
+
+	bool is_unfreezable; // when device have not filesystem and can not be freeze
+	struct rw_semaphore unfreezable_lock; //locking io processing for unfreezable devices
+
+	struct defer_io *defer_io;
+
+	unsigned long long snapshot_id; // current snapshot for this device
+};
+
+void tracker_done(void);
+
+int tracker_find_by_queue(struct gendisk *disk, u8 partno, struct tracker **ptracker);
+int tracker_find_by_dev_id(dev_t dev_id, struct tracker **ptracker);
+
+int tracker_enum_cbt_info(int max_count, struct cbt_info_s *p_cbt_info, int *p_count);
+
+int tracker_capture_snapshot(dev_t *dev_id_set, int dev_id_set_size);
+int tracker_release_snapshot(dev_t *dev_id_set, int dev_id_set_size);
+
+void tracker_cbt_start(struct tracker *tracker, unsigned long long snapshot_id);
+
+int tracker_create(unsigned long long snapshot_id, dev_t dev_id, unsigned int cbt_block_size_degree,
+		   struct cbt_map *cbt_map, struct tracker **ptracker);
+int tracker_remove(struct tracker *tracker);
+void tracker_remove_all(void);
+
+void tracker_cbt_bitmap_set(struct tracker *tracker, sector_t sector, sector_t sector_cnt);
+
+bool tracker_cbt_bitmap_lock(struct tracker *tracker);
+void tracker_cbt_bitmap_unlock(struct tracker *tracker);
+
+void tracker_snapshot_id_set(struct tracker *tracker, unsigned long long snapshot_id);
+unsigned long long tracker_snapshot_id_get(struct tracker *tracker);
diff --git a/drivers/block/blk-snap/tracking.c b/drivers/block/blk-snap/tracking.c
new file mode 100644
index 000000000000..6d48682ffaf8
--- /dev/null
+++ b/drivers/block/blk-snap/tracking.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0
+#define BLK_SNAP_SECTION "-tracking"
+#include "common.h"
+#include "tracking.h"
+#include "tracker.h"
+#include "blk_util.h"
+#include "defer_io.h"
+
+/*
+ * tracking_submit_bio() - Intercept bio by block io layer filter
+ */
+bool tracking_submit_bio(struct bio *bio, blk_qc_t *result)
+{
+	int res;
+	bool bio_redirected = false;
+	struct tracker *tracker = NULL;
+
+	bio_get(bio);
+
+	res = tracker_find_by_queue(bio->bi_disk, bio->bi_partno, &tracker);
+	if (res != SUCCESS) {
+		//do not intercept
+		bio_put(bio);
+		return false;
+	}
+
+	//intercepting
+	if ((bio->bi_end_io != blk_redirect_bio_endio) &&
+	    (bio->bi_end_io != blk_deferred_bio_endio)) {
+		if (tracker->is_unfreezable)
+			down_read(&tracker->unfreezable_lock);
+
+		if (atomic_read(&tracker->is_captured)) {
+			//snapshot is captured, call bio redirect algorithm
+			res = defer_io_redirect_bio(tracker->defer_io, bio, tracker);
+			if (res == SUCCESS) {
+				bio_redirected = true;
+				*result = 0;
+			}
+		}
+
+		if (tracker->is_unfreezable)
+			up_read(&tracker->unfreezable_lock);
+	}
+
+	if (!bio_redirected) {
+		bool cbt_locked = false;
+
+		if (tracker && bio_data_dir(bio) && bio_has_data(bio)) {
+			//call CBT algorithm
+			cbt_locked = tracker_cbt_bitmap_lock(tracker);
+			if (cbt_locked) {
+				sector_t sectStart = bio->bi_iter.bi_sector;
+				sector_t sectCount = bio_sectors(bio);
+
+				tracker_cbt_bitmap_set(tracker, sectStart, sectCount);
+			}
+		}
+		if (cbt_locked)
+			tracker_cbt_bitmap_unlock(tracker);
+	}
+
+	bio_put(bio);
+
+	return bio_redirected;
+}
+
+static int _add_already_tracked(dev_t dev_id, unsigned int cbt_block_size_degree,
+				unsigned long long snapshot_id, struct tracker *tracker)
+{
+	int result = SUCCESS;
+	bool cbt_reset_needed = false;
+
+	if ((snapshot_id != 0ull) && (tracker_snapshot_id_get(tracker) == 0ull))
+		tracker_snapshot_id_set(tracker, snapshot_id);
+
+	if (tracker->cbt_map == NULL) {
+		tracker->cbt_map = cbt_map_create(cbt_block_size_degree - SECTOR_SHIFT,
+						  blk_dev_get_capacity(tracker->target_dev));
+		if (tracker->cbt_map == NULL)
+			return -ENOMEM;
+
+		tracker_cbt_start(tracker, snapshot_id);
+		return SUCCESS;
+	}
+
+	if (!tracker->cbt_map->active) {
+		cbt_reset_needed = true;
+		pr_warn("Nonactive CBT table detected. CBT fault\n");
+	}
+
+	if (tracker->cbt_map->device_capacity != blk_dev_get_capacity(tracker->target_dev)) {
+		cbt_reset_needed = true;
+		pr_warn("Device resize detected. CBT fault\n");
+	}
+
+	if (!cbt_reset_needed)
+		return SUCCESS;
+
+	result = tracker_remove(tracker);
+	if (result != SUCCESS) {
+		pr_err("Failed to remove tracker. errno=%d\n", result);
+		return result;
+	}
+
+	result = tracker_create(snapshot_id, dev_id, cbt_block_size_degree, NULL, &tracker);
+	if (result != SUCCESS)
+		pr_err("Failed to create tracker. errno=%d\n", result);
+
+	return result;
+}
+
+static int _create_new_tracker(dev_t dev_id, unsigned int cbt_block_size_degree,
+			       unsigned long long snapshot_id)
+{
+	int result = SUCCESS;
+	struct block_device *target_dev = NULL;
+	char dev_name[BDEVNAME_SIZE + 1];
+
+	result = blk_dev_open(dev_id, &target_dev);
+	if (result != SUCCESS)
+		return result;
+
+	do {
+		struct tracker *tracker = NULL;
+
+		result = tracker_find_by_queue(target_dev->bd_disk, target_dev->bd_partno,
+					       &tracker);
+		if (result == SUCCESS) {
+			pr_err("Tracker queue already exist.\n");
+
+			result = -EALREADY;
+			break;
+		}
+
+		result = tracker_create(snapshot_id, dev_id, cbt_block_size_degree, NULL,
+					&tracker);
+		if (result != SUCCESS) {
+			pr_err("Failed to create tracker. errno=%d\n", result);
+			break;
+		}
+
+		memset(dev_name, 0, BDEVNAME_SIZE + 1);
+		if (bdevname(target_dev, dev_name))
+			pr_info("Add to tracking device %s\n", dev_name);
+
+		if (target_dev->bd_super)
+			pr_info("fs id: %s\n", target_dev->bd_super->s_id);
+		else
+			pr_info("Filesystem not found\n");
+
+	} while (false);
+
+	blk_dev_close(target_dev);
+
+	return result;
+}
+
+int tracking_add(dev_t dev_id, unsigned int cbt_block_size_degree, unsigned long long snapshot_id)
+{
+	int result;
+	struct tracker *tracker = NULL;
+
+	pr_info("Adding device [%d:%d] under tracking\n", MAJOR(dev_id), MINOR(dev_id));
+
+	result = tracker_find_by_dev_id(dev_id, &tracker);
+	if (result == SUCCESS) {
+		//pr_info("Device [%d:%d] is already tracked\n", MAJOR(dev_id), MINOR(dev_id));
+		result = _add_already_tracked(dev_id, cbt_block_size_degree, snapshot_id, tracker);
+		if (result == SUCCESS)
+			result = -EALREADY;
+	} else if (-ENODATA == result)
+		result = _create_new_tracker(dev_id, cbt_block_size_degree, snapshot_id);
+	else {
+		pr_err("Unable to add device [%d:%d] under tracking\n", MAJOR(dev_id),
+			MINOR(dev_id));
+		pr_err("Invalid trackers container. errno=%d\n", result);
+	}
+
+	return result;
+}
+
+int tracking_remove(dev_t dev_id)
+{
+	int result = SUCCESS;
+	struct tracker *tracker = NULL;
+
+	pr_info("Removing device [%d:%d] from tracking\n", MAJOR(dev_id), MINOR(dev_id));
+
+	result = tracker_find_by_dev_id(dev_id, &tracker);
+	if (result != SUCCESS) {
+		pr_err("Unable to remove device [%d:%d] from tracking: ",
+		       MAJOR(dev_id), MINOR(dev_id));
+
+		if (-ENODATA == result)
+			pr_err("tracker not found\n");
+		else
+			pr_err("tracker container failed. errno=%d\n", result);
+
+		return result;
+	}
+
+	if (tracker_snapshot_id_get(tracker) != 0ull) {
+		pr_err("Unable to remove device [%d:%d] from tracking: ",
+		       MAJOR(dev_id), MINOR(dev_id));
+		pr_err("snapshot [0x%llx] already exist\n", tracker_snapshot_id_get(tracker));
+		return -EBUSY;
+	}
+
+	result = tracker_remove(tracker);
+	if (result != SUCCESS) {
+		pr_err("Unable to remove device [%d:%d] from tracking: ",
+		       MAJOR(dev_id), MINOR(dev_id));
+		pr_err("failed to remove tracker. errno=%d\n", result);
+	}
+
+	return result;
+}
+
+int tracking_collect(int max_count, struct cbt_info_s *p_cbt_info, int *p_count)
+{
+	int res = tracker_enum_cbt_info(max_count, p_cbt_info, p_count);
+
+	if (res == SUCCESS)
+		pr_info("%d devices found under tracking\n", *p_count);
+	else if (res == -ENODATA) {
+		pr_info("There are no devices under tracking\n");
+		*p_count = 0;
+		res = SUCCESS;
+	} else
+		pr_err("Failed to collect devices under tracking. errno=%d", res);
+
+	return res;
+}
+
+int tracking_read_cbt_bitmap(dev_t dev_id, unsigned int offset, size_t length,
+			     void __user *user_buff)
+{
+	int result = SUCCESS;
+	struct tracker *tracker = NULL;
+
+	result = tracker_find_by_dev_id(dev_id, &tracker);
+	if (result == SUCCESS) {
+		if (atomic_read(&tracker->is_captured))
+			result = cbt_map_read_to_user(tracker->cbt_map, user_buff, offset, length);
+		else {
+			pr_err("Unable to read CBT bitmap for device [%d:%d]: ", MAJOR(dev_id),
+			       MINOR(dev_id));
+			pr_err("device is not captured by snapshot\n");
+			result = -EPERM;
+		}
+	} else if (-ENODATA == result) {
+		pr_err("Unable to read CBT bitmap for device [%d:%d]: ", MAJOR(dev_id),
+		       MINOR(dev_id));
+		pr_err("device not found\n");
+	} else
+		pr_err("Failed to find devices under tracking. errno=%d", result);
+
+	return result;
+}
diff --git a/drivers/block/blk-snap/tracking.h b/drivers/block/blk-snap/tracking.h
new file mode 100644
index 000000000000..c15b054fce6e
--- /dev/null
+++ b/drivers/block/blk-snap/tracking.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+#include "blk-snap-ctl.h"
+#include <linux/bio.h>
+
+int tracking_add(dev_t dev_id, unsigned int cbt_block_size_degree, unsigned long long snapshot_id);
+int tracking_remove(dev_t dev_id);
+int tracking_collect(int max_count, struct cbt_info_s *p_cbt_info, int *p_count);
+int tracking_read_cbt_bitmap(dev_t dev_id, unsigned int offset, size_t length,
+			     void __user *user_buff);
+
+bool tracking_submit_bio(struct bio *bio, blk_qc_t *result);
diff --git a/drivers/block/blk-snap/version.h b/drivers/block/blk-snap/version.h
new file mode 100644
index 000000000000..a4431da73611
--- /dev/null
+++ b/drivers/block/blk-snap/version.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#pragma once
+
+#define FILEVER_MAJOR 5
+#define FILEVER_MINOR 0
+#define FILEVER_REVISION 0
+#define FILEVER_STR "5.0.0"
diff --git a/include/linux/blk-filter.h b/include/linux/blk-filter.h
new file mode 100644
index 000000000000..201613168864
--- /dev/null
+++ b/include/linux/blk-filter.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * API declarations for kernel modules utilizing block device filters
+ */
+
+#ifndef BLK_FILTER_H
+#define BLK_FILTER_H
+
+#ifdef CONFIG_BLK_FILTER
+#define BLK_FILTER_ALTITUDE_MAX 4
+#define BLK_FILTER_ALTITUDE_MIN 1
+
+struct blk_filter_ops {
+	void (*disk_add)(struct gendisk *disk);
+	void (*disk_del)(struct gendisk *disk);
+	void (*disk_release)(struct gendisk *disk);
+	blk_qc_t (*submit_bio)(struct bio *bio);
+};
+
+struct blk_filter {
+	const char *name;
+	const struct blk_filter_ops *ops;
+	size_t altitude;
+	void *blk_filter_ctx;
+};
+
+
+int blk_filter_register(struct blk_filter *filter);
+
+int blk_filter_unregister(struct blk_filter *filter);
+
+const char *blk_filter_check_altitude(size_t altitude);
+
+int blk_filter_attach_disks(struct blk_filter *filter);
+
+blk_qc_t blk_filter_submit_bio_next(struct blk_filter *filter, struct bio *bio);
+
+#endif /* CONFIG_BLK_FILTER */
+
+#endif
diff --git a/include/linux/genhd.h b/include/linux/genhd.h
index 4ab853461dff..6a6736d25ba7 100644
--- a/include/linux/genhd.h
+++ b/include/linux/genhd.h
@@ -4,7 +4,7 @@
 
 /*
  * 	genhd.h Copyright (C) 1992 Drew Eckhardt
- *	Generic hard disk header file by  
+ *	Generic hard disk header file by
  * 		Drew Eckhardt
  *
  *		<drew@...orado.edu>
@@ -318,6 +318,7 @@ extern void disk_flush_events(struct gendisk *disk, unsigned int mask);
 extern void set_capacity_revalidate_and_notify(struct gendisk *disk,
 			sector_t size, bool revalidate);
 extern unsigned int disk_clear_events(struct gendisk *disk, unsigned int mask);
+extern int disk_enumerate(void (*fn)(struct gendisk *disk, void *cxt), void *cxt);
 
 /* drivers/char/random.c */
 extern void add_disk_randomness(struct gendisk *disk) __latent_entropy;
-- 
2.28.0

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ