diff mbox

[v2] mmc-utils: add eMMC 5.0 FFU support

Message ID 1390506264-17107-1-git-send-email-grundler@chromium.org (mailing list archive)
State New, archived
Headers show

Commit Message

Grant Grundler Jan. 23, 2014, 7:44 p.m. UTC
Field Firmware Update feature is new for 5.0 spec.
Code written per JESD84-B50.pdf spec available from:
    http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc

Change-Id: I0a22e9862876421630f53ac27fc0a161a9c70131
Signed-off-by: Grant Grundler <grundler@chromium.org>
---
V2:
Fix compiler borkage in use of strncmp().
Use '\0' instead of 0 in char assignment.

V1:
This patch needs to be reviewed and tested by _ANY_ eMMC 5.0 HW vendor.

If you represent an eMMC 5.0 HW vendor and expect your part will be used
in a ChromeOS device, make sure FFU works with mmc-utils.

mmc-utils is now shipping with ChromeOS (starting with R34 or R35 release):
    https://chromium-review.googlesource.com/#/c/182716/

Please also make sure your FFU implementation works reliably and
is robust (e.g. sign your binaries and check the signature).

Two general trends in eMMC are driving the requirement for FFU support:
1) longer support life time for devices (Chromebooks are > 5 years).
I.e. odds of requiring a bug fix later is higher.
I believe this trend applies to tablets and phones as well.

2) eMMC 5.0 devices are more complex --> testing will not find all the bugs.
e.g. TLC vs MLC vs SLC error handling _and_ implementations which support
a write cache, reliable write, and the plethora of other eMMC features.
More code means more interactions and more bugs. Fact of (SW) Life.
It's no longer a question of whether code will have bugs. It's a question
of how severe and can they be mitigated (FFU is just one way to mitigate).

I've written similar FFU patches for three different eMMC 4.5 products.
Some of those will get published and will conflict with this patch.
I'm happy resolve those conflicts and repost.
Please just make sure I'm CC'd.

I'm also willing to write additional mmc-utils FFU patches for any other
eMMC 4.5 vendor.  Please send technical details to my google.com email
and promise to test and publish the result.

Thanks for reading this far! :)


 Makefile         |   4 +-
 mmc.c            |  31 ++++++--
 mmc.h            |  29 ++++++-
 mmc_cmds-emmc5.c | 129 +++++++++++++++++++++++++++++++
 mmc_cmds.c       | 232 ++++++++++++++++++++++++++++++++++++++++++++-----------
 mmc_cmds.h       |   6 ++
 6 files changed, 378 insertions(+), 53 deletions(-)
 create mode 100644 mmc_cmds-emmc5.c

Comments

Chris Ball Jan. 29, 2014, 1:34 a.m. UTC | #1
Hi Grant, still one warning/error left:

On Thu, Jan 23 2014, Grant Grundler wrote:
> V2:
> Fix compiler borkage in use of strncmp().
> Use '\0' instead of 0 in char assignment.

? make
cc  -Wall -Werror -Wuninitialized -Wundef -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2 -g -O2 -Wp,-MMD,./.mmc_cmds.o.d,-MT,mmc_cmds.o -c mmc_cmds.c
mmc_cmds.c: In function ‘do_firmware_update’:
mmc_cmds.c:1290:4: error: format ‘%d’ expects argument of type ‘int’,
but argument 3 has type ‘size_t’ [-Werror=format=]
    fwsize, fwfilename, ret);
    ^
cc1: all warnings being treated as errors
Chris Ball Feb. 2, 2014, 5:52 p.m. UTC | #2
Hi Grant,

Still waiting on the remaining warning/error, and also noticed a
trivial typo:

On Thu, Jan 23 2014, Grant Grundler wrote:
> +	/* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.opcode = MMC_SWITCH;
> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
> +			(30 << 16) |	/* index */
> +			(1 << 8) |	/* value */
> +			EXT_CSD_CMD_SET_NORMAL;
> +	idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
> +		goto abort_update;
> +	}
> +
> +	/* 2) send CMD25 0x0000FFFF */

I think that first 2) should be a 1).  Thanks,

- Chris.
Grant Grundler Feb. 3, 2014, 5:21 p.m. UTC | #3
On Sun, Feb 2, 2014 at 9:52 AM, Chris Ball <chris@printf.net> wrote:
> Hi Grant,
>
> Still waiting on the remaining warning/error,

Sorry - was out thurs/fri. I'm wondering why I didn't see that warning before.

> and also noticed a trivial typo:

Typo fixed (locally).

I got good feedback from one of the HW vendors and I'm missing two
parts of the eMMC 5.0 spec:
1) send ext_cad[FFU_ARG] as the parameter to CMD25 (not 0x0000ffff)
2) follow the description of MODE_OPERATION_CODES instead of just sending CMD0.

I was also told sending CMD0 might confuse the kernel state. Do you
know if that's true?

What state does the kernel expect the device to be in after FFU?

cheers,
grant
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alex Lemberg Feb. 6, 2014, 11:57 a.m. UTC | #4
Hi Grant,

We have few comments on the code below

> +	fwsize = fwfilestat.st_size;
> +	if (fwsize > MMC_IOC_MAX_BYTES) {
> +		fprintf(stderr,"ERROR: %s is > %ld bytes long (max
> allowed)\n",
> +			fwfilename, MMC_IOC_MAX_BYTES);
> +		goto out_fw;
> +	}

By this solution, the FW file size is limited to MMC_IOC_MAX_BYTES (128KB???). Is there a guarantee that all vendors will have FW image file in size < MMC_IOC_MAX_BYTES?

> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
> +				char *emmc_fw, size_t fwsize)
> +{
> +	struct mmc_ioc_cmd idata;
> +	int retcode = 0;
> +	int ret;
> +
> +	/* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.opcode = MMC_SWITCH;
> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
> +			(30 << 16) |	/* index */
> +			(1 << 8) |	/* value */
> +			EXT_CSD_CMD_SET_NORMAL;
> +	idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
> +		goto abort_update;
> +	}
> +

Can we be certain that no NORMAL commands (not related to FFU) will be sent by the host, after switching to FFU mode?
Is it possible that MMC driver will send R/W requests in the middle (Steps #1 - #4)?

> +	/* 2) send CMD25 0x0000FFFF */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.write_flag = 1;
> +	idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this
> FW */
> +	idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
> +	idata.arg = 0x0000FFFF;
> +	idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW
> Update) %m\n");
> +		goto abort_update;
> +	}
> +
> +	/* 3) check FFU_STATUS[26] */
> +	ret = read_extcsd(fd, ext_csd);
> +	if (ret) {
> +		retcode = ret;
> +		fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
> +		goto abort_update;
> +	}
> +
> +	switch(ext_csd[26]) {
> +	case 0:  break;
> +	case 0x10:
> +		fprintf(stderr, "eMMC 5.0 FFU had general error and failed
> (EIO).\n");
> +		retcode = -EIO;
> +		break;
> +	case 0x11:
> +		fprintf(stderr, "eMMC 5.0 FFU did not complete
> (EAGAIN).\n");
> +		retcode = -EAGAIN;
> +		break;
> +	case 0x12:
> +		fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
> +			"mismatch or was interrupted (EINTR).\n");
> +		retcode = -EINTR;
> +		break;
> +	default:
> +		fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",
> ext_csd[26]);
> +		retcode = -EIO;
> +	}
> +
> +	/* 4) check
> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
> +	ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
> +		(ext_csd[304] << 16) | (ext_csd[305] << 24);
> +
> +	/* convert that gibberish to bytes */
> +	ret *= 512;
> +	if (ext_csd[61] == 1)
> +		ret *= 8;	/* 4K sectors */
> +
> +	if (ret != fwsize) {
> +		fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"
> +			" PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
> +	}
> +

By the spec, there are two ways to install new FW. The right way should be set according to MODE_OPERATION_CODES field of EXT_CSD.

> +abort_update:
> +	/* 4) send CMD0
> +	 * This should reset the device and force use of the new firmware.
> +	 */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.opcode = MMC_GO_IDLE_STATE;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
> +	}
> +
> +	return retcode;
> +}


Thanks,
Alex, Avi


