[mmc] Add Field Firmware Upgrade command

Add ffu command for updating eMMC firmware.
The kernel must have support for 2 new ioctls:
MMC_FFU_DOWNLOAD_OP and MMC_FFU_INSTALL_OP.

TEST=Upgrading Hynix eMMC on gnawty
BUG=None

Change-Id: I10b846a56f80b72775da94365c3269571dfa632f
Reviewed-on: https://chromium-review.googlesource.com/206888
Reviewed-by: Puthikorn Voravootivat <puthik@chromium.org>
Commit-Queue: Gwendal Grignou <gwendal@chromium.org>
Tested-by: Gwendal Grignou <gwendal@chromium.org>
diff --git a/mmc.c b/mmc.c
index a30426d..ffddb77 100644
--- a/mmc.c
+++ b/mmc.c
@@ -115,6 +115,11 @@
 		"Send Sanitize command to the <device>.\nThis will delete the unmapped memory region of the device.",
 	  NULL
 	},
+	{ do_emmc50_ffu, -2,
+	  "ffu", "<image name> <device>\n"
+		"run eMMC 5.0 Field firmware update.\n.",
+	  NULL
+	},
 	{ 0, 0, 0, 0 }
 };
 
diff --git a/mmc.h b/mmc.h
index 9871d62..b338e1d 100644
--- a/mmc.h
+++ b/mmc.h
@@ -64,6 +64,7 @@
 #define EXT_CSD_ENH_START_ADDR_2	138
 #define EXT_CSD_ENH_START_ADDR_1	137
 #define EXT_CSD_ENH_START_ADDR_0	136
+#define EXT_CSD_REV			192
 #define EXT_CSD_NATIVE_SECTOR_SIZE	63 /* R */
 #define EXT_CSD_USE_NATIVE_SECTOR	62 /* R/W */
 #define EXT_CSD_DATA_SECTOR_SIZE	61 /* R */
@@ -79,6 +80,9 @@
  */
 #define BKOPS_ENABLE	(1<<0)
 
+#define MMC_FFU_DOWNLOAD_OP	302
+#define MMC_FFU_INSTALL_OP	303
+
 /*
  * EXT_CSD field definitions
  */
diff --git a/mmc_cmds.c b/mmc_cmds.c
index 3fda502..1793da6 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -14,10 +14,12 @@
  * Boston, MA 021110-1307, USA.
  */
 
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/param.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <sys/stat.h>
@@ -31,6 +33,7 @@
 #include "mmc_cmds.h"
 
 #define EXT_CSD_SIZE	512
+#define FFU_PATH_SIZE (512 - 1)
 #define CID_SIZE 16
 
 
@@ -1315,3 +1318,69 @@
 
 }
 
+static int ffu_download_image(char *fname, int mmc_fd)
+{
+	struct mmc_ioc_cmd mmc_ioc_cmd;
+
+	/* prepare and send ioctl */
+	memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
+	mmc_ioc_cmd.opcode = MMC_FFU_DOWNLOAD_OP;
+	mmc_ioc_cmd.blksz = MIN(strlen(fname), FFU_PATH_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, fname);
+	return ioctl(mmc_fd, MMC_IOC_CMD, &mmc_ioc_cmd);
+}
+
+static int ffu_install(int mmc_fd)
+{
+	struct mmc_ioc_cmd mmc_ioc_cmd;
+
+	memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
+	mmc_ioc_cmd.opcode = MMC_FFU_INSTALL_OP;
+	mmc_ioc_cmd.blocks = 0;
+	mmc_ioc_cmd.arg = 0;
+	mmc_ioc_cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+	mmc_ioc_cmd.write_flag = 0;
+	return ioctl(mmc_fd, MMC_IOC_CMD, &mmc_ioc_cmd);
+}
+
+int do_emmc50_ffu (int nargs, char **argv)
+{
+	int fd, ret;
+	char *device;
+	char *path;
+
+	CHECK(nargs != 3, "Usage: ffu <image name> </path/to/mmcblkX> \n",
+		exit(1));
+
+	path = argv[1];
+	if (strlen(path) > FFU_PATH_SIZE) {
+		fprintf(stderr, "Filename \"%.20s\" too long\n", path);
+		exit(1);
+	}
+	device = argv[2];
+	fd = open(device, O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		exit(1);
+	}
+
+	ret = ffu_download_image(path, fd);
+	if (ret) {
+		fprintf(stderr, "FFU download failed : %s\n", strerror(errno));
+		exit(1);
+	}
+
+	ret = ffu_install(fd);
+	if (ret) {
+		fprintf(stderr, "FFU install failed : %s\n", strerror(errno));
+		exit(1);
+	}
+
+	close(fd);
+	return 0;
+}
+
diff --git a/mmc_cmds.h b/mmc_cmds.h
index f023fb0..4c7b6ed 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -29,3 +29,4 @@
 int do_status_get(int nargs, char **argv);
 int do_enh_area_set(int nargs, char **argv);
 int do_write_reliability_set(int nargs, char **argv);
+int do_emmc50_ffu(int nargs, char **argv);