cmd: add clone command

This patch adds a feature for block device cloning similar to dd
command, this should be useful for boot-strapping a device where
usb gadget or networking is not available. For instance one can
clone a factory image into a blank emmc from an external sd card.

Signed-off-by: John Chau <john@harmon.hk>
diff --git a/arch/Kconfig b/arch/Kconfig
index 7f3cbe2..2174336 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -139,6 +139,7 @@
 	imply ACPI_PMC
 	imply ACPI_PMC_SANDBOX
 	imply CMD_PMC
+	imply CMD_CLONE
 
 config SH
 	bool "SuperH architecture"
diff --git a/cmd/Kconfig b/cmd/Kconfig
index d7136b0..e111764 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -1148,6 +1148,14 @@
 
 endif
 
+config CMD_CLONE
+	bool "clone"
+	depends on BLK
+	help
+	  Enable storage cloning over block devices, useful for
+	  initial flashing by external block device without network
+	  or usb support.
+
 config CMD_MTD
 	bool "mtd"
 	depends on MTD
diff --git a/cmd/Makefile b/cmd/Makefile
index 6e0086b..7075037 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -98,6 +98,7 @@
 obj-$(CONFIG_MP) += mp.o
 obj-$(CONFIG_CMD_MTD) += mtd.o
 obj-$(CONFIG_CMD_MTDPARTS) += mtdparts.o
+obj-$(CONFIG_CMD_CLONE) += clone.o
 ifneq ($(CONFIG_CMD_NAND)$(CONFIG_CMD_SF),)
 obj-y += legacy-mtd-utils.o
 endif
diff --git a/cmd/clone.c b/cmd/clone.c
new file mode 100644
index 0000000..97747f8
--- /dev/null
+++ b/cmd/clone.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 John Chau <john@harmon.hk>
+ *
+ */
+
+#include <common.h>
+#include <command.h>
+#include <malloc.h>
+#include <part.h>
+#include <blk.h>
+#include <vsprintf.h>
+
+#define BUFSIZE (1 * 1024 * 1024)
+static int do_clone(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
+{
+	int srcdev, destdev;
+	struct blk_desc *srcdesc, *destdesc;
+	int srcbz, destbz, ret;
+	char *unit, *buf;
+	unsigned long wrcnt, rdcnt, requested, srcblk, destblk;
+	unsigned long timer;
+	const unsigned long buffersize = 1024 * 1024;
+
+	if (argc < 6)
+		return CMD_RET_USAGE;
+
+	srcdev = blk_get_device_by_str(argv[1], argv[2], &srcdesc);
+	destdev = blk_get_device_by_str(argv[3], argv[4], &destdesc);
+	if (srcdev < 0) {
+		printf("Unable to open source device\n");
+		return 1;
+	} else if (destdev < 0) {
+		printf("Unable to open destination device\n");
+		return 1;
+	}
+	requested = simple_strtoul(argv[5], &unit, 10);
+	srcbz = srcdesc->blksz;
+	destbz = destdesc->blksz;
+
+	if ((srcbz * (buffersize / srcbz) != buffersize) &&
+	    (destbz * (buffersize / destbz) != buffersize)) {
+		printf("failed: cannot match device block sizes\n");
+		return 1;
+	}
+	if (requested == 0) {
+		unsigned long a = srcdesc->lba * srcdesc->blksz;
+		unsigned long b = destdesc->lba * destdesc->blksz;
+
+		if (a > b)
+			requested = a;
+		else
+			requested = b;
+	} else {
+		switch (unit[0]) {
+		case 'g':
+		case 'G':
+			requested *= 1024;
+		case 'm':
+		case 'M':
+			requested *= 1024;
+		case 'k':
+		case 'K':
+			requested *= 1024;
+			break;
+		}
+	}
+	printf("Copying %ld bytes from %s:%s to %s:%s\n",
+	       requested, argv[1], argv[2], argv[3], argv[4]);
+	wrcnt = 0;
+	rdcnt = 0;
+	buf = (char *)malloc(BUFSIZE);
+	srcblk = 0;
+	destblk = 0;
+	timer = get_timer(0);
+	while (wrcnt < requested) {
+		unsigned long toread = BUFSIZE / srcbz;
+		unsigned long towrite = BUFSIZE / destbz;
+		unsigned long offset = 0;
+
+read:
+		ret = blk_dread(srcdesc, srcblk, toread, buf + offset);
+		if (ret < 0) {
+			printf("Src read error @blk %ld\n", srcblk);
+			goto exit;
+		}
+		rdcnt += ret * srcbz;
+		srcblk += ret;
+		if (ret < toread) {
+			toread -= ret;
+			offset += ret * srcbz;
+			goto read;
+		}
+		offset = 0;
+write:
+		ret = blk_dwrite(destdesc, destblk, towrite, buf + offset);
+		if (ret < 0) {
+			printf("Dest write error @blk %ld\n", srcblk);
+			goto exit;
+		}
+		wrcnt += ret * destbz;
+		destblk += ret;
+		if (ret < towrite) {
+			towrite -= ret;
+			offset += ret * destbz;
+			goto write;
+		}
+	}
+
+exit:
+	timer = get_timer(timer);
+	timer = 1000 * timer / CONFIG_SYS_HZ;
+	printf("%ld read\n", rdcnt);
+	printf("%ld written\n", wrcnt);
+	printf("%ldms, %ldkB/s\n", timer, wrcnt / timer);
+	free(buf);
+
+	return 0;
+}
+
+U_BOOT_CMD(
+	clone, 6, 1, do_clone,
+	"simple storage cloning",
+	"<src interface> <src dev> <dest interface> <dest dev> <size[K/M/G]>\n"
+	"clone storage from 'src dev' on 'src interface' to 'dest dev' on 'dest interface' with maximum 'size' bytes (or 0 for clone to end)"
+);