> -----Original Message-----
> From: linux-mmc-owner@vger.kernel.org [mailto:linux-mmc-
> owner@vger.kernel.org] On Behalf Of Grant Grundler
> Sent: Thursday, January 23, 2014 9:44 PM
> To: Chris Ball
> Cc: linux-mmc; Puthikorn Voravootivat; Gwendal Grignou; Grant Grundler
> Subject: [PATCH v2] mmc-utils: add eMMC 5.0 FFU support
> 
> Field Firmware Update feature is new for 5.0 spec.
> Code written per JESD84-B50.pdf spec available from:
>     http://www.jedec.org/standards-documents/technology-focus-
> areas/flash-memory-ssds-ufs-emmc/e-mmc
> 
> Change-Id: I0a22e9862876421630f53ac27fc0a161a9c70131
> Signed-off-by: Grant Grundler <grundler@chromium.org>
> ---
> V2:
> Fix compiler borkage in use of strncmp().
> Use '\0' instead of 0 in char assignment.
> 
> V1:
> This patch needs to be reviewed and tested by _ANY_ eMMC 5.0 HW vendor.
> 
> If you represent an eMMC 5.0 HW vendor and expect your part will be used
> in a ChromeOS device, make sure FFU works with mmc-utils.
> 
> mmc-utils is now shipping with ChromeOS (starting with R34 or R35 release):
>     https://chromium-review.googlesource.com/#/c/182716/
> 
> Please also make sure your FFU implementation works reliably and is robust
> (e.g. sign your binaries and check the signature).
> 
> Two general trends in eMMC are driving the requirement for FFU support:
> 1) longer support life time for devices (Chromebooks are > 5 years).
> I.e. odds of requiring a bug fix later is higher.
> I believe this trend applies to tablets and phones as well.
> 
> 2) eMMC 5.0 devices are more complex --> testing will not find all the bugs.
> e.g. TLC vs MLC vs SLC error handling _and_ implementations which support
> a write cache, reliable write, and the plethora of other eMMC features.
> More code means more interactions and more bugs. Fact of (SW) Life.
> It's no longer a question of whether code will have bugs. It's a question of
> how severe and can they be mitigated (FFU is just one way to mitigate).
> 
> I've written similar FFU patches for three different eMMC 4.5 products.
> Some of those will get published and will conflict with this patch.
> I'm happy resolve those conflicts and repost.
> Please just make sure I'm CC'd.
> 
> I'm also willing to write additional mmc-utils FFU patches for any other
> eMMC 4.5 vendor.  Please send technical details to my google.com email
> and promise to test and publish the result.
> 
> Thanks for reading this far! :)
> 
> 
>  Makefile         |   4 +-
>  mmc.c            |  31 ++++++--
>  mmc.h            |  29 ++++++-
>  mmc_cmds-emmc5.c | 129 +++++++++++++++++++++++++++++++
>  mmc_cmds.c       | 232
> ++++++++++++++++++++++++++++++++++++++++++++-----------
>  mmc_cmds.h       |   6 ++
>  6 files changed, 378 insertions(+), 53 deletions(-)  create mode 100644
> mmc_cmds-emmc5.c
> 
> diff --git a/Makefile b/Makefile
> index 91cfc35..1cdd97f 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1,7 +1,7 @@
>  CC ?= gcc
>  AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2  CFLAGS ?=
> -g -O2 -objects = mmc.o mmc_cmds.o
> +objects = mmc.o mmc_cmds.o mmc_cmds-emmc5.o
> 
>  CHECKFLAGS = -Wall -Werror -Wuninitialized -Wundef
> 
> @@ -44,7 +44,7 @@ clean:
>  	$(MAKE) -C man clean
> 
>  install: $(progs) install-man
> -	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
> +	$(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
>  	$(INSTALL) $(progs) $(DESTDIR)$(bindir)
> 
>  .PHONY: all clean install manpages install-man diff --git a/mmc.c b/mmc.c
> index 926e92f..01a5074 100644
> --- a/mmc.c
> +++ b/mmc.c
> @@ -20,7 +20,10 @@
>  #include <stdio.h>
>  #include <stdlib.h>
>  #include <string.h>
> +#include <errno.h>
> +#include <linux/types.h>
> 
> +#include "mmc.h"
>  #include "mmc_cmds.h"
> 
>  #define MMC_VERSION	"0.1"
> @@ -37,9 +40,9 @@ struct Command {
>  				   if < 0, _minimum_ number of arguments */
>  	char	*verb;		/* verb */
>  	char	*help;		/* help lines; from the 2nd line onward they
> -                                   are automatically indented */
> -        char    *adv_help;      /* advanced help message; from the 2nd line
> -                                   onward they are automatically indented */
> +				   are automatically indented */
> +	char	*adv_help;	/* advanced help message; from the 2nd line
> +				   onward they are automatically indented */
> 
>  	/* the following fields are run-time filled by the program */
>  	char	**cmds;		/* array of subcommands */
> @@ -110,9 +113,24 @@ static struct Command commands[] = {
>  		"Send Sanitize command to the <device>.\nThis will delete
> the unmapped memory region of the device.",
>  	  NULL
>  	},
> +	{ do_firmware_update, -2,
> +	  "firmware upload", "<firmware_file> " "<device>\n"
> +		"Upload/Update firmware on <device> using
> <firmware_file>.\n",
> +	  NULL
> +	},
>  	{ 0, 0, 0, 0 }
>  };
> 
> +const char *manfid_lookup[0x100] = {
> +	[MANFID_PANASONIC] = "Panasonic",
> +	[MANFID_KINGSTON] = "Kingston",
> +	[MANFID_SANDISK] = "Sandisk",
> +	[MANFID_SAMSUNG] = "Samsung",
> +	[MANFID_TOSHIBA] = "Toshiba",
> +	[MANFID_SANDISK_SEM] = "Sandisk_SEM",
> +	[MANFID_KINGSTON_MMC] = "Kingston_MMC"
> +};
> +
>  static char *get_prgname(char *programname)  {
>  	char	*np;
> @@ -363,11 +381,10 @@ static int parse_args(int argc, char **argv,
>  			return -2;
>  	}
> 
> -        if (prepare_args( nargs_, args_, prgname, matchcmd )){
> -                fprintf(stderr, "ERROR: not enough memory\\n");
> +	if (prepare_args( nargs_, args_, prgname, matchcmd )){
> +		fprintf(stderr, "ERROR: not enough memory\\n");
>  		return -20;
> -        }
> -
> +	}
> 
>  	return 1;
>  }
> diff --git a/mmc.h b/mmc.h
> index 9871d62..61c0303 100644
> --- a/mmc.h
> +++ b/mmc.h
> @@ -24,12 +24,25 @@
>  #define MMC_BLOCK_MAJOR			179
> 
>  /* From kernel linux/mmc/mmc.h */
> +#define MMC_GO_IDLE_STATE	0	/* bc                               */
> +#define MMC_ALL_SEND_CID	2	/* bcr				R2
> */
>  #define MMC_SWITCH		6	/* ac	[31:0] See below	R1b
> */
> +#define MMC_SELECT_CARD		7	/* ac	[31:16] RCA
> 	R1  */
>  #define MMC_SEND_EXT_CSD	8	/* adtc				R1
> */
> -#define MMC_SEND_STATUS		13	/* ac   [31:16] RCA        R1  */
> +#define MMC_SEND_CID		10	/* ac   [31:16] RCA
> 	R2  */
> +#define MMC_STOP_TRANSMISSION	12	/* ac
> 	R1b */
> +#define MMC_SEND_STATUS		13	/* ac   [31:16] RCA
> 	R1  */
> +#define MMC_READ_SINGLE_BLOCK	17	/* adtc [31:0] data addr
> 	R1  */
> +#define MMC_READ_MULTIPLE_BLOCK	18	/* adtc [31:0] data
> addr	R1  */
> +#define MMC_WRITE_BLOCK		24	/* adtc [31:0] data addr
> 	R1  */
> +#define MMC_WRITE_MULTIPLE_BLOCK 25	/* adtc
> 	R1  */
> +
> +#define MMC_GEN_CMD		56	/* adtc [0] RD/WR
> 	R1  */
> +
>  #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
>  #define MMC_SWITCH_MODE_WRITE_BYTE	0x03	/* Set target to value
> */
> 
> +
>  /*
>   * EXT_CSD fields
>   */
> @@ -125,3 +138,17 @@
> 
>  #define MMC_RSP_R1
> 	(MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
>  #define MMC_RSP_R1B
> 	(MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_R
> SP_BUSY)
> +
> +
> +/* Manufacturer IDs as shown by /sys/block/mmcblk0/device/manfid */
> +#define MANFID_PANASONIC	0x000001
> +#define MANFID_KINGSTON		0x000002
> +#define MANFID_SANDISK		0x000003
> +#define MANFID_SAMSUNG		0x000015
> +#define MANFID_TOSHIBA		0x00001d
> +#define MANFID_SANDISK_SEM	0x000045	/* seen with
> "SEM16G" */
> +#define MANFID_KINGSTON_MMC	0x000070	/* Seen on
> embedded device */
> +
> +#if 0
> +const char *manfid_lookup[];
> +#endif
> diff --git a/mmc_cmds-emmc5.c b/mmc_cmds-emmc5.c new file mode
> 100644 index 0000000..43fe3f2
> --- /dev/null
> +++ b/mmc_cmds-emmc5.c
> @@ -0,0 +1,129 @@
> +/*
> + * eMMC 5.0+ Specific tools
> + *
> + * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
> + *
> + * 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.
> + */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>	/* for memset() */
> +#include <errno.h>
> +#include <linux/types.h>
> +#include <sys/ioctl.h>
> +#include <dirent.h>
> +#include <fcntl.h>
> +#include <libgen.h>
> +#include <limits.h>
> +#include <ctype.h>
> +
> +#include "mmc.h"
> +#include "mmc_cmds.h"
> +
> +
> +
> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
> +				char *emmc_fw, size_t fwsize)
> +{
> +	struct mmc_ioc_cmd idata;
> +	int retcode = 0;
> +	int ret;
> +
> +	/* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.opcode = MMC_SWITCH;
> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
> +			(30 << 16) |	/* index */
> +			(1 << 8) |	/* value */
> +			EXT_CSD_CMD_SET_NORMAL;
> +	idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
> +		goto abort_update;
> +	}
> +
> +	/* 2) send CMD25 0x0000FFFF */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.write_flag = 1;
> +	idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this
> FW */
> +	idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
> +	idata.arg = 0x0000FFFF;
> +	idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW
> Update) %m\n");
> +		goto abort_update;
> +	}
> +
> +	/* 3) check FFU_STATUS[26] */
> +	ret = read_extcsd(fd, ext_csd);
> +	if (ret) {
> +		retcode = ret;
> +		fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
> +		goto abort_update;
> +	}
> +
> +	switch(ext_csd[26]) {
> +	case 0:  break;
> +	case 0x10:
> +		fprintf(stderr, "eMMC 5.0 FFU had general error and failed
> (EIO).\n");
> +		retcode = -EIO;
> +		break;
> +	case 0x11:
> +		fprintf(stderr, "eMMC 5.0 FFU did not complete
> (EAGAIN).\n");
> +		retcode = -EAGAIN;
> +		break;
> +	case 0x12:
> +		fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
> +			"mismatch or was interrupted (EINTR).\n");
> +		retcode = -EINTR;
> +		break;
> +	default:
> +		fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",
> ext_csd[26]);
> +		retcode = -EIO;
> +	}
> +
> +	/* 4) check
> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
> +	ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
> +		(ext_csd[304] << 16) | (ext_csd[305] << 24);
> +
> +	/* convert that gibberish to bytes */
> +	ret *= 512;
> +	if (ext_csd[61] == 1)
> +		ret *= 8;	/* 4K sectors */
> +
> +	if (ret != fwsize) {
> +		fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"
> +			" PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
> +	}
> +
> +abort_update:
> +	/* 4) send CMD0
> +	 * This should reset the device and force use of the new firmware.
> +	 */
> +	memset(&idata, 0, sizeof(idata));
> +	idata.opcode = MMC_GO_IDLE_STATE;
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret) {
> +		retcode = ret;
> +		perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
> +	}
> +
> +	return retcode;
> +}
> diff --git a/mmc_cmds.c b/mmc_cmds.c
> index b8afa74..afbed77 100644
> --- a/mmc_cmds.c
> +++ b/mmc_cmds.c
> @@ -1,3 +1,4 @@
> +
>  /*
>   * This program is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU General Public @@ -17,9 +18,10
> @@  #include <stdio.h>  #include <stdlib.h>  #include <string.h> -#include
> <sys/ioctl.h> -#include <sys/types.h>
> +#include <errno.h>
>  #include <dirent.h>
> +#include <sys/ioctl.h>
> +#include <linux/types.h>
>  #include <sys/stat.h>
>  #include <unistd.h>
>  #include <fcntl.h>
> @@ -30,23 +32,27 @@
>  #include "mmc.h"
>  #include "mmc_cmds.h"
> 
> +#define EXT_CSD_SIZE	512
> +#define CID_SIZE 16
> +
> +
>  int read_extcsd(int fd, __u8 *ext_csd)
>  {
>  	int ret = 0;
>  	struct mmc_ioc_cmd idata;
>  	memset(&idata, 0, sizeof(idata));
> -	memset(ext_csd, 0, sizeof(__u8) * 512);
> +	memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);
>  	idata.write_flag = 0;
>  	idata.opcode = MMC_SEND_EXT_CSD;
>  	idata.arg = 0;
>  	idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
> -	idata.blksz = 512;
> +	idata.blksz = EXT_CSD_SIZE;
>  	idata.blocks = 1;
>  	mmc_ioc_cmd_set_data(idata, ext_csd);
> 
>  	ret = ioctl(fd, MMC_IOC_CMD, &idata);
>  	if (ret)
> -		perror("ioctl");
> +		perror("ioctl SEND_EXT_CSD");
> 
>  	return ret;
>  }
> @@ -67,7 +73,30 @@ int write_extcsd_value(int fd, __u8 index, __u8 value)
> 
>  	ret = ioctl(fd, MMC_IOC_CMD, &idata);
>  	if (ret)
> -		perror("ioctl");
> +		perror("ioctl Write EXT CSD");
> +
> +	return ret;
> +}
> +
> +int read_cid(int fd, __u8 *cid)
> +{
> +	int ret = 0;
> +	struct mmc_ioc_cmd idata;
> +
> +	memset(&idata, 0, sizeof(idata));
> +	memset(cid, 0, sizeof(__u8) * CID_SIZE);
> +
> +	idata.write_flag = 0;
> +	idata.opcode = MMC_SEND_CID;
> +	idata.arg    = 0;
> +	idata.flags  = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
> +	idata.blksz  = CID_SIZE;
> +	idata.blocks = 1;
> +	mmc_ioc_cmd_set_data(idata, cid);
> +
> +	ret = ioctl(fd, MMC_IOC_CMD, &idata);
> +	if (ret)
> +		perror("ioctl SEND_CID");
> 
>  	return ret;
>  }
> @@ -103,17 +132,11 @@ void print_writeprotect_status(__u8 *ext_csd)
> 
>  		reg = ext_csd[EXT_CSD_BOOT_WP];
>  		printf("Boot Area Write protection [BOOT_WP]: 0x%02x\n",
> reg);
> -		printf(" Power ro locking: ");
> -		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)
> -			printf("not possible\n");
> -		else
> -			printf("possible\n");
> +		printf(" Power ro locking: %spossible\n",
> +			(reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) ? "not
> " : "");
> 
> -		printf(" Permanent ro locking: ");
> -		if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)
> -			printf("not possible\n");
> -		else
> -			printf("possible\n");
> +		printf(" Permanent ro locking: %spossible\n",
> +			(reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS) ?
> "not " : "");
> 
>  		printf(" ro lock status: ");
>  		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN) @@ -127,7
> +150,7 @@ void print_writeprotect_status(__u8 *ext_csd)
> 
>  int do_writeprotect_get(int nargs, char **argv)  {
> -	__u8 ext_csd[512];
> +	__u8 ext_csd[EXT_CSD_SIZE];
>  	int fd, ret;
>  	char *device;
> 
> @@ -155,7 +178,7 @@ int do_writeprotect_get(int nargs, char **argv)
> 
>  int do_writeprotect_set(int nargs, char **argv)  {
> -	__u8 ext_csd[512], value;
> +	__u8 ext_csd[EXT_CSD_SIZE], value;
>  	int fd, ret;
>  	char *device;
> 
> @@ -191,7 +214,7 @@ int do_writeprotect_set(int nargs, char **argv)
> 
>  int do_disable_512B_emulation(int nargs, char **argv)  {
> -	__u8 ext_csd[512], native_sector_size, data_sector_size,
> wr_rel_param;
> +	__u8 ext_csd[EXT_CSD_SIZE], native_sector_size, data_sector_size,
> +wr_rel_param;
>  	int fd, ret;
>  	char *device;
> 
> @@ -301,7 +324,7 @@ int do_write_boot_en(int nargs, char **argv)
> 
>  int do_hwreset(int value, int nargs, char **argv)  {
> -	__u8 ext_csd[512];
> +	__u8 ext_csd[EXT_CSD_SIZE];
>  	int fd, ret;
>  	char *device;
> 
> @@ -360,7 +383,7 @@ int do_hwreset_dis(int nargs, char **argv)
> 
>  int do_write_bkops_en(int nargs, char **argv)  {
> -	__u8 ext_csd[512], value = 0;
> +	__u8 ext_csd[EXT_CSD_SIZE], value = 0;
>  	int fd, ret;
>  	char *device;
> 
> @@ -497,7 +520,7 @@ int set_partitioning_setting_completed(int dry_run,
> const char * const device,  int do_enh_area_set(int nargs, char **argv)  {
>  	__u8 value;
> -	__u8 ext_csd[512];
> +	__u8 ext_csd[EXT_CSD_SIZE];
>  	int fd, ret;
>  	char *device;
>  	int dry_run = 1;
> @@ -635,7 +658,7 @@ int do_enh_area_set(int nargs, char **argv)  int
> do_write_reliability_set(int nargs, char **argv)  {
>  	__u8 value;
> -	__u8 ext_csd[512];
> +	__u8 ext_csd[EXT_CSD_SIZE];
>  	int fd, ret;
> 
>  	int dry_run = 1;
> @@ -696,7 +719,7 @@ int do_write_reliability_set(int nargs, char **argv)
> 
>  int do_read_extcsd(int nargs, char **argv)  {
> -	__u8 ext_csd[512], ext_csd_rev, reg;
> +	__u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;
>  	__u32 regl;
>  	int fd, ret;
>  	char *device;
> @@ -722,24 +745,13 @@ int do_read_extcsd(int nargs, char **argv)
>  	ext_csd_rev = ext_csd[192];
> 
>  	switch (ext_csd_rev) {
> -	case 6:
> -		str = "4.5";
> -		break;
> -	case 5:
> -		str = "4.41";
> -		break;
> -	case 3:
> -		str = "4.3";
> -		break;
> -	case 2:
> -		str = "4.2";
> -		break;
> -	case 1:
> -		str = "4.1";
> -		break;
> -	case 0:
> -		str = "4.0";
> -		break;
> +	case 7: str = "5.0"; break;
> +	case 6: str = "4.5"; break;
> +	case 5: str = "4.41"; break;
> +	case 3: str = "4.3"; break;
> +	case 2: str = "4.2"; break;
> +	case 1: str = "4.1"; break;
> +	case 0: str = "4.0"; break;
>  	default:
>  		goto out_free;
>  	}
> @@ -1160,6 +1172,140 @@ int do_sanitize(int nargs, char **argv)
>  	}
> 
>  	return ret;
> -
>  }
> 
> +int do_firmware_update(int nargs, char **argv) {
> +	char *emmc_fw;
> +	char *device;
> +	char *fwfilename;
> +	struct stat fwfilestat;
> +	size_t fwsize;
> +	int ret = 0;
> +	int devfd,fwfd;
> +	__u8 ext_csd[EXT_CSD_SIZE];
> +	__u8 cid[CID_SIZE];
> +	__u8 ext_csd_rev;
> +
> +
> +	CHECK(nargs != 4, "Usage: mmc firmware update"
> +			" --I_want_to_destroy_my_drive"
> +			" </path/to/mmcblkX> </path/to/FW.bin>\n",
> +			  exit(1));
> +
> +
> +	/* Lesson from hdparm: user must be aware of the risks
> +	 * Key here is the additional command line flag be UNDOCUMENTED.
> +	 * User _must_ read and KNOW this is risky at runtime.
> +	 */
> +	if (strncmp(argv[4], "--I_want_to_destroy_my_drive", 28)) {
> +		fprintf(stderr,"ERROR: Please specify --
> I_want_to_destroy_my_drive"
> +		" as first parameter to firmware update.\n");
> +
> +		exit(1);
> +	}
> +
> +	emmc_fw = malloc(MMC_IOC_MAX_BYTES);
> +	if (!emmc_fw)
> +		return -ENOMEM;
> +
> +	device = argv[5];
> +
> +	devfd = open(device, O_RDWR);
> +	if (devfd < 0) {
> +		fprintf(stderr,"ERROR: open %s: %m\n", device); /* %m =
> errno */
> +		goto out_free;
> +	}
> +
> +	/* 1) read device version and attributes */
> +	ret = read_extcsd(devfd, ext_csd);
> +	if (ret) {
> +		fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",
> device);
> +		goto out_dev;
> +	}
> +
> +	ext_csd_rev = ext_csd[192];
> +
> +	if (ext_csd_rev < 7) {
> +		fprintf(stderr, "ERROR: Can not update firmware"
> +			": %s is pre-emmc 5.0 vintage\n", device);
> +		goto out_dev;
> +	}
> +
> +	printf("%s: FW is currently %8s
> (0x%02x%02x%02x%02x%02x%02x%02x%02x)\n",
> +		device, &(ext_csd[254]),
> +		ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
> +		ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
> +		);
> +
> +	/* 2) confirm SUPPORTED_MODES has FFU bit set */
> +	if (!(ext_csd[493] & 1)) {
> +		fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
> +			"but does not support FFU.\n", device);
> +		goto out_dev;
> +	}
> +
> +	/* 3) confirm FW updated is NOT disabled on this device */
> +	if (ext_csd[169] & 1) {
> +		fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
> +			"but FFU is disabled.\n", device);
> +		goto out_dev;
> +	}
> +
> +	/* 4) read the device manfid */
> +	ret = read_cid(devfd, cid);
> +	if (ret) {
> +		fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",
> device);
> +		goto out_dev;
> +	}
> +
> +	/* 5) Fetch the FW image */
> +	fwfilename = argv[2];
> +
> +	fwfd = open(fwfilename, O_RDONLY);
> +	if (fwfd < 0) {
> +		/* %m = errno */
> +		fprintf(stderr,"ERROR: open %s: %m", fwfilename);
> +		goto out_dev;
> +	}
> +
> +	ret = fstat(fwfd, &fwfilestat);
> +	if (ret) {
> +		/* %m = errno */
> +		fprintf(stderr,"ERROR: fstat %s: %m", fwfilename);
> +		goto out_fw;
> +	}
> +
> +	fwsize = fwfilestat.st_size;
> +	if (fwsize > MMC_IOC_MAX_BYTES) {
> +		fprintf(stderr,"ERROR: %s is > %ld bytes long (max
> allowed)\n",
> +			fwfilename, MMC_IOC_MAX_BYTES);
> +		goto out_fw;
> +	}
> +
> +	ret = read(fwfd, emmc_fw, fwsize);
> +	if (ret < fwsize) {
> +		fprintf(stderr,"ERROR: did not read all %d bytes of %s"
> +				"(%d bytes long)\n",
> +			fwsize, fwfilename, ret);
> +		goto out_fw;
> +	}
> +
> +	do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize);
> +
> +	cid[13] = '\0';	/* make sure string is NULL terminated */
> +
> +	printf("%s: FW updated to %8s
> (0x%2x%2x%2x%2x%2x%2x%2x%2x)\n",
> +		device, &(ext_csd[254]),
> +		ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
> +		ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
> +		);
> +
> +out_fw:
> +	close(fwfd);
> +out_dev:
> +	close(devfd);
> +out_free:
> +	free(emmc_fw);
> +	return ret;
> +}
> diff --git a/mmc_cmds.h b/mmc_cmds.h
> index f06cc10..549b851 100644
> --- a/mmc_cmds.h
> +++ b/mmc_cmds.h
> @@ -28,3 +28,9 @@ int do_sanitize(int nargs, char **argv);  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_firmware_update(int nargs, char **argv);
> +
> +int read_extcsd(int fd, __u8 *ext_csd); int write_extcsd_value(int fd,
> +__u8 index, __u8 value);
> +
> +int do_emmc5_fw_update(int devfd, __u8 *cid, __u8 *ext_csd, char
> +*emmc_fw, size_t fwsize);
> --
> 1.8.1.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the
> body of a message to majordomo@vger.kernel.org More majordomo info at
> http://vger.kernel.org/majordomo-info.html


