[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_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;
+}
+