Add hack support for FFU

Some device are not fully eMMC 5.0 FFU compliant.
Support ability to send hack to the kernel to support them.

BUG=chrome-os-partner:31155
TEST=Check Samsung upgrade

Change-Id: Icd2efbe0c4a78f1acfebd53ad1f147e5c77a38b1
Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/239953
Reviewed-by: Puthikorn Voravootivat <puthik@chromium.org>
diff --git a/ffu.h b/ffu.h
new file mode 100644
index 0000000..e0770a5
--- /dev/null
+++ b/ffu.h
@@ -0,0 +1,45 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+#ifndef _FFU_H_
+#define _FFU_H_
+
+#include <linux/types.h>
+
+/*
+ * eMMC5.0 Field Firmware Update (FFU) opcodes
+*/
+#define MMC_FFU_INVOKE_OP 302
+
+#define FFU_NAME_LEN 80  /* Name of the firmware file udev should find */
+
+enum mmc_ffu_hack_type {
+	MMC_OVERRIDE_FFU_ARG = 0,
+	MMC_HACK_LEN,
+};
+
+struct mmc_ffu_hack {
+	enum mmc_ffu_hack_type type;
+	__u64 value;
+};
+
+struct mmc_ffu_args {
+	char name[FFU_NAME_LEN];
+	__u32 hack_nb;
+	struct mmc_ffu_hack hack[0];
+};
+
+#endif /* _FFU_H_ */
diff --git a/mmc.c b/mmc.c
index 3d051ce..fb346ef 100644
--- a/mmc.c
+++ b/mmc.c
@@ -116,8 +116,9 @@
 	  NULL
 	},
 	{ do_emmc50_ffu, -2,
-	  "ffu", "<image name> <device>\n"
-		"run eMMC 5.0 Field firmware update.\n.",
+	  "ffu", "[-k hack_type[:hack_value]] <image name> <device>\n"
+		"run eMMC 5.0 Field firmware update.\n"
+		"Device specific hacks can be specificied.",
 	  NULL
 	},
 	{ 0, 0, 0, 0 }
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 3c7322e..3ea1a61 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -31,9 +31,10 @@
 
 #include "mmc.h"
 #include "mmc_cmds.h"
+#include "ffu.h"
 
 #define EXT_CSD_SIZE	512
-#define FFU_PATH_SIZE (512 - 1)
+#define FFU_DATA_SIZE	512
 #define CID_SIZE 16
 
 
@@ -1318,22 +1319,66 @@
 
 }
 
+static const char* const mmc_ffu_hack_names[] = {
+	[MMC_OVERRIDE_FFU_ARG] = "ffu_arg",
+};
+
 int do_emmc50_ffu (int nargs, char **argv)
 {
-	int fd, ret;
-	char *device;
-	char *path;
+	int fd, ret, i, argc=1, ffu_hack=0;
+	char *device, *type, *path;
+	__u64 value;
+	union {
+		__u8 data[FFU_DATA_SIZE];
+		struct mmc_ffu_args ffu_args;
+	} ffu_data;
+	struct mmc_ffu_args *ffu_args = &ffu_data.ffu_args;
 	struct mmc_ioc_cmd mmc_ioc_cmd;
 
-	CHECK(nargs != 3, "Usage: ffu <image name> </path/to/mmcblkX> \n",
-		exit(1));
+	while (!strcmp("-k", argv[argc])) {
+		ret = sscanf(argv[++argc], "%m[^:]:0x%llx", &type, &value);
+		if (ret < 1) {
+			fprintf(stderr, "Invalid hack: %s\n", argv[argc]);
+			exit(1);
+		}
+		for (i = 0; i < MMC_HACK_LEN; i++) {
+			if (!strcmp(type, mmc_ffu_hack_names[i])) {
+				ffu_args->hack[ffu_hack].type = i;
+				if (ret == 2) {
+					ffu_args->hack[ffu_hack].value = value;
+				}
+				ffu_hack++;
+				if (ffu_hack * sizeof(struct mmc_ffu_hack) +
+				    sizeof(struct mmc_ffu_args) >
+				    FFU_DATA_SIZE) {
+					fprintf(stderr, "Too many %d hacks",
+						ffu_hack);
+					exit(1);
+				}
+				break;
+			}
+		}
+		if (i == MMC_HACK_LEN) {
+			fprintf(stderr, "Hack type %s not found\n", type);
+			fprintf(stderr, "Supported types are: ");
+			for (i = 0; i < MMC_HACK_LEN; i++)
+				fprintf(stderr, "%s%s", mmc_ffu_hack_names[i],
+					(i == MMC_HACK_LEN-1 ? "\n": ", "));
 
-	path = argv[1];
-	if (strlen(path) > FFU_PATH_SIZE) {
+			exit(1);
+		}
+		free(type);
+		argc++;
+	}
+	ffu_args->hack_nb = ffu_hack;
+
+	path = argv[argc++];
+	if (strlen(path) >= FFU_NAME_LEN) {
 		fprintf(stderr, "Filename \"%.20s\" too long\n", path);
 		exit(1);
 	}
-	device = argv[2];
+	strcpy(ffu_args->name, path);
+	device = argv[argc++];
 	fd = open(device, O_RDWR);
 	if (fd < 0) {
 		perror("open");
@@ -1343,12 +1388,12 @@
 	/* prepare and send ioctl */
 	memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
 	mmc_ioc_cmd.opcode = MMC_FFU_INVOKE_OP;
-	mmc_ioc_cmd.blksz = MIN(strlen(path), FFU_PATH_SIZE);
+	mmc_ioc_cmd.blksz = FFU_DATA_SIZE;
 	mmc_ioc_cmd.blocks = 1;
 	mmc_ioc_cmd.arg = 0;
 	mmc_ioc_cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
 	mmc_ioc_cmd.write_flag = 1;
-	mmc_ioc_cmd_set_data(mmc_ioc_cmd, path);
+	mmc_ioc_cmd_set_data(mmc_ioc_cmd, ffu_args);
 	ret = ioctl(fd, MMC_IOC_CMD, &mmc_ioc_cmd);
 	if (ret) {
 		fprintf(stderr, "FFU install failed : %s\n", strerror(errno));