--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Grant Grundler Feb. 6, 2014, 6:41 p.m. UTC | #5
On Thu, Feb 6, 2014 at 3:57 AM, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> Hi Grant,
>
> We have few comments on the code below
>
>> +     fwsize = fwfilestat.st_size;
>> +     if (fwsize > MMC_IOC_MAX_BYTES) {
>> +             fprintf(stderr,"ERROR: %s is > %ld bytes long (max
>> allowed)\n",
>> +                     fwfilename, MMC_IOC_MAX_BYTES);
>> +             goto out_fw;
>> +     }
>
> By this solution, the FW file size is limited to MMC_IOC_MAX_BYTES (128KB???). Is there a guarantee that all vendors will have FW image file in size < MMC_IOC_MAX_BYTES?

MMC_IOC_MAX_BYTES is something that existed in mmc-utils before and I
don't see any reference to a "max size" in the eMMC 5.0 spec. The eMMC
5.0 spec does provide an additional method to send multiple blocks
that I haven't implemented. So I don't believe there is any particular
size limit for firmware (longer term - mmc-utils needs more work to
support > MMC_IOC_MAX_BYTES).


>> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
>> +                             char *emmc_fw, size_t fwsize)
>> +{
>> +     struct mmc_ioc_cmd idata;
>> +     int retcode = 0;
>> +     int ret;
>> +
>> +     /* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.opcode = MMC_SWITCH;
>> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
>> +                     (30 << 16) |    /* index */
>> +                     (1 << 8) |      /* value */
>> +                     EXT_CSD_CMD_SET_NORMAL;
>> +     idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
>> +             goto abort_update;
>> +     }
>> +
>
> Can we be certain that no NORMAL commands (not related to FFU) will be sent by the host, after switching to FFU mode?

Yes and No :)

Yes: get exclusive access to the device - e.g . boot from other media
and then perform the update.

No: If the FFU is targeting the boot device, other commands can be
sent when in FFU mode and FFU should gracefully fail according my
(novice) reading of the eMMC 5.0 spec. I'm really not sure what can go
wrong here  But early in the system boot (e.g. init hasn't gone
multiple user yet) we can cache the FW image and be reasonably sure no
other IOs will be issued during the FFU.

My point is there are a range of opportunities to mitigate this risk
depending on how much trouble you want to go through WITHOUT making
any additional changes to the tool or kernel.

> Is it possible that MMC driver will send R/W requests in the middle (Steps #1 - #4)?

Yes - as outlined above. This can certainly not a perfect solution -
it's just the first one.

Long term, to make FFU more robust and secure on a "live" (ie boot)
device, Gwendal Grignou and Kees Cook (co-workers) would like to
implement an "in kernel" hook to guarantee the FW image is an "atomic
operation" (not interrupted by other IO) and the firmware image is
pulled from a "well known" location (e.g. /lib/firmware). On a
chromebook, that means the firmware is part of a signed OS image
(using dm-verity).

>
>> +     /* 2) send CMD25 0x0000FFFF */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.write_flag = 1;
>> +     idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this
>> FW */
>> +     idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
>> +     idata.arg = 0x0000FFFF;
>> +     idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW
>> Update) %m\n");
>> +             goto abort_update;
>> +     }
>> +
>> +     /* 3) check FFU_STATUS[26] */
>> +     ret = read_extcsd(fd, ext_csd);
>> +     if (ret) {
>> +             retcode = ret;
>> +             fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
>> +             goto abort_update;
>> +     }
>> +
>> +     switch(ext_csd[26]) {
>> +     case 0:  break;
>> +     case 0x10:
>> +             fprintf(stderr, "eMMC 5.0 FFU had general error and failed
>> (EIO).\n");
>> +             retcode = -EIO;
>> +             break;
>> +     case 0x11:
>> +             fprintf(stderr, "eMMC 5.0 FFU did not complete
>> (EAGAIN).\n");
>> +             retcode = -EAGAIN;
>> +             break;
>> +     case 0x12:
>> +             fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
>> +                     "mismatch or was interrupted (EINTR).\n");
>> +             retcode = -EINTR;
>> +             break;
>> +     default:
>> +             fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",
>> ext_csd[26]);
>> +             retcode = -EIO;
>> +     }
>> +
>> +     /* 4) check
>> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
>> +     ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
>> +             (ext_csd[304] << 16) | (ext_csd[305] << 24);
>> +
>> +     /* convert that gibberish to bytes */
>> +     ret *= 512;
>> +     if (ext_csd[61] == 1)
>> +             ret *= 8;       /* 4K sectors */
>> +
>> +     if (ret != fwsize) {
>> +             fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"
>> +                     " PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
>> +     }
>> +
>
> By the spec, there are two ways to install new FW. The right way should be set according to MODE_OPERATION_CODES field of EXT_CSD.

I'm reworking this code now based on similar feedback. If I still
don't get this right, please please please send a patch to make it
work right.  I would in no way be offended by anyone fixing up code
I've got wrong.

>
>> +abort_update:
>> +     /* 4) send CMD0
>> +      * This should reset the device and force use of the new firmware.
>> +      */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.opcode = MMC_GO_IDLE_STATE;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
>> +     }
>> +
>> +     return retcode;
>> +}
>
>
> Thanks,
> Alex, Avi

Thanks Alex, Avi! I appreciate the review.

cheers,
grant

