mmc-utils: ffu: Allow ffu of large images

ffu is done using a single multi-ioctl to carry the entire firmware
image. This is limiting the fw image size to be at most 512KB, as the
mmc driver restricts each single ioc data to be at most
MMC_IOC_MAX_BYTES.

the spec however, allows the fw image to be written using multiple write
commands.

To overcome this limitation, if the fw image is larger than 512KB,
split it into a series of 512KB chunks.

fixes: 1b8b13beb424 (mmc-utils: let FFU mode use CMD23 and CMD25)
Reported-by: Lund Austin <Austin.Lund@garmin.com>
Tested-by: Lund Austin <Austin.Lund@garmin.com>
Signed-off-by: Avri Altman <avri.altman@wdc.com>
Link: https://lore.kernel.org/r/20230625103814.105-6-avri.altman@wdc.com
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 0321118..a1adbde 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -2831,7 +2831,7 @@
 	unsigned int sect_size;
 	__u8 ext_csd[512];
 	__u8 *buf = NULL;
-	off_t fw_size;
+	off_t fw_size, bytes_left, off;
 	char *device;
 	struct mmc_ioc_multi_cmd *multi_cmd = NULL;
 
@@ -2877,7 +2877,7 @@
 	}
 
 	fw_size = lseek(img_fd, 0, SEEK_END);
-	if (fw_size > MMC_IOC_MAX_BYTES || fw_size == 0) {
+	if (fw_size == 0) {
 		fprintf(stderr, "Wrong firmware size");
 		goto out;
 	}
@@ -2906,8 +2906,6 @@
 	fill_switch_cmd(&multi_cmd->cmds[0], EXT_CSD_MODE_CONFIG,
 			EXT_CSD_FFU_MODE);
 
-	set_ffu_single_cmd(multi_cmd, ext_csd, fw_size, buf, 0);
-
 	/* return device into normal mode */
 	fill_switch_cmd(&multi_cmd->cmds[3], EXT_CSD_MODE_CONFIG,
 			EXT_CSD_NORMAL_MODE);
@@ -2921,14 +2919,30 @@
 	}
 
 do_retry:
-	/* send ioctl with multi-cmd */
-	ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
+	bytes_left = fw_size;
+	off = 0;
+	while (bytes_left) {
+		unsigned int chunk_size = bytes_left < MMC_IOC_MAX_BYTES ?
+					  bytes_left : MMC_IOC_MAX_BYTES;
 
-	if (ret) {
-		perror("Multi-cmd ioctl");
-		/* In case multi-cmd ioctl failed before exiting from ffu mode */
-		ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[3]);
-		goto out;
+		/* prepare multi_cmd for FFU based on cmd to be used */
+		set_ffu_single_cmd(multi_cmd, ext_csd, chunk_size, buf, off);
+
+		/* send ioctl with multi-cmd */
+		ret = ioctl(dev_fd, MMC_IOC_MULTI_CMD, multi_cmd);
+
+		if (ret) {
+			perror("Multi-cmd ioctl");
+			/*
+			 * In case multi-cmd ioctl failed before exiting from
+			 * ffu mode
+			 */
+			ioctl(dev_fd, MMC_IOC_CMD, &multi_cmd->cmds[3]);
+			goto out;
+		}
+
+		bytes_left -= chunk_size;
+		off += chunk_size;
 	}
 
 	/*