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@xxxxxxxxxxxx> --- V3: - reference FFU_ARG instead of hardcoding 0x0000ffff - Set MODE_CONFIG[30] to 1 as part of CMD6 - fixed sequence numbers in comments - fix compile warning. - Still missing support for MODE_OPERATION_CODES This version can be cherry-picked into local branch with: git fetch https://chromium.googlesource.com/chromiumos/third_party/mmc-utils refs/changes/71/185471/1 && git cherry-pick FETCH_HEAD Gwendal Grignou has added eMMC 5.0 support to "mmc extcsd read" and I don't see that in Chris' mmc-utils tree. Might want to first grab: "Decode EXT_CSD of eMMC 5.0 device" git fetch https://chromium.googlesource.com/chromiumos/third_party/mmc-utils refs/changes/75/184175/5 && git cherry-pick FETCH_HEAD 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. ... .gitignore | 2 + Makefile | 4 +- mmc.c | 31 ++++++-- mmc.h | 29 +++++++- mmc_cmds-emmc5.c | 140 +++++++++++++++++++++++++++++++++++++ mmc_cmds.c | 210 ++++++++++++++++++++++++++++++++++++++++++++++++------- mmc_cmds.h | 6 ++ 7 files changed, 387 insertions(+), 35 deletions(-) create mode 100644 mmc_cmds-emmc5.c diff --git a/.gitignore b/.gitignore index 5a94d47..1de13cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .mmc.o.d .mmc_cmds.o.d +.mmc_cmds-emmc5.o.d mmc mmc.o mmc_cmds.o +mmc_cmds-emmc5.o 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..155fcad --- /dev/null +++ b/mmc_cmds-emmc5.c @@ -0,0 +1,140 @@ +/* + * 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" + + +/* ext_csd[] offsets specific to FFU */ +#define FFU_STATUS 26 +#define FFU_ARG 487 +#define FFU_FEATURES 492 +#define SUPPORTED_MODES 493 + +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; + + /* 1) Confirm device supports FFU */ + if ((ext_csd[SUPPORTED_MODES] & 1) == 0) { + fprintf(stderr, "eMMC 5.0 FFU not supported\n"); + return -EINVAL; + } + + /* 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; + } + + /* 3) send CMD25 with FFU_ARG */ + 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 = le32toh( *((__u32 *) &ext_csd[FFU_ARG])); + 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[FFU_STATUS]) { + 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[FFU_STATUS]); + 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: %zu," + " 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 4b9b12e..6673837 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; @@ -502,7 +525,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; @@ -640,7 +663,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; @@ -701,7 +724,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; @@ -1218,6 +1241,143 @@ 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 %c%c%c%c%c%c%c%c\n", + device, + 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 %zu bytes of %s" + "(%d bytes long)\n", + fwsize, fwfilename, ret); + goto out_fw; + } + + ret = do_emmc5_fw_update(devfd, cid, ext_csd, emmc_fw, fwsize); + if (ret) { + fprintf(stderr,"ERROR: emmc5_fw_update failed for %s: %d", fwfilename, ret); + goto out_fw; + } + 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.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html