>
>
>> -----Original Message-----
>> From: linux-mmc-owner@vger.kernel.org [mailto:linux-mmc-
>> owner@vger.kernel.org] On Behalf Of Grant Grundler
>> Sent: Thursday, January 23, 2014 9:44 PM
>> To: Chris Ball
>> Cc: linux-mmc; Puthikorn Voravootivat; Gwendal Grignou; Grant Grundler
>> Subject: [PATCH v2] mmc-utils: add eMMC 5.0 FFU support
>>
>> Field Firmware Update feature is new for 5.0 spec.
>> Code written per JESD84-B50.pdf spec available from:
>>     http://www.jedec.org/standards-documents/technology-focus-
>> areas/flash-memory-ssds-ufs-emmc/e-mmc
>>
>> Change-Id: I0a22e9862876421630f53ac27fc0a161a9c70131
>> Signed-off-by: Grant Grundler <grundler@chromium.org>
>> ---
>> V2:
>> Fix compiler borkage in use of strncmp().
>> Use '\0' instead of 0 in char assignment.
>>
>> V1:
>> This patch needs to be reviewed and tested by _ANY_ eMMC 5.0 HW vendor.
>>
>> If you represent an eMMC 5.0 HW vendor and expect your part will be used
>> in a ChromeOS device, make sure FFU works with mmc-utils.
>>
>> mmc-utils is now shipping with ChromeOS (starting with R34 or R35 release):
>>     https://chromium-review.googlesource.com/#/c/182716/
>>
>> Please also make sure your FFU implementation works reliably and is robust
>> (e.g. sign your binaries and check the signature).
>>
>> Two general trends in eMMC are driving the requirement for FFU support:
>> 1) longer support life time for devices (Chromebooks are > 5 years).
>> I.e. odds of requiring a bug fix later is higher.
>> I believe this trend applies to tablets and phones as well.
>>
>> 2) eMMC 5.0 devices are more complex --> testing will not find all the bugs.
>> e.g. TLC vs MLC vs SLC error handling _and_ implementations which support
>> a write cache, reliable write, and the plethora of other eMMC features.
>> More code means more interactions and more bugs. Fact of (SW) Life.
>> It's no longer a question of whether code will have bugs. It's a question of
>> how severe and can they be mitigated (FFU is just one way to mitigate).
>>
>> I've written similar FFU patches for three different eMMC 4.5 products.
>> Some of those will get published and will conflict with this patch.
>> I'm happy resolve those conflicts and repost.
>> Please just make sure I'm CC'd.
>>
>> I'm also willing to write additional mmc-utils FFU patches for any other
>> eMMC 4.5 vendor.  Please send technical details to my google.com email
>> and promise to test and publish the result.
>>
>> Thanks for reading this far! :)
>>
>>
>>  Makefile         |   4 +-
>>  mmc.c            |  31 ++++++--
>>  mmc.h            |  29 ++++++-
>>  mmc_cmds-emmc5.c | 129 +++++++++++++++++++++++++++++++
>>  mmc_cmds.c       | 232
>> ++++++++++++++++++++++++++++++++++++++++++++-----------
>>  mmc_cmds.h       |   6 ++
>>  6 files changed, 378 insertions(+), 53 deletions(-)  create mode 100644
>> mmc_cmds-emmc5.c
>>
>> diff --git a/Makefile b/Makefile
>> index 91cfc35..1cdd97f 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -1,7 +1,7 @@
>>  CC ?= gcc
>>  AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2  CFLAGS ?=
>> -g -O2 -objects = mmc.o mmc_cmds.o
>> +objects = mmc.o mmc_cmds.o mmc_cmds-emmc5.o
>>
>>  CHECKFLAGS = -Wall -Werror -Wuninitialized -Wundef
>>
>> @@ -44,7 +44,7 @@ clean:
>>       $(MAKE) -C man clean
>>
>>  install: $(progs) install-man
>> -     $(INSTALL) -m755 -d $(DESTDIR)$(bindir)
>> +     $(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
>>       $(INSTALL) $(progs) $(DESTDIR)$(bindir)
>>
>>  .PHONY: all clean install manpages install-man diff --git a/mmc.c b/mmc.c
>> index 926e92f..01a5074 100644
>> --- a/mmc.c
>> +++ b/mmc.c
>> @@ -20,7 +20,10 @@
>>  #include <stdio.h>
>>  #include <stdlib.h>
>>  #include <string.h>
>> +#include <errno.h>
>> +#include <linux/types.h>
>>
>> +#include "mmc.h"
>>  #include "mmc_cmds.h"
>>
>>  #define MMC_VERSION  "0.1"
>> @@ -37,9 +40,9 @@ struct Command {
>>                                  if < 0, _minimum_ number of arguments */
>>       char    *verb;          /* verb */
>>       char    *help;          /* help lines; from the 2nd line onward they
>> -                                   are automatically indented */
>> -        char    *adv_help;      /* advanced help message; from the 2nd line
>> -                                   onward they are automatically indented */
>> +                                are automatically indented */
>> +     char    *adv_help;      /* advanced help message; from the 2nd line
>> +                                onward they are automatically indented */
>>
>>       /* the following fields are run-time filled by the program */
>>       char    **cmds;         /* array of subcommands */
>> @@ -110,9 +113,24 @@ static struct Command commands[] = {
>>               "Send Sanitize command to the <device>.\nThis will delete
>> the unmapped memory region of the device.",
>>         NULL
>>       },
>> +     { do_firmware_update, -2,
>> +       "firmware upload", "<firmware_file> " "<device>\n"
>> +             "Upload/Update firmware on <device> using
>> <firmware_file>.\n",
>> +       NULL
>> +     },
>>       { 0, 0, 0, 0 }
>>  };
>>
>> +const char *manfid_lookup[0x100] = {
>> +     [MANFID_PANASONIC] = "Panasonic",
>> +     [MANFID_KINGSTON] = "Kingston",
>> +     [MANFID_SANDISK] = "Sandisk",
>> +     [MANFID_SAMSUNG] = "Samsung",
>> +     [MANFID_TOSHIBA] = "Toshiba",
>> +     [MANFID_SANDISK_SEM] = "Sandisk_SEM",
>> +     [MANFID_KINGSTON_MMC] = "Kingston_MMC"
>> +};
>> +
>>  static char *get_prgname(char *programname)  {
>>       char    *np;
>> @@ -363,11 +381,10 @@ static int parse_args(int argc, char **argv,
>>                       return -2;
>>       }
>>
>> -        if (prepare_args( nargs_, args_, prgname, matchcmd )){
>> -                fprintf(stderr, "ERROR: not enough memory\\n");
>> +     if (prepare_args( nargs_, args_, prgname, matchcmd )){
>> +             fprintf(stderr, "ERROR: not enough memory\\n");
>>               return -20;
>> -        }
>> -
>> +     }
>>
>>       return 1;
>>  }
>> diff --git a/mmc.h b/mmc.h
>> index 9871d62..61c0303 100644
>> --- a/mmc.h
>> +++ b/mmc.h
>> @@ -24,12 +24,25 @@
>>  #define MMC_BLOCK_MAJOR                      179
>>
>>  /* From kernel linux/mmc/mmc.h */
>> +#define MMC_GO_IDLE_STATE    0       /* bc                               */
>> +#define MMC_ALL_SEND_CID     2       /* bcr                          R2
>> */
>>  #define MMC_SWITCH           6       /* ac   [31:0] See below        R1b
>> */
>> +#define MMC_SELECT_CARD              7       /* ac   [31:16] RCA
>>       R1  */
>>  #define MMC_SEND_EXT_CSD     8       /* adtc                         R1
>> */
>> -#define MMC_SEND_STATUS              13      /* ac   [31:16] RCA        R1  */
>> +#define MMC_SEND_CID         10      /* ac   [31:16] RCA
>>       R2  */
>> +#define MMC_STOP_TRANSMISSION        12      /* ac
>>       R1b */
>> +#define MMC_SEND_STATUS              13      /* ac   [31:16] RCA
>>       R1  */
>> +#define MMC_READ_SINGLE_BLOCK        17      /* adtc [31:0] data addr
>>       R1  */
>> +#define MMC_READ_MULTIPLE_BLOCK      18      /* adtc [31:0] data
>> addr  R1  */
>> +#define MMC_WRITE_BLOCK              24      /* adtc [31:0] data addr
>>       R1  */
>> +#define MMC_WRITE_MULTIPLE_BLOCK 25  /* adtc
>>       R1  */
>> +
>> +#define MMC_GEN_CMD          56      /* adtc [0] RD/WR
>>       R1  */
>> +
>>  #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
>>  #define MMC_SWITCH_MODE_WRITE_BYTE   0x03    /* Set target to value
>> */
>>
>> +
>>  /*
>>   * EXT_CSD fields
>>   */
>> @@ -125,3 +138,17 @@
>>
>>  #define MMC_RSP_R1
>>       (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
>>  #define MMC_RSP_R1B
>>       (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_R
>> SP_BUSY)
>> +
>> +
>> +/* Manufacturer IDs as shown by /sys/block/mmcblk0/device/manfid */
>> +#define MANFID_PANASONIC     0x000001
>> +#define MANFID_KINGSTON              0x000002
>> +#define MANFID_SANDISK               0x000003
>> +#define MANFID_SAMSUNG               0x000015
>> +#define MANFID_TOSHIBA               0x00001d
>> +#define MANFID_SANDISK_SEM   0x000045        /* seen with
>> "SEM16G" */
>> +#define MANFID_KINGSTON_MMC  0x000070        /* Seen on
>> embedded device */
>> +
>> +#if 0
>> +const char *manfid_lookup[];
>> +#endif
>> diff --git a/mmc_cmds-emmc5.c b/mmc_cmds-emmc5.c new file mode
>> 100644 index 0000000..43fe3f2
>> --- /dev/null
>> +++ b/mmc_cmds-emmc5.c
>> @@ -0,0 +1,129 @@
>> +/*
>> + * eMMC 5.0+ Specific tools
>> + *
>> + * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
>> + *
>> + * 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.
>> + */
>> +
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>  /* for memset() */
>> +#include <errno.h>
>> +#include <linux/types.h>
>> +#include <sys/ioctl.h>
>> +#include <dirent.h>
>> +#include <fcntl.h>
>> +#include <libgen.h>
>> +#include <limits.h>
>> +#include <ctype.h>
>> +
>> +#include "mmc.h"
>> +#include "mmc_cmds.h"
>> +
>> +
>> +
>> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
>> +                             char *emmc_fw, size_t fwsize)
>> +{
>> +     struct mmc_ioc_cmd idata;
>> +     int retcode = 0;
>> +     int ret;
>> +
>> +     /* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.opcode = MMC_SWITCH;
>> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
>> +                     (30 << 16) |    /* index */
>> +                     (1 << 8) |      /* value */
>> +                     EXT_CSD_CMD_SET_NORMAL;
>> +     idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
>> +             goto abort_update;
>> +     }
>> +
>> +     /* 2) send CMD25 0x0000FFFF */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.write_flag = 1;
>> +     idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this
>> FW */
>> +     idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
>> +     idata.arg = 0x0000FFFF;
>> +     idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW
>> Update) %m\n");
>> +             goto abort_update;
>> +     }
>> +
>> +     /* 3) check FFU_STATUS[26] */
>> +     ret = read_extcsd(fd, ext_csd);
>> +     if (ret) {
>> +             retcode = ret;
>> +             fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
>> +             goto abort_update;
>> +     }
>> +
>> +     switch(ext_csd[26]) {
>> +     case 0:  break;
>> +     case 0x10:
>> +             fprintf(stderr, "eMMC 5.0 FFU had general error and failed
>> (EIO).\n");
>> +             retcode = -EIO;
>> +             break;
>> +     case 0x11:
>> +             fprintf(stderr, "eMMC 5.0 FFU did not complete
>> (EAGAIN).\n");
>> +             retcode = -EAGAIN;
>> +             break;
>> +     case 0x12:
>> +             fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
>> +                     "mismatch or was interrupted (EINTR).\n");
>> +             retcode = -EINTR;
>> +             break;
>> +     default:
>> +             fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",
>> ext_csd[26]);
>> +             retcode = -EIO;
>> +     }
>> +
>> +     /* 4) check
>> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
>> +     ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
>> +             (ext_csd[304] << 16) | (ext_csd[305] << 24);
>> +
>> +     /* convert that gibberish to bytes */
>> +     ret *= 512;
>> +     if (ext_csd[61] == 1)
>> +             ret *= 8;       /* 4K sectors */
>> +
>> +     if (ret != fwsize) {
>> +             fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"
>> +                     " PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
>> +     }
>> +
>> +abort_update:
>> +     /* 4) send CMD0
>> +      * This should reset the device and force use of the new firmware.
>> +      */
>> +     memset(&idata, 0, sizeof(idata));
>> +     idata.opcode = MMC_GO_IDLE_STATE;
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret) {
>> +             retcode = ret;
>> +             perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
>> +     }
>> +
>> +     return retcode;
>> +}
>> diff --git a/mmc_cmds.c b/mmc_cmds.c
>> index b8afa74..afbed77 100644
>> --- a/mmc_cmds.c
>> +++ b/mmc_cmds.c
>> @@ -1,3 +1,4 @@
>> +
>>  /*
>>   * This program is free software; you can redistribute it and/or
>>   * modify it under the terms of the GNU General Public @@ -17,9 +18,10
>> @@  #include <stdio.h>  #include <stdlib.h>  #include <string.h> -#include
>> <sys/ioctl.h> -#include <sys/types.h>
>> +#include <errno.h>
>>  #include <dirent.h>
>> +#include <sys/ioctl.h>
>> +#include <linux/types.h>
>>  #include <sys/stat.h>
>>  #include <unistd.h>
>>  #include <fcntl.h>
>> @@ -30,23 +32,27 @@
>>  #include "mmc.h"
>>  #include "mmc_cmds.h"
>>
>> +#define EXT_CSD_SIZE 512
>> +#define CID_SIZE 16
>> +
>> +
>>  int read_extcsd(int fd, __u8 *ext_csd)
>>  {
>>       int ret = 0;
>>       struct mmc_ioc_cmd idata;
>>       memset(&idata, 0, sizeof(idata));
>> -     memset(ext_csd, 0, sizeof(__u8) * 512);
>> +     memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);
>>       idata.write_flag = 0;
>>       idata.opcode = MMC_SEND_EXT_CSD;
>>       idata.arg = 0;
>>       idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
>> -     idata.blksz = 512;
>> +     idata.blksz = EXT_CSD_SIZE;
>>       idata.blocks = 1;
>>       mmc_ioc_cmd_set_data(idata, ext_csd);
>>
>>       ret = ioctl(fd, MMC_IOC_CMD, &idata);
>>       if (ret)
>> -             perror("ioctl");
>> +             perror("ioctl SEND_EXT_CSD");
>>
>>       return ret;
>>  }
>> @@ -67,7 +73,30 @@ int write_extcsd_value(int fd, __u8 index, __u8 value)
>>
>>       ret = ioctl(fd, MMC_IOC_CMD, &idata);
>>       if (ret)
>> -             perror("ioctl");
>> +             perror("ioctl Write EXT CSD");
>> +
>> +     return ret;
>> +}
>> +
>> +int read_cid(int fd, __u8 *cid)
>> +{
>> +     int ret = 0;
>> +     struct mmc_ioc_cmd idata;
>> +
>> +     memset(&idata, 0, sizeof(idata));
>> +     memset(cid, 0, sizeof(__u8) * CID_SIZE);
>> +
>> +     idata.write_flag = 0;
>> +     idata.opcode = MMC_SEND_CID;
>> +     idata.arg    = 0;
>> +     idata.flags  = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
>> +     idata.blksz  = CID_SIZE;
>> +     idata.blocks = 1;
>> +     mmc_ioc_cmd_set_data(idata, cid);
>> +
>> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);
>> +     if (ret)
>> +             perror("ioctl SEND_CID");
>>
>>       return ret;
>>  }
>> @@ -103,17 +132,11 @@ void print_writeprotect_status(__u8 *ext_csd)
>>
>>               reg = ext_csd[EXT_CSD_BOOT_WP];
>>               printf("Boot Area Write protection [BOOT_WP]: 0x%02x\n",
>> reg);
>> -             printf(" Power ro locking: ");
>> -             if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)
>> -                     printf("not possible\n");
>> -             else
>> -                     printf("possible\n");
>> +             printf(" Power ro locking: %spossible\n",
>> +                     (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) ? "not
>> " : "");
>>
>> -             printf(" Permanent ro locking: ");
>> -             if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)
>> -                     printf("not possible\n");
>> -             else
>> -                     printf("possible\n");
>> +             printf(" Permanent ro locking: %spossible\n",
>> +                     (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS) ?
>> "not " : "");
>>
>>               printf(" ro lock status: ");
>>               if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN) @@ -127,7
>> +150,7 @@ void print_writeprotect_status(__u8 *ext_csd)
>>
>>  int do_writeprotect_get(int nargs, char **argv)  {
>> -     __u8 ext_csd[512];
>> +     __u8 ext_csd[EXT_CSD_SIZE];
>>       int fd, ret;
>>       char *device;
>>
>> @@ -155,7 +178,7 @@ int do_writeprotect_get(int nargs, char **argv)
>>
>>  int do_writeprotect_set(int nargs, char **argv)  {
>> -     __u8 ext_csd[512], value;
>> +     __u8 ext_csd[EXT_CSD_SIZE], value;
>>       int fd, ret;
>>       char *device;
>>
>> @@ -191,7 +214,7 @@ int do_writeprotect_set(int nargs, char **argv)
>>
>>  int do_disable_512B_emulation(int nargs, char **argv)  {
>> -     __u8 ext_csd[512], native_sector_size, data_sector_size,
>> wr_rel_param;
>> +     __u8 ext_csd[EXT_CSD_SIZE], native_sector_size, data_sector_size,
>> +wr_rel_param;
>>       int fd, ret;
>>       char *device;
>>
>> @@ -301,7 +324,7 @@ int do_write_boot_en(int nargs, char **argv)
>>
>>  int do_hwreset(int value, int nargs, char **argv)  {
>> -     __u8 ext_csd[512];
>> +     __u8 ext_csd[EXT_CSD_SIZE];
>>       int fd, ret;
>>       char *device;
>>
>> @@ -360,7 +383,7 @@ int do_hwreset_dis(int nargs, char **argv)
>>
>>  int do_write_bkops_en(int nargs, char **argv)  {
>> -     __u8 ext_csd[512], value = 0;
>> +     __u8 ext_csd[EXT_CSD_SIZE], value = 0;
>>       int fd, ret;
>>       char *device;
>>
>> @@ -497,7 +520,7 @@ int set_partitioning_setting_completed(int dry_run,
>> const char * const device,  int do_enh_area_set(int nargs, char **argv)  {
>>       __u8 value;
>> -     __u8 ext_csd[512];
>> +     __u8 ext_csd[EXT_CSD_SIZE];
>>       int fd, ret;
>>       char *device;
>>       int dry_run = 1;
>> @@ -635,7 +658,7 @@ int do_enh_area_set(int nargs, char **argv)  int
>> do_write_reliability_set(int nargs, char **argv)  {
>>       __u8 value;
>> -     __u8 ext_csd[512];
>> +     __u8 ext_csd[EXT_CSD_SIZE];
>>       int fd, ret;
>>
>>       int dry_run = 1;
>> @@ -696,7 +719,7 @@ int do_write_reliability_set(int nargs, char **argv)
>>
>>  int do_read_extcsd(int nargs, char **argv)  {
>> -     __u8 ext_csd[512], ext_csd_rev, reg;
>> +     __u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;
>>       __u32 regl;
>>       int fd, ret;
>>       char *device;
>> @@ -722,24 +745,13 @@ int do_read_extcsd(int nargs, char **argv)
>>       ext_csd_rev = ext_csd[192];
>>
>>       switch (ext_csd_rev) {
>> -     case 6:
>> -             str = "4.5";
>> -             break;
>> -     case 5:
>> -             str = "4.41";
>> -             break;
>> -     case 3:
>> -             str = "4.3";
>> -             break;
>> -     case 2:
>> -             str = "4.2";
>> -             break;
>> -     case 1:
>> -             str = "4.1";
>> -             break;
>> -     case 0:
>> -             str = "4.0";
>> -             break;
>> +     case 7: str = "5.0"; break;
>> +     case 6: str = "4.5"; break;
>> +     case 5: str = "4.41"; break;
>> +     case 3: str = "4.3"; break;
>> +     case 2: str = "4.2"; break;
>> +     case 1: str = "4.1"; break;
>> +     case 0: str = "4.0"; break;
>>       default:
>>               goto out_free;
>>       }
>> @@ -1160,6 +1172,140 @@ int do_sanitize(int nargs, char **argv)
>>       }
>>
>>       return ret;
>> -
>>  }
>>
>> +int do_firmware_update(int nargs, char **argv) {
>> +     char *emmc_fw;
>> +     char *device;
>> +     char *fwfilename;
>> +     struct stat fwfilestat;
>> +     size_t fwsize;
>> +     int ret = 0;
>> +     int devfd,fwfd;
>> +     __u8 ext_csd[EXT_CSD_SIZE];
>> +     __u8 cid[CID_SIZE];
>> +     __u8 ext_csd_rev;
>> +
>> +
>> +     CHECK(nargs != 4, "Usage: mmc firmware update"
>> +                     " --I_want_to_destroy_my_drive"
>> +                     " </path/to/mmcblkX> </path/to/FW.bin>\n",
>> +                       exit(1));
>> +
>> +
>> +     /* Lesson from hdparm: user must be aware of the risks
>> +      * Key here is the additional command line flag be UNDOCUMENTED.
>> +      * User _must_ read and KNOW this is risky at runtime.
>> +      */
>> +     if (strncmp(argv[4], "--I_want_to_destroy_my_drive", 28)) {
>> +             fprintf(stderr,"ERROR: Please specify --
>> I_want_to_destroy_my_drive"
>> +             " as first parameter to firmware update.\n");
>> +
>> +             exit(1);
>> +     }
>> +
>> +     emmc_fw = malloc(MMC_IOC_MAX_BYTES);
>> +     if (!emmc_fw)
>> +             return -ENOMEM;
>> +
>> +     device = argv[5];
>> +
>> +     devfd = open(device, O_RDWR);
>> +     if (devfd < 0) {
>> +             fprintf(stderr,"ERROR: open %s: %m\n", device); /* %m =
>> errno */
>> +             goto out_free;
>> +     }
>> +
>> +     /* 1) read device version and attributes */
>> +     ret = read_extcsd(devfd, ext_csd);
>> +     if (ret) {
>> +             fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",
>> device);
>> +             goto out_dev;
>> +     }
>> +
>> +     ext_csd_rev = ext_csd[192];
>> +
>> +     if (ext_csd_rev < 7) {
>> +             fprintf(stderr, "ERROR: Can not update firmware"
>> +                     ": %s is pre-emmc 5.0 vintage\n", device);
>> +             goto out_dev;
>> +     }
>> +
>> +     printf("%s: FW is currently %8s
>> (0x%02x%02x%02x%02x%02x%02x%02x%02x)\n",
>> +             device, &(ext_csd[254]),
>> +             ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
>> +             ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
>> +             );
>> +
>> +     /* 2) confirm SUPPORTED_MODES has FFU bit set */
>> +     if (!(ext_csd[493] & 1)) {
>> +             fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
>> +                     "but does not support FFU.\n", device);
>> +             goto out_dev;
>> +     }
>> +
>> +     /* 3) confirm FW updated is NOT disabled on this device */
>> +     if (ext_csd[169] & 1) {
>> +             fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
>> +                     "but FFU is disabled.\n", device);
>> +             goto out_dev;
>> +     }
>> +
>> +     /* 4) read the device manfid */
>> +     ret = read_cid(devfd, cid);
>> +     if (ret) {
>> +             fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",
>> device);
>> +             goto out_dev;
>> +     }
>> +
>> +     /* 5) Fetch the FW image */
>> +     fwfilename = argv[2];
>> +
>> +     fwfd = open(fwfilename, O_RDONLY);
>> +     if (fwfd < 0) {
>> +             /* %m = errno */
>> +             fprintf(stderr,"ERROR: open %s: %m", fwfilename);
>> +             goto out_dev;
>> +     }
>> +
>> +     ret = fstat(fwfd, &fwfilestat);
>> +     if (ret) {
>> +             /* %m = errno */
>> +             fprintf(stderr,"ERROR: fstat %s: %m", fwfilename);
>> +             goto out_fw;
>> +     }
>> +
>> +     fwsize = fwfilestat.st_size;
>> +     if (fwsize > MMC_IOC_MAX_BYTES) {
>> +             fprintf(stderr,"ERROR: %s is > %ld bytes long (max
>> allowed)\n",
>> +                     fwfilename, MMC_IOC_MAX_BYTES);
>> +             goto out_fw;
>> +     }
>> +
>> +     ret = read(fwfd, emmc_fw, fwsize);
>> +     if (ret < fwsize) {
>> +             fprintf(stderr,"ERROR: did not read all %d bytes of %s"
>> +                             "(%d bytes long)\n",
>> +                     fwsize, fwfilename, ret);
>> +             goto out_fw;
>> +     }
>> +
>> +     do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize);
>> +
>> +     cid[13] = '\0'; /* make sure string is NULL terminated */
>> +
>> +     printf("%s: FW updated to %8s
>> (0x%2x%2x%2x%2x%2x%2x%2x%2x)\n",
>> +             device, &(ext_csd[254]),
>> +             ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
>> +             ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
>> +             );
>> +
>> +out_fw:
>> +     close(fwfd);
>> +out_dev:
>> +     close(devfd);
>> +out_free:
>> +     free(emmc_fw);
>> +     return ret;
>> +}
>> diff --git a/mmc_cmds.h b/mmc_cmds.h
>> index f06cc10..549b851 100644
>> --- a/mmc_cmds.h
>> +++ b/mmc_cmds.h
>> @@ -28,3 +28,9 @@ int do_sanitize(int nargs, char **argv);  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_firmware_update(int nargs, char **argv);
>> +
>> +int read_extcsd(int fd, __u8 *ext_csd); int write_extcsd_value(int fd,
>> +__u8 index, __u8 value);
>> +
>> +int do_emmc5_fw_update(int devfd, __u8 *cid, __u8 *ext_csd, char
>> +*emmc_fw, size_t fwsize);
>> --
>> 1.8.1.5
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the
>> body of a message to majordomo@vger.kernel.org More majordomo info at
>> http://vger.kernel.org/majordomo-info.html
>
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alex Lemberg Feb. 9, 2014, 9:06 a.m. UTC | #6
Hi Grant,

We will contribute shortly our FFU implementation code (RFC) , in which we are addressing both issues discussed below:

- FW File size limitation
- Atomic FFU Process

However our implementation is in both layers - kernel driver and mmc-utils.
We will be happy to get your comments.

Thanks,
Alex and Avi

> -----Original Message-----

> From: grundler@google.com [mailto:grundler@google.com] On Behalf Of

> Grant Grundler

> Sent: Thursday, February 06, 2014 8:41 PM

> To: Alex Lemberg

> Cc: Grant Grundler; Chris Ball; linux-mmc; Puthikorn Voravootivat; Gwendal

> Grignou; Avi Shchislowski

> Subject: Re: [PATCH v2] mmc-utils: add eMMC 5.0 FFU support

> 

> On Thu, Feb 6, 2014 at 3:57 AM, Alex Lemberg <Alex.Lemberg@sandisk.com>

> wrote:

> > Hi Grant,

> >

> > We have few comments on the code below

> >

> >> +     fwsize = fwfilestat.st_size;

> >> +     if (fwsize > MMC_IOC_MAX_BYTES) {

> >> +             fprintf(stderr,"ERROR: %s is > %ld bytes long (max

> >> allowed)\n",

> >> +                     fwfilename, MMC_IOC_MAX_BYTES);

> >> +             goto out_fw;

> >> +     }

> >

> > By this solution, the FW file size is limited to MMC_IOC_MAX_BYTES

> (128KB???). Is there a guarantee that all vendors will have FW image file in

> size < MMC_IOC_MAX_BYTES?

> 

> MMC_IOC_MAX_BYTES is something that existed in mmc-utils before and I

> don't see any reference to a "max size" in the eMMC 5.0 spec. The eMMC

> 5.0 spec does provide an additional method to send multiple blocks that I

> haven't implemented. So I don't believe there is any particular size limit for

> firmware (longer term - mmc-utils needs more work to support >

> MMC_IOC_MAX_BYTES).

> 

> 

> >> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,

> >> +                             char *emmc_fw, size_t fwsize) {

> >> +     struct mmc_ioc_cmd idata;

> >> +     int retcode = 0;

> >> +     int ret;

> >> +

> >> +     /* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.opcode = MMC_SWITCH;

> >> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |

> >> +                     (30 << 16) |    /* index */

> >> +                     (1 << 8) |      /* value */

> >> +                     EXT_CSD_CMD_SET_NORMAL;

> >> +     idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");

> >> +             goto abort_update;

> >> +     }

> >> +

> >

> > Can we be certain that no NORMAL commands (not related to FFU) will be

> sent by the host, after switching to FFU mode?

> 

> Yes and No :)

> 

> Yes: get exclusive access to the device - e.g . boot from other media and

> then perform the update.

> 

> No: If the FFU is targeting the boot device, other commands can be sent

> when in FFU mode and FFU should gracefully fail according my

> (novice) reading of the eMMC 5.0 spec. I'm really not sure what can go

> wrong here  But early in the system boot (e.g. init hasn't gone multiple user

> yet) we can cache the FW image and be reasonably sure no other IOs will be

> issued during the FFU.

> 

> My point is there are a range of opportunities to mitigate this risk depending

> on how much trouble you want to go through WITHOUT making any

> additional changes to the tool or kernel.

> 

> > Is it possible that MMC driver will send R/W requests in the middle (Steps

> #1 - #4)?

> 

> Yes - as outlined above. This can certainly not a perfect solution - it's just the

> first one.

> 

> Long term, to make FFU more robust and secure on a "live" (ie boot) device,

> Gwendal Grignou and Kees Cook (co-workers) would like to implement an "in

> kernel" hook to guarantee the FW image is an "atomic operation" (not

> interrupted by other IO) and the firmware image is pulled from a "well

> known" location (e.g. /lib/firmware). On a chromebook, that means the

> firmware is part of a signed OS image (using dm-verity).

> 

> >

> >> +     /* 2) send CMD25 0x0000FFFF */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.write_flag = 1;

> >> +     idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write

> >> + this

> >> FW */

> >> +     idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;

> >> +     idata.arg = 0x0000FFFF;

> >> +     idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW

> >> Update) %m\n");

> >> +             goto abort_update;

> >> +     }

> >> +

> >> +     /* 3) check FFU_STATUS[26] */

> >> +     ret = read_extcsd(fd, ext_csd);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             fprintf(stderr, "read_extcsd error (%d): %m\n", ret);

> >> +             goto abort_update;

> >> +     }

> >> +

> >> +     switch(ext_csd[26]) {

> >> +     case 0:  break;

> >> +     case 0x10:

> >> +             fprintf(stderr, "eMMC 5.0 FFU had general error and

> >> + failed

> >> (EIO).\n");

> >> +             retcode = -EIO;

> >> +             break;

> >> +     case 0x11:

> >> +             fprintf(stderr, "eMMC 5.0 FFU did not complete

> >> (EAGAIN).\n");

> >> +             retcode = -EAGAIN;

> >> +             break;

> >> +     case 0x12:

> >> +             fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "

> >> +                     "mismatch or was interrupted (EINTR).\n");

> >> +             retcode = -EINTR;

> >> +             break;

> >> +     default:

> >> +             fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",

> >> ext_csd[26]);

> >> +             retcode = -EIO;

> >> +     }

> >> +

> >> +     /* 4) check

> >> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */

> >> +     ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |

> >> +             (ext_csd[304] << 16) | (ext_csd[305] << 24);

> >> +

> >> +     /* convert that gibberish to bytes */

> >> +     ret *= 512;

> >> +     if (ext_csd[61] == 1)

> >> +             ret *= 8;       /* 4K sectors */

> >> +

> >> +     if (ret != fwsize) {

> >> +             fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"

> >> +                     " PROGRAMMED_SECTORS: %d)\n", fwsize, ret);

> >> +     }

> >> +

> >

> > By the spec, there are two ways to install new FW. The right way should be

> set according to MODE_OPERATION_CODES field of EXT_CSD.

> 

> I'm reworking this code now based on similar feedback. If I still don't get this

> right, please please please send a patch to make it work right.  I would in no

> way be offended by anyone fixing up code I've got wrong.

> 

> >

> >> +abort_update:

> >> +     /* 4) send CMD0

> >> +      * This should reset the device and force use of the new firmware.

> >> +      */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.opcode = MMC_GO_IDLE_STATE;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");

> >> +     }

> >> +

> >> +     return retcode;

> >> +}

> >

> >

> > Thanks,

> > Alex, Avi

> 

> Thanks Alex, Avi! I appreciate the review.

> 

> cheers,

> grant

> 

> >

> >

> >> -----Original Message-----

> >> From: linux-mmc-owner@vger.kernel.org [mailto:linux-mmc-

> >> owner@vger.kernel.org] On Behalf Of Grant Grundler

> >> Sent: Thursday, January 23, 2014 9:44 PM

> >> To: Chris Ball

> >> Cc: linux-mmc; Puthikorn Voravootivat; Gwendal Grignou; Grant

> >> Grundler

> >> Subject: [PATCH v2] mmc-utils: add eMMC 5.0 FFU support

> >>

> >> Field Firmware Update feature is new for 5.0 spec.

> >> Code written per JESD84-B50.pdf spec available from:

> >>     http://www.jedec.org/standards-documents/technology-focus-

> >> areas/flash-memory-ssds-ufs-emmc/e-mmc

> >>

> >> Change-Id: I0a22e9862876421630f53ac27fc0a161a9c70131

> >> Signed-off-by: Grant Grundler <grundler@chromium.org>

> >> ---

> >> V2:

> >> Fix compiler borkage in use of strncmp().

> >> Use '\0' instead of 0 in char assignment.

> >>

> >> V1:

> >> This patch needs to be reviewed and tested by _ANY_ eMMC 5.0 HW

> vendor.

> >>

> >> If you represent an eMMC 5.0 HW vendor and expect your part will be

> >> used in a ChromeOS device, make sure FFU works with mmc-utils.

> >>

> >> mmc-utils is now shipping with ChromeOS (starting with R34 or R35

> release):

> >>     https://chromium-review.googlesource.com/#/c/182716/

> >>

> >> Please also make sure your FFU implementation works reliably and is

> >> robust (e.g. sign your binaries and check the signature).

> >>

> >> Two general trends in eMMC are driving the requirement for FFU support:

> >> 1) longer support life time for devices (Chromebooks are > 5 years).

> >> I.e. odds of requiring a bug fix later is higher.

> >> I believe this trend applies to tablets and phones as well.

> >>

> >> 2) eMMC 5.0 devices are more complex --> testing will not find all the

> bugs.

> >> e.g. TLC vs MLC vs SLC error handling _and_ implementations which

> >> support a write cache, reliable write, and the plethora of other eMMC

> features.

> >> More code means more interactions and more bugs. Fact of (SW) Life.

> >> It's no longer a question of whether code will have bugs. It's a

> >> question of how severe and can they be mitigated (FFU is just one way to

> mitigate).

> >>

> >> I've written similar FFU patches for three different eMMC 4.5 products.

> >> Some of those will get published and will conflict with this patch.

> >> I'm happy resolve those conflicts and repost.

> >> Please just make sure I'm CC'd.

> >>

> >> I'm also willing to write additional mmc-utils FFU patches for any

> >> other eMMC 4.5 vendor.  Please send technical details to my

> >> google.com email and promise to test and publish the result.

> >>

> >> Thanks for reading this far! :)

> >>

> >>

> >>  Makefile         |   4 +-

> >>  mmc.c            |  31 ++++++--

> >>  mmc.h            |  29 ++++++-

> >>  mmc_cmds-emmc5.c | 129 +++++++++++++++++++++++++++++++

> >>  mmc_cmds.c       | 232

> >> ++++++++++++++++++++++++++++++++++++++++++++-----------

> >>  mmc_cmds.h       |   6 ++

> >>  6 files changed, 378 insertions(+), 53 deletions(-)  create mode

> >> 100644 mmc_cmds-emmc5.c

> >>

> >> diff --git a/Makefile b/Makefile

> >> index 91cfc35..1cdd97f 100644

> >> --- a/Makefile

> >> +++ b/Makefile

> >> @@ -1,7 +1,7 @@

> >>  CC ?= gcc

> >>  AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2

> CFLAGS ?= -g

> >> -O2 -objects = mmc.o mmc_cmds.o

> >> +objects = mmc.o mmc_cmds.o mmc_cmds-emmc5.o

> >>

> >>  CHECKFLAGS = -Wall -Werror -Wuninitialized -Wundef

> >>

> >> @@ -44,7 +44,7 @@ clean:

> >>       $(MAKE) -C man clean

> >>

> >>  install: $(progs) install-man

> >> -     $(INSTALL) -m755 -d $(DESTDIR)$(bindir)

> >> +     $(INSTALL) -m 755 -d $(DESTDIR)$(bindir)

> >>       $(INSTALL) $(progs) $(DESTDIR)$(bindir)

> >>

> >>  .PHONY: all clean install manpages install-man diff --git a/mmc.c

> >> b/mmc.c index 926e92f..01a5074 100644

> >> --- a/mmc.c

> >> +++ b/mmc.c

> >> @@ -20,7 +20,10 @@

> >>  #include <stdio.h>

> >>  #include <stdlib.h>

> >>  #include <string.h>

> >> +#include <errno.h>

> >> +#include <linux/types.h>

> >>

> >> +#include "mmc.h"

> >>  #include "mmc_cmds.h"

> >>

> >>  #define MMC_VERSION  "0.1"

> >> @@ -37,9 +40,9 @@ struct Command {

> >>                                  if < 0, _minimum_ number of arguments */

> >>       char    *verb;          /* verb */

> >>       char    *help;          /* help lines; from the 2nd line onward they

> >> -                                   are automatically indented */

> >> -        char    *adv_help;      /* advanced help message; from the 2nd line

> >> -                                   onward they are automatically indented */

> >> +                                are automatically indented */

> >> +     char    *adv_help;      /* advanced help message; from the 2nd line

> >> +                                onward they are automatically

> >> + indented */

> >>

> >>       /* the following fields are run-time filled by the program */

> >>       char    **cmds;         /* array of subcommands */

> >> @@ -110,9 +113,24 @@ static struct Command commands[] = {

> >>               "Send Sanitize command to the <device>.\nThis will

> >> delete the unmapped memory region of the device.",

> >>         NULL

> >>       },

> >> +     { do_firmware_update, -2,

> >> +       "firmware upload", "<firmware_file> " "<device>\n"

> >> +             "Upload/Update firmware on <device> using

> >> <firmware_file>.\n",

> >> +       NULL

> >> +     },

> >>       { 0, 0, 0, 0 }

> >>  };

> >>

> >> +const char *manfid_lookup[0x100] = {

> >> +     [MANFID_PANASONIC] = "Panasonic",

> >> +     [MANFID_KINGSTON] = "Kingston",

> >> +     [MANFID_SANDISK] = "Sandisk",

> >> +     [MANFID_SAMSUNG] = "Samsung",

> >> +     [MANFID_TOSHIBA] = "Toshiba",

> >> +     [MANFID_SANDISK_SEM] = "Sandisk_SEM",

> >> +     [MANFID_KINGSTON_MMC] = "Kingston_MMC"

> >> +};

> >> +

> >>  static char *get_prgname(char *programname)  {

> >>       char    *np;

> >> @@ -363,11 +381,10 @@ static int parse_args(int argc, char **argv,

> >>                       return -2;

> >>       }

> >>

> >> -        if (prepare_args( nargs_, args_, prgname, matchcmd )){

> >> -                fprintf(stderr, "ERROR: not enough memory\\n");

> >> +     if (prepare_args( nargs_, args_, prgname, matchcmd )){

> >> +             fprintf(stderr, "ERROR: not enough memory\\n");

> >>               return -20;

> >> -        }

> >> -

> >> +     }

> >>

> >>       return 1;

> >>  }

> >> diff --git a/mmc.h b/mmc.h

> >> index 9871d62..61c0303 100644

> >> --- a/mmc.h

> >> +++ b/mmc.h

> >> @@ -24,12 +24,25 @@

> >>  #define MMC_BLOCK_MAJOR                      179

> >>

> >>  /* From kernel linux/mmc/mmc.h */

> >> +#define MMC_GO_IDLE_STATE    0       /* bc                               */

> >> +#define MMC_ALL_SEND_CID     2       /* bcr                          R2

> >> */

> >>  #define MMC_SWITCH           6       /* ac   [31:0] See below        R1b

> >> */

> >> +#define MMC_SELECT_CARD              7       /* ac   [31:16] RCA

> >>       R1  */

> >>  #define MMC_SEND_EXT_CSD     8       /* adtc                         R1

> >> */

> >> -#define MMC_SEND_STATUS              13      /* ac   [31:16] RCA        R1  */

> >> +#define MMC_SEND_CID         10      /* ac   [31:16] RCA

> >>       R2  */

> >> +#define MMC_STOP_TRANSMISSION        12      /* ac

> >>       R1b */

> >> +#define MMC_SEND_STATUS              13      /* ac   [31:16] RCA

> >>       R1  */

> >> +#define MMC_READ_SINGLE_BLOCK        17      /* adtc [31:0] data addr

> >>       R1  */

> >> +#define MMC_READ_MULTIPLE_BLOCK      18      /* adtc [31:0] data

> >> addr  R1  */

> >> +#define MMC_WRITE_BLOCK              24      /* adtc [31:0] data addr

> >>       R1  */

> >> +#define MMC_WRITE_MULTIPLE_BLOCK 25  /* adtc

> >>       R1  */

> >> +

> >> +#define MMC_GEN_CMD          56      /* adtc [0] RD/WR

> >>       R1  */

> >> +

> >>  #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */

> >>  #define MMC_SWITCH_MODE_WRITE_BYTE   0x03    /* Set target to

> value

> >> */

> >>

> >> +

> >>  /*

> >>   * EXT_CSD fields

> >>   */

> >> @@ -125,3 +138,17 @@

> >>

> >>  #define MMC_RSP_R1

> >>       (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)

> >>  #define MMC_RSP_R1B

> >>       (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_R

> >> SP_BUSY)

> >> +

> >> +

> >> +/* Manufacturer IDs as shown by /sys/block/mmcblk0/device/manfid */

> >> +#define MANFID_PANASONIC     0x000001

> >> +#define MANFID_KINGSTON              0x000002

> >> +#define MANFID_SANDISK               0x000003

> >> +#define MANFID_SAMSUNG               0x000015

> >> +#define MANFID_TOSHIBA               0x00001d

> >> +#define MANFID_SANDISK_SEM   0x000045        /* seen with

> >> "SEM16G" */

> >> +#define MANFID_KINGSTON_MMC  0x000070        /* Seen on

> >> embedded device */

> >> +

> >> +#if 0

> >> +const char *manfid_lookup[];

> >> +#endif

> >> diff --git a/mmc_cmds-emmc5.c b/mmc_cmds-emmc5.c new file mode

> >> 100644 index 0000000..43fe3f2

> >> --- /dev/null

> >> +++ b/mmc_cmds-emmc5.c

> >> @@ -0,0 +1,129 @@

> >> +/*

> >> + * eMMC 5.0+ Specific tools

> >> + *

> >> + * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.

> >> + *

> >> + * 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.

> >> + */

> >> +

> >> +#include <stdio.h>

> >> +#include <stdlib.h>

> >> +#include <string.h>  /* for memset() */ #include <errno.h> #include

> >> +<linux/types.h> #include <sys/ioctl.h> #include <dirent.h> #include

> >> +<fcntl.h> #include <libgen.h> #include <limits.h> #include <ctype.h>

> >> +

> >> +#include "mmc.h"

> >> +#include "mmc_cmds.h"

> >> +

> >> +

> >> +

> >> +int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,

> >> +                             char *emmc_fw, size_t fwsize) {

> >> +     struct mmc_ioc_cmd idata;

> >> +     int retcode = 0;

> >> +     int ret;

> >> +

> >> +     /* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.opcode = MMC_SWITCH;

> >> +        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |

> >> +                     (30 << 16) |    /* index */

> >> +                     (1 << 8) |      /* value */

> >> +                     EXT_CSD_CMD_SET_NORMAL;

> >> +     idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");

> >> +             goto abort_update;

> >> +     }

> >> +

> >> +     /* 2) send CMD25 0x0000FFFF */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.write_flag = 1;

> >> +     idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write

> >> + this

> >> FW */

> >> +     idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;

> >> +     idata.arg = 0x0000FFFF;

> >> +     idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW

> >> Update) %m\n");

> >> +             goto abort_update;

> >> +     }

> >> +

> >> +     /* 3) check FFU_STATUS[26] */

> >> +     ret = read_extcsd(fd, ext_csd);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             fprintf(stderr, "read_extcsd error (%d): %m\n", ret);

> >> +             goto abort_update;

> >> +     }

> >> +

> >> +     switch(ext_csd[26]) {

> >> +     case 0:  break;

> >> +     case 0x10:

> >> +             fprintf(stderr, "eMMC 5.0 FFU had general error and

> >> + failed

> >> (EIO).\n");

> >> +             retcode = -EIO;

> >> +             break;

> >> +     case 0x11:

> >> +             fprintf(stderr, "eMMC 5.0 FFU did not complete

> >> (EAGAIN).\n");

> >> +             retcode = -EAGAIN;

> >> +             break;

> >> +     case 0x12:

> >> +             fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "

> >> +                     "mismatch or was interrupted (EINTR).\n");

> >> +             retcode = -EINTR;

> >> +             break;

> >> +     default:

> >> +             fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n",

> >> ext_csd[26]);

> >> +             retcode = -EIO;

> >> +     }

> >> +

> >> +     /* 4) check

> >> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */

> >> +     ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |

> >> +             (ext_csd[304] << 16) | (ext_csd[305] << 24);

> >> +

> >> +     /* convert that gibberish to bytes */

> >> +     ret *= 512;

> >> +     if (ext_csd[61] == 1)

> >> +             ret *= 8;       /* 4K sectors */

> >> +

> >> +     if (ret != fwsize) {

> >> +             fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"

> >> +                     " PROGRAMMED_SECTORS: %d)\n", fwsize, ret);

> >> +     }

> >> +

> >> +abort_update:

> >> +     /* 4) send CMD0

> >> +      * This should reset the device and force use of the new firmware.

> >> +      */

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     idata.opcode = MMC_GO_IDLE_STATE;

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret) {

> >> +             retcode = ret;

> >> +             perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");

> >> +     }

> >> +

> >> +     return retcode;

> >> +}

> >> diff --git a/mmc_cmds.c b/mmc_cmds.c

> >> index b8afa74..afbed77 100644

> >> --- a/mmc_cmds.c

> >> +++ b/mmc_cmds.c

> >> @@ -1,3 +1,4 @@

> >> +

> >>  /*

> >>   * This program is free software; you can redistribute it and/or

> >>   * modify it under the terms of the GNU General Public @@ -17,9

> >> +18,10 @@  #include <stdio.h>  #include <stdlib.h>  #include

> >> <string.h> -#include <sys/ioctl.h> -#include <sys/types.h>

> >> +#include <errno.h>

> >>  #include <dirent.h>

> >> +#include <sys/ioctl.h>

> >> +#include <linux/types.h>

> >>  #include <sys/stat.h>

> >>  #include <unistd.h>

> >>  #include <fcntl.h>

> >> @@ -30,23 +32,27 @@

> >>  #include "mmc.h"

> >>  #include "mmc_cmds.h"

> >>

> >> +#define EXT_CSD_SIZE 512

> >> +#define CID_SIZE 16

> >> +

> >> +

> >>  int read_extcsd(int fd, __u8 *ext_csd)  {

> >>       int ret = 0;

> >>       struct mmc_ioc_cmd idata;

> >>       memset(&idata, 0, sizeof(idata));

> >> -     memset(ext_csd, 0, sizeof(__u8) * 512);

> >> +     memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);

> >>       idata.write_flag = 0;

> >>       idata.opcode = MMC_SEND_EXT_CSD;

> >>       idata.arg = 0;

> >>       idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;

> >> -     idata.blksz = 512;

> >> +     idata.blksz = EXT_CSD_SIZE;

> >>       idata.blocks = 1;

> >>       mmc_ioc_cmd_set_data(idata, ext_csd);

> >>

> >>       ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >>       if (ret)

> >> -             perror("ioctl");

> >> +             perror("ioctl SEND_EXT_CSD");

> >>

> >>       return ret;

> >>  }

> >> @@ -67,7 +73,30 @@ int write_extcsd_value(int fd, __u8 index, __u8

> >> value)

> >>

> >>       ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >>       if (ret)

> >> -             perror("ioctl");

> >> +             perror("ioctl Write EXT CSD");

> >> +

> >> +     return ret;

> >> +}

> >> +

> >> +int read_cid(int fd, __u8 *cid)

> >> +{

> >> +     int ret = 0;

> >> +     struct mmc_ioc_cmd idata;

> >> +

> >> +     memset(&idata, 0, sizeof(idata));

> >> +     memset(cid, 0, sizeof(__u8) * CID_SIZE);

> >> +

> >> +     idata.write_flag = 0;

> >> +     idata.opcode = MMC_SEND_CID;

> >> +     idata.arg    = 0;

> >> +     idata.flags  = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;

> >> +     idata.blksz  = CID_SIZE;

> >> +     idata.blocks = 1;

> >> +     mmc_ioc_cmd_set_data(idata, cid);

> >> +

> >> +     ret = ioctl(fd, MMC_IOC_CMD, &idata);

> >> +     if (ret)

> >> +             perror("ioctl SEND_CID");

> >>

> >>       return ret;

> >>  }

> >> @@ -103,17 +132,11 @@ void print_writeprotect_status(__u8 *ext_csd)

> >>

> >>               reg = ext_csd[EXT_CSD_BOOT_WP];

> >>               printf("Boot Area Write protection [BOOT_WP]:

> >> 0x%02x\n", reg);

> >> -             printf(" Power ro locking: ");

> >> -             if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)

> >> -                     printf("not possible\n");

> >> -             else

> >> -                     printf("possible\n");

> >> +             printf(" Power ro locking: %spossible\n",

> >> +                     (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) ? "not

> >> " : "");

> >>

> >> -             printf(" Permanent ro locking: ");

> >> -             if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)

> >> -                     printf("not possible\n");

> >> -             else

> >> -                     printf("possible\n");

> >> +             printf(" Permanent ro locking: %spossible\n",

> >> +                     (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS) ?

> >> "not " : "");

> >>

> >>               printf(" ro lock status: ");

> >>               if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN) @@ -127,7

> >> +150,7 @@ void print_writeprotect_status(__u8 *ext_csd)

> >>

> >>  int do_writeprotect_get(int nargs, char **argv)  {

> >> -     __u8 ext_csd[512];

> >> +     __u8 ext_csd[EXT_CSD_SIZE];

> >>       int fd, ret;

> >>       char *device;

> >>

> >> @@ -155,7 +178,7 @@ int do_writeprotect_get(int nargs, char **argv)

> >>

> >>  int do_writeprotect_set(int nargs, char **argv)  {

> >> -     __u8 ext_csd[512], value;

> >> +     __u8 ext_csd[EXT_CSD_SIZE], value;

> >>       int fd, ret;

> >>       char *device;

> >>

> >> @@ -191,7 +214,7 @@ int do_writeprotect_set(int nargs, char **argv)

> >>

> >>  int do_disable_512B_emulation(int nargs, char **argv)  {

> >> -     __u8 ext_csd[512], native_sector_size, data_sector_size,

> >> wr_rel_param;

> >> +     __u8 ext_csd[EXT_CSD_SIZE], native_sector_size,

> >> +data_sector_size, wr_rel_param;

> >>       int fd, ret;

> >>       char *device;

> >>

> >> @@ -301,7 +324,7 @@ int do_write_boot_en(int nargs, char **argv)

> >>

> >>  int do_hwreset(int value, int nargs, char **argv)  {

> >> -     __u8 ext_csd[512];

> >> +     __u8 ext_csd[EXT_CSD_SIZE];

> >>       int fd, ret;

> >>       char *device;

> >>

> >> @@ -360,7 +383,7 @@ int do_hwreset_dis(int nargs, char **argv)

> >>

> >>  int do_write_bkops_en(int nargs, char **argv)  {

> >> -     __u8 ext_csd[512], value = 0;

> >> +     __u8 ext_csd[EXT_CSD_SIZE], value = 0;

> >>       int fd, ret;

> >>       char *device;

> >>

> >> @@ -497,7 +520,7 @@ int set_partitioning_setting_completed(int

> >> dry_run, const char * const device,  int do_enh_area_set(int nargs, char

> **argv)  {

> >>       __u8 value;

> >> -     __u8 ext_csd[512];

> >> +     __u8 ext_csd[EXT_CSD_SIZE];

> >>       int fd, ret;

> >>       char *device;

> >>       int dry_run = 1;

> >> @@ -635,7 +658,7 @@ int do_enh_area_set(int nargs, char **argv)  int

> >> do_write_reliability_set(int nargs, char **argv)  {

> >>       __u8 value;

> >> -     __u8 ext_csd[512];

> >> +     __u8 ext_csd[EXT_CSD_SIZE];

> >>       int fd, ret;

> >>

> >>       int dry_run = 1;

> >> @@ -696,7 +719,7 @@ int do_write_reliability_set(int nargs, char

> >> **argv)

> >>

> >>  int do_read_extcsd(int nargs, char **argv)  {

> >> -     __u8 ext_csd[512], ext_csd_rev, reg;

> >> +     __u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;

> >>       __u32 regl;

> >>       int fd, ret;

> >>       char *device;

> >> @@ -722,24 +745,13 @@ int do_read_extcsd(int nargs, char **argv)

> >>       ext_csd_rev = ext_csd[192];

> >>

> >>       switch (ext_csd_rev) {

> >> -     case 6:

> >> -             str = "4.5";

> >> -             break;

> >> -     case 5:

> >> -             str = "4.41";

> >> -             break;

> >> -     case 3:

> >> -             str = "4.3";

> >> -             break;

> >> -     case 2:

> >> -             str = "4.2";

> >> -             break;

> >> -     case 1:

> >> -             str = "4.1";

> >> -             break;

> >> -     case 0:

> >> -             str = "4.0";

> >> -             break;

> >> +     case 7: str = "5.0"; break;

> >> +     case 6: str = "4.5"; break;

> >> +     case 5: str = "4.41"; break;

> >> +     case 3: str = "4.3"; break;

> >> +     case 2: str = "4.2"; break;

> >> +     case 1: str = "4.1"; break;

> >> +     case 0: str = "4.0"; break;

> >>       default:

> >>               goto out_free;

> >>       }

> >> @@ -1160,6 +1172,140 @@ int do_sanitize(int nargs, char **argv)

> >>       }

> >>

> >>       return ret;

> >> -

> >>  }

> >>

> >> +int do_firmware_update(int nargs, char **argv) {

> >> +     char *emmc_fw;

> >> +     char *device;

> >> +     char *fwfilename;

> >> +     struct stat fwfilestat;

> >> +     size_t fwsize;

> >> +     int ret = 0;

> >> +     int devfd,fwfd;

> >> +     __u8 ext_csd[EXT_CSD_SIZE];

> >> +     __u8 cid[CID_SIZE];

> >> +     __u8 ext_csd_rev;

> >> +

> >> +

> >> +     CHECK(nargs != 4, "Usage: mmc firmware update"

> >> +                     " --I_want_to_destroy_my_drive"

> >> +                     " </path/to/mmcblkX> </path/to/FW.bin>\n",

> >> +                       exit(1));

> >> +

> >> +

> >> +     /* Lesson from hdparm: user must be aware of the risks

> >> +      * Key here is the additional command line flag be UNDOCUMENTED.

> >> +      * User _must_ read and KNOW this is risky at runtime.

> >> +      */

> >> +     if (strncmp(argv[4], "--I_want_to_destroy_my_drive", 28)) {

> >> +             fprintf(stderr,"ERROR: Please specify --

> >> I_want_to_destroy_my_drive"

> >> +             " as first parameter to firmware update.\n");

> >> +

> >> +             exit(1);

> >> +     }

> >> +

> >> +     emmc_fw = malloc(MMC_IOC_MAX_BYTES);

> >> +     if (!emmc_fw)

> >> +             return -ENOMEM;

> >> +

> >> +     device = argv[5];

> >> +

> >> +     devfd = open(device, O_RDWR);

> >> +     if (devfd < 0) {

> >> +             fprintf(stderr,"ERROR: open %s: %m\n", device); /* %m =

> >> errno */

> >> +             goto out_free;

> >> +     }

> >> +

> >> +     /* 1) read device version and attributes */

> >> +     ret = read_extcsd(devfd, ext_csd);

> >> +     if (ret) {

> >> +             fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",

> >> device);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     ext_csd_rev = ext_csd[192];

> >> +

> >> +     if (ext_csd_rev < 7) {

> >> +             fprintf(stderr, "ERROR: Can not update firmware"

> >> +                     ": %s is pre-emmc 5.0 vintage\n", device);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     printf("%s: FW is currently %8s

> >> (0x%02x%02x%02x%02x%02x%02x%02x%02x)\n",

> >> +             device, &(ext_csd[254]),

> >> +             ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],

> >> +             ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]

> >> +             );

> >> +

> >> +     /* 2) confirm SUPPORTED_MODES has FFU bit set */

> >> +     if (!(ext_csd[493] & 1)) {

> >> +             fprintf(stderr, "ERROR: %s is eMMC 5.0 device"

> >> +                     "but does not support FFU.\n", device);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     /* 3) confirm FW updated is NOT disabled on this device */

> >> +     if (ext_csd[169] & 1) {

> >> +             fprintf(stderr, "ERROR: %s is eMMC 5.0 device"

> >> +                     "but FFU is disabled.\n", device);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     /* 4) read the device manfid */

> >> +     ret = read_cid(devfd, cid);

> >> +     if (ret) {

> >> +             fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n",

> >> device);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     /* 5) Fetch the FW image */

> >> +     fwfilename = argv[2];

> >> +

> >> +     fwfd = open(fwfilename, O_RDONLY);

> >> +     if (fwfd < 0) {

> >> +             /* %m = errno */

> >> +             fprintf(stderr,"ERROR: open %s: %m", fwfilename);

> >> +             goto out_dev;

> >> +     }

> >> +

> >> +     ret = fstat(fwfd, &fwfilestat);

> >> +     if (ret) {

> >> +             /* %m = errno */

> >> +             fprintf(stderr,"ERROR: fstat %s: %m", fwfilename);

> >> +             goto out_fw;

> >> +     }

> >> +

> >> +     fwsize = fwfilestat.st_size;

> >> +     if (fwsize > MMC_IOC_MAX_BYTES) {

> >> +             fprintf(stderr,"ERROR: %s is > %ld bytes long (max

> >> allowed)\n",

> >> +                     fwfilename, MMC_IOC_MAX_BYTES);

> >> +             goto out_fw;

> >> +     }

> >> +

> >> +     ret = read(fwfd, emmc_fw, fwsize);

> >> +     if (ret < fwsize) {

> >> +             fprintf(stderr,"ERROR: did not read all %d bytes of %s"

> >> +                             "(%d bytes long)\n",

> >> +                     fwsize, fwfilename, ret);

> >> +             goto out_fw;

> >> +     }

> >> +

> >> +     do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize);

> >> +

> >> +     cid[13] = '\0'; /* make sure string is NULL terminated */

> >> +

> >> +     printf("%s: FW updated to %8s

> >> (0x%2x%2x%2x%2x%2x%2x%2x%2x)\n",

> >> +             device, &(ext_csd[254]),

> >> +             ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],

> >> +             ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]

> >> +             );

> >> +

> >> +out_fw:

> >> +     close(fwfd);

> >> +out_dev:

> >> +     close(devfd);

> >> +out_free:

> >> +     free(emmc_fw);

> >> +     return ret;

> >> +}

> >> diff --git a/mmc_cmds.h b/mmc_cmds.h

> >> index f06cc10..549b851 100644

> >> --- a/mmc_cmds.h

> >> +++ b/mmc_cmds.h

> >> @@ -28,3 +28,9 @@ int do_sanitize(int nargs, char **argv);  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_firmware_update(int nargs, char **argv);

> >> +

> >> +int read_extcsd(int fd, __u8 *ext_csd); int write_extcsd_value(int

> >> +fd,

> >> +__u8 index, __u8 value);

> >> +

> >> +int do_emmc5_fw_update(int devfd, __u8 *cid, __u8 *ext_csd, char

> >> +*emmc_fw, size_t fwsize);

> >> --

> >> 1.8.1.5

> >>

> >> --

> >> To unsubscribe from this list: send the line "unsubscribe linux-mmc"

> >> in the body of a message to majordomo@vger.kernel.org More

> majordomo

> >> info at http://vger.kernel.org/majordomo-info.html

> >

> >

> > --

> > To unsubscribe from this list: send the line "unsubscribe linux-mmc"

> > in the body of a message to majordomo@vger.kernel.org More

> majordomo

> > info at  http://vger.kernel.org/majordomo-info.html
Grant Grundler Feb. 21, 2014, 9:27 p.m. UTC | #7
Chris,
I haven't seen anything else get proposed. If not, can you include
this patch in mmc-utils ToT and use it as a basis to drive the FFU
support conversation forward?

On Sun, Feb 9, 2014 at 1:06 AM, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> Hi Grant,
>
> We will contribute shortly our FFU implementation code (RFC) , in which we are addressing both issues discussed below:
>
> - FW File size limitation
> - Atomic FFU Process

Did this get posted? my gmail filters might be misconfigured and
prevented me from seeing it.

> However our implementation is in both layers - kernel driver and mmc-utils.

I have no objection to an implementation which requires both kernel
and mmc-utils. I agree addressing those problems would be a Good Thing
(tm).

However, future promises should NOT prevent improving the current state.

And the patch I proposed would work on existing kernels that are being
shipped to run with eMMC 5.0 devices. I'm not referring to ChromeOS
since I can backport changes to those kernels and deliver those to
ChromeOS users within 8 weeks (worst case...best case is usually about
a week). That's not necessarily true for other distributions/builds.

> We will be happy to get your comments.

excellent! :)

thanks,
grant
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Grant Grundler Feb. 21, 2014, 9:31 p.m. UTC | #8
On Fri, Feb 21, 2014 at 1:27 PM, Grant Grundler <grundler@chromium.org> wrote:
> Chris,
> I haven't seen anything else get proposed. If not, can you include
> this patch in mmc-utils ToT and use it as a basis to drive the FFU
> support conversation forward?


Never mind... :)

>
> On Sun, Feb 9, 2014 at 1:06 AM, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
>> Hi Grant,
>>
>> We will contribute shortly our FFU implementation code (RFC) , in which we are addressing both issues discussed below:
>>
>> - FW File size limitation
>> - Atomic FFU Process
>
> Did this get posted? my gmail filters might be misconfigured and
> prevented me from seeing it.

Yup - this happened. I found the postings and will review.

Thanks!
grant
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Alex Lemberg Feb. 26, 2014, 11:07 p.m. UTC | #9
> -----Original Message-----

> From: grundler@google.com [mailto:grundler@google.com] On Behalf Of

> Grant Grundler

> Sent: Friday, February 21, 2014 11:31 PM

> To: Grant Grundler

> Cc: Alex Lemberg; Chris Ball; linux-mmc; Puthikorn Voravootivat; Gwendal

> Grignou; Avi Shchislowski

> Subject: Re: [PATCH v2] mmc-utils: add eMMC 5.0 FFU support

> 

> On Fri, Feb 21, 2014 at 1:27 PM, Grant Grundler <grundler@chromium.org>

> wrote:

> > Chris,

> > I haven't seen anything else get proposed. If not, can you include

> > this patch in mmc-utils ToT and use it as a basis to drive the FFU

> > support conversation forward?

> 

> 

> Never mind... :)

> 

> >

> > On Sun, Feb 9, 2014 at 1:06 AM, Alex Lemberg

> <Alex.Lemberg@sandisk.com> wrote:

> >> Hi Grant,

> >>

> >> We will contribute shortly our FFU implementation code (RFC) , in which

> we are addressing both issues discussed below:

> >>

> >> - FW File size limitation

> >> - Atomic FFU Process

> >

> > Did this get posted? my gmail filters might be misconfigured and

> > prevented me from seeing it.

> 

> Yup - this happened. I found the postings and will review.


Appreciate it and waiting for your comments!

Thanks,
Alex

> 

> Thanks!

> grant
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 91cfc35..1cdd97f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@ 
 CC ?= gcc
 AM_CFLAGS = -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2
 CFLAGS ?= -g -O2
-objects = mmc.o mmc_cmds.o
+objects = mmc.o mmc_cmds.o mmc_cmds-emmc5.o
 
 CHECKFLAGS = -Wall -Werror -Wuninitialized -Wundef
 
@@ -44,7 +44,7 @@  clean:
 	$(MAKE) -C man clean
 
 install: $(progs) install-man
-	$(INSTALL) -m755 -d $(DESTDIR)$(bindir)
+	$(INSTALL) -m 755 -d $(DESTDIR)$(bindir)
 	$(INSTALL) $(progs) $(DESTDIR)$(bindir)
 
 .PHONY: all clean install manpages install-man
diff --git a/mmc.c b/mmc.c
index 926e92f..01a5074 100644
--- a/mmc.c
+++ b/mmc.c
@@ -20,7 +20,10 @@ 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <errno.h>
+#include <linux/types.h>
 
+#include "mmc.h"
 #include "mmc_cmds.h"
 
 #define MMC_VERSION	"0.1"
@@ -37,9 +40,9 @@  struct Command {
 				   if < 0, _minimum_ number of arguments */
 	char	*verb;		/* verb */
 	char	*help;		/* help lines; from the 2nd line onward they 
-                                   are automatically indented */
-        char    *adv_help;      /* advanced help message; from the 2nd line 
-                                   onward they are automatically indented */
+				   are automatically indented */
+	char	*adv_help;	/* advanced help message; from the 2nd line
+				   onward they are automatically indented */
 
 	/* the following fields are run-time filled by the program */
 	char	**cmds;		/* array of subcommands */
@@ -110,9 +113,24 @@  static struct Command commands[] = {
 		"Send Sanitize command to the <device>.\nThis will delete the unmapped memory region of the device.",
 	  NULL
 	},
+	{ do_firmware_update, -2,
+	  "firmware upload", "<firmware_file> " "<device>\n"
+		"Upload/Update firmware on <device> using <firmware_file>.\n",
+	  NULL
+	},
 	{ 0, 0, 0, 0 }
 };
 
+const char *manfid_lookup[0x100] = {
+	[MANFID_PANASONIC] = "Panasonic",
+	[MANFID_KINGSTON] = "Kingston",
+	[MANFID_SANDISK] = "Sandisk",
+	[MANFID_SAMSUNG] = "Samsung",
+	[MANFID_TOSHIBA] = "Toshiba",
+	[MANFID_SANDISK_SEM] = "Sandisk_SEM",
+	[MANFID_KINGSTON_MMC] = "Kingston_MMC"
+};
+
 static char *get_prgname(char *programname)
 {
 	char	*np;
@@ -363,11 +381,10 @@  static int parse_args(int argc, char **argv,
 			return -2;
 	}
 	
-        if (prepare_args( nargs_, args_, prgname, matchcmd )){
-                fprintf(stderr, "ERROR: not enough memory\\n");
+	if (prepare_args( nargs_, args_, prgname, matchcmd )){
+		fprintf(stderr, "ERROR: not enough memory\\n");
 		return -20;
-        }
-
+	}
 
 	return 1;
 }
diff --git a/mmc.h b/mmc.h
index 9871d62..61c0303 100644
--- a/mmc.h
+++ b/mmc.h
@@ -24,12 +24,25 @@ 
 #define MMC_BLOCK_MAJOR			179
 
 /* From kernel linux/mmc/mmc.h */
+#define MMC_GO_IDLE_STATE	0	/* bc                               */
+#define MMC_ALL_SEND_CID	2	/* bcr				R2  */
 #define MMC_SWITCH		6	/* ac	[31:0] See below	R1b */
+#define MMC_SELECT_CARD		7	/* ac	[31:16] RCA		R1  */
 #define MMC_SEND_EXT_CSD	8	/* adtc				R1  */
-#define MMC_SEND_STATUS		13	/* ac   [31:16] RCA        R1  */
+#define MMC_SEND_CID		10	/* ac   [31:16] RCA		R2  */
+#define MMC_STOP_TRANSMISSION	12	/* ac				R1b */
+#define MMC_SEND_STATUS		13	/* ac   [31:16] RCA		R1  */
+#define MMC_READ_SINGLE_BLOCK	17	/* adtc [31:0] data addr	R1  */
+#define MMC_READ_MULTIPLE_BLOCK	18	/* adtc [31:0] data addr	R1  */
+#define MMC_WRITE_BLOCK		24	/* adtc [31:0] data addr	R1  */
+#define MMC_WRITE_MULTIPLE_BLOCK 25	/* adtc				R1  */
+
+#define MMC_GEN_CMD		56	/* adtc [0] RD/WR		R1  */
+
 #define R1_SWITCH_ERROR   (1 << 7)  /* sx, c */
 #define MMC_SWITCH_MODE_WRITE_BYTE	0x03	/* Set target to value */
 
+
 /*
  * EXT_CSD fields
  */
@@ -125,3 +138,17 @@ 
 
 #define MMC_RSP_R1	(MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE)
 #define MMC_RSP_R1B	(MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY)
+
+
+/* Manufacturer IDs as shown by /sys/block/mmcblk0/device/manfid */
+#define MANFID_PANASONIC	0x000001
+#define MANFID_KINGSTON		0x000002
+#define MANFID_SANDISK		0x000003
+#define MANFID_SAMSUNG		0x000015
+#define MANFID_TOSHIBA		0x00001d
+#define MANFID_SANDISK_SEM	0x000045	/* seen with "SEM16G" */
+#define MANFID_KINGSTON_MMC	0x000070	/* Seen on embedded device */
+
+#if 0
+const char *manfid_lookup[];
+#endif
diff --git a/mmc_cmds-emmc5.c b/mmc_cmds-emmc5.c
new file mode 100644
index 0000000..43fe3f2
--- /dev/null
+++ b/mmc_cmds-emmc5.c
@@ -0,0 +1,129 @@ 
+/*
+ * eMMC 5.0+ Specific tools
+ *
+ * Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>	/* for memset() */
+#include <errno.h>
+#include <linux/types.h>
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "mmc.h"
+#include "mmc_cmds.h"
+
+
+
+int do_emmc5_fw_update(int fd, __u8 *cid, __u8 *ext_csd,
+				char *emmc_fw, size_t fwsize)
+{
+	struct mmc_ioc_cmd idata;
+	int retcode = 0;
+	int ret;
+
+	/* 2) Host send CMD6 to set MODE_CONFIG[30] = 0x01 */
+	memset(&idata, 0, sizeof(idata));
+	idata.opcode = MMC_SWITCH;
+        idata.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) |
+			(30 << 16) |	/* index */
+			(1 << 8) |	/* value */
+			EXT_CSD_CMD_SET_NORMAL;
+	idata.flags = MMC_RSP_R1B | MMC_CMD_AC;;
+	ret = ioctl(fd, MMC_IOC_CMD, &idata);
+	if (ret) {
+		retcode = ret;
+		printf("ioctl: MMC_SWITCH (eMMC 5.0 FW Update) %m\n");
+		goto abort_update;
+	}
+
+	/* 2) send CMD25 0x0000FFFF */
+	memset(&idata, 0, sizeof(idata));
+	idata.write_flag = 1;
+	idata.data_ptr = (__u64) ((unsigned long) emmc_fw); /* Write this FW */
+	idata.opcode = MMC_WRITE_MULTIPLE_BLOCK;
+	idata.arg = 0x0000FFFF;
+	idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;;
+	ret = ioctl(fd, MMC_IOC_CMD, &idata);
+	if (ret) {
+		retcode = ret;
+		printf("ioctl:MMC_WRITE_MULTIPLE_BLOCK (eMMC 5.0 FW Update) %m\n");
+		goto abort_update;
+	}
+
+	/* 3) check FFU_STATUS[26] */
+	ret = read_extcsd(fd, ext_csd);
+	if (ret) {
+		retcode = ret;
+		fprintf(stderr, "read_extcsd error (%d): %m\n", ret);
+		goto abort_update;
+	}
+
+	switch(ext_csd[26]) {
+	case 0:  break;
+	case 0x10:
+		fprintf(stderr, "eMMC 5.0 FFU had general error and failed (EIO).\n");
+		retcode = -EIO;
+		break;
+	case 0x11:
+		fprintf(stderr, "eMMC 5.0 FFU did not complete (EAGAIN).\n");
+		retcode = -EAGAIN;
+		break;
+	case 0x12:
+		fprintf(stderr, "eMMC 5.0 FFU download failed: checksum "
+			"mismatch or was interrupted (EINTR).\n");
+		retcode = -EINTR;
+		break;
+	default:
+		fprintf(stderr, "eMMC 5.0 FFU unknown error: %x\n", ext_csd[26]);
+		retcode = -EIO;
+	}
+
+	/* 4) check NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED[305-302] */
+	ret = ext_csd[302] << 0 | (ext_csd[303] << 8) |
+		(ext_csd[304] << 16) | (ext_csd[305] << 24);
+
+	/* convert that gibberish to bytes */
+	ret *= 512;
+	if (ext_csd[61] == 1)
+		ret *= 8;	/* 4K sectors */
+
+	if (ret != fwsize) {
+		fprintf(stderr, "eMMC 5.0 FFU Failed? (fwsize: %d,"
+			" PROGRAMMED_SECTORS: %d)\n", fwsize, ret);
+	}
+
+abort_update:
+	/* 4) send CMD0
+	 * This should reset the device and force use of the new firmware.
+	 */
+	memset(&idata, 0, sizeof(idata));
+	idata.opcode = MMC_GO_IDLE_STATE;
+	ret = ioctl(fd, MMC_IOC_CMD, &idata);
+	if (ret) {
+		retcode = ret;
+		perror("ioctl:MMC_GO_IDLE_STATE (eMMC 5.0 FFU)");
+	}
+
+	return retcode;
+}
diff --git a/mmc_cmds.c b/mmc_cmds.c
index b8afa74..afbed77 100644
--- a/mmc_cmds.c
+++ b/mmc_cmds.c
@@ -1,3 +1,4 @@ 
+
 /*
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
@@ -17,9 +18,10 @@ 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <sys/ioctl.h>
-#include <sys/types.h>
+#include <errno.h>
 #include <dirent.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
@@ -30,23 +32,27 @@ 
 #include "mmc.h"
 #include "mmc_cmds.h"
 
+#define EXT_CSD_SIZE	512
+#define CID_SIZE 16
+
+
 int read_extcsd(int fd, __u8 *ext_csd)
 {
 	int ret = 0;
 	struct mmc_ioc_cmd idata;
 	memset(&idata, 0, sizeof(idata));
-	memset(ext_csd, 0, sizeof(__u8) * 512);
+	memset(ext_csd, 0, sizeof(__u8) * EXT_CSD_SIZE);
 	idata.write_flag = 0;
 	idata.opcode = MMC_SEND_EXT_CSD;
 	idata.arg = 0;
 	idata.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
-	idata.blksz = 512;
+	idata.blksz = EXT_CSD_SIZE;
 	idata.blocks = 1;
 	mmc_ioc_cmd_set_data(idata, ext_csd);
 
 	ret = ioctl(fd, MMC_IOC_CMD, &idata);
 	if (ret)
-		perror("ioctl");
+		perror("ioctl SEND_EXT_CSD");
 
 	return ret;
 }
@@ -67,7 +73,30 @@  int write_extcsd_value(int fd, __u8 index, __u8 value)
 
 	ret = ioctl(fd, MMC_IOC_CMD, &idata);
 	if (ret)
-		perror("ioctl");
+		perror("ioctl Write EXT CSD");
+
+	return ret;
+}
+
+int read_cid(int fd, __u8 *cid)
+{
+	int ret = 0;
+	struct mmc_ioc_cmd idata;
+
+	memset(&idata, 0, sizeof(idata));
+	memset(cid, 0, sizeof(__u8) * CID_SIZE);
+
+	idata.write_flag = 0;
+	idata.opcode = MMC_SEND_CID;
+	idata.arg    = 0;
+	idata.flags  = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
+	idata.blksz  = CID_SIZE;
+	idata.blocks = 1;
+	mmc_ioc_cmd_set_data(idata, cid);
+
+	ret = ioctl(fd, MMC_IOC_CMD, &idata);
+	if (ret)
+		perror("ioctl SEND_CID");
 
 	return ret;
 }
@@ -103,17 +132,11 @@  void print_writeprotect_status(__u8 *ext_csd)
 
 		reg = ext_csd[EXT_CSD_BOOT_WP];
 		printf("Boot Area Write protection [BOOT_WP]: 0x%02x\n", reg);
-		printf(" Power ro locking: ");
-		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS)
-			printf("not possible\n");
-		else
-			printf("possible\n");
+		printf(" Power ro locking: %spossible\n",
+			(reg & EXT_CSD_BOOT_WP_B_PWR_WP_DIS) ? "not " : "");
 
-		printf(" Permanent ro locking: ");
-		if (reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS)
-			printf("not possible\n");
-		else
-			printf("possible\n");
+		printf(" Permanent ro locking: %spossible\n",
+			(reg & EXT_CSD_BOOT_WP_B_PERM_WP_DIS) ? "not " : "");
 
 		printf(" ro lock status: ");
 		if (reg & EXT_CSD_BOOT_WP_B_PWR_WP_EN)
@@ -127,7 +150,7 @@  void print_writeprotect_status(__u8 *ext_csd)
 
 int do_writeprotect_get(int nargs, char **argv)
 {
-	__u8 ext_csd[512];
+	__u8 ext_csd[EXT_CSD_SIZE];
 	int fd, ret;
 	char *device;
 
@@ -155,7 +178,7 @@  int do_writeprotect_get(int nargs, char **argv)
 
 int do_writeprotect_set(int nargs, char **argv)
 {
-	__u8 ext_csd[512], value;
+	__u8 ext_csd[EXT_CSD_SIZE], value;
 	int fd, ret;
 	char *device;
 
@@ -191,7 +214,7 @@  int do_writeprotect_set(int nargs, char **argv)
 
 int do_disable_512B_emulation(int nargs, char **argv)
 {
-	__u8 ext_csd[512], native_sector_size, data_sector_size, wr_rel_param;
+	__u8 ext_csd[EXT_CSD_SIZE], native_sector_size, data_sector_size, wr_rel_param;
 	int fd, ret;
 	char *device;
 
@@ -301,7 +324,7 @@  int do_write_boot_en(int nargs, char **argv)
 
 int do_hwreset(int value, int nargs, char **argv)
 {
-	__u8 ext_csd[512];
+	__u8 ext_csd[EXT_CSD_SIZE];
 	int fd, ret;
 	char *device;
 
@@ -360,7 +383,7 @@  int do_hwreset_dis(int nargs, char **argv)
 
 int do_write_bkops_en(int nargs, char **argv)
 {
-	__u8 ext_csd[512], value = 0;
+	__u8 ext_csd[EXT_CSD_SIZE], value = 0;
 	int fd, ret;
 	char *device;
 
@@ -497,7 +520,7 @@  int set_partitioning_setting_completed(int dry_run, const char * const device,
 int do_enh_area_set(int nargs, char **argv)
 {
 	__u8 value;
-	__u8 ext_csd[512];
+	__u8 ext_csd[EXT_CSD_SIZE];
 	int fd, ret;
 	char *device;
 	int dry_run = 1;
@@ -635,7 +658,7 @@  int do_enh_area_set(int nargs, char **argv)
 int do_write_reliability_set(int nargs, char **argv)
 {
 	__u8 value;
-	__u8 ext_csd[512];
+	__u8 ext_csd[EXT_CSD_SIZE];
 	int fd, ret;
 
 	int dry_run = 1;
@@ -696,7 +719,7 @@  int do_write_reliability_set(int nargs, char **argv)
 
 int do_read_extcsd(int nargs, char **argv)
 {
-	__u8 ext_csd[512], ext_csd_rev, reg;
+	__u8 ext_csd[EXT_CSD_SIZE], ext_csd_rev, reg;
 	__u32 regl;
 	int fd, ret;
 	char *device;
@@ -722,24 +745,13 @@  int do_read_extcsd(int nargs, char **argv)
 	ext_csd_rev = ext_csd[192];
 
 	switch (ext_csd_rev) {
-	case 6:
-		str = "4.5";
-		break;
-	case 5:
-		str = "4.41";
-		break;
-	case 3:
-		str = "4.3";
-		break;
-	case 2:
-		str = "4.2";
-		break;
-	case 1:
-		str = "4.1";
-		break;
-	case 0:
-		str = "4.0";
-		break;
+	case 7: str = "5.0"; break;
+	case 6: str = "4.5"; break;
+	case 5: str = "4.41"; break;
+	case 3: str = "4.3"; break;
+	case 2: str = "4.2"; break;
+	case 1: str = "4.1"; break;
+	case 0: str = "4.0"; break;
 	default:
 		goto out_free;
 	}
@@ -1160,6 +1172,140 @@  int do_sanitize(int nargs, char **argv)
 	}
 
 	return ret;
-
 }
 
+int do_firmware_update(int nargs, char **argv)
+{
+	char *emmc_fw;
+	char *device;
+	char *fwfilename;
+	struct stat fwfilestat;
+	size_t fwsize;
+	int ret = 0;
+	int devfd,fwfd;
+	__u8 ext_csd[EXT_CSD_SIZE];
+	__u8 cid[CID_SIZE];
+	__u8 ext_csd_rev;
+
+
+	CHECK(nargs != 4, "Usage: mmc firmware update"
+			" --I_want_to_destroy_my_drive"
+			" </path/to/mmcblkX> </path/to/FW.bin>\n",
+			  exit(1));
+
+
+	/* Lesson from hdparm: user must be aware of the risks
+	 * Key here is the additional command line flag be UNDOCUMENTED.
+	 * User _must_ read and KNOW this is risky at runtime.
+	 */
+	if (strncmp(argv[4], "--I_want_to_destroy_my_drive", 28)) {
+		fprintf(stderr,"ERROR: Please specify --I_want_to_destroy_my_drive"
+		" as first parameter to firmware update.\n");
+
+		exit(1);
+	}
+
+	emmc_fw = malloc(MMC_IOC_MAX_BYTES);
+	if (!emmc_fw)
+		return -ENOMEM;
+
+	device = argv[5];
+
+	devfd = open(device, O_RDWR);
+	if (devfd < 0) {
+		fprintf(stderr,"ERROR: open %s: %m\n", device); /* %m = errno */
+		goto out_free;
+	}
+
+	/* 1) read device version and attributes */
+	ret = read_extcsd(devfd, ext_csd);
+	if (ret) {
+		fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n", device);
+		goto out_dev;
+	}
+
+	ext_csd_rev = ext_csd[192];
+
+	if (ext_csd_rev < 7) {
+		fprintf(stderr, "ERROR: Can not update firmware"
+			": %s is pre-emmc 5.0 vintage\n", device);
+		goto out_dev;
+	}
+
+	printf("%s: FW is currently %8s (0x%02x%02x%02x%02x%02x%02x%02x%02x)\n",
+		device, &(ext_csd[254]),
+		ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
+		ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
+		);
+
+	/* 2) confirm SUPPORTED_MODES has FFU bit set */
+	if (!(ext_csd[493] & 1)) {
+		fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
+			"but does not support FFU.\n", device);
+		goto out_dev;
+	}
+
+	/* 3) confirm FW updated is NOT disabled on this device */
+	if (ext_csd[169] & 1) {
+		fprintf(stderr, "ERROR: %s is eMMC 5.0 device"
+			"but FFU is disabled.\n", device);
+		goto out_dev;
+	}
+
+	/* 4) read the device manfid */
+	ret = read_cid(devfd, cid);
+	if (ret) {
+		fprintf(stderr, "ERROR: Read EXT_CSD from %s: %m\n", device);
+		goto out_dev;
+	}
+
+	/* 5) Fetch the FW image */
+	fwfilename = argv[2];
+
+	fwfd = open(fwfilename, O_RDONLY);
+	if (fwfd < 0) {
+		/* %m = errno */
+		fprintf(stderr,"ERROR: open %s: %m", fwfilename);
+		goto out_dev;
+	}
+
+	ret = fstat(fwfd, &fwfilestat);
+	if (ret) {
+		/* %m = errno */
+		fprintf(stderr,"ERROR: fstat %s: %m", fwfilename);
+		goto out_fw;
+	}
+
+	fwsize = fwfilestat.st_size;
+	if (fwsize > MMC_IOC_MAX_BYTES) {
+		fprintf(stderr,"ERROR: %s is > %ld bytes long (max allowed)\n",
+			fwfilename, MMC_IOC_MAX_BYTES);
+		goto out_fw;
+	}
+
+	ret = read(fwfd, emmc_fw, fwsize);
+	if (ret < fwsize) {
+		fprintf(stderr,"ERROR: did not read all %d bytes of %s"
+				"(%d bytes long)\n",
+			fwsize, fwfilename, ret);
+		goto out_fw;
+	}
+
+	do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize);
+
+	cid[13] = '\0';	/* make sure string is NULL terminated */
+
+	printf("%s: FW updated to %8s (0x%2x%2x%2x%2x%2x%2x%2x%2x)\n",
+		device, &(ext_csd[254]),
+		ext_csd[254], ext_csd[255], ext_csd[256], ext_csd[257],
+		ext_csd[258], ext_csd[259], ext_csd[260], ext_csd[261]
+		);
+
+out_fw:
+	close(fwfd);
+out_dev:
+	close(devfd);
+out_free:
+	free(emmc_fw);
+	return ret;
+}
diff --git a/mmc_cmds.h b/mmc_cmds.h
index f06cc10..549b851 100644
--- a/mmc_cmds.h
+++ b/mmc_cmds.h
@@ -28,3 +28,9 @@  int do_sanitize(int nargs, char **argv);
 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_firmware_update(int nargs, char **argv);
+
+int read_extcsd(int fd, __u8 *ext_csd);
+int write_extcsd_value(int fd, __u8 index, __u8 value);
+
+int do_emmc5_fw_update(int devfd, __u8 *cid, __u8 *ext_csd, char *emmc_fw, size_t fwsize);