All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
@ 2014-07-16 15:47 Avi Shchislowski
  2014-07-21 18:01 ` Gwendal Grignou
  2014-08-13 12:46 ` Ulf Hansson
  0 siblings, 2 replies; 17+ messages in thread
From: Avi Shchislowski @ 2014-07-16 15:47 UTC (permalink / raw)
  To: 'linux-mmc@vger.kernel.org', 'cjb@laptop.org'
  Cc: 'Grant Grundler', Alex Lemberg

The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
  http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc
  
An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.

Two new ioctls have been added:
1. FFU download firmware - transfer the new firmware data from user space to the eMMC device 2. FFU install - initializes the new firmware update


Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>

---
V8:
- Modified according to Gwendal Grignou comments and patch:
   [PATCH] Fix on top of sandisk patches
 
V7:
- fixed mangled white space

V5:
- provides udev (request_firmware) implementation as advised in patch
v2 comments.

diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308..19ba729 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -68,3 +68,11 @@ config MMC_TEST
 
 	  This driver is only of interest to those developing or
 	  testing a host driver. Most people should say N here.
+
+config MMC_FFU
+	bool "FFU SUPPORT"
+	depends on MMC != n
+	help
+	  This is an option to run firmware update on eMMC 5.0.
+	  Field firmware updates (FFU) enables features enhancment
+	  in the field.
diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406..1e9223b 100644
--- a/drivers/mmc/card/Makefile
+++ b/drivers/mmc/card/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)		+= mmc_test.o
 
 obj-$(CONFIG_SDIO_UART)		+= sdio_uart.o
 
+obj-$(CONFIG_MMC_FFU)		+= ffu.o
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f..3ed900b 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -41,6 +41,7 @@
 #include <linux/mmc/host.h>
 #include <linux/mmc/mmc.h>
 #include <linux/mmc/sd.h>
+#include <linux/mmc/ffu.h>
 
 #include <asm/uaccess.h>
 
@@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
 
 	mrq.cmd = &cmd;
 
+	if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
+			err = mmc_ffu_download(card, idata->buf);
+			goto cmd_done;
+		}
+
 	mmc_get_card(card);
 
+	if (cmd.opcode == MMC_FFU_INSTALL_OP) {
+		err = mmc_ffu_install(card);
+		goto cmd_rel_host;
+	}
+
 	err = mmc_blk_part_switch(card, md);
 	if (err)
 		goto cmd_rel_host;
diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 100644 index 0000000..783673e
--- /dev/null
+++ b/drivers/mmc/card/ffu.c
@@ -0,0 +1,607 @@
+/*
+ * *  ffu.c
+ *
+ *  Copyright 2007-2008 Pierre Ossman
+ *
+ *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or 
+(at
+ * your option) any later version.
+ *
+ * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
+ * slab.h, ffu.h & swap.h header files
+ * The original, unmodified version of this program - the mmc_test.c
+ * file - is obtained under the GPL v2.0 license that is available via
+ * http://www.gnu.org/licenses/,
+ * or http://www.opensource.org/licenses/gpl-2.0.php
+*/
+
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+#include <linux/mmc/ffu.h>
+#include <linux/firmware.h>
+
+/**
+ * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
+ * @page: first page in the allocation
+ * @order: order of the number of pages allocated  */ struct 
+mmc_ffu_pages {
+	struct page *page;
+	unsigned int order;
+};
+
+/**
+ * struct mmc_ffu_mem - allocated memory.
+ * @arr: array of allocations
+ * @cnt: number of allocations
+ */
+struct mmc_ffu_mem {
+	struct mmc_ffu_pages *arr;
+	unsigned int cnt;
+};
+
+struct mmc_ffu_area {
+	unsigned long max_sz;
+	unsigned int max_tfr;
+	unsigned int max_segs;
+	unsigned int max_seg_sz;
+	unsigned int blocks;
+	unsigned int sg_len;
+	struct mmc_ffu_mem *mem;
+	struct scatterlist *sg;
+};
+
+static void mmc_ffu_prepare_mrq(struct mmc_card *card,
+	struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
+	u32 arg, unsigned int blocks, unsigned int blksz) {
+	BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
+
+	if (blocks > 1)
+		mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
+	else
+		mrq->cmd->opcode = MMC_WRITE_BLOCK;
+
+	mrq->cmd->arg = arg;
+	if (!mmc_card_blockaddr(card))
+		mrq->cmd->arg <<= 9;
+
+	mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+	if (blocks == 1) {
+		mrq->stop = NULL;
+	} else {
+		mrq->stop->opcode = MMC_STOP_TRANSMISSION;
+		mrq->stop->arg = 0;
+		mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
+	}
+
+	mrq->data->blksz = blksz;
+	mrq->data->blocks = blocks;
+	mrq->data->flags = MMC_DATA_WRITE;
+	mrq->data->sg = sg;
+	mrq->data->sg_len = sg_len;
+
+	mmc_set_data_timeout(mrq->data, card); }
+
+/*
+ * Checks that a normal transfer didn't have any errors  */ static int 
+mmc_ffu_check_result(struct mmc_request *mrq) {
+	BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+	if (mrq->cmd->error != 0)
+		return -EINVAL;
+
+	if (mrq->data->error != 0)
+		return -EINVAL;
+
+	if (mrq->stop != NULL && mrq->stop->error != 0)
+		return -1;
+
+	if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int mmc_ffu_busy(struct mmc_command *cmd) {
+	return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
+		(R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
+
+static int mmc_ffu_wait_busy(struct mmc_card *card) {
+	int ret, busy = 0;
+	struct mmc_command cmd = {0};
+
+	memset(&cmd, 0, sizeof(struct mmc_command));
+	cmd.opcode = MMC_SEND_STATUS;
+	cmd.arg = card->rca << 16;
+	cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
+
+	do {
+		ret = mmc_wait_for_cmd(card->host, &cmd, 0);
+		if (ret)
+			break;
+
+		if (!busy && mmc_ffu_busy(&cmd)) {
+			busy = 1;
+			if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
+				pr_warn("%s: Warning: Host did not "
+					"wait for busy state to end.\n",
+					mmc_hostname(card->host));
+			}
+		}
+
+	} while (mmc_ffu_busy(&cmd));
+
+	return ret;
+}
+
+/*
+ * transfer with certain parameters
+ */
+static int mmc_ffu_simple_transfer(struct mmc_card *card,
+	struct scatterlist *sg, unsigned int sg_len, u32 arg,
+	unsigned int blocks, unsigned int blksz) {
+	struct mmc_request mrq = {0};
+	struct mmc_command cmd = {0};
+	struct mmc_command stop = {0};
+	struct mmc_data data = {0};
+
+	mrq.cmd = &cmd;
+	mrq.data = &data;
+	mrq.stop = &stop;
+	mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
+	mmc_wait_for_req(card->host, &mrq);
+
+	mmc_ffu_wait_busy(card);
+
+	return mmc_ffu_check_result(&mrq);
+}
+
+/*
+ * Map memory into a scatterlist.
+ */
+static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
+	struct scatterlist *sglist, unsigned int max_segs,
+	unsigned int max_seg_sz)
+{
+	struct scatterlist *sg = sglist;
+	unsigned int i;
+	unsigned long sz = size;
+	unsigned int sctr_len = 0;
+	unsigned long len;
+
+	sg_init_table(sglist, max_segs);
+
+	for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
+		len = PAGE_SIZE * (1 << mem->arr[i].order);
+
+		if (len > sz) {
+			len = sz;
+			sz = 0;
+		}
+
+		sg_set_page(sg, mem->arr[i].page, len, 0);
+		sg = sg_next(sg);
+		sctr_len += 1;
+	}
+	sg_mark_end(sg);
+
+	return sctr_len;
+}
+
+static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
+	if (!mem)
+		return;
+
+	while (mem->cnt--)
+		__free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
+
+	kfree(mem->arr);
+	kfree(mem);
+}
+
+/*
+ * Cleanup struct mmc_ffu_area.
+ */
+static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
+	kfree(area->sg);
+	mmc_ffu_free_mem(area->mem);
+
+	return 0;
+}
+
+/*
+ * Allocate a lot of memory, preferably max_sz but at least min_sz. In 
+case
+ * there isn't much memory do not exceed 1/16th total low mem pages. 
+Also do
+ * not exceed a maximum number of segments and try not to make segments 
+much
+ * bigger than maximum segment size.
+ */
+static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
+	unsigned long max_sz, unsigned int max_segs, unsigned int max_seg_sz) 
+{
+	unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
+	unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
+	unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
+	unsigned long page_cnt = 0;
+	/* we divide by 16 to ensure we will not allocate a big amount
+	 * of unnecessary pages */
+	unsigned long limit = nr_free_buffer_pages() >> 4;
+	struct mmc_ffu_mem *mem;
+	gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
+
+	if (max_page_cnt > limit)
+		max_page_cnt = limit;
+
+	if (min_page_cnt > max_page_cnt)
+		min_page_cnt = max_page_cnt;
+
+	if (max_segs * max_seg_page_cnt > max_page_cnt)
+		max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
+
+	mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
+	if (!mem)
+		return NULL;
+
+	mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
+		GFP_KERNEL);
+	if (!mem->arr)
+		goto out_free;
+
+	while (max_page_cnt) {
+		struct page *page;
+		unsigned int order;
+
+		order = get_order(max_seg_page_cnt << PAGE_SHIFT);
+
+		do {
+			page = alloc_pages(flags, order);
+		} while (!page && order--);
+
+		if (!page)
+			goto out_free;
+
+		mem->arr[mem->cnt].page = page;
+		mem->arr[mem->cnt].order = order;
+		mem->cnt += 1;
+		if (max_page_cnt <= (1UL << order))
+			break;
+		max_page_cnt -= 1UL << order;
+		page_cnt += 1UL << order;
+	}
+
+	if (page_cnt < min_page_cnt)
+		goto out_free;
+
+	return mem;
+
+out_free:
+	mmc_ffu_free_mem(mem);
+	return NULL;
+}
+
+/*
+ * Initialize an area for data transfers.
+ * Copy the data to the allocated pages.
+ */
+static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
+	const u8 *data, int size)
+{
+	int ret;
+	int i;
+	int length = 0;
+
+	area->max_tfr = size;
+
+	/* Try to allocate enough memory for a max. sized transfer. Less is OK
+	 * because the same memory can be mapped into the scatterlist more than
+	 * once. Also, take into account the limits imposed on scatterlist
+	 * segments by the host driver.
+	 */
+	area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
+		area->max_seg_sz);
+	if (!area->mem)
+		return -ENOMEM;
+
+	/* copy data to page */
+	for (i = 0; i < area->mem->cnt; i++) {
+		if (length > size) {
+			ret = -EINVAL;
+			goto out_free;
+		}
+
+		memcpy(page_address(area->mem->arr[i].page), data + length,
+			min(size - length, (int)area->max_seg_sz));
+		length += area->max_seg_sz;
+	}
+
+	area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
+		GFP_KERNEL);
+	if (!area->sg) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
+		area->max_segs, area->mem->cnt);
+
+	return 0;
+
+out_free:
+	mmc_ffu_area_cleanup(area);
+	return ret;
+}
+
+static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
+	int size)
+{
+	int rc;
+	struct mmc_ffu_area area;
+	int max_tfr;
+
+	area.sg = NULL;
+	area.mem = NULL;
+	area.max_segs = card->host->max_segs;
+	area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
+	do {
+		max_tfr = size;
+		if (max_tfr >> 9 > card->host->max_blk_count)
+			max_tfr = card->host->max_blk_count << 9;
+		if (max_tfr > card->host->max_req_size)
+			max_tfr = card->host->max_req_size;
+		if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
+			max_tfr = area.max_segs * area.max_seg_sz;
+
+		rc = mmc_ffu_area_init(&area, card, src, max_tfr);
+		if (rc != 0)
+			goto exit;
+
+		rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
+			max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
+		if (rc != 0)
+			goto exit;
+
+		src += max_tfr;
+		size -= max_tfr;
+	} while (size > 0);
+
+exit:
+	mmc_ffu_area_cleanup(&area);
+	return rc;
+}
+
+/* Flush all scheduled work from the MMC work queue.
+ * and initialize the MMC device */
+static int mmc_ffu_restart(struct mmc_card *card) {
+	struct mmc_host *host = card->host;
+	int err = 0;
+
+	mmc_cache_ctrl(host, 0);
+	err = mmc_power_save_host(host);
+	if (err) {
+		pr_warn("%s: going to sleep failed (%d)!!!\n",
+			__func__ , err);
+		goto exit;
+	}
+
+	err = mmc_power_restore_host(host);
+
+exit:
+
+	return err;
+}
+
+int mmc_ffu_download(struct mmc_card *card, const char *name) {
+	u8 ext_csd[CARD_BLOCK_SIZE];
+	int err;
+	int ret;
+	u32 arg;
+	u32 fw_prog_bytes;
+	const struct firmware *fw;
+
+	/* Check if FFU is supported */
+	if (!card->ext_csd.ffu_capable) {
+		pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
+			mmc_hostname(card->host), card->ext_csd.ffu_capable,
+			card->ext_csd.rev);
+		return -EOPNOTSUPP;
+	}
+
+	if (strlen(name) > 512) {
+		pr_err("FFU: %s: %.20s is not a valid argument\n",
+			mmc_hostname(card->host), name);
+		return -EINVAL;
+	}
+
+	/* setup FW data buffer */
+	err = request_firmware(&fw, name, &card->dev);
+	if (err) {
+		pr_err("FFU: %s: Firmware request failed %d\n",
+			mmc_hostname(card->host), err);
+		return err;
+	}
+	if ((fw->size % CARD_BLOCK_SIZE)) {
+			pr_warn("FFU: %s: Warning %zd firmware data size "
+			"is not aligned!!!\n",	mmc_hostname(card->host),
+			fw->size);
+	}
+
+	mmc_get_card(card);
+
+	/* set device to FFU mode */
+	err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
+		MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
+	if (err) {
+		pr_err("FFU: %s: error %d FFU is not supported\n",
+			mmc_hostname(card->host), err);
+		goto exit;
+	}
+
+	/* Read the EXT_CSD */
+	err = mmc_send_ext_csd(card, ext_csd);
+	if (err) {
+		pr_err("FFU: %s: error %d sending ext_csd\n",
+			mmc_hostname(card->host), err);
+		goto exit;
+	}
+
+	/* set CMD ARG */
+	arg = ext_csd[EXT_CSD_FFU_ARG] |
+		ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
+		ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
+		ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
+
+	err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
+
+	/* host switch back to work in normal MMC Read/Write commands */
+	ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+		EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
+		card->ext_csd.generic_cmd6_time);
+	if (ret) {
+		err = ret;
+		goto exit;
+	}
+
+	/* Read the EXT_CSD */
+	err = mmc_send_ext_csd(card, ext_csd);
+	if (err) {
+		pr_err("FFU: %s: error %d sending ext_csd\n",
+			mmc_hostname(card->host), err);
+		goto exit;
+	}
+
+	/* check that the eMMC has received the payload */
+	fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
+		ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
+		ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
+		ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
+
+	/* convert sector to bytes */
+	 fw_prog_bytes *=
+		CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
+	if (fw_prog_bytes != fw->size) {
+		err = -EINVAL;
+		pr_err("FFU: %s: error %d number of programmed fw sector\n",
+			mmc_hostname(card->host), err);
+	}
+
+exit:
+	release_firmware(fw);
+	mmc_put_card(card);
+	return err;
+}
+EXPORT_SYMBOL(mmc_ffu_download);
+
+int mmc_ffu_install(struct mmc_card *card) {
+	u8 ext_csd[CARD_BLOCK_SIZE];
+	int err;
+	u32 ffu_data_len;
+	u32 timeout;
+
+	/* Check if FFU is supported */
+	if (!card->ext_csd.ffu_capable) {
+		pr_err("FFU: %s: error FFU is not supported\n",
+			mmc_hostname(card->host));
+		return -EOPNOTSUPP;
+	}
+
+	err = mmc_send_ext_csd(card, ext_csd);
+	if (err) {
+		pr_err("FFU: %s: error %d sending ext_csd\n",
+			mmc_hostname(card->host), err);
+		return err;
+	}
+
+	/* check mode operation */
+	if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
+		/* restart the eMMC */
+		err = mmc_ffu_restart(card);
+		if (err) {
+			pr_err("FFU: %s: install error %d:\n",
+				mmc_hostname(card->host), err);
+			return err;
+		}
+	} else {
+
+		ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
+			ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
+			ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
+			ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
+
+		if (!ffu_data_len) {
+			err = -EPERM;
+			return err;
+		}
+		/* set device to FFU mode */
+		err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+			EXT_CSD_MODE_CONFIG, 0x1,
+			card->ext_csd.generic_cmd6_time);
+
+		if (err) {
+			pr_err("FFU: %s: error %d FFU is not supported\n",
+				mmc_hostname(card->host), err);
+			return err;
+		}
+
+		timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
+		if (timeout == 0 || timeout > 0x17) {
+			timeout = 0x17;
+			pr_warn("FFU: %s: operation code timeout is out "
+				"of range. Using maximum timeout.\n",
+				mmc_hostname(card->host));
+		}
+
+		/* timeout is at millisecond resolution */
+		timeout = (100 * (1 << timeout) / 1000) + 1;
+
+		/* set ext_csd to install mode */
+		err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+			EXT_CSD_MODE_OPERATION_CODES,
+			MMC_FFU_INSTALL_SET, timeout);
+
+		if (err) {
+			pr_err("FFU: %s: error %d setting install mode\n",
+				mmc_hostname(card->host), err);
+			return err;
+		}
+	}
+
+	/* read ext_csd */
+	err = mmc_send_ext_csd(card, ext_csd);
+	if (err) {
+		pr_err("FFU: %s: error %d sending ext_csd\n",
+			mmc_hostname(card->host), err);
+		return err;
+	}
+
+	/* return status */
+	err = ext_csd[EXT_CSD_FFU_STATUS];
+	if (err) {
+		pr_err("FFU: %s: error %d FFU install:\n",
+			mmc_hostname(card->host), err);
+		return  -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(mmc_ffu_install);
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0..a29065a 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
 		card->ext_csd.data_sector_size = 512;
 	}
 
+	/* eMMC v5 or later */
+	if (card->ext_csd.rev >= 7) {
+		card->ext_csd.ffu_capable =
+			((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
+			((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
+	} else {
+		card->ext_csd.ffu_capable = false;
+	}
 out:
 	return err;
 }
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b730272..bc6f6d0 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -87,6 +87,7 @@ struct mmc_ext_csd {
 	unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
 	unsigned int		boot_ro_lock;		/* ro lock support */
 	bool			boot_ro_lockable;
+	bool		ffu_capable;	/* FFU support */
 	u8			raw_exception_status;	/* 54 */
 	u8			raw_partition_support;	/* 160 */
 	u8			raw_rpmb_size_mult;	/* 168 */
diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..7e4133d
--- /dev/null
+++ b/include/linux/mmc/ffu.h
@@ -0,0 +1,51 @@
+/*
+ *
+ *  ffu.h
+ *
+ * Copyright (c) 2013 SanDisk Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or 
+(at
+ * your option) any later version.
+ *
+ * This program was created by SanDisk Corp
+ * The ffu.h file is obtained under the GPL v2.0 license that is
+ * available via http://www.gnu.org/licenses/,
+ * or http://www.opensource.org/licenses/gpl-2.0.php
+*/
+
+#if !defined(_FFU_H_)
+#define _FFU_H_
+
+#include <linux/mmc/card.h>
+
+#define CARD_BLOCK_SIZE 512
+
+/*
+ * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define 
+MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
+
+#define MMC_FFU_MODE_SET 0x1
+#define MMC_FFU_MODE_NORMAL 0x0
+#define MMC_FFU_INSTALL_SET 0x1
+
+#ifdef CONFIG_MMC_FFU
+#define MMC_FFU_FEATURES 0x1
+#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
+
+int mmc_ffu_download(struct mmc_card *card, const char *name); int 
+mmc_ffu_install(struct mmc_card *card); #else static inline int 
+mmc_ffu_download(struct mmc_card *card, const char *name) {
+	return -ENOSYS;
+}
+static inline int mmc_ffu_install(struct mmc_card *card) {
+	return -ENOSYS;
+}
+#endif
+#endif /* FFU_H_ */
diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50bcde3..3651449 100644
--- a/include/linux/mmc/mmc.h
+++ b/include/linux/mmc/mmc.h
@@ -272,6 +272,9 @@ struct _mmc_csd {
  * EXT_CSD fields
  */
 
+#define EXT_CSD_FFU_STATUS		26	/* R */
+#define EXT_CSD_MODE_OPERATION_CODES	29	/* W */
+#define EXT_CSD_MODE_CONFIG		30	/* R/W */
 #define EXT_CSD_FLUSH_CACHE		32      /* W */
 #define EXT_CSD_CACHE_CTRL		33      /* R/W */
 #define EXT_CSD_POWER_OFF_NOTIFICATION	34	/* R/W */
@@ -290,6 +293,7 @@ struct _mmc_csd {
 #define EXT_CSD_SANITIZE_START		165     /* W */
 #define EXT_CSD_WR_REL_PARAM		166	/* RO */
 #define EXT_CSD_RPMB_MULT		168	/* RO */
+#define EXT_CSD_FW_CONFIG		169	/* R/W */
 #define EXT_CSD_BOOT_WP			173	/* R/W */
 #define EXT_CSD_ERASE_GROUP_DEF		175	/* R/W */
 #define EXT_CSD_PART_CONFIG		179	/* R/W */
@@ -325,6 +329,11 @@ struct _mmc_csd {
 #define EXT_CSD_POWER_OFF_LONG_TIME	247	/* RO */
 #define EXT_CSD_GENERIC_CMD6_TIME	248	/* RO */
 #define EXT_CSD_CACHE_SIZE		249	/* RO, 4 bytes */
+#define EXT_CSD_NUM_OF_FW_SEC_PROG	302	/* RO, 4 bytes */
+#define EXT_CSD_FFU_ARG			487	/* RO, 4 bytes */
+#define EXT_CSD_OPERATION_CODE_TIMEOUT	491	/* RO */
+#define EXT_CSD_FFU_FEATURES		492	/* RO */
+#define EXT_CSD_SUPPORTED_MODE		493	/* RO */
 #define EXT_CSD_TAG_UNIT_SIZE		498	/* RO */
 #define EXT_CSD_DATA_TAG_SUPPORT	499	/* RO */
 #define EXT_CSD_MAX_PACKED_WRITES	500	/* RO */
--
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


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-16 15:47 [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0 Avi Shchislowski
@ 2014-07-21 18:01 ` Gwendal Grignou
  2014-07-30  0:14   ` Gwendal Grignou
  2014-08-13 12:46 ` Ulf Hansson
  1 sibling, 1 reply; 17+ messages in thread
From: Gwendal Grignou @ 2014-07-21 18:01 UTC (permalink / raw)
  To: Avi Shchislowski; +Cc: linux-mmc, cjb, Grant Grundler, Alex Lemberg

Avi,

The patch still does not work for me. After the upgrade, the eMMC is
not in a good state timing out every IOs. A power cycle fixes the
problem.
I am investigating how the reset patch is working.

Gwendal.


On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski
<Avi.Shchislowski@sandisk.com> wrote:
>
> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>   http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc
>
> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>
> Two new ioctls have been added:
> 1. FFU download firmware - transfer the new firmware data from user space to the eMMC device 2. FFU install - initializes the new firmware update
>
>
> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>
> ---
> V8:
> - Modified according to Gwendal Grignou comments and patch:
>    [PATCH] Fix on top of sandisk patches
>
> V7:
> - fixed mangled white space
>
> V5:
> - provides udev (request_firmware) implementation as advised in patch
> v2 comments.
>
> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308..19ba729 100644
> --- a/drivers/mmc/card/Kconfig
> +++ b/drivers/mmc/card/Kconfig
> @@ -68,3 +68,11 @@ config MMC_TEST
>
>           This driver is only of interest to those developing or
>           testing a host driver. Most people should say N here.
> +
> +config MMC_FFU
> +       bool "FFU SUPPORT"
> +       depends on MMC != n
> +       help
> +         This is an option to run firmware update on eMMC 5.0.
> +         Field firmware updates (FFU) enables features enhancment
> +         in the field.
> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406..1e9223b 100644
> --- a/drivers/mmc/card/Makefile
> +++ b/drivers/mmc/card/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>
>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>
> +obj-$(CONFIG_MMC_FFU)          += ffu.o
> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f..3ed900b 100644
> --- a/drivers/mmc/card/block.c
> +++ b/drivers/mmc/card/block.c
> @@ -41,6 +41,7 @@
>  #include <linux/mmc/host.h>
>  #include <linux/mmc/mmc.h>
>  #include <linux/mmc/sd.h>
> +#include <linux/mmc/ffu.h>
>
>  #include <asm/uaccess.h>
>
> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>
>         mrq.cmd = &cmd;
>
> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> +                       err = mmc_ffu_download(card, idata->buf);
> +                       goto cmd_done;
> +               }
> +
>         mmc_get_card(card);
>
> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
> +               err = mmc_ffu_install(card);
> +               goto cmd_rel_host;
> +       }
> +
>         err = mmc_blk_part_switch(card, md);
>         if (err)
>                 goto cmd_rel_host;
> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 100644 index 0000000..783673e
> --- /dev/null
> +++ b/drivers/mmc/card/ffu.c
> @@ -0,0 +1,607 @@
> +/*
> + * *  ffu.c
> + *
> + *  Copyright 2007-2008 Pierre Ossman
> + *
> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> +(at
> + * your option) any later version.
> + *
> + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
> + * slab.h, ffu.h & swap.h header files
> + * The original, unmodified version of this program - the mmc_test.c
> + * file - is obtained under the GPL v2.0 license that is available via
> + * http://www.gnu.org/licenses/,
> + * or http://www.opensource.org/licenses/gpl-2.0.php
> +*/
> +
> +#include <linux/bug.h>
> +#include <linux/errno.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/scatterlist.h>
> +#include <linux/slab.h>
> +#include <linux/swap.h>
> +#include <linux/mmc/ffu.h>
> +#include <linux/firmware.h>
> +
> +/**
> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
> + * @page: first page in the allocation
> + * @order: order of the number of pages allocated  */ struct
> +mmc_ffu_pages {
> +       struct page *page;
> +       unsigned int order;
> +};
> +
> +/**
> + * struct mmc_ffu_mem - allocated memory.
> + * @arr: array of allocations
> + * @cnt: number of allocations
> + */
> +struct mmc_ffu_mem {
> +       struct mmc_ffu_pages *arr;
> +       unsigned int cnt;
> +};
> +
> +struct mmc_ffu_area {
> +       unsigned long max_sz;
> +       unsigned int max_tfr;
> +       unsigned int max_segs;
> +       unsigned int max_seg_sz;
> +       unsigned int blocks;
> +       unsigned int sg_len;
> +       struct mmc_ffu_mem *mem;
> +       struct scatterlist *sg;
> +};
> +
> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
> +       u32 arg, unsigned int blocks, unsigned int blksz) {
> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> +
> +       if (blocks > 1)
> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
> +       else
> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
> +
> +       mrq->cmd->arg = arg;
> +       if (!mmc_card_blockaddr(card))
> +               mrq->cmd->arg <<= 9;
> +
> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> +       if (blocks == 1) {
> +               mrq->stop = NULL;
> +       } else {
> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
> +               mrq->stop->arg = 0;
> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
> +       }
> +
> +       mrq->data->blksz = blksz;
> +       mrq->data->blocks = blocks;
> +       mrq->data->flags = MMC_DATA_WRITE;
> +       mrq->data->sg = sg;
> +       mrq->data->sg_len = sg_len;
> +
> +       mmc_set_data_timeout(mrq->data, card); }
> +
> +/*
> + * Checks that a normal transfer didn't have any errors  */ static int
> +mmc_ffu_check_result(struct mmc_request *mrq) {
> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
> +
> +       if (mrq->cmd->error != 0)
> +               return -EINVAL;
> +
> +       if (mrq->data->error != 0)
> +               return -EINVAL;
> +
> +       if (mrq->stop != NULL && mrq->stop->error != 0)
> +               return -1;
> +
> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static int mmc_ffu_busy(struct mmc_command *cmd) {
> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
> +
> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
> +       int ret, busy = 0;
> +       struct mmc_command cmd = {0};
> +
> +       memset(&cmd, 0, sizeof(struct mmc_command));
> +       cmd.opcode = MMC_SEND_STATUS;
> +       cmd.arg = card->rca << 16;
> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
> +
> +       do {
> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
> +               if (ret)
> +                       break;
> +
> +               if (!busy && mmc_ffu_busy(&cmd)) {
> +                       busy = 1;
> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
> +                               pr_warn("%s: Warning: Host did not "
> +                                       "wait for busy state to end.\n",
> +                                       mmc_hostname(card->host));
> +                       }
> +               }
> +
> +       } while (mmc_ffu_busy(&cmd));
> +
> +       return ret;
> +}
> +
> +/*
> + * transfer with certain parameters
> + */
> +static int mmc_ffu_simple_transfer(struct mmc_card *card,
> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
> +       unsigned int blocks, unsigned int blksz) {
> +       struct mmc_request mrq = {0};
> +       struct mmc_command cmd = {0};
> +       struct mmc_command stop = {0};
> +       struct mmc_data data = {0};
> +
> +       mrq.cmd = &cmd;
> +       mrq.data = &data;
> +       mrq.stop = &stop;
> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
> +       mmc_wait_for_req(card->host, &mrq);
> +
> +       mmc_ffu_wait_busy(card);
> +
> +       return mmc_ffu_check_result(&mrq);
> +}
> +
> +/*
> + * Map memory into a scatterlist.
> + */
> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
> +       struct scatterlist *sglist, unsigned int max_segs,
> +       unsigned int max_seg_sz)
> +{
> +       struct scatterlist *sg = sglist;
> +       unsigned int i;
> +       unsigned long sz = size;
> +       unsigned int sctr_len = 0;
> +       unsigned long len;
> +
> +       sg_init_table(sglist, max_segs);
> +
> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
> +
> +               if (len > sz) {
> +                       len = sz;
> +                       sz = 0;
> +               }
> +
> +               sg_set_page(sg, mem->arr[i].page, len, 0);
> +               sg = sg_next(sg);
> +               sctr_len += 1;
> +       }
> +       sg_mark_end(sg);
> +
> +       return sctr_len;
> +}
> +
> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
> +       if (!mem)
> +               return;
> +
> +       while (mem->cnt--)
> +               __free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
> +
> +       kfree(mem->arr);
> +       kfree(mem);
> +}
> +
> +/*
> + * Cleanup struct mmc_ffu_area.
> + */
> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
> +       kfree(area->sg);
> +       mmc_ffu_free_mem(area->mem);
> +
> +       return 0;
> +}
> +
> +/*
> + * Allocate a lot of memory, preferably max_sz but at least min_sz. In
> +case
> + * there isn't much memory do not exceed 1/16th total low mem pages.
> +Also do
> + * not exceed a maximum number of segments and try not to make segments
> +much
> + * bigger than maximum segment size.
> + */
> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
> +       unsigned long max_sz, unsigned int max_segs, unsigned int max_seg_sz)
> +{
> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
> +       unsigned long page_cnt = 0;
> +       /* we divide by 16 to ensure we will not allocate a big amount
> +        * of unnecessary pages */
> +       unsigned long limit = nr_free_buffer_pages() >> 4;
> +       struct mmc_ffu_mem *mem;
> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
> +
> +       if (max_page_cnt > limit)
> +               max_page_cnt = limit;
> +
> +       if (min_page_cnt > max_page_cnt)
> +               min_page_cnt = max_page_cnt;
> +
> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
> +               max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
> +
> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
> +       if (!mem)
> +               return NULL;
> +
> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
> +               GFP_KERNEL);
> +       if (!mem->arr)
> +               goto out_free;
> +
> +       while (max_page_cnt) {
> +               struct page *page;
> +               unsigned int order;
> +
> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
> +
> +               do {
> +                       page = alloc_pages(flags, order);
> +               } while (!page && order--);
> +
> +               if (!page)
> +                       goto out_free;
> +
> +               mem->arr[mem->cnt].page = page;
> +               mem->arr[mem->cnt].order = order;
> +               mem->cnt += 1;
> +               if (max_page_cnt <= (1UL << order))
> +                       break;
> +               max_page_cnt -= 1UL << order;
> +               page_cnt += 1UL << order;
> +       }
> +
> +       if (page_cnt < min_page_cnt)
> +               goto out_free;
> +
> +       return mem;
> +
> +out_free:
> +       mmc_ffu_free_mem(mem);
> +       return NULL;
> +}
> +
> +/*
> + * Initialize an area for data transfers.
> + * Copy the data to the allocated pages.
> + */
> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
> +       const u8 *data, int size)
> +{
> +       int ret;
> +       int i;
> +       int length = 0;
> +
> +       area->max_tfr = size;
> +
> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
> +        * because the same memory can be mapped into the scatterlist more than
> +        * once. Also, take into account the limits imposed on scatterlist
> +        * segments by the host driver.
> +        */
> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
> +               area->max_seg_sz);
> +       if (!area->mem)
> +               return -ENOMEM;
> +
> +       /* copy data to page */
> +       for (i = 0; i < area->mem->cnt; i++) {
> +               if (length > size) {
> +                       ret = -EINVAL;
> +                       goto out_free;
> +               }
> +
> +               memcpy(page_address(area->mem->arr[i].page), data + length,
> +                       min(size - length, (int)area->max_seg_sz));
> +               length += area->max_seg_sz;
> +       }
> +
> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
> +               GFP_KERNEL);
> +       if (!area->sg) {
> +               ret = -ENOMEM;
> +               goto out_free;
> +       }
> +
> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
> +               area->max_segs, area->mem->cnt);
> +
> +       return 0;
> +
> +out_free:
> +       mmc_ffu_area_cleanup(area);
> +       return ret;
> +}
> +
> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
> +       int size)
> +{
> +       int rc;
> +       struct mmc_ffu_area area;
> +       int max_tfr;
> +
> +       area.sg = NULL;
> +       area.mem = NULL;
> +       area.max_segs = card->host->max_segs;
> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
> +       do {
> +               max_tfr = size;
> +               if (max_tfr >> 9 > card->host->max_blk_count)
> +                       max_tfr = card->host->max_blk_count << 9;
> +               if (max_tfr > card->host->max_req_size)
> +                       max_tfr = card->host->max_req_size;
> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
> +                       max_tfr = area.max_segs * area.max_seg_sz;
> +
> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
> +               if (rc != 0)
> +                       goto exit;
> +
> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
> +               if (rc != 0)
> +                       goto exit;
> +
> +               src += max_tfr;
> +               size -= max_tfr;
> +       } while (size > 0);
> +
> +exit:
> +       mmc_ffu_area_cleanup(&area);
> +       return rc;
> +}
> +
> +/* Flush all scheduled work from the MMC work queue.
> + * and initialize the MMC device */
> +static int mmc_ffu_restart(struct mmc_card *card) {
> +       struct mmc_host *host = card->host;
> +       int err = 0;
> +
> +       mmc_cache_ctrl(host, 0);
> +       err = mmc_power_save_host(host);
> +       if (err) {
> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
> +                       __func__ , err);
> +               goto exit;
> +       }
> +
> +       err = mmc_power_restore_host(host);
> +
> +exit:
> +
> +       return err;
> +}
> +
> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
> +       u8 ext_csd[CARD_BLOCK_SIZE];
> +       int err;
> +       int ret;
> +       u32 arg;
> +       u32 fw_prog_bytes;
> +       const struct firmware *fw;
> +
> +       /* Check if FFU is supported */
> +       if (!card->ext_csd.ffu_capable) {
> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
> +                       card->ext_csd.rev);
> +               return -EOPNOTSUPP;
> +       }
> +
> +       if (strlen(name) > 512) {
> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
> +                       mmc_hostname(card->host), name);
> +               return -EINVAL;
> +       }
> +
> +       /* setup FW data buffer */
> +       err = request_firmware(&fw, name, &card->dev);
> +       if (err) {
> +               pr_err("FFU: %s: Firmware request failed %d\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +       if ((fw->size % CARD_BLOCK_SIZE)) {
> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
> +                       fw->size);
> +       }
> +
> +       mmc_get_card(card);
> +
> +       /* set device to FFU mode */
> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
> +       if (err) {
> +               pr_err("FFU: %s: error %d FFU is not supported\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* Read the EXT_CSD */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* set CMD ARG */
> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
> +
> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
> +
> +       /* host switch back to work in normal MMC Read/Write commands */
> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
> +               card->ext_csd.generic_cmd6_time);
> +       if (ret) {
> +               err = ret;
> +               goto exit;
> +       }
> +
> +       /* Read the EXT_CSD */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* check that the eMMC has received the payload */
> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> +
> +       /* convert sector to bytes */
> +        fw_prog_bytes *=
> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
> +       if (fw_prog_bytes != fw->size) {
> +               err = -EINVAL;
> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
> +                       mmc_hostname(card->host), err);
> +       }
> +
> +exit:
> +       release_firmware(fw);
> +       mmc_put_card(card);
> +       return err;
> +}
> +EXPORT_SYMBOL(mmc_ffu_download);
> +
> +int mmc_ffu_install(struct mmc_card *card) {
> +       u8 ext_csd[CARD_BLOCK_SIZE];
> +       int err;
> +       u32 ffu_data_len;
> +       u32 timeout;
> +
> +       /* Check if FFU is supported */
> +       if (!card->ext_csd.ffu_capable) {
> +               pr_err("FFU: %s: error FFU is not supported\n",
> +                       mmc_hostname(card->host));
> +               return -EOPNOTSUPP;
> +       }
> +
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +
> +       /* check mode operation */
> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
> +               /* restart the eMMC */
> +               err = mmc_ffu_restart(card);
> +               if (err) {
> +                       pr_err("FFU: %s: install error %d:\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +       } else {
> +
> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> +
> +               if (!ffu_data_len) {
> +                       err = -EPERM;
> +                       return err;
> +               }
> +               /* set device to FFU mode */
> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +                       EXT_CSD_MODE_CONFIG, 0x1,
> +                       card->ext_csd.generic_cmd6_time);
> +
> +               if (err) {
> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +
> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
> +               if (timeout == 0 || timeout > 0x17) {
> +                       timeout = 0x17;
> +                       pr_warn("FFU: %s: operation code timeout is out "
> +                               "of range. Using maximum timeout.\n",
> +                               mmc_hostname(card->host));
> +               }
> +
> +               /* timeout is at millisecond resolution */
> +               timeout = (100 * (1 << timeout) / 1000) + 1;
> +
> +               /* set ext_csd to install mode */
> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +                       EXT_CSD_MODE_OPERATION_CODES,
> +                       MMC_FFU_INSTALL_SET, timeout);
> +
> +               if (err) {
> +                       pr_err("FFU: %s: error %d setting install mode\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +       }
> +
> +       /* read ext_csd */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +
> +       /* return status */
> +       err = ext_csd[EXT_CSD_FFU_STATUS];
> +       if (err) {
> +               pr_err("FFU: %s: error %d FFU install:\n",
> +                       mmc_hostname(card->host), err);
> +               return  -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(mmc_ffu_install);
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0..a29065a 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>                 card->ext_csd.data_sector_size = 512;
>         }
>
> +       /* eMMC v5 or later */
> +       if (card->ext_csd.rev >= 7) {
> +               card->ext_csd.ffu_capable =
> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
> +       } else {
> +               card->ext_csd.ffu_capable = false;
> +       }
>  out:
>         return err;
>  }
> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b730272..bc6f6d0 100644
> --- a/include/linux/mmc/card.h
> +++ b/include/linux/mmc/card.h
> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>         unsigned int            boot_ro_lock;           /* ro lock support */
>         bool                    boot_ro_lockable;
> +       bool            ffu_capable;    /* FFU support */
>         u8                      raw_exception_status;   /* 54 */
>         u8                      raw_partition_support;  /* 160 */
>         u8                      raw_rpmb_size_mult;     /* 168 */
> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..7e4133d
> --- /dev/null
> +++ b/include/linux/mmc/ffu.h
> @@ -0,0 +1,51 @@
> +/*
> + *
> + *  ffu.h
> + *
> + * Copyright (c) 2013 SanDisk Corp.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> +(at
> + * your option) any later version.
> + *
> + * This program was created by SanDisk Corp
> + * The ffu.h file is obtained under the GPL v2.0 license that is
> + * available via http://www.gnu.org/licenses/,
> + * or http://www.opensource.org/licenses/gpl-2.0.php
> +*/
> +
> +#if !defined(_FFU_H_)
> +#define _FFU_H_
> +
> +#include <linux/mmc/card.h>
> +
> +#define CARD_BLOCK_SIZE 512
> +
> +/*
> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
Something is wrong here. This patch would not compile, some carriage
returns are missing.
> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
Here too.
> +
> +#define MMC_FFU_MODE_SET 0x1
> +#define MMC_FFU_MODE_NORMAL 0x0
> +#define MMC_FFU_INSTALL_SET 0x1
> +
> +#ifdef CONFIG_MMC_FFU
> +#define MMC_FFU_FEATURES 0x1
> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
> +
> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
> +mmc_ffu_install(struct mmc_card *card); #else static inline int
Here too.
> +mmc_ffu_download(struct mmc_card *card, const char *name) {
> +       return -ENOSYS;
> +}
> +static inline int mmc_ffu_install(struct mmc_card *card) {
> +       return -ENOSYS;
> +}
> +#endif
> +#endif /* FFU_H_ */
> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50bcde3..3651449 100644
> --- a/include/linux/mmc/mmc.h
> +++ b/include/linux/mmc/mmc.h
> @@ -272,6 +272,9 @@ struct _mmc_csd {
>   * EXT_CSD fields
>   */
>
> +#define EXT_CSD_FFU_STATUS             26      /* R */
> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
> @@ -290,6 +293,7 @@ struct _mmc_csd {
>  #define EXT_CSD_SANITIZE_START         165     /* W */
>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>  #define EXT_CSD_RPMB_MULT              168     /* RO */
> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
> @@ -325,6 +329,11 @@ struct _mmc_csd {
>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
> --
> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-21 18:01 ` Gwendal Grignou
@ 2014-07-30  0:14   ` Gwendal Grignou
  2014-07-30  0:31     ` Gwendal Grignou
  0 siblings, 1 reply; 17+ messages in thread
From: Gwendal Grignou @ 2014-07-30  0:14 UTC (permalink / raw)
  To: Gwendal Grignou
  Cc: Avi Shchislowski, linux-mmc, cjb, Grant Grundler, Alex Lemberg

Avi,
We should revisit the 2 ioctls approach: Accroding to the spec:
"""When in FFU_MODE and host sends other commands which are not part
of the recommended flow, device behavior may be undefined."""

To be safe, no command should be sent to the device which are not
strictly necessary for the downloading the firmware.
Therefore, we should restrict to only the firmware download command
while in FFU mode. With the 2 ioctls approach, any commands can be
send in between the 2 ioctl calls.
Moreover, we read ext_csd while in FFU mode. we should get rid of that
call as well.

Gwendal.

On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
> Avi,
>
> The patch still does not work for me. After the upgrade, the eMMC is
> not in a good state timing out every IOs. A power cycle fixes the
> problem.
> I am investigating how the reset patch is working.
>
> Gwendal.
>
>
> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski
> <Avi.Shchislowski@sandisk.com> wrote:
>>
>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>>   http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc
>>
>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>
>> Two new ioctls have been added:
>> 1. FFU download firmware - transfer the new firmware data from user space to the eMMC device 2. FFU install - initializes the new firmware update
>>
>>
>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>
>> ---
>> V8:
>> - Modified according to Gwendal Grignou comments and patch:
>>    [PATCH] Fix on top of sandisk patches
>>
>> V7:
>> - fixed mangled white space
>>
>> V5:
>> - provides udev (request_firmware) implementation as advised in patch
>> v2 comments.
>>
>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308..19ba729 100644
>> --- a/drivers/mmc/card/Kconfig
>> +++ b/drivers/mmc/card/Kconfig
>> @@ -68,3 +68,11 @@ config MMC_TEST
>>
>>           This driver is only of interest to those developing or
>>           testing a host driver. Most people should say N here.
>> +
>> +config MMC_FFU
>> +       bool "FFU SUPPORT"
>> +       depends on MMC != n
>> +       help
>> +         This is an option to run firmware update on eMMC 5.0.
>> +         Field firmware updates (FFU) enables features enhancment
>> +         in the field.
>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406..1e9223b 100644
>> --- a/drivers/mmc/card/Makefile
>> +++ b/drivers/mmc/card/Makefile
>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>
>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>
>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f..3ed900b 100644
>> --- a/drivers/mmc/card/block.c
>> +++ b/drivers/mmc/card/block.c
>> @@ -41,6 +41,7 @@
>>  #include <linux/mmc/host.h>
>>  #include <linux/mmc/mmc.h>
>>  #include <linux/mmc/sd.h>
>> +#include <linux/mmc/ffu.h>
>>
>>  #include <asm/uaccess.h>
>>
>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>>
>>         mrq.cmd = &cmd;
>>
>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>> +                       err = mmc_ffu_download(card, idata->buf);
>> +                       goto cmd_done;
>> +               }
>> +
>>         mmc_get_card(card);
>>
>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>> +               err = mmc_ffu_install(card);
>> +               goto cmd_rel_host;
>> +       }
>> +
>>         err = mmc_blk_part_switch(card, md);
>>         if (err)
>>                 goto cmd_rel_host;
>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 100644 index 0000000..783673e
>> --- /dev/null
>> +++ b/drivers/mmc/card/ffu.c
>> @@ -0,0 +1,607 @@
>> +/*
>> + * *  ffu.c
>> + *
>> + *  Copyright 2007-2008 Pierre Ossman
>> + *
>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> +(at
>> + * your option) any later version.
>> + *
>> + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
>> + * slab.h, ffu.h & swap.h header files
>> + * The original, unmodified version of this program - the mmc_test.c
>> + * file - is obtained under the GPL v2.0 license that is available via
>> + * http://www.gnu.org/licenses/,
>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>> +*/
>> +
>> +#include <linux/bug.h>
>> +#include <linux/errno.h>
>> +#include <linux/mmc/card.h>
>> +#include <linux/mmc/host.h>
>> +#include <linux/mmc/mmc.h>
>> +#include <linux/scatterlist.h>
>> +#include <linux/slab.h>
>> +#include <linux/swap.h>
>> +#include <linux/mmc/ffu.h>
>> +#include <linux/firmware.h>
>> +
>> +/**
>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>> + * @page: first page in the allocation
>> + * @order: order of the number of pages allocated  */ struct
>> +mmc_ffu_pages {
>> +       struct page *page;
>> +       unsigned int order;
>> +};
>> +
>> +/**
>> + * struct mmc_ffu_mem - allocated memory.
>> + * @arr: array of allocations
>> + * @cnt: number of allocations
>> + */
>> +struct mmc_ffu_mem {
>> +       struct mmc_ffu_pages *arr;
>> +       unsigned int cnt;
>> +};
>> +
>> +struct mmc_ffu_area {
>> +       unsigned long max_sz;
>> +       unsigned int max_tfr;
>> +       unsigned int max_segs;
>> +       unsigned int max_seg_sz;
>> +       unsigned int blocks;
>> +       unsigned int sg_len;
>> +       struct mmc_ffu_mem *mem;
>> +       struct scatterlist *sg;
>> +};
>> +
>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>> +
>> +       if (blocks > 1)
>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>> +       else
>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>> +
>> +       mrq->cmd->arg = arg;
>> +       if (!mmc_card_blockaddr(card))
>> +               mrq->cmd->arg <<= 9;
>> +
>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>> +       if (blocks == 1) {
>> +               mrq->stop = NULL;
>> +       } else {
>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>> +               mrq->stop->arg = 0;
>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>> +       }
>> +
>> +       mrq->data->blksz = blksz;
>> +       mrq->data->blocks = blocks;
>> +       mrq->data->flags = MMC_DATA_WRITE;
>> +       mrq->data->sg = sg;
>> +       mrq->data->sg_len = sg_len;
>> +
>> +       mmc_set_data_timeout(mrq->data, card); }
>> +
>> +/*
>> + * Checks that a normal transfer didn't have any errors  */ static int
>> +mmc_ffu_check_result(struct mmc_request *mrq) {
>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>> +
>> +       if (mrq->cmd->error != 0)
>> +               return -EINVAL;
>> +
>> +       if (mrq->data->error != 0)
>> +               return -EINVAL;
>> +
>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>> +               return -1;
>> +
>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>> +               return -EINVAL;
>> +
>> +       return 0;
>> +}
>> +
>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>> +
>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>> +       int ret, busy = 0;
>> +       struct mmc_command cmd = {0};
>> +
>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>> +       cmd.opcode = MMC_SEND_STATUS;
>> +       cmd.arg = card->rca << 16;
>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>> +
>> +       do {
>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>> +               if (ret)
>> +                       break;
>> +
>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>> +                       busy = 1;
>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>> +                               pr_warn("%s: Warning: Host did not "
>> +                                       "wait for busy state to end.\n",
>> +                                       mmc_hostname(card->host));
>> +                       }
>> +               }
>> +
>> +       } while (mmc_ffu_busy(&cmd));
>> +
>> +       return ret;
>> +}
>> +
>> +/*
>> + * transfer with certain parameters
>> + */
>> +static int mmc_ffu_simple_transfer(struct mmc_card *card,
>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>> +       unsigned int blocks, unsigned int blksz) {
>> +       struct mmc_request mrq = {0};
>> +       struct mmc_command cmd = {0};
>> +       struct mmc_command stop = {0};
>> +       struct mmc_data data = {0};
>> +
>> +       mrq.cmd = &cmd;
>> +       mrq.data = &data;
>> +       mrq.stop = &stop;
>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>> +       mmc_wait_for_req(card->host, &mrq);
>> +
>> +       mmc_ffu_wait_busy(card);
>> +
>> +       return mmc_ffu_check_result(&mrq);
>> +}
>> +
>> +/*
>> + * Map memory into a scatterlist.
>> + */
>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>> +       struct scatterlist *sglist, unsigned int max_segs,
>> +       unsigned int max_seg_sz)
>> +{
>> +       struct scatterlist *sg = sglist;
>> +       unsigned int i;
>> +       unsigned long sz = size;
>> +       unsigned int sctr_len = 0;
>> +       unsigned long len;
>> +
>> +       sg_init_table(sglist, max_segs);
>> +
>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>> +
>> +               if (len > sz) {
>> +                       len = sz;
>> +                       sz = 0;
>> +               }
>> +
>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>> +               sg = sg_next(sg);
>> +               sctr_len += 1;
>> +       }
>> +       sg_mark_end(sg);
>> +
>> +       return sctr_len;
>> +}
>> +
>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>> +       if (!mem)
>> +               return;
>> +
>> +       while (mem->cnt--)
>> +               __free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
>> +
>> +       kfree(mem->arr);
>> +       kfree(mem);
>> +}
>> +
>> +/*
>> + * Cleanup struct mmc_ffu_area.
>> + */
>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>> +       kfree(area->sg);
>> +       mmc_ffu_free_mem(area->mem);
>> +
>> +       return 0;
>> +}
>> +
>> +/*
>> + * Allocate a lot of memory, preferably max_sz but at least min_sz. In
>> +case
>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>> +Also do
>> + * not exceed a maximum number of segments and try not to make segments
>> +much
>> + * bigger than maximum segment size.
>> + */
>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>> +       unsigned long max_sz, unsigned int max_segs, unsigned int max_seg_sz)
>> +{
>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>> +       unsigned long page_cnt = 0;
>> +       /* we divide by 16 to ensure we will not allocate a big amount
>> +        * of unnecessary pages */
>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>> +       struct mmc_ffu_mem *mem;
>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
>> +
>> +       if (max_page_cnt > limit)
>> +               max_page_cnt = limit;
>> +
>> +       if (min_page_cnt > max_page_cnt)
>> +               min_page_cnt = max_page_cnt;
>> +
>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>> +               max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
>> +
>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>> +       if (!mem)
>> +               return NULL;
>> +
>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>> +               GFP_KERNEL);
>> +       if (!mem->arr)
>> +               goto out_free;
>> +
>> +       while (max_page_cnt) {
>> +               struct page *page;
>> +               unsigned int order;
>> +
>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>> +
>> +               do {
>> +                       page = alloc_pages(flags, order);
>> +               } while (!page && order--);
>> +
>> +               if (!page)
>> +                       goto out_free;
>> +
>> +               mem->arr[mem->cnt].page = page;
>> +               mem->arr[mem->cnt].order = order;
>> +               mem->cnt += 1;
>> +               if (max_page_cnt <= (1UL << order))
>> +                       break;
>> +               max_page_cnt -= 1UL << order;
>> +               page_cnt += 1UL << order;
>> +       }
>> +
>> +       if (page_cnt < min_page_cnt)
>> +               goto out_free;
>> +
>> +       return mem;
>> +
>> +out_free:
>> +       mmc_ffu_free_mem(mem);
>> +       return NULL;
>> +}
>> +
>> +/*
>> + * Initialize an area for data transfers.
>> + * Copy the data to the allocated pages.
>> + */
>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>> +       const u8 *data, int size)
>> +{
>> +       int ret;
>> +       int i;
>> +       int length = 0;
>> +
>> +       area->max_tfr = size;
>> +
>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>> +        * because the same memory can be mapped into the scatterlist more than
>> +        * once. Also, take into account the limits imposed on scatterlist
>> +        * segments by the host driver.
>> +        */
>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>> +               area->max_seg_sz);
>> +       if (!area->mem)
>> +               return -ENOMEM;
>> +
>> +       /* copy data to page */
>> +       for (i = 0; i < area->mem->cnt; i++) {
>> +               if (length > size) {
>> +                       ret = -EINVAL;
>> +                       goto out_free;
>> +               }
>> +
>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>> +                       min(size - length, (int)area->max_seg_sz));
>> +               length += area->max_seg_sz;
>> +       }
>> +
>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>> +               GFP_KERNEL);
>> +       if (!area->sg) {
>> +               ret = -ENOMEM;
>> +               goto out_free;
>> +       }
>> +
>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>> +               area->max_segs, area->mem->cnt);
>> +
>> +       return 0;
>> +
>> +out_free:
>> +       mmc_ffu_area_cleanup(area);
>> +       return ret;
>> +}
>> +
>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>> +       int size)
>> +{
>> +       int rc;
>> +       struct mmc_ffu_area area;
>> +       int max_tfr;
>> +
>> +       area.sg = NULL;
>> +       area.mem = NULL;
>> +       area.max_segs = card->host->max_segs;
>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>> +       do {
>> +               max_tfr = size;
>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>> +                       max_tfr = card->host->max_blk_count << 9;
>> +               if (max_tfr > card->host->max_req_size)
>> +                       max_tfr = card->host->max_req_size;
>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>> +
>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>> +               if (rc != 0)
>> +                       goto exit;
>> +
>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>> +               if (rc != 0)
>> +                       goto exit;
>> +
>> +               src += max_tfr;
>> +               size -= max_tfr;
>> +       } while (size > 0);
>> +
>> +exit:
>> +       mmc_ffu_area_cleanup(&area);
>> +       return rc;
>> +}
>> +
>> +/* Flush all scheduled work from the MMC work queue.
>> + * and initialize the MMC device */
>> +static int mmc_ffu_restart(struct mmc_card *card) {
>> +       struct mmc_host *host = card->host;
>> +       int err = 0;
>> +
>> +       mmc_cache_ctrl(host, 0);
>> +       err = mmc_power_save_host(host);
>> +       if (err) {
>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>> +                       __func__ , err);
>> +               goto exit;
>> +       }
>> +
>> +       err = mmc_power_restore_host(host);
>> +
>> +exit:
>> +
>> +       return err;
>> +}
>> +
>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>> +       int err;
>> +       int ret;
>> +       u32 arg;
>> +       u32 fw_prog_bytes;
>> +       const struct firmware *fw;
>> +
>> +       /* Check if FFU is supported */
>> +       if (!card->ext_csd.ffu_capable) {
>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>> +                       card->ext_csd.rev);
>> +               return -EOPNOTSUPP;
>> +       }
>> +
>> +       if (strlen(name) > 512) {
>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>> +                       mmc_hostname(card->host), name);
>> +               return -EINVAL;
>> +       }
>> +
>> +       /* setup FW data buffer */
>> +       err = request_firmware(&fw, name, &card->dev);
>> +       if (err) {
>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>> +                       mmc_hostname(card->host), err);
>> +               return err;
>> +       }
>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>> +                       fw->size);
>> +       }
>> +
>> +       mmc_get_card(card);
>> +
>> +       /* set device to FFU mode */
>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>> +                       mmc_hostname(card->host), err);
>> +               goto exit;
>> +       }
>> +
>> +       /* Read the EXT_CSD */
>> +       err = mmc_send_ext_csd(card, ext_csd);
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>> +                       mmc_hostname(card->host), err);
>> +               goto exit;
>> +       }
>> +
>> +       /* set CMD ARG */
>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>> +
>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>> +
>> +       /* host switch back to work in normal MMC Read/Write commands */
>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>> +               card->ext_csd.generic_cmd6_time);
>> +       if (ret) {
>> +               err = ret;
>> +               goto exit;
>> +       }
>> +
>> +       /* Read the EXT_CSD */
>> +       err = mmc_send_ext_csd(card, ext_csd);
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>> +                       mmc_hostname(card->host), err);
>> +               goto exit;
>> +       }
>> +
>> +       /* check that the eMMC has received the payload */
>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>> +
>> +       /* convert sector to bytes */
>> +        fw_prog_bytes *=
>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>> +       if (fw_prog_bytes != fw->size) {
>> +               err = -EINVAL;
>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>> +                       mmc_hostname(card->host), err);
>> +       }
>> +
>> +exit:
>> +       release_firmware(fw);
>> +       mmc_put_card(card);
>> +       return err;
>> +}
>> +EXPORT_SYMBOL(mmc_ffu_download);
>> +
>> +int mmc_ffu_install(struct mmc_card *card) {
>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>> +       int err;
>> +       u32 ffu_data_len;
>> +       u32 timeout;
>> +
>> +       /* Check if FFU is supported */
>> +       if (!card->ext_csd.ffu_capable) {
>> +               pr_err("FFU: %s: error FFU is not supported\n",
>> +                       mmc_hostname(card->host));
>> +               return -EOPNOTSUPP;
>> +       }
>> +
>> +       err = mmc_send_ext_csd(card, ext_csd);
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>> +                       mmc_hostname(card->host), err);
>> +               return err;
>> +       }
>> +
>> +       /* check mode operation */
>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>> +               /* restart the eMMC */
>> +               err = mmc_ffu_restart(card);
>> +               if (err) {
>> +                       pr_err("FFU: %s: install error %d:\n",
>> +                               mmc_hostname(card->host), err);
>> +                       return err;
>> +               }
>> +       } else {
>> +
>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>> +
>> +               if (!ffu_data_len) {
>> +                       err = -EPERM;
>> +                       return err;
>> +               }
>> +               /* set device to FFU mode */
>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>> +                       card->ext_csd.generic_cmd6_time);
>> +
>> +               if (err) {
>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>> +                               mmc_hostname(card->host), err);
>> +                       return err;
>> +               }
>> +
>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>> +               if (timeout == 0 || timeout > 0x17) {
>> +                       timeout = 0x17;
>> +                       pr_warn("FFU: %s: operation code timeout is out "
>> +                               "of range. Using maximum timeout.\n",
>> +                               mmc_hostname(card->host));
>> +               }
>> +
>> +               /* timeout is at millisecond resolution */
>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>> +
>> +               /* set ext_csd to install mode */
>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>> +                       EXT_CSD_MODE_OPERATION_CODES,
>> +                       MMC_FFU_INSTALL_SET, timeout);
>> +
>> +               if (err) {
>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>> +                               mmc_hostname(card->host), err);
>> +                       return err;
>> +               }
>> +       }
>> +
>> +       /* read ext_csd */
>> +       err = mmc_send_ext_csd(card, ext_csd);
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>> +                       mmc_hostname(card->host), err);
>> +               return err;
>> +       }
>> +
>> +       /* return status */
>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>> +       if (err) {
>> +               pr_err("FFU: %s: error %d FFU install:\n",
>> +                       mmc_hostname(card->host), err);
>> +               return  -EINVAL;
>> +       }
>> +
>> +       return 0;
>> +}
>> +EXPORT_SYMBOL(mmc_ffu_install);
>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0..a29065a 100644
>> --- a/drivers/mmc/core/mmc.c
>> +++ b/drivers/mmc/core/mmc.c
>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>                 card->ext_csd.data_sector_size = 512;
>>         }
>>
>> +       /* eMMC v5 or later */
>> +       if (card->ext_csd.rev >= 7) {
>> +               card->ext_csd.ffu_capable =
>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>> +       } else {
>> +               card->ext_csd.ffu_capable = false;
>> +       }
>>  out:
>>         return err;
>>  }
>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b730272..bc6f6d0 100644
>> --- a/include/linux/mmc/card.h
>> +++ b/include/linux/mmc/card.h
>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>         bool                    boot_ro_lockable;
>> +       bool            ffu_capable;    /* FFU support */
>>         u8                      raw_exception_status;   /* 54 */
>>         u8                      raw_partition_support;  /* 160 */
>>         u8                      raw_rpmb_size_mult;     /* 168 */
>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..7e4133d
>> --- /dev/null
>> +++ b/include/linux/mmc/ffu.h
>> @@ -0,0 +1,51 @@
>> +/*
>> + *
>> + *  ffu.h
>> + *
>> + * Copyright (c) 2013 SanDisk Corp.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> +(at
>> + * your option) any later version.
>> + *
>> + * This program was created by SanDisk Corp
>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>> + * available via http://www.gnu.org/licenses/,
>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>> +*/
>> +
>> +#if !defined(_FFU_H_)
>> +#define _FFU_H_
>> +
>> +#include <linux/mmc/card.h>
>> +
>> +#define CARD_BLOCK_SIZE 512
>> +
>> +/*
>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
> Something is wrong here. This patch would not compile, some carriage
> returns are missing.
>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
> Here too.
>> +
>> +#define MMC_FFU_MODE_SET 0x1
>> +#define MMC_FFU_MODE_NORMAL 0x0
>> +#define MMC_FFU_INSTALL_SET 0x1
>> +
>> +#ifdef CONFIG_MMC_FFU
>> +#define MMC_FFU_FEATURES 0x1
>> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
>> +
>> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
>> +mmc_ffu_install(struct mmc_card *card); #else static inline int
> Here too.
>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>> +       return -ENOSYS;
>> +}
>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>> +       return -ENOSYS;
>> +}
>> +#endif
>> +#endif /* FFU_H_ */
>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50bcde3..3651449 100644
>> --- a/include/linux/mmc/mmc.h
>> +++ b/include/linux/mmc/mmc.h
>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>   * EXT_CSD fields
>>   */
>>
>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>> --
>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-30  0:14   ` Gwendal Grignou
@ 2014-07-30  0:31     ` Gwendal Grignou
  2014-07-30 14:08       ` Avi Shchislowski
  0 siblings, 1 reply; 17+ messages in thread
From: Gwendal Grignou @ 2014-07-30  0:31 UTC (permalink / raw)
  To: Gwendal Grignou
  Cc: Avi Shchislowski, linux-mmc, cjb, Grant Grundler, Alex Lemberg

I take it back, we can still use the 2 ioctl approach:
We just need to remove the mmc_send_ext_csd() between the calls to set
MMC_FFU_MODE_SET and MMC_FFU_MODE_NORMAL.
Your patch v7 did not have this error, I introduced it in the comments
I made on v7.

Gwendal.

On Tue, Jul 29, 2014 at 5:14 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
> Avi,
> We should revisit the 2 ioctls approach: Accroding to the spec:
> """When in FFU_MODE and host sends other commands which are not part
> of the recommended flow, device behavior may be undefined."""
>
> To be safe, no command should be sent to the device which are not
> strictly necessary for the downloading the firmware.
> Therefore, we should restrict to only the firmware download command
> while in FFU mode. With the 2 ioctls approach, any commands can be
> send in between the 2 ioctl calls.
> Moreover, we read ext_csd while in FFU mode. we should get rid of that
> call as well.
>
> Gwendal.
>
> On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
>> Avi,
>>
>> The patch still does not work for me. After the upgrade, the eMMC is
>> not in a good state timing out every IOs. A power cycle fixes the
>> problem.
>> I am investigating how the reset patch is working.
>>
>> Gwendal.
>>
>>
>> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski
>> <Avi.Shchislowski@sandisk.com> wrote:
>>>
>>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>>>   http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc
>>>
>>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>>
>>> Two new ioctls have been added:
>>> 1. FFU download firmware - transfer the new firmware data from user space to the eMMC device 2. FFU install - initializes the new firmware update
>>>
>>>
>>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>>
>>> ---
>>> V8:
>>> - Modified according to Gwendal Grignou comments and patch:
>>>    [PATCH] Fix on top of sandisk patches
>>>
>>> V7:
>>> - fixed mangled white space
>>>
>>> V5:
>>> - provides udev (request_firmware) implementation as advised in patch
>>> v2 comments.
>>>
>>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308..19ba729 100644
>>> --- a/drivers/mmc/card/Kconfig
>>> +++ b/drivers/mmc/card/Kconfig
>>> @@ -68,3 +68,11 @@ config MMC_TEST
>>>
>>>           This driver is only of interest to those developing or
>>>           testing a host driver. Most people should say N here.
>>> +
>>> +config MMC_FFU
>>> +       bool "FFU SUPPORT"
>>> +       depends on MMC != n
>>> +       help
>>> +         This is an option to run firmware update on eMMC 5.0.
>>> +         Field firmware updates (FFU) enables features enhancment
>>> +         in the field.
>>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406..1e9223b 100644
>>> --- a/drivers/mmc/card/Makefile
>>> +++ b/drivers/mmc/card/Makefile
>>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>>
>>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>>
>>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f..3ed900b 100644
>>> --- a/drivers/mmc/card/block.c
>>> +++ b/drivers/mmc/card/block.c
>>> @@ -41,6 +41,7 @@
>>>  #include <linux/mmc/host.h>
>>>  #include <linux/mmc/mmc.h>
>>>  #include <linux/mmc/sd.h>
>>> +#include <linux/mmc/ffu.h>
>>>
>>>  #include <asm/uaccess.h>
>>>
>>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>>>
>>>         mrq.cmd = &cmd;
>>>
>>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>> +                       err = mmc_ffu_download(card, idata->buf);
>>> +                       goto cmd_done;
>>> +               }
>>> +
>>>         mmc_get_card(card);
>>>
>>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>>> +               err = mmc_ffu_install(card);
>>> +               goto cmd_rel_host;
>>> +       }
>>> +
>>>         err = mmc_blk_part_switch(card, md);
>>>         if (err)
>>>                 goto cmd_rel_host;
>>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 100644 index 0000000..783673e
>>> --- /dev/null
>>> +++ b/drivers/mmc/card/ffu.c
>>> @@ -0,0 +1,607 @@
>>> +/*
>>> + * *  ffu.c
>>> + *
>>> + *  Copyright 2007-2008 Pierre Ossman
>>> + *
>>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License as published by
>>> + * the Free Software Foundation; either version 2 of the License, or
>>> +(at
>>> + * your option) any later version.
>>> + *
>>> + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
>>> + * slab.h, ffu.h & swap.h header files
>>> + * The original, unmodified version of this program - the mmc_test.c
>>> + * file - is obtained under the GPL v2.0 license that is available via
>>> + * http://www.gnu.org/licenses/,
>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>> +*/
>>> +
>>> +#include <linux/bug.h>
>>> +#include <linux/errno.h>
>>> +#include <linux/mmc/card.h>
>>> +#include <linux/mmc/host.h>
>>> +#include <linux/mmc/mmc.h>
>>> +#include <linux/scatterlist.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/swap.h>
>>> +#include <linux/mmc/ffu.h>
>>> +#include <linux/firmware.h>
>>> +
>>> +/**
>>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>>> + * @page: first page in the allocation
>>> + * @order: order of the number of pages allocated  */ struct
>>> +mmc_ffu_pages {
>>> +       struct page *page;
>>> +       unsigned int order;
>>> +};
>>> +
>>> +/**
>>> + * struct mmc_ffu_mem - allocated memory.
>>> + * @arr: array of allocations
>>> + * @cnt: number of allocations
>>> + */
>>> +struct mmc_ffu_mem {
>>> +       struct mmc_ffu_pages *arr;
>>> +       unsigned int cnt;
>>> +};
>>> +
>>> +struct mmc_ffu_area {
>>> +       unsigned long max_sz;
>>> +       unsigned int max_tfr;
>>> +       unsigned int max_segs;
>>> +       unsigned int max_seg_sz;
>>> +       unsigned int blocks;
>>> +       unsigned int sg_len;
>>> +       struct mmc_ffu_mem *mem;
>>> +       struct scatterlist *sg;
>>> +};
>>> +
>>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>> +
>>> +       if (blocks > 1)
>>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>> +       else
>>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>> +
>>> +       mrq->cmd->arg = arg;
>>> +       if (!mmc_card_blockaddr(card))
>>> +               mrq->cmd->arg <<= 9;
>>> +
>>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>> +       if (blocks == 1) {
>>> +               mrq->stop = NULL;
>>> +       } else {
>>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>>> +               mrq->stop->arg = 0;
>>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>>> +       }
>>> +
>>> +       mrq->data->blksz = blksz;
>>> +       mrq->data->blocks = blocks;
>>> +       mrq->data->flags = MMC_DATA_WRITE;
>>> +       mrq->data->sg = sg;
>>> +       mrq->data->sg_len = sg_len;
>>> +
>>> +       mmc_set_data_timeout(mrq->data, card); }
>>> +
>>> +/*
>>> + * Checks that a normal transfer didn't have any errors  */ static int
>>> +mmc_ffu_check_result(struct mmc_request *mrq) {
>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>>> +
>>> +       if (mrq->cmd->error != 0)
>>> +               return -EINVAL;
>>> +
>>> +       if (mrq->data->error != 0)
>>> +               return -EINVAL;
>>> +
>>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>>> +               return -1;
>>> +
>>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>>> +               return -EINVAL;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>>> +
>>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>> +       int ret, busy = 0;
>>> +       struct mmc_command cmd = {0};
>>> +
>>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>>> +       cmd.opcode = MMC_SEND_STATUS;
>>> +       cmd.arg = card->rca << 16;
>>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>>> +
>>> +       do {
>>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>>> +               if (ret)
>>> +                       break;
>>> +
>>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>>> +                       busy = 1;
>>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>>> +                               pr_warn("%s: Warning: Host did not "
>>> +                                       "wait for busy state to end.\n",
>>> +                                       mmc_hostname(card->host));
>>> +                       }
>>> +               }
>>> +
>>> +       } while (mmc_ffu_busy(&cmd));
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +/*
>>> + * transfer with certain parameters
>>> + */
>>> +static int mmc_ffu_simple_transfer(struct mmc_card *card,
>>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>>> +       unsigned int blocks, unsigned int blksz) {
>>> +       struct mmc_request mrq = {0};
>>> +       struct mmc_command cmd = {0};
>>> +       struct mmc_command stop = {0};
>>> +       struct mmc_data data = {0};
>>> +
>>> +       mrq.cmd = &cmd;
>>> +       mrq.data = &data;
>>> +       mrq.stop = &stop;
>>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>> +       mmc_wait_for_req(card->host, &mrq);
>>> +
>>> +       mmc_ffu_wait_busy(card);
>>> +
>>> +       return mmc_ffu_check_result(&mrq);
>>> +}
>>> +
>>> +/*
>>> + * Map memory into a scatterlist.
>>> + */
>>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>>> +       struct scatterlist *sglist, unsigned int max_segs,
>>> +       unsigned int max_seg_sz)
>>> +{
>>> +       struct scatterlist *sg = sglist;
>>> +       unsigned int i;
>>> +       unsigned long sz = size;
>>> +       unsigned int sctr_len = 0;
>>> +       unsigned long len;
>>> +
>>> +       sg_init_table(sglist, max_segs);
>>> +
>>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>>> +
>>> +               if (len > sz) {
>>> +                       len = sz;
>>> +                       sz = 0;
>>> +               }
>>> +
>>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>>> +               sg = sg_next(sg);
>>> +               sctr_len += 1;
>>> +       }
>>> +       sg_mark_end(sg);
>>> +
>>> +       return sctr_len;
>>> +}
>>> +
>>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>>> +       if (!mem)
>>> +               return;
>>> +
>>> +       while (mem->cnt--)
>>> +               __free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
>>> +
>>> +       kfree(mem->arr);
>>> +       kfree(mem);
>>> +}
>>> +
>>> +/*
>>> + * Cleanup struct mmc_ffu_area.
>>> + */
>>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>>> +       kfree(area->sg);
>>> +       mmc_ffu_free_mem(area->mem);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +/*
>>> + * Allocate a lot of memory, preferably max_sz but at least min_sz. In
>>> +case
>>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>>> +Also do
>>> + * not exceed a maximum number of segments and try not to make segments
>>> +much
>>> + * bigger than maximum segment size.
>>> + */
>>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>>> +       unsigned long max_sz, unsigned int max_segs, unsigned int max_seg_sz)
>>> +{
>>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>>> +       unsigned long page_cnt = 0;
>>> +       /* we divide by 16 to ensure we will not allocate a big amount
>>> +        * of unnecessary pages */
>>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>>> +       struct mmc_ffu_mem *mem;
>>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
>>> +
>>> +       if (max_page_cnt > limit)
>>> +               max_page_cnt = limit;
>>> +
>>> +       if (min_page_cnt > max_page_cnt)
>>> +               min_page_cnt = max_page_cnt;
>>> +
>>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>>> +               max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
>>> +
>>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>>> +       if (!mem)
>>> +               return NULL;
>>> +
>>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>>> +               GFP_KERNEL);
>>> +       if (!mem->arr)
>>> +               goto out_free;
>>> +
>>> +       while (max_page_cnt) {
>>> +               struct page *page;
>>> +               unsigned int order;
>>> +
>>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>>> +
>>> +               do {
>>> +                       page = alloc_pages(flags, order);
>>> +               } while (!page && order--);
>>> +
>>> +               if (!page)
>>> +                       goto out_free;
>>> +
>>> +               mem->arr[mem->cnt].page = page;
>>> +               mem->arr[mem->cnt].order = order;
>>> +               mem->cnt += 1;
>>> +               if (max_page_cnt <= (1UL << order))
>>> +                       break;
>>> +               max_page_cnt -= 1UL << order;
>>> +               page_cnt += 1UL << order;
>>> +       }
>>> +
>>> +       if (page_cnt < min_page_cnt)
>>> +               goto out_free;
>>> +
>>> +       return mem;
>>> +
>>> +out_free:
>>> +       mmc_ffu_free_mem(mem);
>>> +       return NULL;
>>> +}
>>> +
>>> +/*
>>> + * Initialize an area for data transfers.
>>> + * Copy the data to the allocated pages.
>>> + */
>>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>>> +       const u8 *data, int size)
>>> +{
>>> +       int ret;
>>> +       int i;
>>> +       int length = 0;
>>> +
>>> +       area->max_tfr = size;
>>> +
>>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>>> +        * because the same memory can be mapped into the scatterlist more than
>>> +        * once. Also, take into account the limits imposed on scatterlist
>>> +        * segments by the host driver.
>>> +        */
>>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>>> +               area->max_seg_sz);
>>> +       if (!area->mem)
>>> +               return -ENOMEM;
>>> +
>>> +       /* copy data to page */
>>> +       for (i = 0; i < area->mem->cnt; i++) {
>>> +               if (length > size) {
>>> +                       ret = -EINVAL;
>>> +                       goto out_free;
>>> +               }
>>> +
>>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>>> +                       min(size - length, (int)area->max_seg_sz));
>>> +               length += area->max_seg_sz;
>>> +       }
>>> +
>>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>>> +               GFP_KERNEL);
>>> +       if (!area->sg) {
>>> +               ret = -ENOMEM;
>>> +               goto out_free;
>>> +       }
>>> +
>>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>>> +               area->max_segs, area->mem->cnt);
>>> +
>>> +       return 0;
>>> +
>>> +out_free:
>>> +       mmc_ffu_area_cleanup(area);
>>> +       return ret;
>>> +}
>>> +
>>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>> +       int size)
>>> +{
>>> +       int rc;
>>> +       struct mmc_ffu_area area;
>>> +       int max_tfr;
>>> +
>>> +       area.sg = NULL;
>>> +       area.mem = NULL;
>>> +       area.max_segs = card->host->max_segs;
>>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>>> +       do {
>>> +               max_tfr = size;
>>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>>> +                       max_tfr = card->host->max_blk_count << 9;
>>> +               if (max_tfr > card->host->max_req_size)
>>> +                       max_tfr = card->host->max_req_size;
>>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>>> +
>>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>>> +               if (rc != 0)
>>> +                       goto exit;
>>> +
>>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>>> +               if (rc != 0)
>>> +                       goto exit;
>>> +
>>> +               src += max_tfr;
>>> +               size -= max_tfr;
>>> +       } while (size > 0);
>>> +
>>> +exit:
>>> +       mmc_ffu_area_cleanup(&area);
>>> +       return rc;
>>> +}
>>> +
>>> +/* Flush all scheduled work from the MMC work queue.
>>> + * and initialize the MMC device */
>>> +static int mmc_ffu_restart(struct mmc_card *card) {
>>> +       struct mmc_host *host = card->host;
>>> +       int err = 0;
>>> +
>>> +       mmc_cache_ctrl(host, 0);
>>> +       err = mmc_power_save_host(host);
>>> +       if (err) {
>>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>>> +                       __func__ , err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       err = mmc_power_restore_host(host);
>>> +
>>> +exit:
>>> +
>>> +       return err;
>>> +}
>>> +
>>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> +       int err;
>>> +       int ret;
>>> +       u32 arg;
>>> +       u32 fw_prog_bytes;
>>> +       const struct firmware *fw;
>>> +
>>> +       /* Check if FFU is supported */
>>> +       if (!card->ext_csd.ffu_capable) {
>>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>>> +                       card->ext_csd.rev);
>>> +               return -EOPNOTSUPP;
>>> +       }
>>> +
>>> +       if (strlen(name) > 512) {
>>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>>> +                       mmc_hostname(card->host), name);
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       /* setup FW data buffer */
>>> +       err = request_firmware(&fw, name, &card->dev);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>>> +                       fw->size);
>>> +       }
>>> +
>>> +       mmc_get_card(card);
>>> +
>>> +       /* set device to FFU mode */
>>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* Read the EXT_CSD */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* set CMD ARG */
>>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>>> +
>>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>> +
>>> +       /* host switch back to work in normal MMC Read/Write commands */
>>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>> +               card->ext_csd.generic_cmd6_time);
>>> +       if (ret) {
>>> +               err = ret;
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* Read the EXT_CSD */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* check that the eMMC has received the payload */
>>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>> +
>>> +       /* convert sector to bytes */
>>> +        fw_prog_bytes *=
>>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>>> +       if (fw_prog_bytes != fw->size) {
>>> +               err = -EINVAL;
>>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>>> +                       mmc_hostname(card->host), err);
>>> +       }
>>> +
>>> +exit:
>>> +       release_firmware(fw);
>>> +       mmc_put_card(card);
>>> +       return err;
>>> +}
>>> +EXPORT_SYMBOL(mmc_ffu_download);
>>> +
>>> +int mmc_ffu_install(struct mmc_card *card) {
>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> +       int err;
>>> +       u32 ffu_data_len;
>>> +       u32 timeout;
>>> +
>>> +       /* Check if FFU is supported */
>>> +       if (!card->ext_csd.ffu_capable) {
>>> +               pr_err("FFU: %s: error FFU is not supported\n",
>>> +                       mmc_hostname(card->host));
>>> +               return -EOPNOTSUPP;
>>> +       }
>>> +
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +
>>> +       /* check mode operation */
>>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>> +               /* restart the eMMC */
>>> +               err = mmc_ffu_restart(card);
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: install error %d:\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +       } else {
>>> +
>>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>> +
>>> +               if (!ffu_data_len) {
>>> +                       err = -EPERM;
>>> +                       return err;
>>> +               }
>>> +               /* set device to FFU mode */
>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>>> +                       card->ext_csd.generic_cmd6_time);
>>> +
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +
>>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>>> +               if (timeout == 0 || timeout > 0x17) {
>>> +                       timeout = 0x17;
>>> +                       pr_warn("FFU: %s: operation code timeout is out "
>>> +                               "of range. Using maximum timeout.\n",
>>> +                               mmc_hostname(card->host));
>>> +               }
>>> +
>>> +               /* timeout is at millisecond resolution */
>>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>>> +
>>> +               /* set ext_csd to install mode */
>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +                       EXT_CSD_MODE_OPERATION_CODES,
>>> +                       MMC_FFU_INSTALL_SET, timeout);
>>> +
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +       }
>>> +
>>> +       /* read ext_csd */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +
>>> +       /* return status */
>>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d FFU install:\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return  -EINVAL;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +EXPORT_SYMBOL(mmc_ffu_install);
>>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0..a29065a 100644
>>> --- a/drivers/mmc/core/mmc.c
>>> +++ b/drivers/mmc/core/mmc.c
>>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>>                 card->ext_csd.data_sector_size = 512;
>>>         }
>>>
>>> +       /* eMMC v5 or later */
>>> +       if (card->ext_csd.rev >= 7) {
>>> +               card->ext_csd.ffu_capable =
>>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>>> +       } else {
>>> +               card->ext_csd.ffu_capable = false;
>>> +       }
>>>  out:
>>>         return err;
>>>  }
>>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b730272..bc6f6d0 100644
>>> --- a/include/linux/mmc/card.h
>>> +++ b/include/linux/mmc/card.h
>>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>>         bool                    boot_ro_lockable;
>>> +       bool            ffu_capable;    /* FFU support */
>>>         u8                      raw_exception_status;   /* 54 */
>>>         u8                      raw_partition_support;  /* 160 */
>>>         u8                      raw_rpmb_size_mult;     /* 168 */
>>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..7e4133d
>>> --- /dev/null
>>> +++ b/include/linux/mmc/ffu.h
>>> @@ -0,0 +1,51 @@
>>> +/*
>>> + *
>>> + *  ffu.h
>>> + *
>>> + * Copyright (c) 2013 SanDisk Corp.
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify
>>> + * it under the terms of the GNU General Public License as published by
>>> + * the Free Software Foundation; either version 2 of the License, or
>>> +(at
>>> + * your option) any later version.
>>> + *
>>> + * This program was created by SanDisk Corp
>>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>>> + * available via http://www.gnu.org/licenses/,
>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>> +*/
>>> +
>>> +#if !defined(_FFU_H_)
>>> +#define _FFU_H_
>>> +
>>> +#include <linux/mmc/card.h>
>>> +
>>> +#define CARD_BLOCK_SIZE 512
>>> +
>>> +/*
>>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
>> Something is wrong here. This patch would not compile, some carriage
>> returns are missing.
>>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
>> Here too.
>>> +
>>> +#define MMC_FFU_MODE_SET 0x1
>>> +#define MMC_FFU_MODE_NORMAL 0x0
>>> +#define MMC_FFU_INSTALL_SET 0x1
>>> +
>>> +#ifdef CONFIG_MMC_FFU
>>> +#define MMC_FFU_FEATURES 0x1
>>> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
>>> +
>>> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
>>> +mmc_ffu_install(struct mmc_card *card); #else static inline int
>> Here too.
>>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>>> +       return -ENOSYS;
>>> +}
>>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>>> +       return -ENOSYS;
>>> +}
>>> +#endif
>>> +#endif /* FFU_H_ */
>>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50bcde3..3651449 100644
>>> --- a/include/linux/mmc/mmc.h
>>> +++ b/include/linux/mmc/mmc.h
>>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>>   * EXT_CSD fields
>>>   */
>>>
>>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>>> --
>>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-30  0:31     ` Gwendal Grignou
@ 2014-07-30 14:08       ` Avi Shchislowski
  2014-08-01  2:26         ` Hsin-Hsiang Tseng
  0 siblings, 1 reply; 17+ messages in thread
From: Avi Shchislowski @ 2014-07-30 14:08 UTC (permalink / raw)
  To: Gwendal Grignou, Gwendal Grignou
  Cc: linux-mmc, cjb, Grant Grundler, Alex Lemberg

Hi Gwendal
From spec perspective there should not be any problem to read the EXT_CSD value in FFU mode, the spec refer to read/write commands 
But if it causes issues I will read the EXT_CSD before entering FFU mode.

Please let me know.

Thanks
Avi.


-----Original Message-----
From: Gwendal Grignou [mailto:gwendal@google.com] 
Sent: Wednesday, July 30, 2014 3:32 AM
To: Gwendal Grignou
Cc: Avi Shchislowski; linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Alex Lemberg
Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0

I take it back, we can still use the 2 ioctl approach:
We just need to remove the mmc_send_ext_csd() between the calls to set MMC_FFU_MODE_SET and MMC_FFU_MODE_NORMAL.
Your patch v7 did not have this error, I introduced it in the comments I made on v7.

Gwendal.

On Tue, Jul 29, 2014 at 5:14 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
> Avi,
> We should revisit the 2 ioctls approach: Accroding to the spec:
> """When in FFU_MODE and host sends other commands which are not part 
> of the recommended flow, device behavior may be undefined."""
>
> To be safe, no command should be sent to the device which are not 
> strictly necessary for the downloading the firmware.
> Therefore, we should restrict to only the firmware download command 
> while in FFU mode. With the 2 ioctls approach, any commands can be 
> send in between the 2 ioctl calls.
> Moreover, we read ext_csd while in FFU mode. we should get rid of that 
> call as well.
>
> Gwendal.
>
> On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
>> Avi,
>>
>> The patch still does not work for me. After the upgrade, the eMMC is 
>> not in a good state timing out every IOs. A power cycle fixes the 
>> problem.
>> I am investigating how the reset patch is working.
>>
>> Gwendal.
>>
>>
>> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski 
>> <Avi.Shchislowski@sandisk.com> wrote:
>>>
>>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>>>   
>>> http://www.jedec.org/standards-documents/technology-focus-areas/flas
>>> h-memory-ssds-ufs-emmc/e-mmc
>>>
>>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>>
>>> Two new ioctls have been added:
>>> 1. FFU download firmware - transfer the new firmware data from user 
>>> space to the eMMC device 2. FFU install - initializes the new 
>>> firmware update
>>>
>>>
>>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>>
>>> ---
>>> V8:
>>> - Modified according to Gwendal Grignou comments and patch:
>>>    [PATCH] Fix on top of sandisk patches
>>>
>>> V7:
>>> - fixed mangled white space
>>>
>>> V5:
>>> - provides udev (request_firmware) implementation as advised in 
>>> patch
>>> v2 comments.
>>>
>>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig 
>>> index 5562308..19ba729 100644
>>> --- a/drivers/mmc/card/Kconfig
>>> +++ b/drivers/mmc/card/Kconfig
>>> @@ -68,3 +68,11 @@ config MMC_TEST
>>>
>>>           This driver is only of interest to those developing or
>>>           testing a host driver. Most people should say N here.
>>> +
>>> +config MMC_FFU
>>> +       bool "FFU SUPPORT"
>>> +       depends on MMC != n
>>> +       help
>>> +         This is an option to run firmware update on eMMC 5.0.
>>> +         Field firmware updates (FFU) enables features enhancment
>>> +         in the field.
>>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile 
>>> index c73b406..1e9223b 100644
>>> --- a/drivers/mmc/card/Makefile
>>> +++ b/drivers/mmc/card/Makefile
>>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>>
>>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>>
>>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c 
>>> index 7b5424f..3ed900b 100644
>>> --- a/drivers/mmc/card/block.c
>>> +++ b/drivers/mmc/card/block.c
>>> @@ -41,6 +41,7 @@
>>>  #include <linux/mmc/host.h>
>>>  #include <linux/mmc/mmc.h>
>>>  #include <linux/mmc/sd.h>
>>> +#include <linux/mmc/ffu.h>
>>>
>>>  #include <asm/uaccess.h>
>>>
>>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct 
>>> block_device *bdev,
>>>
>>>         mrq.cmd = &cmd;
>>>
>>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>> +                       err = mmc_ffu_download(card, idata->buf);
>>> +                       goto cmd_done;
>>> +               }
>>> +
>>>         mmc_get_card(card);
>>>
>>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>>> +               err = mmc_ffu_install(card);
>>> +               goto cmd_rel_host;
>>> +       }
>>> +
>>>         err = mmc_blk_part_switch(card, md);
>>>         if (err)
>>>                 goto cmd_rel_host;
>>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new 
>>> file mode 100644 index 0000000..783673e
>>> --- /dev/null
>>> +++ b/drivers/mmc/card/ffu.c
>>> @@ -0,0 +1,607 @@
>>> +/*
>>> + * *  ffu.c
>>> + *
>>> + *  Copyright 2007-2008 Pierre Ossman
>>> + *
>>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>>> + *
>>> + * This program is free software; you can redistribute it and/or 
>>> +modify
>>> + * it under the terms of the GNU General Public License as 
>>> +published by
>>> + * the Free Software Foundation; either version 2 of the License, 
>>> +or (at
>>> + * your option) any later version.
>>> + *
>>> + * This program includes bug.h, card.h, host.h, mmc.h, 
>>> +scatterlist.h,
>>> + * slab.h, ffu.h & swap.h header files
>>> + * The original, unmodified version of this program - the 
>>> +mmc_test.c
>>> + * file - is obtained under the GPL v2.0 license that is available 
>>> +via
>>> + * http://www.gnu.org/licenses/,
>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>> +*/
>>> +
>>> +#include <linux/bug.h>
>>> +#include <linux/errno.h>
>>> +#include <linux/mmc/card.h>
>>> +#include <linux/mmc/host.h>
>>> +#include <linux/mmc/mmc.h>
>>> +#include <linux/scatterlist.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/swap.h>
>>> +#include <linux/mmc/ffu.h>
>>> +#include <linux/firmware.h>
>>> +
>>> +/**
>>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>>> + * @page: first page in the allocation
>>> + * @order: order of the number of pages allocated  */ struct 
>>> +mmc_ffu_pages {
>>> +       struct page *page;
>>> +       unsigned int order;
>>> +};
>>> +
>>> +/**
>>> + * struct mmc_ffu_mem - allocated memory.
>>> + * @arr: array of allocations
>>> + * @cnt: number of allocations
>>> + */
>>> +struct mmc_ffu_mem {
>>> +       struct mmc_ffu_pages *arr;
>>> +       unsigned int cnt;
>>> +};
>>> +
>>> +struct mmc_ffu_area {
>>> +       unsigned long max_sz;
>>> +       unsigned int max_tfr;
>>> +       unsigned int max_segs;
>>> +       unsigned int max_seg_sz;
>>> +       unsigned int blocks;
>>> +       unsigned int sg_len;
>>> +       struct mmc_ffu_mem *mem;
>>> +       struct scatterlist *sg;
>>> +};
>>> +
>>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>> +
>>> +       if (blocks > 1)
>>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>> +       else
>>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>> +
>>> +       mrq->cmd->arg = arg;
>>> +       if (!mmc_card_blockaddr(card))
>>> +               mrq->cmd->arg <<= 9;
>>> +
>>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>> +       if (blocks == 1) {
>>> +               mrq->stop = NULL;
>>> +       } else {
>>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>>> +               mrq->stop->arg = 0;
>>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>>> +       }
>>> +
>>> +       mrq->data->blksz = blksz;
>>> +       mrq->data->blocks = blocks;
>>> +       mrq->data->flags = MMC_DATA_WRITE;
>>> +       mrq->data->sg = sg;
>>> +       mrq->data->sg_len = sg_len;
>>> +
>>> +       mmc_set_data_timeout(mrq->data, card); }
>>> +
>>> +/*
>>> + * Checks that a normal transfer didn't have any errors  */ static 
>>> +int mmc_ffu_check_result(struct mmc_request *mrq) {
>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>>> +
>>> +       if (mrq->cmd->error != 0)
>>> +               return -EINVAL;
>>> +
>>> +       if (mrq->data->error != 0)
>>> +               return -EINVAL;
>>> +
>>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>>> +               return -1;
>>> +
>>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>>> +               return -EINVAL;
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>>> +
>>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>> +       int ret, busy = 0;
>>> +       struct mmc_command cmd = {0};
>>> +
>>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>>> +       cmd.opcode = MMC_SEND_STATUS;
>>> +       cmd.arg = card->rca << 16;
>>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>>> +
>>> +       do {
>>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>>> +               if (ret)
>>> +                       break;
>>> +
>>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>>> +                       busy = 1;
>>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>>> +                               pr_warn("%s: Warning: Host did not "
>>> +                                       "wait for busy state to end.\n",
>>> +                                       mmc_hostname(card->host));
>>> +                       }
>>> +               }
>>> +
>>> +       } while (mmc_ffu_busy(&cmd));
>>> +
>>> +       return ret;
>>> +}
>>> +
>>> +/*
>>> + * transfer with certain parameters  */ static int 
>>> +mmc_ffu_simple_transfer(struct mmc_card *card,
>>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>>> +       unsigned int blocks, unsigned int blksz) {
>>> +       struct mmc_request mrq = {0};
>>> +       struct mmc_command cmd = {0};
>>> +       struct mmc_command stop = {0};
>>> +       struct mmc_data data = {0};
>>> +
>>> +       mrq.cmd = &cmd;
>>> +       mrq.data = &data;
>>> +       mrq.stop = &stop;
>>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>> +       mmc_wait_for_req(card->host, &mrq);
>>> +
>>> +       mmc_ffu_wait_busy(card);
>>> +
>>> +       return mmc_ffu_check_result(&mrq); }
>>> +
>>> +/*
>>> + * Map memory into a scatterlist.
>>> + */
>>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>>> +       struct scatterlist *sglist, unsigned int max_segs,
>>> +       unsigned int max_seg_sz)
>>> +{
>>> +       struct scatterlist *sg = sglist;
>>> +       unsigned int i;
>>> +       unsigned long sz = size;
>>> +       unsigned int sctr_len = 0;
>>> +       unsigned long len;
>>> +
>>> +       sg_init_table(sglist, max_segs);
>>> +
>>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>>> +
>>> +               if (len > sz) {
>>> +                       len = sz;
>>> +                       sz = 0;
>>> +               }
>>> +
>>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>>> +               sg = sg_next(sg);
>>> +               sctr_len += 1;
>>> +       }
>>> +       sg_mark_end(sg);
>>> +
>>> +       return sctr_len;
>>> +}
>>> +
>>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>>> +       if (!mem)
>>> +               return;
>>> +
>>> +       while (mem->cnt--)
>>> +               __free_pages(mem->arr[mem->cnt].page, 
>>> + mem->arr[mem->cnt].order);
>>> +
>>> +       kfree(mem->arr);
>>> +       kfree(mem);
>>> +}
>>> +
>>> +/*
>>> + * Cleanup struct mmc_ffu_area.
>>> + */
>>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>>> +       kfree(area->sg);
>>> +       mmc_ffu_free_mem(area->mem);
>>> +
>>> +       return 0;
>>> +}
>>> +
>>> +/*
>>> + * Allocate a lot of memory, preferably max_sz but at least min_sz. 
>>> +In case
>>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>>> +Also do
>>> + * not exceed a maximum number of segments and try not to make 
>>> +segments much
>>> + * bigger than maximum segment size.
>>> + */
>>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>>> +       unsigned long max_sz, unsigned int max_segs, unsigned int 
>>> +max_seg_sz) {
>>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>>> +       unsigned long page_cnt = 0;
>>> +       /* we divide by 16 to ensure we will not allocate a big amount
>>> +        * of unnecessary pages */
>>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>>> +       struct mmc_ffu_mem *mem;
>>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | 
>>> +__GFP_NORETRY;
>>> +
>>> +       if (max_page_cnt > limit)
>>> +               max_page_cnt = limit;
>>> +
>>> +       if (min_page_cnt > max_page_cnt)
>>> +               min_page_cnt = max_page_cnt;
>>> +
>>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>>> +               max_segs = DIV_ROUND_UP(max_page_cnt, 
>>> + max_seg_page_cnt);
>>> +
>>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>>> +       if (!mem)
>>> +               return NULL;
>>> +
>>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>>> +               GFP_KERNEL);
>>> +       if (!mem->arr)
>>> +               goto out_free;
>>> +
>>> +       while (max_page_cnt) {
>>> +               struct page *page;
>>> +               unsigned int order;
>>> +
>>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>>> +
>>> +               do {
>>> +                       page = alloc_pages(flags, order);
>>> +               } while (!page && order--);
>>> +
>>> +               if (!page)
>>> +                       goto out_free;
>>> +
>>> +               mem->arr[mem->cnt].page = page;
>>> +               mem->arr[mem->cnt].order = order;
>>> +               mem->cnt += 1;
>>> +               if (max_page_cnt <= (1UL << order))
>>> +                       break;
>>> +               max_page_cnt -= 1UL << order;
>>> +               page_cnt += 1UL << order;
>>> +       }
>>> +
>>> +       if (page_cnt < min_page_cnt)
>>> +               goto out_free;
>>> +
>>> +       return mem;
>>> +
>>> +out_free:
>>> +       mmc_ffu_free_mem(mem);
>>> +       return NULL;
>>> +}
>>> +
>>> +/*
>>> + * Initialize an area for data transfers.
>>> + * Copy the data to the allocated pages.
>>> + */
>>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>>> +       const u8 *data, int size)
>>> +{
>>> +       int ret;
>>> +       int i;
>>> +       int length = 0;
>>> +
>>> +       area->max_tfr = size;
>>> +
>>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>>> +        * because the same memory can be mapped into the scatterlist more than
>>> +        * once. Also, take into account the limits imposed on scatterlist
>>> +        * segments by the host driver.
>>> +        */
>>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>>> +               area->max_seg_sz);
>>> +       if (!area->mem)
>>> +               return -ENOMEM;
>>> +
>>> +       /* copy data to page */
>>> +       for (i = 0; i < area->mem->cnt; i++) {
>>> +               if (length > size) {
>>> +                       ret = -EINVAL;
>>> +                       goto out_free;
>>> +               }
>>> +
>>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>>> +                       min(size - length, (int)area->max_seg_sz));
>>> +               length += area->max_seg_sz;
>>> +       }
>>> +
>>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>>> +               GFP_KERNEL);
>>> +       if (!area->sg) {
>>> +               ret = -ENOMEM;
>>> +               goto out_free;
>>> +       }
>>> +
>>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>>> +               area->max_segs, area->mem->cnt);
>>> +
>>> +       return 0;
>>> +
>>> +out_free:
>>> +       mmc_ffu_area_cleanup(area);
>>> +       return ret;
>>> +}
>>> +
>>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>> +       int size)
>>> +{
>>> +       int rc;
>>> +       struct mmc_ffu_area area;
>>> +       int max_tfr;
>>> +
>>> +       area.sg = NULL;
>>> +       area.mem = NULL;
>>> +       area.max_segs = card->host->max_segs;
>>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>>> +       do {
>>> +               max_tfr = size;
>>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>>> +                       max_tfr = card->host->max_blk_count << 9;
>>> +               if (max_tfr > card->host->max_req_size)
>>> +                       max_tfr = card->host->max_req_size;
>>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>>> +
>>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>>> +               if (rc != 0)
>>> +                       goto exit;
>>> +
>>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>>> +               if (rc != 0)
>>> +                       goto exit;
>>> +
>>> +               src += max_tfr;
>>> +               size -= max_tfr;
>>> +       } while (size > 0);
>>> +
>>> +exit:
>>> +       mmc_ffu_area_cleanup(&area);
>>> +       return rc;
>>> +}
>>> +
>>> +/* Flush all scheduled work from the MMC work queue.
>>> + * and initialize the MMC device */ static int 
>>> +mmc_ffu_restart(struct mmc_card *card) {
>>> +       struct mmc_host *host = card->host;
>>> +       int err = 0;
>>> +
>>> +       mmc_cache_ctrl(host, 0);
>>> +       err = mmc_power_save_host(host);
>>> +       if (err) {
>>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>>> +                       __func__ , err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       err = mmc_power_restore_host(host);
>>> +
>>> +exit:
>>> +
>>> +       return err;
>>> +}
>>> +
>>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> +       int err;
>>> +       int ret;
>>> +       u32 arg;
>>> +       u32 fw_prog_bytes;
>>> +       const struct firmware *fw;
>>> +
>>> +       /* Check if FFU is supported */
>>> +       if (!card->ext_csd.ffu_capable) {
>>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>>> +                       card->ext_csd.rev);
>>> +               return -EOPNOTSUPP;
>>> +       }
>>> +
>>> +       if (strlen(name) > 512) {
>>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>>> +                       mmc_hostname(card->host), name);
>>> +               return -EINVAL;
>>> +       }
>>> +
>>> +       /* setup FW data buffer */
>>> +       err = request_firmware(&fw, name, &card->dev);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>>> +                       fw->size);
>>> +       }
>>> +
>>> +       mmc_get_card(card);
>>> +
>>> +       /* set device to FFU mode */
>>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* Read the EXT_CSD */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* set CMD ARG */
>>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>>> +
>>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>> +
>>> +       /* host switch back to work in normal MMC Read/Write commands */
>>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>> +               card->ext_csd.generic_cmd6_time);
>>> +       if (ret) {
>>> +               err = ret;
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* Read the EXT_CSD */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               goto exit;
>>> +       }
>>> +
>>> +       /* check that the eMMC has received the payload */
>>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>> +
>>> +       /* convert sector to bytes */
>>> +        fw_prog_bytes *=
>>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>>> +       if (fw_prog_bytes != fw->size) {
>>> +               err = -EINVAL;
>>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>>> +                       mmc_hostname(card->host), err);
>>> +       }
>>> +
>>> +exit:
>>> +       release_firmware(fw);
>>> +       mmc_put_card(card);
>>> +       return err;
>>> +}
>>> +EXPORT_SYMBOL(mmc_ffu_download);
>>> +
>>> +int mmc_ffu_install(struct mmc_card *card) {
>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> +       int err;
>>> +       u32 ffu_data_len;
>>> +       u32 timeout;
>>> +
>>> +       /* Check if FFU is supported */
>>> +       if (!card->ext_csd.ffu_capable) {
>>> +               pr_err("FFU: %s: error FFU is not supported\n",
>>> +                       mmc_hostname(card->host));
>>> +               return -EOPNOTSUPP;
>>> +       }
>>> +
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +
>>> +       /* check mode operation */
>>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>> +               /* restart the eMMC */
>>> +               err = mmc_ffu_restart(card);
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: install error %d:\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +       } else {
>>> +
>>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 
>>> + 24;
>>> +
>>> +               if (!ffu_data_len) {
>>> +                       err = -EPERM;
>>> +                       return err;
>>> +               }
>>> +               /* set device to FFU mode */
>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>>> +                       card->ext_csd.generic_cmd6_time);
>>> +
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +
>>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>>> +               if (timeout == 0 || timeout > 0x17) {
>>> +                       timeout = 0x17;
>>> +                       pr_warn("FFU: %s: operation code timeout is out "
>>> +                               "of range. Using maximum timeout.\n",
>>> +                               mmc_hostname(card->host));
>>> +               }
>>> +
>>> +               /* timeout is at millisecond resolution */
>>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>>> +
>>> +               /* set ext_csd to install mode */
>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>> +                       EXT_CSD_MODE_OPERATION_CODES,
>>> +                       MMC_FFU_INSTALL_SET, timeout);
>>> +
>>> +               if (err) {
>>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>>> +                               mmc_hostname(card->host), err);
>>> +                       return err;
>>> +               }
>>> +       }
>>> +
>>> +       /* read ext_csd */
>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return err;
>>> +       }
>>> +
>>> +       /* return status */
>>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>>> +       if (err) {
>>> +               pr_err("FFU: %s: error %d FFU install:\n",
>>> +                       mmc_hostname(card->host), err);
>>> +               return  -EINVAL;
>>> +       }
>>> +
>>> +       return 0;
>>> +}
>>> +EXPORT_SYMBOL(mmc_ffu_install);
>>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 
>>> 98e9eb0..a29065a 100644
>>> --- a/drivers/mmc/core/mmc.c
>>> +++ b/drivers/mmc/core/mmc.c
>>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>>                 card->ext_csd.data_sector_size = 512;
>>>         }
>>>
>>> +       /* eMMC v5 or later */
>>> +       if (card->ext_csd.rev >= 7) {
>>> +               card->ext_csd.ffu_capable =
>>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>>> +       } else {
>>> +               card->ext_csd.ffu_capable = false;
>>> +       }
>>>  out:
>>>         return err;
>>>  }
>>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h 
>>> index b730272..bc6f6d0 100644
>>> --- a/include/linux/mmc/card.h
>>> +++ b/include/linux/mmc/card.h
>>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>>         bool                    boot_ro_lockable;
>>> +       bool            ffu_capable;    /* FFU support */
>>>         u8                      raw_exception_status;   /* 54 */
>>>         u8                      raw_partition_support;  /* 160 */
>>>         u8                      raw_rpmb_size_mult;     /* 168 */
>>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new 
>>> file mode 100644 index 0000000..7e4133d
>>> --- /dev/null
>>> +++ b/include/linux/mmc/ffu.h
>>> @@ -0,0 +1,51 @@
>>> +/*
>>> + *
>>> + *  ffu.h
>>> + *
>>> + * Copyright (c) 2013 SanDisk Corp.
>>> + *
>>> + * This program is free software; you can redistribute it and/or 
>>> +modify
>>> + * it under the terms of the GNU General Public License as 
>>> +published by
>>> + * the Free Software Foundation; either version 2 of the License, 
>>> +or (at
>>> + * your option) any later version.
>>> + *
>>> + * This program was created by SanDisk Corp
>>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>>> + * available via http://www.gnu.org/licenses/,
>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>> +*/
>>> +
>>> +#if !defined(_FFU_H_)
>>> +#define _FFU_H_
>>> +
>>> +#include <linux/mmc/card.h>
>>> +
>>> +#define CARD_BLOCK_SIZE 512
>>> +
>>> +/*
>>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
>> Something is wrong here. This patch would not compile, some carriage 
>> returns are missing.
>>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
>> Here too.
>>> +
>>> +#define MMC_FFU_MODE_SET 0x1
>>> +#define MMC_FFU_MODE_NORMAL 0x0
>>> +#define MMC_FFU_INSTALL_SET 0x1
>>> +
>>> +#ifdef CONFIG_MMC_FFU
>>> +#define MMC_FFU_FEATURES 0x1
>>> +#define FFU_FEATURES(ffu_features) (ffu_features & 
>>> +MMC_FFU_FEATURES)
>>> +
>>> +int mmc_ffu_download(struct mmc_card *card, const char *name); int 
>>> +mmc_ffu_install(struct mmc_card *card); #else static inline int
>> Here too.
>>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>>> +       return -ENOSYS;
>>> +}
>>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>>> +       return -ENOSYS;
>>> +}
>>> +#endif
>>> +#endif /* FFU_H_ */
>>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 
>>> 50bcde3..3651449 100644
>>> --- a/include/linux/mmc/mmc.h
>>> +++ b/include/linux/mmc/mmc.h
>>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>>   * EXT_CSD fields
>>>   */
>>>
>>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>>> --
>>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-30 14:08       ` Avi Shchislowski
@ 2014-08-01  2:26         ` Hsin-Hsiang Tseng
  2014-08-01  8:08           ` Hsin-Hsiang Tseng
  0 siblings, 1 reply; 17+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-08-01  2:26 UTC (permalink / raw)
  To: Avi Shchislowski
  Cc: Gwendal Grignou, Gwendal Grignou, linux-mmc, cjb, Grant Grundler,
	Alex Lemberg

Hi ,Avi,

According to EXT_CSD_FFU_STATUS can report the status of the FFU process.
If it necessary to check Error in downloading firmware(value: 0x12)
before we continue to execute mmc_ffu_install?

Thanks.

2014-07-30 22:08 GMT+08:00 Avi Shchislowski <Avi.Shchislowski@sandisk.com>:
> Hi Gwendal
> From spec perspective there should not be any problem to read the EXT_CSD value in FFU mode, the spec refer to read/write commands
> But if it causes issues I will read the EXT_CSD before entering FFU mode.
>
> Please let me know.
>
> Thanks
> Avi.
>
>
> -----Original Message-----
> From: Gwendal Grignou [mailto:gwendal@google.com]
> Sent: Wednesday, July 30, 2014 3:32 AM
> To: Gwendal Grignou
> Cc: Avi Shchislowski; linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Alex Lemberg
> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
>
> I take it back, we can still use the 2 ioctl approach:
> We just need to remove the mmc_send_ext_csd() between the calls to set MMC_FFU_MODE_SET and MMC_FFU_MODE_NORMAL.
> Your patch v7 did not have this error, I introduced it in the comments I made on v7.
>
> Gwendal.
>
> On Tue, Jul 29, 2014 at 5:14 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
>> Avi,
>> We should revisit the 2 ioctls approach: Accroding to the spec:
>> """When in FFU_MODE and host sends other commands which are not part
>> of the recommended flow, device behavior may be undefined."""
>>
>> To be safe, no command should be sent to the device which are not
>> strictly necessary for the downloading the firmware.
>> Therefore, we should restrict to only the firmware download command
>> while in FFU mode. With the 2 ioctls approach, any commands can be
>> send in between the 2 ioctl calls.
>> Moreover, we read ext_csd while in FFU mode. we should get rid of that
>> call as well.
>>
>> Gwendal.
>>
>> On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
>>> Avi,
>>>
>>> The patch still does not work for me. After the upgrade, the eMMC is
>>> not in a good state timing out every IOs. A power cycle fixes the
>>> problem.
>>> I am investigating how the reset patch is working.
>>>
>>> Gwendal.
>>>
>>>
>>> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski
>>> <Avi.Shchislowski@sandisk.com> wrote:
>>>>
>>>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>>>>
>>>> http://www.jedec.org/standards-documents/technology-focus-areas/flas
>>>> h-memory-ssds-ufs-emmc/e-mmc
>>>>
>>>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>>>
>>>> Two new ioctls have been added:
>>>> 1. FFU download firmware - transfer the new firmware data from user
>>>> space to the eMMC device 2. FFU install - initializes the new
>>>> firmware update
>>>>
>>>>
>>>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>>>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>>>
>>>> ---
>>>> V8:
>>>> - Modified according to Gwendal Grignou comments and patch:
>>>>    [PATCH] Fix on top of sandisk patches
>>>>
>>>> V7:
>>>> - fixed mangled white space
>>>>
>>>> V5:
>>>> - provides udev (request_firmware) implementation as advised in
>>>> patch
>>>> v2 comments.
>>>>
>>>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
>>>> index 5562308..19ba729 100644
>>>> --- a/drivers/mmc/card/Kconfig
>>>> +++ b/drivers/mmc/card/Kconfig
>>>> @@ -68,3 +68,11 @@ config MMC_TEST
>>>>
>>>>           This driver is only of interest to those developing or
>>>>           testing a host driver. Most people should say N here.
>>>> +
>>>> +config MMC_FFU
>>>> +       bool "FFU SUPPORT"
>>>> +       depends on MMC != n
>>>> +       help
>>>> +         This is an option to run firmware update on eMMC 5.0.
>>>> +         Field firmware updates (FFU) enables features enhancment
>>>> +         in the field.
>>>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
>>>> index c73b406..1e9223b 100644
>>>> --- a/drivers/mmc/card/Makefile
>>>> +++ b/drivers/mmc/card/Makefile
>>>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>>>
>>>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>>>
>>>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>>>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
>>>> index 7b5424f..3ed900b 100644
>>>> --- a/drivers/mmc/card/block.c
>>>> +++ b/drivers/mmc/card/block.c
>>>> @@ -41,6 +41,7 @@
>>>>  #include <linux/mmc/host.h>
>>>>  #include <linux/mmc/mmc.h>
>>>>  #include <linux/mmc/sd.h>
>>>> +#include <linux/mmc/ffu.h>
>>>>
>>>>  #include <asm/uaccess.h>
>>>>
>>>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct
>>>> block_device *bdev,
>>>>
>>>>         mrq.cmd = &cmd;
>>>>
>>>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>>> +                       err = mmc_ffu_download(card, idata->buf);
>>>> +                       goto cmd_done;
>>>> +               }
>>>> +
>>>>         mmc_get_card(card);
>>>>
>>>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>>>> +               err = mmc_ffu_install(card);
>>>> +               goto cmd_rel_host;
>>>> +       }
>>>> +
>>>>         err = mmc_blk_part_switch(card, md);
>>>>         if (err)
>>>>                 goto cmd_rel_host;
>>>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new
>>>> file mode 100644 index 0000000..783673e
>>>> --- /dev/null
>>>> +++ b/drivers/mmc/card/ffu.c
>>>> @@ -0,0 +1,607 @@
>>>> +/*
>>>> + * *  ffu.c
>>>> + *
>>>> + *  Copyright 2007-2008 Pierre Ossman
>>>> + *
>>>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or
>>>> +modify
>>>> + * it under the terms of the GNU General Public License as
>>>> +published by
>>>> + * the Free Software Foundation; either version 2 of the License,
>>>> +or (at
>>>> + * your option) any later version.
>>>> + *
>>>> + * This program includes bug.h, card.h, host.h, mmc.h,
>>>> +scatterlist.h,
>>>> + * slab.h, ffu.h & swap.h header files
>>>> + * The original, unmodified version of this program - the
>>>> +mmc_test.c
>>>> + * file - is obtained under the GPL v2.0 license that is available
>>>> +via
>>>> + * http://www.gnu.org/licenses/,
>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>> +*/
>>>> +
>>>> +#include <linux/bug.h>
>>>> +#include <linux/errno.h>
>>>> +#include <linux/mmc/card.h>
>>>> +#include <linux/mmc/host.h>
>>>> +#include <linux/mmc/mmc.h>
>>>> +#include <linux/scatterlist.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/swap.h>
>>>> +#include <linux/mmc/ffu.h>
>>>> +#include <linux/firmware.h>
>>>> +
>>>> +/**
>>>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>>>> + * @page: first page in the allocation
>>>> + * @order: order of the number of pages allocated  */ struct
>>>> +mmc_ffu_pages {
>>>> +       struct page *page;
>>>> +       unsigned int order;
>>>> +};
>>>> +
>>>> +/**
>>>> + * struct mmc_ffu_mem - allocated memory.
>>>> + * @arr: array of allocations
>>>> + * @cnt: number of allocations
>>>> + */
>>>> +struct mmc_ffu_mem {
>>>> +       struct mmc_ffu_pages *arr;
>>>> +       unsigned int cnt;
>>>> +};
>>>> +
>>>> +struct mmc_ffu_area {
>>>> +       unsigned long max_sz;
>>>> +       unsigned int max_tfr;
>>>> +       unsigned int max_segs;
>>>> +       unsigned int max_seg_sz;
>>>> +       unsigned int blocks;
>>>> +       unsigned int sg_len;
>>>> +       struct mmc_ffu_mem *mem;
>>>> +       struct scatterlist *sg;
>>>> +};
>>>> +
>>>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>>>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>>>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>>> +
>>>> +       if (blocks > 1)
>>>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>>> +       else
>>>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>>> +
>>>> +       mrq->cmd->arg = arg;
>>>> +       if (!mmc_card_blockaddr(card))
>>>> +               mrq->cmd->arg <<= 9;
>>>> +
>>>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>>> +       if (blocks == 1) {
>>>> +               mrq->stop = NULL;
>>>> +       } else {
>>>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>>>> +               mrq->stop->arg = 0;
>>>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>>>> +       }
>>>> +
>>>> +       mrq->data->blksz = blksz;
>>>> +       mrq->data->blocks = blocks;
>>>> +       mrq->data->flags = MMC_DATA_WRITE;
>>>> +       mrq->data->sg = sg;
>>>> +       mrq->data->sg_len = sg_len;
>>>> +
>>>> +       mmc_set_data_timeout(mrq->data, card); }
>>>> +
>>>> +/*
>>>> + * Checks that a normal transfer didn't have any errors  */ static
>>>> +int mmc_ffu_check_result(struct mmc_request *mrq) {
>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>>>> +
>>>> +       if (mrq->cmd->error != 0)
>>>> +               return -EINVAL;
>>>> +
>>>> +       if (mrq->data->error != 0)
>>>> +               return -EINVAL;
>>>> +
>>>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>>>> +               return -1;
>>>> +
>>>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>>>> +               return -EINVAL;
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>>>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>>>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>>>> +
>>>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>>> +       int ret, busy = 0;
>>>> +       struct mmc_command cmd = {0};
>>>> +
>>>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>>>> +       cmd.opcode = MMC_SEND_STATUS;
>>>> +       cmd.arg = card->rca << 16;
>>>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>>>> +
>>>> +       do {
>>>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>>>> +               if (ret)
>>>> +                       break;
>>>> +
>>>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>>>> +                       busy = 1;
>>>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>>>> +                               pr_warn("%s: Warning: Host did not "
>>>> +                                       "wait for busy state to end.\n",
>>>> +                                       mmc_hostname(card->host));
>>>> +                       }
>>>> +               }
>>>> +
>>>> +       } while (mmc_ffu_busy(&cmd));
>>>> +
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +/*
>>>> + * transfer with certain parameters  */ static int
>>>> +mmc_ffu_simple_transfer(struct mmc_card *card,
>>>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>>>> +       unsigned int blocks, unsigned int blksz) {
>>>> +       struct mmc_request mrq = {0};
>>>> +       struct mmc_command cmd = {0};
>>>> +       struct mmc_command stop = {0};
>>>> +       struct mmc_data data = {0};
>>>> +
>>>> +       mrq.cmd = &cmd;
>>>> +       mrq.data = &data;
>>>> +       mrq.stop = &stop;
>>>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>>> +       mmc_wait_for_req(card->host, &mrq);
>>>> +
>>>> +       mmc_ffu_wait_busy(card);
>>>> +
>>>> +       return mmc_ffu_check_result(&mrq); }
>>>> +
>>>> +/*
>>>> + * Map memory into a scatterlist.
>>>> + */
>>>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>>>> +       struct scatterlist *sglist, unsigned int max_segs,
>>>> +       unsigned int max_seg_sz)
>>>> +{
>>>> +       struct scatterlist *sg = sglist;
>>>> +       unsigned int i;
>>>> +       unsigned long sz = size;
>>>> +       unsigned int sctr_len = 0;
>>>> +       unsigned long len;
>>>> +
>>>> +       sg_init_table(sglist, max_segs);
>>>> +
>>>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>>>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>>>> +
>>>> +               if (len > sz) {
>>>> +                       len = sz;
>>>> +                       sz = 0;
>>>> +               }
>>>> +
>>>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>>>> +               sg = sg_next(sg);
>>>> +               sctr_len += 1;
>>>> +       }
>>>> +       sg_mark_end(sg);
>>>> +
>>>> +       return sctr_len;
>>>> +}
>>>> +
>>>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>>>> +       if (!mem)
>>>> +               return;
>>>> +
>>>> +       while (mem->cnt--)
>>>> +               __free_pages(mem->arr[mem->cnt].page,
>>>> + mem->arr[mem->cnt].order);
>>>> +
>>>> +       kfree(mem->arr);
>>>> +       kfree(mem);
>>>> +}
>>>> +
>>>> +/*
>>>> + * Cleanup struct mmc_ffu_area.
>>>> + */
>>>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>>>> +       kfree(area->sg);
>>>> +       mmc_ffu_free_mem(area->mem);
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Allocate a lot of memory, preferably max_sz but at least min_sz.
>>>> +In case
>>>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>>>> +Also do
>>>> + * not exceed a maximum number of segments and try not to make
>>>> +segments much
>>>> + * bigger than maximum segment size.
>>>> + */
>>>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>>>> +       unsigned long max_sz, unsigned int max_segs, unsigned int
>>>> +max_seg_sz) {
>>>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>>>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>>>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>>>> +       unsigned long page_cnt = 0;
>>>> +       /* we divide by 16 to ensure we will not allocate a big amount
>>>> +        * of unnecessary pages */
>>>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>>>> +       struct mmc_ffu_mem *mem;
>>>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
>>>> +__GFP_NORETRY;
>>>> +
>>>> +       if (max_page_cnt > limit)
>>>> +               max_page_cnt = limit;
>>>> +
>>>> +       if (min_page_cnt > max_page_cnt)
>>>> +               min_page_cnt = max_page_cnt;
>>>> +
>>>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>>>> +               max_segs = DIV_ROUND_UP(max_page_cnt,
>>>> + max_seg_page_cnt);
>>>> +
>>>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>>>> +       if (!mem)
>>>> +               return NULL;
>>>> +
>>>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>>>> +               GFP_KERNEL);
>>>> +       if (!mem->arr)
>>>> +               goto out_free;
>>>> +
>>>> +       while (max_page_cnt) {
>>>> +               struct page *page;
>>>> +               unsigned int order;
>>>> +
>>>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>>>> +
>>>> +               do {
>>>> +                       page = alloc_pages(flags, order);
>>>> +               } while (!page && order--);
>>>> +
>>>> +               if (!page)
>>>> +                       goto out_free;
>>>> +
>>>> +               mem->arr[mem->cnt].page = page;
>>>> +               mem->arr[mem->cnt].order = order;
>>>> +               mem->cnt += 1;
>>>> +               if (max_page_cnt <= (1UL << order))
>>>> +                       break;
>>>> +               max_page_cnt -= 1UL << order;
>>>> +               page_cnt += 1UL << order;
>>>> +       }
>>>> +
>>>> +       if (page_cnt < min_page_cnt)
>>>> +               goto out_free;
>>>> +
>>>> +       return mem;
>>>> +
>>>> +out_free:
>>>> +       mmc_ffu_free_mem(mem);
>>>> +       return NULL;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Initialize an area for data transfers.
>>>> + * Copy the data to the allocated pages.
>>>> + */
>>>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>>>> +       const u8 *data, int size)
>>>> +{
>>>> +       int ret;
>>>> +       int i;
>>>> +       int length = 0;
>>>> +
>>>> +       area->max_tfr = size;
>>>> +
>>>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>>>> +        * because the same memory can be mapped into the scatterlist more than
>>>> +        * once. Also, take into account the limits imposed on scatterlist
>>>> +        * segments by the host driver.
>>>> +        */
>>>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>>>> +               area->max_seg_sz);
>>>> +       if (!area->mem)
>>>> +               return -ENOMEM;
>>>> +
>>>> +       /* copy data to page */
>>>> +       for (i = 0; i < area->mem->cnt; i++) {
>>>> +               if (length > size) {
>>>> +                       ret = -EINVAL;
>>>> +                       goto out_free;
>>>> +               }
>>>> +
>>>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>>>> +                       min(size - length, (int)area->max_seg_sz));
>>>> +               length += area->max_seg_sz;
>>>> +       }
>>>> +
>>>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>>>> +               GFP_KERNEL);
>>>> +       if (!area->sg) {
>>>> +               ret = -ENOMEM;
>>>> +               goto out_free;
>>>> +       }
>>>> +
>>>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>>>> +               area->max_segs, area->mem->cnt);
>>>> +
>>>> +       return 0;
>>>> +
>>>> +out_free:
>>>> +       mmc_ffu_area_cleanup(area);
>>>> +       return ret;
>>>> +}
>>>> +
>>>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>>> +       int size)
>>>> +{
>>>> +       int rc;
>>>> +       struct mmc_ffu_area area;
>>>> +       int max_tfr;
>>>> +
>>>> +       area.sg = NULL;
>>>> +       area.mem = NULL;
>>>> +       area.max_segs = card->host->max_segs;
>>>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>>>> +       do {
>>>> +               max_tfr = size;
>>>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>>>> +                       max_tfr = card->host->max_blk_count << 9;
>>>> +               if (max_tfr > card->host->max_req_size)
>>>> +                       max_tfr = card->host->max_req_size;
>>>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>>>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>>>> +
>>>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>>>> +               if (rc != 0)
>>>> +                       goto exit;
>>>> +
>>>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>>>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>>>> +               if (rc != 0)
>>>> +                       goto exit;
>>>> +
>>>> +               src += max_tfr;
>>>> +               size -= max_tfr;
>>>> +       } while (size > 0);
>>>> +
>>>> +exit:
>>>> +       mmc_ffu_area_cleanup(&area);
>>>> +       return rc;
>>>> +}
>>>> +
>>>> +/* Flush all scheduled work from the MMC work queue.
>>>> + * and initialize the MMC device */ static int
>>>> +mmc_ffu_restart(struct mmc_card *card) {
>>>> +       struct mmc_host *host = card->host;
>>>> +       int err = 0;
>>>> +
>>>> +       mmc_cache_ctrl(host, 0);
>>>> +       err = mmc_power_save_host(host);
>>>> +       if (err) {
>>>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>>>> +                       __func__ , err);
>>>> +               goto exit;
>>>> +       }
>>>> +
>>>> +       err = mmc_power_restore_host(host);
>>>> +
>>>> +exit:
>>>> +
>>>> +       return err;
>>>> +}
>>>> +
>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>> +       int err;
>>>> +       int ret;
>>>> +       u32 arg;
>>>> +       u32 fw_prog_bytes;
>>>> +       const struct firmware *fw;
>>>> +
>>>> +       /* Check if FFU is supported */
>>>> +       if (!card->ext_csd.ffu_capable) {
>>>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>>>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>>>> +                       card->ext_csd.rev);
>>>> +               return -EOPNOTSUPP;
>>>> +       }
>>>> +
>>>> +       if (strlen(name) > 512) {
>>>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>>>> +                       mmc_hostname(card->host), name);
>>>> +               return -EINVAL;
>>>> +       }
>>>> +
>>>> +       /* setup FW data buffer */
>>>> +       err = request_firmware(&fw, name, &card->dev);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               return err;
>>>> +       }
>>>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>>>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>>>> +                       fw->size);
>>>> +       }
>>>> +
>>>> +       mmc_get_card(card);
>>>> +
>>>> +       /* set device to FFU mode */
>>>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>>>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               goto exit;
>>>> +       }
>>>> +
>>>> +       /* Read the EXT_CSD */
>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               goto exit;
>>>> +       }
>>>> +
>>>> +       /* set CMD ARG */
>>>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>>>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>>>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>>>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>>>> +
>>>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>>> +
>>>> +       /* host switch back to work in normal MMC Read/Write commands */
>>>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>>> +               card->ext_csd.generic_cmd6_time);
>>>> +       if (ret) {
>>>> +               err = ret;
>>>> +               goto exit;
>>>> +       }
>>>> +
>>>> +       /* Read the EXT_CSD */
>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               goto exit;
>>>> +       }
>>>> +
>>>> +       /* check that the eMMC has received the payload */
>>>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>>> +
>>>> +       /* convert sector to bytes */
>>>> +        fw_prog_bytes *=
>>>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>>>> +       if (fw_prog_bytes != fw->size) {
>>>> +               err = -EINVAL;
>>>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +       }
>>>> +
>>>> +exit:
>>>> +       release_firmware(fw);
>>>> +       mmc_put_card(card);
>>>> +       return err;
>>>> +}
>>>> +EXPORT_SYMBOL(mmc_ffu_download);
>>>> +
>>>> +int mmc_ffu_install(struct mmc_card *card) {
>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>> +       int err;
>>>> +       u32 ffu_data_len;
>>>> +       u32 timeout;
>>>> +
>>>> +       /* Check if FFU is supported */
>>>> +       if (!card->ext_csd.ffu_capable) {
>>>> +               pr_err("FFU: %s: error FFU is not supported\n",
>>>> +                       mmc_hostname(card->host));
>>>> +               return -EOPNOTSUPP;
>>>> +       }
>>>> +
>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               return err;
>>>> +       }
>>>> +
>>>> +       /* check mode operation */
>>>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>>> +               /* restart the eMMC */
>>>> +               err = mmc_ffu_restart(card);
>>>> +               if (err) {
>>>> +                       pr_err("FFU: %s: install error %d:\n",
>>>> +                               mmc_hostname(card->host), err);
>>>> +                       return err;
>>>> +               }
>>>> +       } else {
>>>> +
>>>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] <<
>>>> + 24;
>>>> +
>>>> +               if (!ffu_data_len) {
>>>> +                       err = -EPERM;
>>>> +                       return err;
>>>> +               }
>>>> +               /* set device to FFU mode */
>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>>>> +                       card->ext_csd.generic_cmd6_time);
>>>> +
>>>> +               if (err) {
>>>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>>>> +                               mmc_hostname(card->host), err);
>>>> +                       return err;
>>>> +               }
>>>> +
>>>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>>>> +               if (timeout == 0 || timeout > 0x17) {
>>>> +                       timeout = 0x17;
>>>> +                       pr_warn("FFU: %s: operation code timeout is out "
>>>> +                               "of range. Using maximum timeout.\n",
>>>> +                               mmc_hostname(card->host));
>>>> +               }
>>>> +
>>>> +               /* timeout is at millisecond resolution */
>>>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>>>> +
>>>> +               /* set ext_csd to install mode */
>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>> +                       EXT_CSD_MODE_OPERATION_CODES,
>>>> +                       MMC_FFU_INSTALL_SET, timeout);
>>>> +
>>>> +               if (err) {
>>>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>>>> +                               mmc_hostname(card->host), err);
>>>> +                       return err;
>>>> +               }
>>>> +       }
>>>> +
>>>> +       /* read ext_csd */
>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               return err;
>>>> +       }
>>>> +
>>>> +       /* return status */
>>>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>>>> +       if (err) {
>>>> +               pr_err("FFU: %s: error %d FFU install:\n",
>>>> +                       mmc_hostname(card->host), err);
>>>> +               return  -EINVAL;
>>>> +       }
>>>> +
>>>> +       return 0;
>>>> +}
>>>> +EXPORT_SYMBOL(mmc_ffu_install);
>>>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
>>>> 98e9eb0..a29065a 100644
>>>> --- a/drivers/mmc/core/mmc.c
>>>> +++ b/drivers/mmc/core/mmc.c
>>>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>>>                 card->ext_csd.data_sector_size = 512;
>>>>         }
>>>>
>>>> +       /* eMMC v5 or later */
>>>> +       if (card->ext_csd.rev >= 7) {
>>>> +               card->ext_csd.ffu_capable =
>>>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>>>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>>>> +       } else {
>>>> +               card->ext_csd.ffu_capable = false;
>>>> +       }
>>>>  out:
>>>>         return err;
>>>>  }
>>>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
>>>> index b730272..bc6f6d0 100644
>>>> --- a/include/linux/mmc/card.h
>>>> +++ b/include/linux/mmc/card.h
>>>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>>>         bool                    boot_ro_lockable;
>>>> +       bool            ffu_capable;    /* FFU support */
>>>>         u8                      raw_exception_status;   /* 54 */
>>>>         u8                      raw_partition_support;  /* 160 */
>>>>         u8                      raw_rpmb_size_mult;     /* 168 */
>>>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new
>>>> file mode 100644 index 0000000..7e4133d
>>>> --- /dev/null
>>>> +++ b/include/linux/mmc/ffu.h
>>>> @@ -0,0 +1,51 @@
>>>> +/*
>>>> + *
>>>> + *  ffu.h
>>>> + *
>>>> + * Copyright (c) 2013 SanDisk Corp.
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or
>>>> +modify
>>>> + * it under the terms of the GNU General Public License as
>>>> +published by
>>>> + * the Free Software Foundation; either version 2 of the License,
>>>> +or (at
>>>> + * your option) any later version.
>>>> + *
>>>> + * This program was created by SanDisk Corp
>>>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>>>> + * available via http://www.gnu.org/licenses/,
>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>> +*/
>>>> +
>>>> +#if !defined(_FFU_H_)
>>>> +#define _FFU_H_
>>>> +
>>>> +#include <linux/mmc/card.h>
>>>> +
>>>> +#define CARD_BLOCK_SIZE 512
>>>> +
>>>> +/*
>>>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
>>> Something is wrong here. This patch would not compile, some carriage
>>> returns are missing.
>>>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
>>> Here too.
>>>> +
>>>> +#define MMC_FFU_MODE_SET 0x1
>>>> +#define MMC_FFU_MODE_NORMAL 0x0
>>>> +#define MMC_FFU_INSTALL_SET 0x1
>>>> +
>>>> +#ifdef CONFIG_MMC_FFU
>>>> +#define MMC_FFU_FEATURES 0x1
>>>> +#define FFU_FEATURES(ffu_features) (ffu_features &
>>>> +MMC_FFU_FEATURES)
>>>> +
>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
>>>> +mmc_ffu_install(struct mmc_card *card); #else static inline int
>>> Here too.
>>>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>> +       return -ENOSYS;
>>>> +}
>>>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>>>> +       return -ENOSYS;
>>>> +}
>>>> +#endif
>>>> +#endif /* FFU_H_ */
>>>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index
>>>> 50bcde3..3651449 100644
>>>> --- a/include/linux/mmc/mmc.h
>>>> +++ b/include/linux/mmc/mmc.h
>>>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>>>   * EXT_CSD fields
>>>>   */
>>>>
>>>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>>>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>>>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>>>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>>>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>>>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>>>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>>>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>>>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>>>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>>>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>>>> --
>>>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-01  2:26         ` Hsin-Hsiang Tseng
@ 2014-08-01  8:08           ` Hsin-Hsiang Tseng
  2014-08-07 10:09             ` Avi Shchislowski
  0 siblings, 1 reply; 17+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-08-01  8:08 UTC (permalink / raw)
  To: Avi Shchislowski
  Cc: Gwendal Grignou, Gwendal Grignou, linux-mmc, cjb, Grant Grundler,
	Alex Lemberg

Hi, Avi,

I have another question want to consult,
In your patch, after download the required firmware file, and back to
normal mode.
you will /* check that the eMMC has received the payload */ by read
NUMBER_OF_FW_SECTORS_CORRECTLY_PROGAMMED field and compare to the size
of the required firmware file.

>From the spec JESD84-B50, Field Firmware Update portion.

If the host finished downloading successfully the firmware bundle to
the device it
may set MODE_OPERATION_CODES to FFU_INSTALL, the device shall set
NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED field to zero, install the
new firmware
and set MODE_CONFIG to Normal state which would regain regular
operation of read and write
commands.

When we  check that the eMMC has received the payload. In my test eMMC
sample return 0, so always blocked here.

Thanks.


2014-08-01 10:26 GMT+08:00 Hsin-Hsiang Tseng <hsinhsiangtseng@gmail.com>:
> Hi ,Avi,
>
> According to EXT_CSD_FFU_STATUS can report the status of the FFU process.
> If it necessary to check Error in downloading firmware(value: 0x12)
> before we continue to execute mmc_ffu_install?
>
> Thanks.
>
> 2014-07-30 22:08 GMT+08:00 Avi Shchislowski <Avi.Shchislowski@sandisk.com>:
>> Hi Gwendal
>> From spec perspective there should not be any problem to read the EXT_CSD value in FFU mode, the spec refer to read/write commands
>> But if it causes issues I will read the EXT_CSD before entering FFU mode.
>>
>> Please let me know.
>>
>> Thanks
>> Avi.
>>
>>
>> -----Original Message-----
>> From: Gwendal Grignou [mailto:gwendal@google.com]
>> Sent: Wednesday, July 30, 2014 3:32 AM
>> To: Gwendal Grignou
>> Cc: Avi Shchislowski; linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Alex Lemberg
>> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
>>
>> I take it back, we can still use the 2 ioctl approach:
>> We just need to remove the mmc_send_ext_csd() between the calls to set MMC_FFU_MODE_SET and MMC_FFU_MODE_NORMAL.
>> Your patch v7 did not have this error, I introduced it in the comments I made on v7.
>>
>> Gwendal.
>>
>> On Tue, Jul 29, 2014 at 5:14 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
>>> Avi,
>>> We should revisit the 2 ioctls approach: Accroding to the spec:
>>> """When in FFU_MODE and host sends other commands which are not part
>>> of the recommended flow, device behavior may be undefined."""
>>>
>>> To be safe, no command should be sent to the device which are not
>>> strictly necessary for the downloading the firmware.
>>> Therefore, we should restrict to only the firmware download command
>>> while in FFU mode. With the 2 ioctls approach, any commands can be
>>> send in between the 2 ioctl calls.
>>> Moreover, we read ext_csd while in FFU mode. we should get rid of that
>>> call as well.
>>>
>>> Gwendal.
>>>
>>> On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
>>>> Avi,
>>>>
>>>> The patch still does not work for me. After the upgrade, the eMMC is
>>>> not in a good state timing out every IOs. A power cycle fixes the
>>>> problem.
>>>> I am investigating how the reset patch is working.
>>>>
>>>> Gwendal.
>>>>
>>>>
>>>> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski
>>>> <Avi.Shchislowski@sandisk.com> wrote:
>>>>>
>>>>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>>>>>
>>>>> http://www.jedec.org/standards-documents/technology-focus-areas/flas
>>>>> h-memory-ssds-ufs-emmc/e-mmc
>>>>>
>>>>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>>>>
>>>>> Two new ioctls have been added:
>>>>> 1. FFU download firmware - transfer the new firmware data from user
>>>>> space to the eMMC device 2. FFU install - initializes the new
>>>>> firmware update
>>>>>
>>>>>
>>>>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>>>>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>>>>
>>>>> ---
>>>>> V8:
>>>>> - Modified according to Gwendal Grignou comments and patch:
>>>>>    [PATCH] Fix on top of sandisk patches
>>>>>
>>>>> V7:
>>>>> - fixed mangled white space
>>>>>
>>>>> V5:
>>>>> - provides udev (request_firmware) implementation as advised in
>>>>> patch
>>>>> v2 comments.
>>>>>
>>>>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
>>>>> index 5562308..19ba729 100644
>>>>> --- a/drivers/mmc/card/Kconfig
>>>>> +++ b/drivers/mmc/card/Kconfig
>>>>> @@ -68,3 +68,11 @@ config MMC_TEST
>>>>>
>>>>>           This driver is only of interest to those developing or
>>>>>           testing a host driver. Most people should say N here.
>>>>> +
>>>>> +config MMC_FFU
>>>>> +       bool "FFU SUPPORT"
>>>>> +       depends on MMC != n
>>>>> +       help
>>>>> +         This is an option to run firmware update on eMMC 5.0.
>>>>> +         Field firmware updates (FFU) enables features enhancment
>>>>> +         in the field.
>>>>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
>>>>> index c73b406..1e9223b 100644
>>>>> --- a/drivers/mmc/card/Makefile
>>>>> +++ b/drivers/mmc/card/Makefile
>>>>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>>>>
>>>>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>>>>
>>>>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>>>>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
>>>>> index 7b5424f..3ed900b 100644
>>>>> --- a/drivers/mmc/card/block.c
>>>>> +++ b/drivers/mmc/card/block.c
>>>>> @@ -41,6 +41,7 @@
>>>>>  #include <linux/mmc/host.h>
>>>>>  #include <linux/mmc/mmc.h>
>>>>>  #include <linux/mmc/sd.h>
>>>>> +#include <linux/mmc/ffu.h>
>>>>>
>>>>>  #include <asm/uaccess.h>
>>>>>
>>>>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct
>>>>> block_device *bdev,
>>>>>
>>>>>         mrq.cmd = &cmd;
>>>>>
>>>>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>>>> +                       err = mmc_ffu_download(card, idata->buf);
>>>>> +                       goto cmd_done;
>>>>> +               }
>>>>> +
>>>>>         mmc_get_card(card);
>>>>>
>>>>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>>>>> +               err = mmc_ffu_install(card);
>>>>> +               goto cmd_rel_host;
>>>>> +       }
>>>>> +
>>>>>         err = mmc_blk_part_switch(card, md);
>>>>>         if (err)
>>>>>                 goto cmd_rel_host;
>>>>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new
>>>>> file mode 100644 index 0000000..783673e
>>>>> --- /dev/null
>>>>> +++ b/drivers/mmc/card/ffu.c
>>>>> @@ -0,0 +1,607 @@
>>>>> +/*
>>>>> + * *  ffu.c
>>>>> + *
>>>>> + *  Copyright 2007-2008 Pierre Ossman
>>>>> + *
>>>>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>>>>> + *
>>>>> + * This program is free software; you can redistribute it and/or
>>>>> +modify
>>>>> + * it under the terms of the GNU General Public License as
>>>>> +published by
>>>>> + * the Free Software Foundation; either version 2 of the License,
>>>>> +or (at
>>>>> + * your option) any later version.
>>>>> + *
>>>>> + * This program includes bug.h, card.h, host.h, mmc.h,
>>>>> +scatterlist.h,
>>>>> + * slab.h, ffu.h & swap.h header files
>>>>> + * The original, unmodified version of this program - the
>>>>> +mmc_test.c
>>>>> + * file - is obtained under the GPL v2.0 license that is available
>>>>> +via
>>>>> + * http://www.gnu.org/licenses/,
>>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>>> +*/
>>>>> +
>>>>> +#include <linux/bug.h>
>>>>> +#include <linux/errno.h>
>>>>> +#include <linux/mmc/card.h>
>>>>> +#include <linux/mmc/host.h>
>>>>> +#include <linux/mmc/mmc.h>
>>>>> +#include <linux/scatterlist.h>
>>>>> +#include <linux/slab.h>
>>>>> +#include <linux/swap.h>
>>>>> +#include <linux/mmc/ffu.h>
>>>>> +#include <linux/firmware.h>
>>>>> +
>>>>> +/**
>>>>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>>>>> + * @page: first page in the allocation
>>>>> + * @order: order of the number of pages allocated  */ struct
>>>>> +mmc_ffu_pages {
>>>>> +       struct page *page;
>>>>> +       unsigned int order;
>>>>> +};
>>>>> +
>>>>> +/**
>>>>> + * struct mmc_ffu_mem - allocated memory.
>>>>> + * @arr: array of allocations
>>>>> + * @cnt: number of allocations
>>>>> + */
>>>>> +struct mmc_ffu_mem {
>>>>> +       struct mmc_ffu_pages *arr;
>>>>> +       unsigned int cnt;
>>>>> +};
>>>>> +
>>>>> +struct mmc_ffu_area {
>>>>> +       unsigned long max_sz;
>>>>> +       unsigned int max_tfr;
>>>>> +       unsigned int max_segs;
>>>>> +       unsigned int max_seg_sz;
>>>>> +       unsigned int blocks;
>>>>> +       unsigned int sg_len;
>>>>> +       struct mmc_ffu_mem *mem;
>>>>> +       struct scatterlist *sg;
>>>>> +};
>>>>> +
>>>>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>>>>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>>>>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>>>> +
>>>>> +       if (blocks > 1)
>>>>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>>>> +       else
>>>>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>>>> +
>>>>> +       mrq->cmd->arg = arg;
>>>>> +       if (!mmc_card_blockaddr(card))
>>>>> +               mrq->cmd->arg <<= 9;
>>>>> +
>>>>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>>>> +       if (blocks == 1) {
>>>>> +               mrq->stop = NULL;
>>>>> +       } else {
>>>>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>>>>> +               mrq->stop->arg = 0;
>>>>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>>>>> +       }
>>>>> +
>>>>> +       mrq->data->blksz = blksz;
>>>>> +       mrq->data->blocks = blocks;
>>>>> +       mrq->data->flags = MMC_DATA_WRITE;
>>>>> +       mrq->data->sg = sg;
>>>>> +       mrq->data->sg_len = sg_len;
>>>>> +
>>>>> +       mmc_set_data_timeout(mrq->data, card); }
>>>>> +
>>>>> +/*
>>>>> + * Checks that a normal transfer didn't have any errors  */ static
>>>>> +int mmc_ffu_check_result(struct mmc_request *mrq) {
>>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>>>>> +
>>>>> +       if (mrq->cmd->error != 0)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       if (mrq->data->error != 0)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>>>>> +               return -1;
>>>>> +
>>>>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>>>>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>>>>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>>>>> +
>>>>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>>>> +       int ret, busy = 0;
>>>>> +       struct mmc_command cmd = {0};
>>>>> +
>>>>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>>>>> +       cmd.opcode = MMC_SEND_STATUS;
>>>>> +       cmd.arg = card->rca << 16;
>>>>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>>>>> +
>>>>> +       do {
>>>>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>>>>> +               if (ret)
>>>>> +                       break;
>>>>> +
>>>>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>>>>> +                       busy = 1;
>>>>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>>>>> +                               pr_warn("%s: Warning: Host did not "
>>>>> +                                       "wait for busy state to end.\n",
>>>>> +                                       mmc_hostname(card->host));
>>>>> +                       }
>>>>> +               }
>>>>> +
>>>>> +       } while (mmc_ffu_busy(&cmd));
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * transfer with certain parameters  */ static int
>>>>> +mmc_ffu_simple_transfer(struct mmc_card *card,
>>>>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>>>>> +       unsigned int blocks, unsigned int blksz) {
>>>>> +       struct mmc_request mrq = {0};
>>>>> +       struct mmc_command cmd = {0};
>>>>> +       struct mmc_command stop = {0};
>>>>> +       struct mmc_data data = {0};
>>>>> +
>>>>> +       mrq.cmd = &cmd;
>>>>> +       mrq.data = &data;
>>>>> +       mrq.stop = &stop;
>>>>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>>>> +       mmc_wait_for_req(card->host, &mrq);
>>>>> +
>>>>> +       mmc_ffu_wait_busy(card);
>>>>> +
>>>>> +       return mmc_ffu_check_result(&mrq); }
>>>>> +
>>>>> +/*
>>>>> + * Map memory into a scatterlist.
>>>>> + */
>>>>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>>>>> +       struct scatterlist *sglist, unsigned int max_segs,
>>>>> +       unsigned int max_seg_sz)
>>>>> +{
>>>>> +       struct scatterlist *sg = sglist;
>>>>> +       unsigned int i;
>>>>> +       unsigned long sz = size;
>>>>> +       unsigned int sctr_len = 0;
>>>>> +       unsigned long len;
>>>>> +
>>>>> +       sg_init_table(sglist, max_segs);
>>>>> +
>>>>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>>>>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>>>>> +
>>>>> +               if (len > sz) {
>>>>> +                       len = sz;
>>>>> +                       sz = 0;
>>>>> +               }
>>>>> +
>>>>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>>>>> +               sg = sg_next(sg);
>>>>> +               sctr_len += 1;
>>>>> +       }
>>>>> +       sg_mark_end(sg);
>>>>> +
>>>>> +       return sctr_len;
>>>>> +}
>>>>> +
>>>>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>>>>> +       if (!mem)
>>>>> +               return;
>>>>> +
>>>>> +       while (mem->cnt--)
>>>>> +               __free_pages(mem->arr[mem->cnt].page,
>>>>> + mem->arr[mem->cnt].order);
>>>>> +
>>>>> +       kfree(mem->arr);
>>>>> +       kfree(mem);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Cleanup struct mmc_ffu_area.
>>>>> + */
>>>>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>>>>> +       kfree(area->sg);
>>>>> +       mmc_ffu_free_mem(area->mem);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Allocate a lot of memory, preferably max_sz but at least min_sz.
>>>>> +In case
>>>>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>>>>> +Also do
>>>>> + * not exceed a maximum number of segments and try not to make
>>>>> +segments much
>>>>> + * bigger than maximum segment size.
>>>>> + */
>>>>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>>>>> +       unsigned long max_sz, unsigned int max_segs, unsigned int
>>>>> +max_seg_sz) {
>>>>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>>>>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>>>>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>>>>> +       unsigned long page_cnt = 0;
>>>>> +       /* we divide by 16 to ensure we will not allocate a big amount
>>>>> +        * of unnecessary pages */
>>>>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>>>>> +       struct mmc_ffu_mem *mem;
>>>>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
>>>>> +__GFP_NORETRY;
>>>>> +
>>>>> +       if (max_page_cnt > limit)
>>>>> +               max_page_cnt = limit;
>>>>> +
>>>>> +       if (min_page_cnt > max_page_cnt)
>>>>> +               min_page_cnt = max_page_cnt;
>>>>> +
>>>>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>>>>> +               max_segs = DIV_ROUND_UP(max_page_cnt,
>>>>> + max_seg_page_cnt);
>>>>> +
>>>>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>>>>> +       if (!mem)
>>>>> +               return NULL;
>>>>> +
>>>>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>>>>> +               GFP_KERNEL);
>>>>> +       if (!mem->arr)
>>>>> +               goto out_free;
>>>>> +
>>>>> +       while (max_page_cnt) {
>>>>> +               struct page *page;
>>>>> +               unsigned int order;
>>>>> +
>>>>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>>>>> +
>>>>> +               do {
>>>>> +                       page = alloc_pages(flags, order);
>>>>> +               } while (!page && order--);
>>>>> +
>>>>> +               if (!page)
>>>>> +                       goto out_free;
>>>>> +
>>>>> +               mem->arr[mem->cnt].page = page;
>>>>> +               mem->arr[mem->cnt].order = order;
>>>>> +               mem->cnt += 1;
>>>>> +               if (max_page_cnt <= (1UL << order))
>>>>> +                       break;
>>>>> +               max_page_cnt -= 1UL << order;
>>>>> +               page_cnt += 1UL << order;
>>>>> +       }
>>>>> +
>>>>> +       if (page_cnt < min_page_cnt)
>>>>> +               goto out_free;
>>>>> +
>>>>> +       return mem;
>>>>> +
>>>>> +out_free:
>>>>> +       mmc_ffu_free_mem(mem);
>>>>> +       return NULL;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Initialize an area for data transfers.
>>>>> + * Copy the data to the allocated pages.
>>>>> + */
>>>>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>>>>> +       const u8 *data, int size)
>>>>> +{
>>>>> +       int ret;
>>>>> +       int i;
>>>>> +       int length = 0;
>>>>> +
>>>>> +       area->max_tfr = size;
>>>>> +
>>>>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>>>>> +        * because the same memory can be mapped into the scatterlist more than
>>>>> +        * once. Also, take into account the limits imposed on scatterlist
>>>>> +        * segments by the host driver.
>>>>> +        */
>>>>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>>>>> +               area->max_seg_sz);
>>>>> +       if (!area->mem)
>>>>> +               return -ENOMEM;
>>>>> +
>>>>> +       /* copy data to page */
>>>>> +       for (i = 0; i < area->mem->cnt; i++) {
>>>>> +               if (length > size) {
>>>>> +                       ret = -EINVAL;
>>>>> +                       goto out_free;
>>>>> +               }
>>>>> +
>>>>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>>>>> +                       min(size - length, (int)area->max_seg_sz));
>>>>> +               length += area->max_seg_sz;
>>>>> +       }
>>>>> +
>>>>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>>>>> +               GFP_KERNEL);
>>>>> +       if (!area->sg) {
>>>>> +               ret = -ENOMEM;
>>>>> +               goto out_free;
>>>>> +       }
>>>>> +
>>>>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>>>>> +               area->max_segs, area->mem->cnt);
>>>>> +
>>>>> +       return 0;
>>>>> +
>>>>> +out_free:
>>>>> +       mmc_ffu_area_cleanup(area);
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>>>> +       int size)
>>>>> +{
>>>>> +       int rc;
>>>>> +       struct mmc_ffu_area area;
>>>>> +       int max_tfr;
>>>>> +
>>>>> +       area.sg = NULL;
>>>>> +       area.mem = NULL;
>>>>> +       area.max_segs = card->host->max_segs;
>>>>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>>>>> +       do {
>>>>> +               max_tfr = size;
>>>>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>>>>> +                       max_tfr = card->host->max_blk_count << 9;
>>>>> +               if (max_tfr > card->host->max_req_size)
>>>>> +                       max_tfr = card->host->max_req_size;
>>>>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>>>>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>>>>> +
>>>>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>>>>> +               if (rc != 0)
>>>>> +                       goto exit;
>>>>> +
>>>>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>>>>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>>>>> +               if (rc != 0)
>>>>> +                       goto exit;
>>>>> +
>>>>> +               src += max_tfr;
>>>>> +               size -= max_tfr;
>>>>> +       } while (size > 0);
>>>>> +
>>>>> +exit:
>>>>> +       mmc_ffu_area_cleanup(&area);
>>>>> +       return rc;
>>>>> +}
>>>>> +
>>>>> +/* Flush all scheduled work from the MMC work queue.
>>>>> + * and initialize the MMC device */ static int
>>>>> +mmc_ffu_restart(struct mmc_card *card) {
>>>>> +       struct mmc_host *host = card->host;
>>>>> +       int err = 0;
>>>>> +
>>>>> +       mmc_cache_ctrl(host, 0);
>>>>> +       err = mmc_power_save_host(host);
>>>>> +       if (err) {
>>>>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>>>>> +                       __func__ , err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       err = mmc_power_restore_host(host);
>>>>> +
>>>>> +exit:
>>>>> +
>>>>> +       return err;
>>>>> +}
>>>>> +
>>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>>> +       int err;
>>>>> +       int ret;
>>>>> +       u32 arg;
>>>>> +       u32 fw_prog_bytes;
>>>>> +       const struct firmware *fw;
>>>>> +
>>>>> +       /* Check if FFU is supported */
>>>>> +       if (!card->ext_csd.ffu_capable) {
>>>>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>>>>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>>>>> +                       card->ext_csd.rev);
>>>>> +               return -EOPNOTSUPP;
>>>>> +       }
>>>>> +
>>>>> +       if (strlen(name) > 512) {
>>>>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>>>>> +                       mmc_hostname(card->host), name);
>>>>> +               return -EINVAL;
>>>>> +       }
>>>>> +
>>>>> +       /* setup FW data buffer */
>>>>> +       err = request_firmware(&fw, name, &card->dev);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>>>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>>>>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>>>>> +                       fw->size);
>>>>> +       }
>>>>> +
>>>>> +       mmc_get_card(card);
>>>>> +
>>>>> +       /* set device to FFU mode */
>>>>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>>>>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* Read the EXT_CSD */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* set CMD ARG */
>>>>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>>>>> +
>>>>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>>>> +
>>>>> +       /* host switch back to work in normal MMC Read/Write commands */
>>>>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>>>> +               card->ext_csd.generic_cmd6_time);
>>>>> +       if (ret) {
>>>>> +               err = ret;
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* Read the EXT_CSD */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* check that the eMMC has received the payload */
>>>>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>>>> +
>>>>> +       /* convert sector to bytes */
>>>>> +        fw_prog_bytes *=
>>>>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>>>>> +       if (fw_prog_bytes != fw->size) {
>>>>> +               err = -EINVAL;
>>>>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +       }
>>>>> +
>>>>> +exit:
>>>>> +       release_firmware(fw);
>>>>> +       mmc_put_card(card);
>>>>> +       return err;
>>>>> +}
>>>>> +EXPORT_SYMBOL(mmc_ffu_download);
>>>>> +
>>>>> +int mmc_ffu_install(struct mmc_card *card) {
>>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>>> +       int err;
>>>>> +       u32 ffu_data_len;
>>>>> +       u32 timeout;
>>>>> +
>>>>> +       /* Check if FFU is supported */
>>>>> +       if (!card->ext_csd.ffu_capable) {
>>>>> +               pr_err("FFU: %s: error FFU is not supported\n",
>>>>> +                       mmc_hostname(card->host));
>>>>> +               return -EOPNOTSUPP;
>>>>> +       }
>>>>> +
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +
>>>>> +       /* check mode operation */
>>>>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>>>> +               /* restart the eMMC */
>>>>> +               err = mmc_ffu_restart(card);
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: install error %d:\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +       } else {
>>>>> +
>>>>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] <<
>>>>> + 24;
>>>>> +
>>>>> +               if (!ffu_data_len) {
>>>>> +                       err = -EPERM;
>>>>> +                       return err;
>>>>> +               }
>>>>> +               /* set device to FFU mode */
>>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>>>>> +                       card->ext_csd.generic_cmd6_time);
>>>>> +
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +
>>>>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>>>>> +               if (timeout == 0 || timeout > 0x17) {
>>>>> +                       timeout = 0x17;
>>>>> +                       pr_warn("FFU: %s: operation code timeout is out "
>>>>> +                               "of range. Using maximum timeout.\n",
>>>>> +                               mmc_hostname(card->host));
>>>>> +               }
>>>>> +
>>>>> +               /* timeout is at millisecond resolution */
>>>>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>>>>> +
>>>>> +               /* set ext_csd to install mode */
>>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +                       EXT_CSD_MODE_OPERATION_CODES,
>>>>> +                       MMC_FFU_INSTALL_SET, timeout);
>>>>> +
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +       }
>>>>> +
>>>>> +       /* read ext_csd */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +
>>>>> +       /* return status */
>>>>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d FFU install:\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return  -EINVAL;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL(mmc_ffu_install);
>>>>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
>>>>> 98e9eb0..a29065a 100644
>>>>> --- a/drivers/mmc/core/mmc.c
>>>>> +++ b/drivers/mmc/core/mmc.c
>>>>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>>>>                 card->ext_csd.data_sector_size = 512;
>>>>>         }
>>>>>
>>>>> +       /* eMMC v5 or later */
>>>>> +       if (card->ext_csd.rev >= 7) {
>>>>> +               card->ext_csd.ffu_capable =
>>>>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>>>>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>>>>> +       } else {
>>>>> +               card->ext_csd.ffu_capable = false;
>>>>> +       }
>>>>>  out:
>>>>>         return err;
>>>>>  }
>>>>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
>>>>> index b730272..bc6f6d0 100644
>>>>> --- a/include/linux/mmc/card.h
>>>>> +++ b/include/linux/mmc/card.h
>>>>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>>>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>>>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>>>>         bool                    boot_ro_lockable;
>>>>> +       bool            ffu_capable;    /* FFU support */
>>>>>         u8                      raw_exception_status;   /* 54 */
>>>>>         u8                      raw_partition_support;  /* 160 */
>>>>>         u8                      raw_rpmb_size_mult;     /* 168 */
>>>>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new
>>>>> file mode 100644 index 0000000..7e4133d
>>>>> --- /dev/null
>>>>> +++ b/include/linux/mmc/ffu.h
>>>>> @@ -0,0 +1,51 @@
>>>>> +/*
>>>>> + *
>>>>> + *  ffu.h
>>>>> + *
>>>>> + * Copyright (c) 2013 SanDisk Corp.
>>>>> + *
>>>>> + * This program is free software; you can redistribute it and/or
>>>>> +modify
>>>>> + * it under the terms of the GNU General Public License as
>>>>> +published by
>>>>> + * the Free Software Foundation; either version 2 of the License,
>>>>> +or (at
>>>>> + * your option) any later version.
>>>>> + *
>>>>> + * This program was created by SanDisk Corp
>>>>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>>>>> + * available via http://www.gnu.org/licenses/,
>>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>>> +*/
>>>>> +
>>>>> +#if !defined(_FFU_H_)
>>>>> +#define _FFU_H_
>>>>> +
>>>>> +#include <linux/mmc/card.h>
>>>>> +
>>>>> +#define CARD_BLOCK_SIZE 512
>>>>> +
>>>>> +/*
>>>>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
>>>> Something is wrong here. This patch would not compile, some carriage
>>>> returns are missing.
>>>>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
>>>> Here too.
>>>>> +
>>>>> +#define MMC_FFU_MODE_SET 0x1
>>>>> +#define MMC_FFU_MODE_NORMAL 0x0
>>>>> +#define MMC_FFU_INSTALL_SET 0x1
>>>>> +
>>>>> +#ifdef CONFIG_MMC_FFU
>>>>> +#define MMC_FFU_FEATURES 0x1
>>>>> +#define FFU_FEATURES(ffu_features) (ffu_features &
>>>>> +MMC_FFU_FEATURES)
>>>>> +
>>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
>>>>> +mmc_ffu_install(struct mmc_card *card); #else static inline int
>>>> Here too.
>>>>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>>> +       return -ENOSYS;
>>>>> +}
>>>>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>>>>> +       return -ENOSYS;
>>>>> +}
>>>>> +#endif
>>>>> +#endif /* FFU_H_ */
>>>>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index
>>>>> 50bcde3..3651449 100644
>>>>> --- a/include/linux/mmc/mmc.h
>>>>> +++ b/include/linux/mmc/mmc.h
>>>>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>>>>   * EXT_CSD fields
>>>>>   */
>>>>>
>>>>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>>>>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>>>>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>>>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>>>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>>>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>>>>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>>>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>>>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>>>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>>>>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>>>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>>>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>>>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>>>>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>>>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>>>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>>>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>>>>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>>>>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>>>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>>>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>>>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>>>>> --
>>>>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-01  8:08           ` Hsin-Hsiang Tseng
@ 2014-08-07 10:09             ` Avi Shchislowski
  0 siblings, 0 replies; 17+ messages in thread
From: Avi Shchislowski @ 2014-08-07 10:09 UTC (permalink / raw)
  To: Hsin-Hsiang Tseng
  Cc: Gwendal Grignou, Gwendal Grignou, linux-mmc, cjb, Grant Grundler,
	Alex Lemberg

Hi Hsin

When downloading the firmware before moving to FFU_INSTALL or executing mmc_card_int (depending on MODE_OPERATION_CODES) the value in NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED should return the firmware size.
If you getting 0 (zero), one possibility, is that not all the firmware data was written to the eMMC.
I found that there are some host controllers that the max_seg_size (card->host->max_seg_size) is not aligned to 512 (it could be 64K- 1) this will result in losing data (when sending to the eMMC)
Is it the case that you encounter it?

In the upcoming days I will release a fix to this issue.


Thanks
Avi.


 


-----Original Message-----
From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com] 
Sent: Friday, August 01, 2014 11:09 AM
To: Avi Shchislowski
Cc: Gwendal Grignou; Gwendal Grignou; linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Alex Lemberg
Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0

Hi, Avi,

I have another question want to consult, In your patch, after download the required firmware file, and back to normal mode.
you will /* check that the eMMC has received the payload */ by read NUMBER_OF_FW_SECTORS_CORRECTLY_PROGAMMED field and compare to the size of the required firmware file.

From the spec JESD84-B50, Field Firmware Update portion.

If the host finished downloading successfully the firmware bundle to the device it may set MODE_OPERATION_CODES to FFU_INSTALL, the device shall set NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED field to zero, install the new firmware and set MODE_CONFIG to Normal state which would regain regular operation of read and write commands.

When we  check that the eMMC has received the payload. In my test eMMC sample return 0, so always blocked here.

Thanks.


2014-08-01 10:26 GMT+08:00 Hsin-Hsiang Tseng <hsinhsiangtseng@gmail.com>:
> Hi ,Avi,
>
> According to EXT_CSD_FFU_STATUS can report the status of the FFU process.
> If it necessary to check Error in downloading firmware(value: 0x12) 
> before we continue to execute mmc_ffu_install?
>
> Thanks.
>
> 2014-07-30 22:08 GMT+08:00 Avi Shchislowski <Avi.Shchislowski@sandisk.com>:
>> Hi Gwendal
>> From spec perspective there should not be any problem to read the 
>> EXT_CSD value in FFU mode, the spec refer to read/write commands But if it causes issues I will read the EXT_CSD before entering FFU mode.
>>
>> Please let me know.
>>
>> Thanks
>> Avi.
>>
>>
>> -----Original Message-----
>> From: Gwendal Grignou [mailto:gwendal@google.com]
>> Sent: Wednesday, July 30, 2014 3:32 AM
>> To: Gwendal Grignou
>> Cc: Avi Shchislowski; linux-mmc@vger.kernel.org; cjb@laptop.org; 
>> Grant Grundler; Alex Lemberg
>> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
>>
>> I take it back, we can still use the 2 ioctl approach:
>> We just need to remove the mmc_send_ext_csd() between the calls to set MMC_FFU_MODE_SET and MMC_FFU_MODE_NORMAL.
>> Your patch v7 did not have this error, I introduced it in the comments I made on v7.
>>
>> Gwendal.
>>
>> On Tue, Jul 29, 2014 at 5:14 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
>>> Avi,
>>> We should revisit the 2 ioctls approach: Accroding to the spec:
>>> """When in FFU_MODE and host sends other commands which are not part 
>>> of the recommended flow, device behavior may be undefined."""
>>>
>>> To be safe, no command should be sent to the device which are not 
>>> strictly necessary for the downloading the firmware.
>>> Therefore, we should restrict to only the firmware download command 
>>> while in FFU mode. With the 2 ioctls approach, any commands can be 
>>> send in between the 2 ioctl calls.
>>> Moreover, we read ext_csd while in FFU mode. we should get rid of 
>>> that call as well.
>>>
>>> Gwendal.
>>>
>>> On Mon, Jul 21, 2014 at 11:01 AM, Gwendal Grignou <gwendal@chromium.org> wrote:
>>>> Avi,
>>>>
>>>> The patch still does not work for me. After the upgrade, the eMMC 
>>>> is not in a good state timing out every IOs. A power cycle fixes 
>>>> the problem.
>>>> I am investigating how the reset patch is working.
>>>>
>>>> Gwendal.
>>>>
>>>>
>>>> On Wed, Jul 16, 2014 at 8:47 AM, Avi Shchislowski 
>>>> <Avi.Shchislowski@sandisk.com> wrote:
>>>>>
>>>>> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec 
>>>>> (Jedec: JESD84-B50.pdf)
>>>>>
>>>>> http://www.jedec.org/standards-documents/technology-focus-areas/fl
>>>>> as
>>>>> h-memory-ssds-ufs-emmc/e-mmc
>>>>>
>>>>> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>>>>>
>>>>> Two new ioctls have been added:
>>>>> 1. FFU download firmware - transfer the new firmware data from 
>>>>> user space to the eMMC device 2. FFU install - initializes the new 
>>>>> firmware update
>>>>>
>>>>>
>>>>> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>>>>> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>>>>
>>>>> ---
>>>>> V8:
>>>>> - Modified according to Gwendal Grignou comments and patch:
>>>>>    [PATCH] Fix on top of sandisk patches
>>>>>
>>>>> V7:
>>>>> - fixed mangled white space
>>>>>
>>>>> V5:
>>>>> - provides udev (request_firmware) implementation as advised in 
>>>>> patch
>>>>> v2 comments.
>>>>>
>>>>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig 
>>>>> index 5562308..19ba729 100644
>>>>> --- a/drivers/mmc/card/Kconfig
>>>>> +++ b/drivers/mmc/card/Kconfig
>>>>> @@ -68,3 +68,11 @@ config MMC_TEST
>>>>>
>>>>>           This driver is only of interest to those developing or
>>>>>           testing a host driver. Most people should say N here.
>>>>> +
>>>>> +config MMC_FFU
>>>>> +       bool "FFU SUPPORT"
>>>>> +       depends on MMC != n
>>>>> +       help
>>>>> +         This is an option to run firmware update on eMMC 5.0.
>>>>> +         Field firmware updates (FFU) enables features enhancment
>>>>> +         in the field.
>>>>> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile 
>>>>> index c73b406..1e9223b 100644
>>>>> --- a/drivers/mmc/card/Makefile
>>>>> +++ b/drivers/mmc/card/Makefile
>>>>> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>>>>>
>>>>>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>>>>>
>>>>> +obj-$(CONFIG_MMC_FFU)          += ffu.o
>>>>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c 
>>>>> index 7b5424f..3ed900b 100644
>>>>> --- a/drivers/mmc/card/block.c
>>>>> +++ b/drivers/mmc/card/block.c
>>>>> @@ -41,6 +41,7 @@
>>>>>  #include <linux/mmc/host.h>
>>>>>  #include <linux/mmc/mmc.h>
>>>>>  #include <linux/mmc/sd.h>
>>>>> +#include <linux/mmc/ffu.h>
>>>>>
>>>>>  #include <asm/uaccess.h>
>>>>>
>>>>> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct 
>>>>> block_device *bdev,
>>>>>
>>>>>         mrq.cmd = &cmd;
>>>>>
>>>>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>>>> +                       err = mmc_ffu_download(card, idata->buf);
>>>>> +                       goto cmd_done;
>>>>> +               }
>>>>> +
>>>>>         mmc_get_card(card);
>>>>>
>>>>> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>>>>> +               err = mmc_ffu_install(card);
>>>>> +               goto cmd_rel_host;
>>>>> +       }
>>>>> +
>>>>>         err = mmc_blk_part_switch(card, md);
>>>>>         if (err)
>>>>>                 goto cmd_rel_host; diff --git 
>>>>> a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 
>>>>> 100644 index 0000000..783673e
>>>>> --- /dev/null
>>>>> +++ b/drivers/mmc/card/ffu.c
>>>>> @@ -0,0 +1,607 @@
>>>>> +/*
>>>>> + * *  ffu.c
>>>>> + *
>>>>> + *  Copyright 2007-2008 Pierre Ossman
>>>>> + *
>>>>> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>>>>> + *
>>>>> + * This program is free software; you can redistribute it and/or 
>>>>> +modify
>>>>> + * it under the terms of the GNU General Public License as 
>>>>> +published by
>>>>> + * the Free Software Foundation; either version 2 of the License, 
>>>>> +or (at
>>>>> + * your option) any later version.
>>>>> + *
>>>>> + * This program includes bug.h, card.h, host.h, mmc.h, 
>>>>> +scatterlist.h,
>>>>> + * slab.h, ffu.h & swap.h header files
>>>>> + * The original, unmodified version of this program - the 
>>>>> +mmc_test.c
>>>>> + * file - is obtained under the GPL v2.0 license that is 
>>>>> +available via
>>>>> + * http://www.gnu.org/licenses/,
>>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>>> +*/
>>>>> +
>>>>> +#include <linux/bug.h>
>>>>> +#include <linux/errno.h>
>>>>> +#include <linux/mmc/card.h>
>>>>> +#include <linux/mmc/host.h>
>>>>> +#include <linux/mmc/mmc.h>
>>>>> +#include <linux/scatterlist.h>
>>>>> +#include <linux/slab.h>
>>>>> +#include <linux/swap.h>
>>>>> +#include <linux/mmc/ffu.h>
>>>>> +#include <linux/firmware.h>
>>>>> +
>>>>> +/**
>>>>> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>>>>> + * @page: first page in the allocation
>>>>> + * @order: order of the number of pages allocated  */ struct 
>>>>> +mmc_ffu_pages {
>>>>> +       struct page *page;
>>>>> +       unsigned int order;
>>>>> +};
>>>>> +
>>>>> +/**
>>>>> + * struct mmc_ffu_mem - allocated memory.
>>>>> + * @arr: array of allocations
>>>>> + * @cnt: number of allocations
>>>>> + */
>>>>> +struct mmc_ffu_mem {
>>>>> +       struct mmc_ffu_pages *arr;
>>>>> +       unsigned int cnt;
>>>>> +};
>>>>> +
>>>>> +struct mmc_ffu_area {
>>>>> +       unsigned long max_sz;
>>>>> +       unsigned int max_tfr;
>>>>> +       unsigned int max_segs;
>>>>> +       unsigned int max_seg_sz;
>>>>> +       unsigned int blocks;
>>>>> +       unsigned int sg_len;
>>>>> +       struct mmc_ffu_mem *mem;
>>>>> +       struct scatterlist *sg;
>>>>> +};
>>>>> +
>>>>> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>>>>> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>>>>> +       u32 arg, unsigned int blocks, unsigned int blksz) {
>>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>>>> +
>>>>> +       if (blocks > 1)
>>>>> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>>>> +       else
>>>>> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>>>> +
>>>>> +       mrq->cmd->arg = arg;
>>>>> +       if (!mmc_card_blockaddr(card))
>>>>> +               mrq->cmd->arg <<= 9;
>>>>> +
>>>>> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>>>>> +       if (blocks == 1) {
>>>>> +               mrq->stop = NULL;
>>>>> +       } else {
>>>>> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>>>>> +               mrq->stop->arg = 0;
>>>>> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>>>>> +       }
>>>>> +
>>>>> +       mrq->data->blksz = blksz;
>>>>> +       mrq->data->blocks = blocks;
>>>>> +       mrq->data->flags = MMC_DATA_WRITE;
>>>>> +       mrq->data->sg = sg;
>>>>> +       mrq->data->sg_len = sg_len;
>>>>> +
>>>>> +       mmc_set_data_timeout(mrq->data, card); }
>>>>> +
>>>>> +/*
>>>>> + * Checks that a normal transfer didn't have any errors  */ 
>>>>> +static int mmc_ffu_check_result(struct mmc_request *mrq) {
>>>>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>>>>> +
>>>>> +       if (mrq->cmd->error != 0)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       if (mrq->data->error != 0)
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       if (mrq->stop != NULL && mrq->stop->error != 0)
>>>>> +               return -1;
>>>>> +
>>>>> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
>>>>> +               return -EINVAL;
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +static int mmc_ffu_busy(struct mmc_command *cmd) {
>>>>> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>>>>> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); 
>>>>> +}
>>>>> +
>>>>> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>>>> +       int ret, busy = 0;
>>>>> +       struct mmc_command cmd = {0};
>>>>> +
>>>>> +       memset(&cmd, 0, sizeof(struct mmc_command));
>>>>> +       cmd.opcode = MMC_SEND_STATUS;
>>>>> +       cmd.arg = card->rca << 16;
>>>>> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>>>>> +
>>>>> +       do {
>>>>> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>>>>> +               if (ret)
>>>>> +                       break;
>>>>> +
>>>>> +               if (!busy && mmc_ffu_busy(&cmd)) {
>>>>> +                       busy = 1;
>>>>> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>>>>> +                               pr_warn("%s: Warning: Host did not "
>>>>> +                                       "wait for busy state to end.\n",
>>>>> +                                       mmc_hostname(card->host));
>>>>> +                       }
>>>>> +               }
>>>>> +
>>>>> +       } while (mmc_ffu_busy(&cmd));
>>>>> +
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * transfer with certain parameters  */ static int 
>>>>> +mmc_ffu_simple_transfer(struct mmc_card *card,
>>>>> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>>>>> +       unsigned int blocks, unsigned int blksz) {
>>>>> +       struct mmc_request mrq = {0};
>>>>> +       struct mmc_command cmd = {0};
>>>>> +       struct mmc_command stop = {0};
>>>>> +       struct mmc_data data = {0};
>>>>> +
>>>>> +       mrq.cmd = &cmd;
>>>>> +       mrq.data = &data;
>>>>> +       mrq.stop = &stop;
>>>>> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>>>> +       mmc_wait_for_req(card->host, &mrq);
>>>>> +
>>>>> +       mmc_ffu_wait_busy(card);
>>>>> +
>>>>> +       return mmc_ffu_check_result(&mrq); }
>>>>> +
>>>>> +/*
>>>>> + * Map memory into a scatterlist.
>>>>> + */
>>>>> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
>>>>> +       struct scatterlist *sglist, unsigned int max_segs,
>>>>> +       unsigned int max_seg_sz)
>>>>> +{
>>>>> +       struct scatterlist *sg = sglist;
>>>>> +       unsigned int i;
>>>>> +       unsigned long sz = size;
>>>>> +       unsigned int sctr_len = 0;
>>>>> +       unsigned long len;
>>>>> +
>>>>> +       sg_init_table(sglist, max_segs);
>>>>> +
>>>>> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>>>>> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>>>>> +
>>>>> +               if (len > sz) {
>>>>> +                       len = sz;
>>>>> +                       sz = 0;
>>>>> +               }
>>>>> +
>>>>> +               sg_set_page(sg, mem->arr[i].page, len, 0);
>>>>> +               sg = sg_next(sg);
>>>>> +               sctr_len += 1;
>>>>> +       }
>>>>> +       sg_mark_end(sg);
>>>>> +
>>>>> +       return sctr_len;
>>>>> +}
>>>>> +
>>>>> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>>>>> +       if (!mem)
>>>>> +               return;
>>>>> +
>>>>> +       while (mem->cnt--)
>>>>> +               __free_pages(mem->arr[mem->cnt].page,
>>>>> + mem->arr[mem->cnt].order);
>>>>> +
>>>>> +       kfree(mem->arr);
>>>>> +       kfree(mem);
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Cleanup struct mmc_ffu_area.
>>>>> + */
>>>>> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>>>>> +       kfree(area->sg);
>>>>> +       mmc_ffu_free_mem(area->mem);
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Allocate a lot of memory, preferably max_sz but at least min_sz.
>>>>> +In case
>>>>> + * there isn't much memory do not exceed 1/16th total low mem pages.
>>>>> +Also do
>>>>> + * not exceed a maximum number of segments and try not to make 
>>>>> +segments much
>>>>> + * bigger than maximum segment size.
>>>>> + */
>>>>> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>>>>> +       unsigned long max_sz, unsigned int max_segs, unsigned int
>>>>> +max_seg_sz) {
>>>>> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>>>>> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>>>>> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
>>>>> +       unsigned long page_cnt = 0;
>>>>> +       /* we divide by 16 to ensure we will not allocate a big amount
>>>>> +        * of unnecessary pages */
>>>>> +       unsigned long limit = nr_free_buffer_pages() >> 4;
>>>>> +       struct mmc_ffu_mem *mem;
>>>>> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | 
>>>>> +__GFP_NORETRY;
>>>>> +
>>>>> +       if (max_page_cnt > limit)
>>>>> +               max_page_cnt = limit;
>>>>> +
>>>>> +       if (min_page_cnt > max_page_cnt)
>>>>> +               min_page_cnt = max_page_cnt;
>>>>> +
>>>>> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>>>>> +               max_segs = DIV_ROUND_UP(max_page_cnt, 
>>>>> + max_seg_page_cnt);
>>>>> +
>>>>> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>>>>> +       if (!mem)
>>>>> +               return NULL;
>>>>> +
>>>>> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>>>>> +               GFP_KERNEL);
>>>>> +       if (!mem->arr)
>>>>> +               goto out_free;
>>>>> +
>>>>> +       while (max_page_cnt) {
>>>>> +               struct page *page;
>>>>> +               unsigned int order;
>>>>> +
>>>>> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>>>>> +
>>>>> +               do {
>>>>> +                       page = alloc_pages(flags, order);
>>>>> +               } while (!page && order--);
>>>>> +
>>>>> +               if (!page)
>>>>> +                       goto out_free;
>>>>> +
>>>>> +               mem->arr[mem->cnt].page = page;
>>>>> +               mem->arr[mem->cnt].order = order;
>>>>> +               mem->cnt += 1;
>>>>> +               if (max_page_cnt <= (1UL << order))
>>>>> +                       break;
>>>>> +               max_page_cnt -= 1UL << order;
>>>>> +               page_cnt += 1UL << order;
>>>>> +       }
>>>>> +
>>>>> +       if (page_cnt < min_page_cnt)
>>>>> +               goto out_free;
>>>>> +
>>>>> +       return mem;
>>>>> +
>>>>> +out_free:
>>>>> +       mmc_ffu_free_mem(mem);
>>>>> +       return NULL;
>>>>> +}
>>>>> +
>>>>> +/*
>>>>> + * Initialize an area for data transfers.
>>>>> + * Copy the data to the allocated pages.
>>>>> + */
>>>>> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
>>>>> +       const u8 *data, int size)
>>>>> +{
>>>>> +       int ret;
>>>>> +       int i;
>>>>> +       int length = 0;
>>>>> +
>>>>> +       area->max_tfr = size;
>>>>> +
>>>>> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>>>>> +        * because the same memory can be mapped into the scatterlist more than
>>>>> +        * once. Also, take into account the limits imposed on scatterlist
>>>>> +        * segments by the host driver.
>>>>> +        */
>>>>> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
>>>>> +               area->max_seg_sz);
>>>>> +       if (!area->mem)
>>>>> +               return -ENOMEM;
>>>>> +
>>>>> +       /* copy data to page */
>>>>> +       for (i = 0; i < area->mem->cnt; i++) {
>>>>> +               if (length > size) {
>>>>> +                       ret = -EINVAL;
>>>>> +                       goto out_free;
>>>>> +               }
>>>>> +
>>>>> +               memcpy(page_address(area->mem->arr[i].page), data + length,
>>>>> +                       min(size - length, (int)area->max_seg_sz));
>>>>> +               length += area->max_seg_sz;
>>>>> +       }
>>>>> +
>>>>> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>>>>> +               GFP_KERNEL);
>>>>> +       if (!area->sg) {
>>>>> +               ret = -ENOMEM;
>>>>> +               goto out_free;
>>>>> +       }
>>>>> +
>>>>> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>>>>> +               area->max_segs, area->mem->cnt);
>>>>> +
>>>>> +       return 0;
>>>>> +
>>>>> +out_free:
>>>>> +       mmc_ffu_area_cleanup(area);
>>>>> +       return ret;
>>>>> +}
>>>>> +
>>>>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>>>> +       int size)
>>>>> +{
>>>>> +       int rc;
>>>>> +       struct mmc_ffu_area area;
>>>>> +       int max_tfr;
>>>>> +
>>>>> +       area.sg = NULL;
>>>>> +       area.mem = NULL;
>>>>> +       area.max_segs = card->host->max_segs;
>>>>> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
>>>>> +       do {
>>>>> +               max_tfr = size;
>>>>> +               if (max_tfr >> 9 > card->host->max_blk_count)
>>>>> +                       max_tfr = card->host->max_blk_count << 9;
>>>>> +               if (max_tfr > card->host->max_req_size)
>>>>> +                       max_tfr = card->host->max_req_size;
>>>>> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>>>>> +                       max_tfr = area.max_segs * area.max_seg_sz;
>>>>> +
>>>>> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>>>>> +               if (rc != 0)
>>>>> +                       goto exit;
>>>>> +
>>>>> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>>>>> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>>>>> +               if (rc != 0)
>>>>> +                       goto exit;
>>>>> +
>>>>> +               src += max_tfr;
>>>>> +               size -= max_tfr;
>>>>> +       } while (size > 0);
>>>>> +
>>>>> +exit:
>>>>> +       mmc_ffu_area_cleanup(&area);
>>>>> +       return rc;
>>>>> +}
>>>>> +
>>>>> +/* Flush all scheduled work from the MMC work queue.
>>>>> + * and initialize the MMC device */ static int 
>>>>> +mmc_ffu_restart(struct mmc_card *card) {
>>>>> +       struct mmc_host *host = card->host;
>>>>> +       int err = 0;
>>>>> +
>>>>> +       mmc_cache_ctrl(host, 0);
>>>>> +       err = mmc_power_save_host(host);
>>>>> +       if (err) {
>>>>> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
>>>>> +                       __func__ , err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       err = mmc_power_restore_host(host);
>>>>> +
>>>>> +exit:
>>>>> +
>>>>> +       return err;
>>>>> +}
>>>>> +
>>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>>> +       int err;
>>>>> +       int ret;
>>>>> +       u32 arg;
>>>>> +       u32 fw_prog_bytes;
>>>>> +       const struct firmware *fw;
>>>>> +
>>>>> +       /* Check if FFU is supported */
>>>>> +       if (!card->ext_csd.ffu_capable) {
>>>>> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
>>>>> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
>>>>> +                       card->ext_csd.rev);
>>>>> +               return -EOPNOTSUPP;
>>>>> +       }
>>>>> +
>>>>> +       if (strlen(name) > 512) {
>>>>> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
>>>>> +                       mmc_hostname(card->host), name);
>>>>> +               return -EINVAL;
>>>>> +       }
>>>>> +
>>>>> +       /* setup FW data buffer */
>>>>> +       err = request_firmware(&fw, name, &card->dev);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: Firmware request failed %d\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>>>> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
>>>>> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
>>>>> +                       fw->size);
>>>>> +       }
>>>>> +
>>>>> +       mmc_get_card(card);
>>>>> +
>>>>> +       /* set device to FFU mode */
>>>>> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
>>>>> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* Read the EXT_CSD */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* set CMD ARG */
>>>>> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
>>>>> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
>>>>> +
>>>>> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>>>> +
>>>>> +       /* host switch back to work in normal MMC Read/Write commands */
>>>>> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>>>> +               card->ext_csd.generic_cmd6_time);
>>>>> +       if (ret) {
>>>>> +               err = ret;
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* Read the EXT_CSD */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               goto exit;
>>>>> +       }
>>>>> +
>>>>> +       /* check that the eMMC has received the payload */
>>>>> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>>> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
>>>>> +
>>>>> +       /* convert sector to bytes */
>>>>> +        fw_prog_bytes *=
>>>>> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
>>>>> +       if (fw_prog_bytes != fw->size) {
>>>>> +               err = -EINVAL;
>>>>> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +       }
>>>>> +
>>>>> +exit:
>>>>> +       release_firmware(fw);
>>>>> +       mmc_put_card(card);
>>>>> +       return err;
>>>>> +}
>>>>> +EXPORT_SYMBOL(mmc_ffu_download);
>>>>> +
>>>>> +int mmc_ffu_install(struct mmc_card *card) {
>>>>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>>>>> +       int err;
>>>>> +       u32 ffu_data_len;
>>>>> +       u32 timeout;
>>>>> +
>>>>> +       /* Check if FFU is supported */
>>>>> +       if (!card->ext_csd.ffu_capable) {
>>>>> +               pr_err("FFU: %s: error FFU is not supported\n",
>>>>> +                       mmc_hostname(card->host));
>>>>> +               return -EOPNOTSUPP;
>>>>> +       }
>>>>> +
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +
>>>>> +       /* check mode operation */
>>>>> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>>>> +               /* restart the eMMC */
>>>>> +               err = mmc_ffu_restart(card);
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: install error %d:\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +       } else {
>>>>> +
>>>>> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
>>>>> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 
>>>>> + 24;
>>>>> +
>>>>> +               if (!ffu_data_len) {
>>>>> +                       err = -EPERM;
>>>>> +                       return err;
>>>>> +               }
>>>>> +               /* set device to FFU mode */
>>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +                       EXT_CSD_MODE_CONFIG, 0x1,
>>>>> +                       card->ext_csd.generic_cmd6_time);
>>>>> +
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +
>>>>> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
>>>>> +               if (timeout == 0 || timeout > 0x17) {
>>>>> +                       timeout = 0x17;
>>>>> +                       pr_warn("FFU: %s: operation code timeout is out "
>>>>> +                               "of range. Using maximum timeout.\n",
>>>>> +                               mmc_hostname(card->host));
>>>>> +               }
>>>>> +
>>>>> +               /* timeout is at millisecond resolution */
>>>>> +               timeout = (100 * (1 << timeout) / 1000) + 1;
>>>>> +
>>>>> +               /* set ext_csd to install mode */
>>>>> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>>>> +                       EXT_CSD_MODE_OPERATION_CODES,
>>>>> +                       MMC_FFU_INSTALL_SET, timeout);
>>>>> +
>>>>> +               if (err) {
>>>>> +                       pr_err("FFU: %s: error %d setting install mode\n",
>>>>> +                               mmc_hostname(card->host), err);
>>>>> +                       return err;
>>>>> +               }
>>>>> +       }
>>>>> +
>>>>> +       /* read ext_csd */
>>>>> +       err = mmc_send_ext_csd(card, ext_csd);
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d sending ext_csd\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return err;
>>>>> +       }
>>>>> +
>>>>> +       /* return status */
>>>>> +       err = ext_csd[EXT_CSD_FFU_STATUS];
>>>>> +       if (err) {
>>>>> +               pr_err("FFU: %s: error %d FFU install:\n",
>>>>> +                       mmc_hostname(card->host), err);
>>>>> +               return  -EINVAL;
>>>>> +       }
>>>>> +
>>>>> +       return 0;
>>>>> +}
>>>>> +EXPORT_SYMBOL(mmc_ffu_install);
>>>>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 
>>>>> 98e9eb0..a29065a 100644
>>>>> --- a/drivers/mmc/core/mmc.c
>>>>> +++ b/drivers/mmc/core/mmc.c
>>>>> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>>>>>                 card->ext_csd.data_sector_size = 512;
>>>>>         }
>>>>>
>>>>> +       /* eMMC v5 or later */
>>>>> +       if (card->ext_csd.rev >= 7) {
>>>>> +               card->ext_csd.ffu_capable =
>>>>> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>>>>> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>>>>> +       } else {
>>>>> +               card->ext_csd.ffu_capable = false;
>>>>> +       }
>>>>>  out:
>>>>>         return err;
>>>>>  }
>>>>> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h 
>>>>> index b730272..bc6f6d0 100644
>>>>> --- a/include/linux/mmc/card.h
>>>>> +++ b/include/linux/mmc/card.h
>>>>> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>>>>>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>>>>>         unsigned int            boot_ro_lock;           /* ro lock support */
>>>>>         bool                    boot_ro_lockable;
>>>>> +       bool            ffu_capable;    /* FFU support */
>>>>>         u8                      raw_exception_status;   /* 54 */
>>>>>         u8                      raw_partition_support;  /* 160 */
>>>>>         u8                      raw_rpmb_size_mult;     /* 168 */
>>>>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new 
>>>>> file mode 100644 index 0000000..7e4133d
>>>>> --- /dev/null
>>>>> +++ b/include/linux/mmc/ffu.h
>>>>> @@ -0,0 +1,51 @@
>>>>> +/*
>>>>> + *
>>>>> + *  ffu.h
>>>>> + *
>>>>> + * Copyright (c) 2013 SanDisk Corp.
>>>>> + *
>>>>> + * This program is free software; you can redistribute it and/or 
>>>>> +modify
>>>>> + * it under the terms of the GNU General Public License as 
>>>>> +published by
>>>>> + * the Free Software Foundation; either version 2 of the License, 
>>>>> +or (at
>>>>> + * your option) any later version.
>>>>> + *
>>>>> + * This program was created by SanDisk Corp
>>>>> + * The ffu.h file is obtained under the GPL v2.0 license that is
>>>>> + * available via http://www.gnu.org/licenses/,
>>>>> + * or http://www.opensource.org/licenses/gpl-2.0.php
>>>>> +*/
>>>>> +
>>>>> +#if !defined(_FFU_H_)
>>>>> +#define _FFU_H_
>>>>> +
>>>>> +#include <linux/mmc/card.h>
>>>>> +
>>>>> +#define CARD_BLOCK_SIZE 512
>>>>> +
>>>>> +/*
>>>>> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
>>>> Something is wrong here. This patch would not compile, some 
>>>> carriage returns are missing.
>>>>> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
>>>> Here too.
>>>>> +
>>>>> +#define MMC_FFU_MODE_SET 0x1
>>>>> +#define MMC_FFU_MODE_NORMAL 0x0
>>>>> +#define MMC_FFU_INSTALL_SET 0x1
>>>>> +
>>>>> +#ifdef CONFIG_MMC_FFU
>>>>> +#define MMC_FFU_FEATURES 0x1
>>>>> +#define FFU_FEATURES(ffu_features) (ffu_features &
>>>>> +MMC_FFU_FEATURES)
>>>>> +
>>>>> +int mmc_ffu_download(struct mmc_card *card, const char *name); 
>>>>> +int mmc_ffu_install(struct mmc_card *card); #else static inline 
>>>>> +int
>>>> Here too.
>>>>> +mmc_ffu_download(struct mmc_card *card, const char *name) {
>>>>> +       return -ENOSYS;
>>>>> +}
>>>>> +static inline int mmc_ffu_install(struct mmc_card *card) {
>>>>> +       return -ENOSYS;
>>>>> +}
>>>>> +#endif
>>>>> +#endif /* FFU_H_ */
>>>>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h 
>>>>> index
>>>>> 50bcde3..3651449 100644
>>>>> --- a/include/linux/mmc/mmc.h
>>>>> +++ b/include/linux/mmc/mmc.h
>>>>> @@ -272,6 +272,9 @@ struct _mmc_csd {
>>>>>   * EXT_CSD fields
>>>>>   */
>>>>>
>>>>> +#define EXT_CSD_FFU_STATUS             26      /* R */
>>>>> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
>>>>> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>>>>>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>>>>>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>>>>>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
>>>>> @@ -290,6 +293,7 @@ struct _mmc_csd {
>>>>>  #define EXT_CSD_SANITIZE_START         165     /* W */
>>>>>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>>>>>  #define EXT_CSD_RPMB_MULT              168     /* RO */
>>>>> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>>>>>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>>>>>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>>>>>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
>>>>> @@ -325,6 +329,11 @@ struct _mmc_csd {
>>>>>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>>>>>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>>>>>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
>>>>> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
>>>>> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
>>>>> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>>>>>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>>>>>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>>>>>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
>>>>> --
>>>>> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-16 15:47 [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0 Avi Shchislowski
  2014-07-21 18:01 ` Gwendal Grignou
@ 2014-08-13 12:46 ` Ulf Hansson
  2014-08-14 12:37   ` Alex Lemberg
  1 sibling, 1 reply; 17+ messages in thread
From: Ulf Hansson @ 2014-08-13 12:46 UTC (permalink / raw)
  To: Avi Shchislowski, Grant Grundler, Alex Lemberg; +Cc: linux-mmc, cjb

On 16 July 2014 17:47, Avi Shchislowski <Avi.Shchislowski@sandisk.com> wrote:
> The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec: JESD84-B50.pdf)
>   http://www.jedec.org/standards-documents/technology-focus-areas/flash-memory-ssds-ufs-emmc/e-mmc
>
> An ioctl has been added to provide the new firmware image's file name to the  mmc driver and udev is then used to retrieve its data.
>
> Two new ioctls have been added:
> 1. FFU download firmware - transfer the new firmware data from user space to the eMMC device 2. FFU install - initializes the new firmware update
>
>
> Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
> Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>

Hi guys,

Sorry for the delay in providing feeback, but hey better late than never. :-)

>
> ---
> V8:
> - Modified according to Gwendal Grignou comments and patch:
>    [PATCH] Fix on top of sandisk patches
>
> V7:
> - fixed mangled white space
>
> V5:
> - provides udev (request_firmware) implementation as advised in patch
> v2 comments.
>
> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index 5562308..19ba729 100644
> --- a/drivers/mmc/card/Kconfig
> +++ b/drivers/mmc/card/Kconfig
> @@ -68,3 +68,11 @@ config MMC_TEST
>
>           This driver is only of interest to those developing or
>           testing a host driver. Most people should say N here.
> +
> +config MMC_FFU
> +       bool "FFU SUPPORT"
> +       depends on MMC != n
> +       help
> +         This is an option to run firmware update on eMMC 5.0.
> +         Field firmware updates (FFU) enables features enhancment
> +         in the field.

Why do we need a new Kconfig for this? I think we can drop it.

> diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index c73b406..1e9223b 100644
> --- a/drivers/mmc/card/Makefile
> +++ b/drivers/mmc/card/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>
>  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>
> +obj-$(CONFIG_MMC_FFU)          += ffu.o
> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 7b5424f..3ed900b 100644
> --- a/drivers/mmc/card/block.c
> +++ b/drivers/mmc/card/block.c
> @@ -41,6 +41,7 @@
>  #include <linux/mmc/host.h>
>  #include <linux/mmc/mmc.h>
>  #include <linux/mmc/sd.h>
> +#include <linux/mmc/ffu.h>
>
>  #include <asm/uaccess.h>
>
> @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>
>         mrq.cmd = &cmd;
>
> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> +                       err = mmc_ffu_download(card, idata->buf);
> +                       goto cmd_done;
> +               }
> +
>         mmc_get_card(card);
>
> +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
> +               err = mmc_ffu_install(card);
> +               goto cmd_rel_host;
> +       }
> +

I think one FFU operation code should be enough. Overall that would
also simplify management and thus code.

Additionally I think it's better to move these "if statements" prior a
potential data transfer is set up. You don't use the mrq struct
anyway.

>         err = mmc_blk_part_switch(card, md);
>         if (err)
>                 goto cmd_rel_host;
> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file mode 100644 index 0000000..783673e
> --- /dev/null
> +++ b/drivers/mmc/card/ffu.c

I am wondering whether this code should belong to
drivers/mmc/core/mmc.c instead. Or unless it becomes too big, we could
have a separate file called mmc_ffu.[c|h], but in the core directory.

> @@ -0,0 +1,607 @@
> +/*
> + * *  ffu.c
> + *
> + *  Copyright 2007-2008 Pierre Ossman
> + *
> + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> +(at
> + * your option) any later version.
> + *
> + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
> + * slab.h, ffu.h & swap.h header files
> + * The original, unmodified version of this program - the mmc_test.c
> + * file - is obtained under the GPL v2.0 license that is available via
> + * http://www.gnu.org/licenses/,
> + * or http://www.opensource.org/licenses/gpl-2.0.php
> +*/
> +
> +#include <linux/bug.h>
> +#include <linux/errno.h>
> +#include <linux/mmc/card.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/scatterlist.h>
> +#include <linux/slab.h>
> +#include <linux/swap.h>
> +#include <linux/mmc/ffu.h>
> +#include <linux/firmware.h>
> +
> +/**
> + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
> + * @page: first page in the allocation
> + * @order: order of the number of pages allocated  */ struct
> +mmc_ffu_pages {
> +       struct page *page;
> +       unsigned int order;
> +};
> +
> +/**
> + * struct mmc_ffu_mem - allocated memory.
> + * @arr: array of allocations
> + * @cnt: number of allocations
> + */
> +struct mmc_ffu_mem {
> +       struct mmc_ffu_pages *arr;
> +       unsigned int cnt;
> +};
> +
> +struct mmc_ffu_area {
> +       unsigned long max_sz;
> +       unsigned int max_tfr;
> +       unsigned int max_segs;
> +       unsigned int max_seg_sz;
> +       unsigned int blocks;
> +       unsigned int sg_len;
> +       struct mmc_ffu_mem *mem;
> +       struct scatterlist *sg;
> +};
> +
> +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
> +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
> +       u32 arg, unsigned int blocks, unsigned int blksz) {
> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> +
> +       if (blocks > 1)
> +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
> +       else
> +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
> +
> +       mrq->cmd->arg = arg;
> +       if (!mmc_card_blockaddr(card))
> +               mrq->cmd->arg <<= 9;
> +
> +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> +       if (blocks == 1) {
> +               mrq->stop = NULL;
> +       } else {
> +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
> +               mrq->stop->arg = 0;
> +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
> +       }
> +
> +       mrq->data->blksz = blksz;
> +       mrq->data->blocks = blocks;
> +       mrq->data->flags = MMC_DATA_WRITE;
> +       mrq->data->sg = sg;
> +       mrq->data->sg_len = sg_len;
> +
> +       mmc_set_data_timeout(mrq->data, card); }
> +
> +/*
> + * Checks that a normal transfer didn't have any errors  */ static int
> +mmc_ffu_check_result(struct mmc_request *mrq) {
> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
> +
> +       if (mrq->cmd->error != 0)
> +               return -EINVAL;
> +
> +       if (mrq->data->error != 0)
> +               return -EINVAL;
> +
> +       if (mrq->stop != NULL && mrq->stop->error != 0)
> +               return -1;
> +
> +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data->blksz))
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +
> +static int mmc_ffu_busy(struct mmc_command *cmd) {
> +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
> +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
> +
> +static int mmc_ffu_wait_busy(struct mmc_card *card) {
> +       int ret, busy = 0;
> +       struct mmc_command cmd = {0};
> +
> +       memset(&cmd, 0, sizeof(struct mmc_command));
> +       cmd.opcode = MMC_SEND_STATUS;
> +       cmd.arg = card->rca << 16;
> +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
> +
> +       do {
> +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
> +               if (ret)
> +                       break;
> +
> +               if (!busy && mmc_ffu_busy(&cmd)) {
> +                       busy = 1;
> +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
> +                               pr_warn("%s: Warning: Host did not "
> +                                       "wait for busy state to end.\n",
> +                                       mmc_hostname(card->host));
> +                       }
> +               }
> +
> +       } while (mmc_ffu_busy(&cmd));
> +
> +       return ret;
> +}

By just browsing through the code for preparing and handling the mmc
data request in the above functions, I find quite some duplication of
code from the card/*.c files. Could we maybe share some code between
these layers to avoid duplications, and thus errors?

> +
> +/*
> + * transfer with certain parameters
> + */
> +static int mmc_ffu_simple_transfer(struct mmc_card *card,
> +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
> +       unsigned int blocks, unsigned int blksz) {
> +       struct mmc_request mrq = {0};
> +       struct mmc_command cmd = {0};
> +       struct mmc_command stop = {0};
> +       struct mmc_data data = {0};
> +
> +       mrq.cmd = &cmd;
> +       mrq.data = &data;
> +       mrq.stop = &stop;
> +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
> +       mmc_wait_for_req(card->host, &mrq);
> +
> +       mmc_ffu_wait_busy(card);
> +
> +       return mmc_ffu_check_result(&mrq);
> +}
> +
> +/*
> + * Map memory into a scatterlist.
> + */
> +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
> +       struct scatterlist *sglist, unsigned int max_segs,
> +       unsigned int max_seg_sz)
> +{
> +       struct scatterlist *sg = sglist;
> +       unsigned int i;
> +       unsigned long sz = size;
> +       unsigned int sctr_len = 0;
> +       unsigned long len;
> +
> +       sg_init_table(sglist, max_segs);
> +
> +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
> +               len = PAGE_SIZE * (1 << mem->arr[i].order);
> +
> +               if (len > sz) {
> +                       len = sz;
> +                       sz = 0;
> +               }
> +
> +               sg_set_page(sg, mem->arr[i].page, len, 0);
> +               sg = sg_next(sg);
> +               sctr_len += 1;
> +       }
> +       sg_mark_end(sg);
> +
> +       return sctr_len;
> +}
> +
> +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
> +       if (!mem)
> +               return;
> +
> +       while (mem->cnt--)
> +               __free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
> +
> +       kfree(mem->arr);
> +       kfree(mem);
> +}
> +
> +/*
> + * Cleanup struct mmc_ffu_area.
> + */
> +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
> +       kfree(area->sg);
> +       mmc_ffu_free_mem(area->mem);
> +
> +       return 0;
> +}
> +
> +/*
> + * Allocate a lot of memory, preferably max_sz but at least min_sz. In
> +case
> + * there isn't much memory do not exceed 1/16th total low mem pages.
> +Also do
> + * not exceed a maximum number of segments and try not to make segments
> +much
> + * bigger than maximum segment size.
> + */
> +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
> +       unsigned long max_sz, unsigned int max_segs, unsigned int max_seg_sz)
> +{
> +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
> +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
> +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz, PAGE_SIZE);
> +       unsigned long page_cnt = 0;
> +       /* we divide by 16 to ensure we will not allocate a big amount
> +        * of unnecessary pages */
> +       unsigned long limit = nr_free_buffer_pages() >> 4;
> +       struct mmc_ffu_mem *mem;
> +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
> +
> +       if (max_page_cnt > limit)
> +               max_page_cnt = limit;
> +
> +       if (min_page_cnt > max_page_cnt)
> +               min_page_cnt = max_page_cnt;
> +
> +       if (max_segs * max_seg_page_cnt > max_page_cnt)
> +               max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
> +
> +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
> +       if (!mem)
> +               return NULL;
> +
> +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
> +               GFP_KERNEL);
> +       if (!mem->arr)
> +               goto out_free;
> +
> +       while (max_page_cnt) {
> +               struct page *page;
> +               unsigned int order;
> +
> +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
> +
> +               do {
> +                       page = alloc_pages(flags, order);
> +               } while (!page && order--);
> +
> +               if (!page)
> +                       goto out_free;
> +
> +               mem->arr[mem->cnt].page = page;
> +               mem->arr[mem->cnt].order = order;
> +               mem->cnt += 1;
> +               if (max_page_cnt <= (1UL << order))
> +                       break;
> +               max_page_cnt -= 1UL << order;
> +               page_cnt += 1UL << order;
> +       }
> +
> +       if (page_cnt < min_page_cnt)
> +               goto out_free;
> +
> +       return mem;
> +
> +out_free:
> +       mmc_ffu_free_mem(mem);
> +       return NULL;
> +}
> +
> +/*
> + * Initialize an area for data transfers.
> + * Copy the data to the allocated pages.
> + */
> +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
> +       const u8 *data, int size)
> +{
> +       int ret;
> +       int i;
> +       int length = 0;
> +
> +       area->max_tfr = size;
> +
> +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
> +        * because the same memory can be mapped into the scatterlist more than
> +        * once. Also, take into account the limits imposed on scatterlist
> +        * segments by the host driver.
> +        */
> +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area->max_segs,
> +               area->max_seg_sz);
> +       if (!area->mem)
> +               return -ENOMEM;
> +
> +       /* copy data to page */
> +       for (i = 0; i < area->mem->cnt; i++) {
> +               if (length > size) {
> +                       ret = -EINVAL;
> +                       goto out_free;
> +               }
> +
> +               memcpy(page_address(area->mem->arr[i].page), data + length,
> +                       min(size - length, (int)area->max_seg_sz));
> +               length += area->max_seg_sz;
> +       }
> +
> +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
> +               GFP_KERNEL);
> +       if (!area->sg) {
> +               ret = -ENOMEM;
> +               goto out_free;
> +       }
> +
> +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
> +               area->max_segs, area->mem->cnt);
> +
> +       return 0;
> +
> +out_free:
> +       mmc_ffu_area_cleanup(area);
> +       return ret;
> +}
> +
> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
> +       int size)
> +{
> +       int rc;
> +       struct mmc_ffu_area area;
> +       int max_tfr;
> +
> +       area.sg = NULL;
> +       area.mem = NULL;
> +       area.max_segs = card->host->max_segs;
> +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE - 1);
> +       do {
> +               max_tfr = size;
> +               if (max_tfr >> 9 > card->host->max_blk_count)
> +                       max_tfr = card->host->max_blk_count << 9;
> +               if (max_tfr > card->host->max_req_size)
> +                       max_tfr = card->host->max_req_size;
> +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
> +                       max_tfr = area.max_segs * area.max_seg_sz;
> +
> +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
> +               if (rc != 0)
> +                       goto exit;
> +
> +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
> +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
> +               if (rc != 0)
> +                       goto exit;
> +
> +               src += max_tfr;
> +               size -= max_tfr;
> +       } while (size > 0);
> +
> +exit:
> +       mmc_ffu_area_cleanup(&area);
> +       return rc;
> +}
> +
> +/* Flush all scheduled work from the MMC work queue.
> + * and initialize the MMC device */
> +static int mmc_ffu_restart(struct mmc_card *card) {
> +       struct mmc_host *host = card->host;
> +       int err = 0;
> +
> +       mmc_cache_ctrl(host, 0);

Isn't cache flushing a bit late to consider here?

I would, as stated earlier, prefer one FFU operational code. Thus it
would be possible, before starting the FFU seuquence, to for example
do cache flushing, stopping BKOPS and suspend/flush the blk queue etc.
I think this would be more safe.

> +       err = mmc_power_save_host(host);

This is fragile. There are no guarantees the eMMC card will be
properly power cycled, I guess that is the requirement.

In some cases we can power cycle the card in some cases not, depending
on the hardware design. Are there other ways of telling the card to
start using the new firmware, but doing a full power cycle?

Another concern I see, is if the card might change identity due to the
firmware upgrade. The card identity is validated each system resume
cycle and failed validation is an error we can't recover from.

> +       if (err) {
> +               pr_warn("%s: going to sleep failed (%d)!!!\n",
> +                       __func__ , err);
> +               goto exit;
> +       }
> +
> +       err = mmc_power_restore_host(host);
> +
> +exit:
> +
> +       return err;
> +}
> +
> +int mmc_ffu_download(struct mmc_card *card, const char *name) {
> +       u8 ext_csd[CARD_BLOCK_SIZE];
> +       int err;
> +       int ret;
> +       u32 arg;
> +       u32 fw_prog_bytes;
> +       const struct firmware *fw;
> +
> +       /* Check if FFU is supported */
> +       if (!card->ext_csd.ffu_capable) {
> +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
> +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
> +                       card->ext_csd.rev);
> +               return -EOPNOTSUPP;
> +       }
> +
> +       if (strlen(name) > 512) {
> +               pr_err("FFU: %s: %.20s is not a valid argument\n",
> +                       mmc_hostname(card->host), name);
> +               return -EINVAL;
> +       }
> +
> +       /* setup FW data buffer */
> +       err = request_firmware(&fw, name, &card->dev);
> +       if (err) {
> +               pr_err("FFU: %s: Firmware request failed %d\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +       if ((fw->size % CARD_BLOCK_SIZE)) {
> +                       pr_warn("FFU: %s: Warning %zd firmware data size "
> +                       "is not aligned!!!\n",  mmc_hostname(card->host),
> +                       fw->size);
> +       }
> +
> +       mmc_get_card(card);
> +
> +       /* set device to FFU mode */
> +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_MODE_CONFIG,
> +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
> +       if (err) {
> +               pr_err("FFU: %s: error %d FFU is not supported\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* Read the EXT_CSD */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* set CMD ARG */
> +       arg = ext_csd[EXT_CSD_FFU_ARG] |
> +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
> +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
> +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
> +
> +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
> +
> +       /* host switch back to work in normal MMC Read/Write commands */
> +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
> +               card->ext_csd.generic_cmd6_time);
> +       if (ret) {
> +               err = ret;
> +               goto exit;
> +       }
> +
> +       /* Read the EXT_CSD */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* check that the eMMC has received the payload */
> +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> +
> +       /* convert sector to bytes */
> +        fw_prog_bytes *=
> +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
> +       if (fw_prog_bytes != fw->size) {
> +               err = -EINVAL;
> +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
> +                       mmc_hostname(card->host), err);
> +       }
> +
> +exit:
> +       release_firmware(fw);
> +       mmc_put_card(card);
> +       return err;
> +}
> +EXPORT_SYMBOL(mmc_ffu_download);
> +
> +int mmc_ffu_install(struct mmc_card *card) {
> +       u8 ext_csd[CARD_BLOCK_SIZE];
> +       int err;
> +       u32 ffu_data_len;
> +       u32 timeout;
> +
> +       /* Check if FFU is supported */
> +       if (!card->ext_csd.ffu_capable) {
> +               pr_err("FFU: %s: error FFU is not supported\n",
> +                       mmc_hostname(card->host));
> +               return -EOPNOTSUPP;
> +       }
> +
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +
> +       /* check mode operation */
> +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
> +               /* restart the eMMC */
> +               err = mmc_ffu_restart(card);
> +               if (err) {
> +                       pr_err("FFU: %s: install error %d:\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +       } else {
> +
> +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> +
> +               if (!ffu_data_len) {
> +                       err = -EPERM;
> +                       return err;
> +               }
> +               /* set device to FFU mode */
> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +                       EXT_CSD_MODE_CONFIG, 0x1,
> +                       card->ext_csd.generic_cmd6_time);
> +
> +               if (err) {
> +                       pr_err("FFU: %s: error %d FFU is not supported\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +
> +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
> +               if (timeout == 0 || timeout > 0x17) {
> +                       timeout = 0x17;
> +                       pr_warn("FFU: %s: operation code timeout is out "
> +                               "of range. Using maximum timeout.\n",
> +                               mmc_hostname(card->host));
> +               }
> +
> +               /* timeout is at millisecond resolution */
> +               timeout = (100 * (1 << timeout) / 1000) + 1;
> +
> +               /* set ext_csd to install mode */
> +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> +                       EXT_CSD_MODE_OPERATION_CODES,
> +                       MMC_FFU_INSTALL_SET, timeout);
> +
> +               if (err) {
> +                       pr_err("FFU: %s: error %d setting install mode\n",
> +                               mmc_hostname(card->host), err);
> +                       return err;
> +               }
> +       }
> +
> +       /* read ext_csd */
> +       err = mmc_send_ext_csd(card, ext_csd);
> +       if (err) {
> +               pr_err("FFU: %s: error %d sending ext_csd\n",
> +                       mmc_hostname(card->host), err);
> +               return err;
> +       }
> +
> +       /* return status */
> +       err = ext_csd[EXT_CSD_FFU_STATUS];
> +       if (err) {
> +               pr_err("FFU: %s: error %d FFU install:\n",
> +                       mmc_hostname(card->host), err);
> +               return  -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL(mmc_ffu_install);
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 98e9eb0..a29065a 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
>                 card->ext_csd.data_sector_size = 512;
>         }
>
> +       /* eMMC v5 or later */
> +       if (card->ext_csd.rev >= 7) {
> +               card->ext_csd.ffu_capable =
> +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
> +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
> +       } else {
> +               card->ext_csd.ffu_capable = false;
> +       }

I would like to add the "ffu_capable" field from a standalone separate patch.

>  out:
>         return err;
>  }
> diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b730272..bc6f6d0 100644
> --- a/include/linux/mmc/card.h
> +++ b/include/linux/mmc/card.h
> @@ -87,6 +87,7 @@ struct mmc_ext_csd {
>         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
>         unsigned int            boot_ro_lock;           /* ro lock support */
>         bool                    boot_ro_lockable;
> +       bool            ffu_capable;    /* FFU support */
>         u8                      raw_exception_status;   /* 54 */
>         u8                      raw_partition_support;  /* 160 */
>         u8                      raw_rpmb_size_mult;     /* 168 */
> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..7e4133d
> --- /dev/null
> +++ b/include/linux/mmc/ffu.h
> @@ -0,0 +1,51 @@
> +/*
> + *
> + *  ffu.h
> + *
> + * Copyright (c) 2013 SanDisk Corp.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> +(at
> + * your option) any later version.
> + *
> + * This program was created by SanDisk Corp
> + * The ffu.h file is obtained under the GPL v2.0 license that is
> + * available via http://www.gnu.org/licenses/,
> + * or http://www.opensource.org/licenses/gpl-2.0.php
> +*/
> +
> +#if !defined(_FFU_H_)
> +#define _FFU_H_
> +
> +#include <linux/mmc/card.h>
> +
> +#define CARD_BLOCK_SIZE 512
> +
> +/*
> + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
> +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
> +
> +#define MMC_FFU_MODE_SET 0x1
> +#define MMC_FFU_MODE_NORMAL 0x0
> +#define MMC_FFU_INSTALL_SET 0x1
> +
> +#ifdef CONFIG_MMC_FFU
> +#define MMC_FFU_FEATURES 0x1
> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
> +
> +int mmc_ffu_download(struct mmc_card *card, const char *name); int
> +mmc_ffu_install(struct mmc_card *card); #else static inline int
> +mmc_ffu_download(struct mmc_card *card, const char *name) {
> +       return -ENOSYS;
> +}
> +static inline int mmc_ffu_install(struct mmc_card *card) {
> +       return -ENOSYS;
> +}
> +#endif
> +#endif /* FFU_H_ */
> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50bcde3..3651449 100644
> --- a/include/linux/mmc/mmc.h
> +++ b/include/linux/mmc/mmc.h
> @@ -272,6 +272,9 @@ struct _mmc_csd {
>   * EXT_CSD fields
>   */
>
> +#define EXT_CSD_FFU_STATUS             26      /* R */
> +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
> +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
>  #define EXT_CSD_FLUSH_CACHE            32      /* W */
>  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
>  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
> @@ -290,6 +293,7 @@ struct _mmc_csd {
>  #define EXT_CSD_SANITIZE_START         165     /* W */
>  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
>  #define EXT_CSD_RPMB_MULT              168     /* RO */
> +#define EXT_CSD_FW_CONFIG              169     /* R/W */
>  #define EXT_CSD_BOOT_WP                        173     /* R/W */
>  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
>  #define EXT_CSD_PART_CONFIG            179     /* R/W */
> @@ -325,6 +329,11 @@ struct _mmc_csd {
>  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
>  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
>  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
> +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
> +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
> +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
> +#define EXT_CSD_FFU_FEATURES           492     /* RO */
> +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
>  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
>  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
>  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
> --
> 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

Kind regards
Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-13 12:46 ` Ulf Hansson
@ 2014-08-14 12:37   ` Alex Lemberg
  2014-08-18 10:56     ` Ulf Hansson
  0 siblings, 1 reply; 17+ messages in thread
From: Alex Lemberg @ 2014-08-14 12:37 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

Hi Ulf,

> -----Original Message-----
> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]
> Sent: Wednesday, August 13, 2014 3:47 PM
> To: Avi Shchislowski; Grant Grundler; Alex Lemberg
> Cc: linux-mmc@vger.kernel.org; cjb@laptop.org
> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
> 
> On 16 July 2014 17:47, Avi Shchislowski <Avi.Shchislowski@sandisk.com>
> wrote:
> > The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec:
> JESD84-B50.pdf)
> >
> > http://www.jedec.org/standards-documents/technology-focus-
> areas/flash-
> > memory-ssds-ufs-emmc/e-mmc
> >
> > An ioctl has been added to provide the new firmware image's file name to
> the  mmc driver and udev is then used to retrieve its data.
> >
> > Two new ioctls have been added:
> > 1. FFU download firmware - transfer the new firmware data from user
> > space to the eMMC device 2. FFU install - initializes the new firmware
> > update
> >
> >
> > Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
> > Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
> 
> Hi guys,
> 
> Sorry for the delay in providing feeback, but hey better late than never. :-)

Thanks, we appreciate your comments!
> 
> >
> > ---
> > V8:
> > - Modified according to Gwendal Grignou comments and patch:
> >    [PATCH] Fix on top of sandisk patches
> >
> > V7:
> > - fixed mangled white space
> >
> > V5:
> > - provides udev (request_firmware) implementation as advised in patch
> > v2 comments.
> >
> > diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index
> > 5562308..19ba729 100644
> > --- a/drivers/mmc/card/Kconfig
> > +++ b/drivers/mmc/card/Kconfig
> > @@ -68,3 +68,11 @@ config MMC_TEST
> >
> >           This driver is only of interest to those developing or
> >           testing a host driver. Most people should say N here.
> > +
> > +config MMC_FFU
> > +       bool "FFU SUPPORT"
> > +       depends on MMC != n
> > +       help
> > +         This is an option to run firmware update on eMMC 5.0.
> > +         Field firmware updates (FFU) enables features enhancment
> > +         in the field.
> 
> Why do we need a new Kconfig for this? I think we can drop it.

The motivation was to let vendor decide if FFU code should be included in his release or not.
Some vendors prefer using this FFU functionality during production/testing/development only.

> 
> > diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
> > index c73b406..1e9223b 100644
> > --- a/drivers/mmc/card/Makefile
> > +++ b/drivers/mmc/card/Makefile
> > @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
> >
> >  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
> >
> > +obj-$(CONFIG_MMC_FFU)          += ffu.o
> > diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index
> > 7b5424f..3ed900b 100644
> > --- a/drivers/mmc/card/block.c
> > +++ b/drivers/mmc/card/block.c
> > @@ -41,6 +41,7 @@
> >  #include <linux/mmc/host.h>
> >  #include <linux/mmc/mmc.h>
> >  #include <linux/mmc/sd.h>
> > +#include <linux/mmc/ffu.h>
> >
> >  #include <asm/uaccess.h>
> >
> > @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device
> > *bdev,
> >
> >         mrq.cmd = &cmd;
> >
> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> > +                       err = mmc_ffu_download(card, idata->buf);
> > +                       goto cmd_done;
> > +               }
> > +
> >         mmc_get_card(card);
> >
> > +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
> > +               err = mmc_ffu_install(card);
> > +               goto cmd_rel_host;
> > +       }
> > +
> 
> I think one FFU operation code should be enough. Overall that would also
> simplify management and thus code.

The reason for splitting it to two operation codes is providing flexibility to hosts
due to different implementation of power_off routine.
Some mmc hosts could not  support suspend/resume, so this split will allow to make 
"manual" power reset instead of running MMC_FFU_INSTALL_OP. (JEDEC spec allows doing power cycle).



> 
> Additionally I think it's better to move these "if statements" prior a potential
> data transfer is set up. You don't use the mrq struct anyway.

Agree

> 
> >         err = mmc_blk_part_switch(card, md);
> >         if (err)
> >                 goto cmd_rel_host;
> > diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file
> > mode 100644 index 0000000..783673e
> > --- /dev/null
> > +++ b/drivers/mmc/card/ffu.c
> 
> I am wondering whether this code should belong to
> drivers/mmc/core/mmc.c instead. Or unless it becomes too big, we could
> have a separate file called mmc_ffu.[c|h], but in the core directory.

For easiest maintenance we prefer using separate file.
mmc_ffu.[c|h] in core directory - will do.

> 
> > @@ -0,0 +1,607 @@
> > +/*
> > + * *  ffu.c
> > + *
> > + *  Copyright 2007-2008 Pierre Ossman
> > + *
> > + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
> > + *
> > + * This program is free software; you can redistribute it and/or
> > +modify
> > + * it under the terms of the GNU General Public License as published
> > +by
> > + * the Free Software Foundation; either version 2 of the License, or
> > +(at
> > + * your option) any later version.
> > + *
> > + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
> > + * slab.h, ffu.h & swap.h header files
> > + * The original, unmodified version of this program - the mmc_test.c
> > + * file - is obtained under the GPL v2.0 license that is available
> > +via
> > + * http://www.gnu.org/licenses/,
> > + * or http://www.opensource.org/licenses/gpl-2.0.php
> > +*/
> > +
> > +#include <linux/bug.h>
> > +#include <linux/errno.h>
> > +#include <linux/mmc/card.h>
> > +#include <linux/mmc/host.h>
> > +#include <linux/mmc/mmc.h>
> > +#include <linux/scatterlist.h>
> > +#include <linux/slab.h>
> > +#include <linux/swap.h>
> > +#include <linux/mmc/ffu.h>
> > +#include <linux/firmware.h>
> > +
> > +/**
> > + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
> > + * @page: first page in the allocation
> > + * @order: order of the number of pages allocated  */ struct
> > +mmc_ffu_pages {
> > +       struct page *page;
> > +       unsigned int order;
> > +};
> > +
> > +/**
> > + * struct mmc_ffu_mem - allocated memory.
> > + * @arr: array of allocations
> > + * @cnt: number of allocations
> > + */
> > +struct mmc_ffu_mem {
> > +       struct mmc_ffu_pages *arr;
> > +       unsigned int cnt;
> > +};
> > +
> > +struct mmc_ffu_area {
> > +       unsigned long max_sz;
> > +       unsigned int max_tfr;
> > +       unsigned int max_segs;
> > +       unsigned int max_seg_sz;
> > +       unsigned int blocks;
> > +       unsigned int sg_len;
> > +       struct mmc_ffu_mem *mem;
> > +       struct scatterlist *sg;
> > +};
> > +
> > +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
> > +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
> > +       u32 arg, unsigned int blocks, unsigned int blksz) {
> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> > +
> > +       if (blocks > 1)
> > +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
> > +       else
> > +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
> > +
> > +       mrq->cmd->arg = arg;
> > +       if (!mmc_card_blockaddr(card))
> > +               mrq->cmd->arg <<= 9;
> > +
> > +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> > +       if (blocks == 1) {
> > +               mrq->stop = NULL;
> > +       } else {
> > +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
> > +               mrq->stop->arg = 0;
> > +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
> > +       }
> > +
> > +       mrq->data->blksz = blksz;
> > +       mrq->data->blocks = blocks;
> > +       mrq->data->flags = MMC_DATA_WRITE;
> > +       mrq->data->sg = sg;
> > +       mrq->data->sg_len = sg_len;
> > +
> > +       mmc_set_data_timeout(mrq->data, card); }
> > +
> > +/*
> > + * Checks that a normal transfer didn't have any errors  */ static
> > +int mmc_ffu_check_result(struct mmc_request *mrq) {
> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
> > +
> > +       if (mrq->cmd->error != 0)
> > +               return -EINVAL;
> > +
> > +       if (mrq->data->error != 0)
> > +               return -EINVAL;
> > +
> > +       if (mrq->stop != NULL && mrq->stop->error != 0)
> > +               return -1;
> > +
> > +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data-
> >blksz))
> > +               return -EINVAL;
> > +
> > +       return 0;
> > +}
> > +
> > +static int mmc_ffu_busy(struct mmc_command *cmd) {
> > +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
> > +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
> > +
> > +static int mmc_ffu_wait_busy(struct mmc_card *card) {
> > +       int ret, busy = 0;
> > +       struct mmc_command cmd = {0};
> > +
> > +       memset(&cmd, 0, sizeof(struct mmc_command));
> > +       cmd.opcode = MMC_SEND_STATUS;
> > +       cmd.arg = card->rca << 16;
> > +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
> > +
> > +       do {
> > +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
> > +               if (ret)
> > +                       break;
> > +
> > +               if (!busy && mmc_ffu_busy(&cmd)) {
> > +                       busy = 1;
> > +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
> > +                               pr_warn("%s: Warning: Host did not "
> > +                                       "wait for busy state to end.\n",
> > +                                       mmc_hostname(card->host));
> > +                       }
> > +               }
> > +
> > +       } while (mmc_ffu_busy(&cmd));
> > +
> > +       return ret;
> > +}
> 
> By just browsing through the code for preparing and handling the mmc data
> request in the above functions, I find quite some duplication of code from
> the card/*.c files. Could we maybe share some code between these layers to
> avoid duplications, and thus errors?

Yes, we are aware of some duplications and tried to avoid it.
Eventually, sharing code to avoid duplications will require changes in existing card/*c files. 
We preferred not changing existing code as much as possible.

> 
> > +
> > +/*
> > + * transfer with certain parameters
> > + */
> > +static int mmc_ffu_simple_transfer(struct mmc_card *card,
> > +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
> > +       unsigned int blocks, unsigned int blksz) {
> > +       struct mmc_request mrq = {0};
> > +       struct mmc_command cmd = {0};
> > +       struct mmc_command stop = {0};
> > +       struct mmc_data data = {0};
> > +
> > +       mrq.cmd = &cmd;
> > +       mrq.data = &data;
> > +       mrq.stop = &stop;
> > +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
> > +       mmc_wait_for_req(card->host, &mrq);
> > +
> > +       mmc_ffu_wait_busy(card);
> > +
> > +       return mmc_ffu_check_result(&mrq); }
> > +
> > +/*
> > + * Map memory into a scatterlist.
> > + */
> > +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int
> size,
> > +       struct scatterlist *sglist, unsigned int max_segs,
> > +       unsigned int max_seg_sz)
> > +{
> > +       struct scatterlist *sg = sglist;
> > +       unsigned int i;
> > +       unsigned long sz = size;
> > +       unsigned int sctr_len = 0;
> > +       unsigned long len;
> > +
> > +       sg_init_table(sglist, max_segs);
> > +
> > +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
> > +               len = PAGE_SIZE * (1 << mem->arr[i].order);
> > +
> > +               if (len > sz) {
> > +                       len = sz;
> > +                       sz = 0;
> > +               }
> > +
> > +               sg_set_page(sg, mem->arr[i].page, len, 0);
> > +               sg = sg_next(sg);
> > +               sctr_len += 1;
> > +       }
> > +       sg_mark_end(sg);
> > +
> > +       return sctr_len;
> > +}
> > +
> > +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
> > +       if (!mem)
> > +               return;
> > +
> > +       while (mem->cnt--)
> > +               __free_pages(mem->arr[mem->cnt].page,
> > + mem->arr[mem->cnt].order);
> > +
> > +       kfree(mem->arr);
> > +       kfree(mem);
> > +}
> > +
> > +/*
> > + * Cleanup struct mmc_ffu_area.
> > + */
> > +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
> > +       kfree(area->sg);
> > +       mmc_ffu_free_mem(area->mem);
> > +
> > +       return 0;
> > +}
> > +
> > +/*
> > + * Allocate a lot of memory, preferably max_sz but at least min_sz.
> > +In case
> > + * there isn't much memory do not exceed 1/16th total low mem pages.
> > +Also do
> > + * not exceed a maximum number of segments and try not to make
> > +segments much
> > + * bigger than maximum segment size.
> > + */
> > +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
> > +       unsigned long max_sz, unsigned int max_segs, unsigned int
> > +max_seg_sz) {
> > +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
> > +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
> > +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz,
> PAGE_SIZE);
> > +       unsigned long page_cnt = 0;
> > +       /* we divide by 16 to ensure we will not allocate a big amount
> > +        * of unnecessary pages */
> > +       unsigned long limit = nr_free_buffer_pages() >> 4;
> > +       struct mmc_ffu_mem *mem;
> > +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
> > +__GFP_NORETRY;
> > +
> > +       if (max_page_cnt > limit)
> > +               max_page_cnt = limit;
> > +
> > +       if (min_page_cnt > max_page_cnt)
> > +               min_page_cnt = max_page_cnt;
> > +
> > +       if (max_segs * max_seg_page_cnt > max_page_cnt)
> > +               max_segs = DIV_ROUND_UP(max_page_cnt,
> > + max_seg_page_cnt);
> > +
> > +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
> > +       if (!mem)
> > +               return NULL;
> > +
> > +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
> > +               GFP_KERNEL);
> > +       if (!mem->arr)
> > +               goto out_free;
> > +
> > +       while (max_page_cnt) {
> > +               struct page *page;
> > +               unsigned int order;
> > +
> > +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
> > +
> > +               do {
> > +                       page = alloc_pages(flags, order);
> > +               } while (!page && order--);
> > +
> > +               if (!page)
> > +                       goto out_free;
> > +
> > +               mem->arr[mem->cnt].page = page;
> > +               mem->arr[mem->cnt].order = order;
> > +               mem->cnt += 1;
> > +               if (max_page_cnt <= (1UL << order))
> > +                       break;
> > +               max_page_cnt -= 1UL << order;
> > +               page_cnt += 1UL << order;
> > +       }
> > +
> > +       if (page_cnt < min_page_cnt)
> > +               goto out_free;
> > +
> > +       return mem;
> > +
> > +out_free:
> > +       mmc_ffu_free_mem(mem);
> > +       return NULL;
> > +}
> > +
> > +/*
> > + * Initialize an area for data transfers.
> > + * Copy the data to the allocated pages.
> > + */
> > +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct
> mmc_card *card,
> > +       const u8 *data, int size)
> > +{
> > +       int ret;
> > +       int i;
> > +       int length = 0;
> > +
> > +       area->max_tfr = size;
> > +
> > +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
> > +        * because the same memory can be mapped into the scatterlist more
> than
> > +        * once. Also, take into account the limits imposed on scatterlist
> > +        * segments by the host driver.
> > +        */
> > +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area-
> >max_segs,
> > +               area->max_seg_sz);
> > +       if (!area->mem)
> > +               return -ENOMEM;
> > +
> > +       /* copy data to page */
> > +       for (i = 0; i < area->mem->cnt; i++) {
> > +               if (length > size) {
> > +                       ret = -EINVAL;
> > +                       goto out_free;
> > +               }
> > +
> > +               memcpy(page_address(area->mem->arr[i].page), data + length,
> > +                       min(size - length, (int)area->max_seg_sz));
> > +               length += area->max_seg_sz;
> > +       }
> > +
> > +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
> > +               GFP_KERNEL);
> > +       if (!area->sg) {
> > +               ret = -ENOMEM;
> > +               goto out_free;
> > +       }
> > +
> > +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
> > +               area->max_segs, area->mem->cnt);
> > +
> > +       return 0;
> > +
> > +out_free:
> > +       mmc_ffu_area_cleanup(area);
> > +       return ret;
> > +}
> > +
> > +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
> > +       int size)
> > +{
> > +       int rc;
> > +       struct mmc_ffu_area area;
> > +       int max_tfr;
> > +
> > +       area.sg = NULL;
> > +       area.mem = NULL;
> > +       area.max_segs = card->host->max_segs;
> > +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE
> - 1);
> > +       do {
> > +               max_tfr = size;
> > +               if (max_tfr >> 9 > card->host->max_blk_count)
> > +                       max_tfr = card->host->max_blk_count << 9;
> > +               if (max_tfr > card->host->max_req_size)
> > +                       max_tfr = card->host->max_req_size;
> > +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
> > +                       max_tfr = area.max_segs * area.max_seg_sz;
> > +
> > +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
> > +               if (rc != 0)
> > +                       goto exit;
> > +
> > +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
> > +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
> > +               if (rc != 0)
> > +                       goto exit;
> > +
> > +               src += max_tfr;
> > +               size -= max_tfr;
> > +       } while (size > 0);
> > +
> > +exit:
> > +       mmc_ffu_area_cleanup(&area);
> > +       return rc;
> > +}
> > +
> > +/* Flush all scheduled work from the MMC work queue.
> > + * and initialize the MMC device */
> > +static int mmc_ffu_restart(struct mmc_card *card) {
> > +       struct mmc_host *host = card->host;
> > +       int err = 0;
> > +
> > +       mmc_cache_ctrl(host, 0);
> 
> Isn't cache flushing a bit late to consider here?

I think we need to call mmc_flush_cache() instead of mmc_cache_ctrl().

> 
> I would, as stated earlier, prefer one FFU operational code. Thus it would be
> possible, before starting the FFU seuquence, to for example do cache
> flushing, stopping BKOPS and suspend/flush the blk queue etc.
> I think this would be more safe.

Agree, it probably  would be more safe, but:
- as mentioned before, we would like to split the process into two operational codes due 
   to different host power implementations
- We implemented FFU as non-blocking procedure, which allows host sending regular R/W commands 
   between  downloading FW data process and FW install


> 
> > +       err = mmc_power_save_host(host);
> 
> This is fragile. There are no guarantees the eMMC card will be properly
> power cycled, I guess that is the requirement.

The FFU requirement is not necessarily making power cycle, but performing "eMMC init" 
sequence (CMD0, CMD1,...).
Once host cycling the power, it will run eMMC init sequence anyway. 
   

> 
> In some cases we can power cycle the card in some cases not, depending on
> the hardware design. Are there other ways of telling the card to start using
> the new firmware, but doing a full power cycle?
> 
> Another concern I see, is if the card might change identity due to the
> firmware upgrade. The card identity is validated each system resume cycle
> and failed validation is an error we can't recover from.

The current assumption is that device will change its identity during the FFU.
But your concern is correct.
In order to support this, the mmc_init_card() function call should be modified and assume that the card is not "oldcard".

> 
> > +       if (err) {
> > +               pr_warn("%s: going to sleep failed (%d)!!!\n",
> > +                       __func__ , err);
> > +               goto exit;
> > +       }
> > +
> > +       err = mmc_power_restore_host(host);
> > +
> > +exit:
> > +
> > +       return err;
> > +}
> > +
> > +int mmc_ffu_download(struct mmc_card *card, const char *name) {
> > +       u8 ext_csd[CARD_BLOCK_SIZE];
> > +       int err;
> > +       int ret;
> > +       u32 arg;
> > +       u32 fw_prog_bytes;
> > +       const struct firmware *fw;
> > +
> > +       /* Check if FFU is supported */
> > +       if (!card->ext_csd.ffu_capable) {
> > +               pr_err("FFU: %s: error FFU is not supported %d rev %d\n",
> > +                       mmc_hostname(card->host), card->ext_csd.ffu_capable,
> > +                       card->ext_csd.rev);
> > +               return -EOPNOTSUPP;
> > +       }
> > +
> > +       if (strlen(name) > 512) {
> > +               pr_err("FFU: %s: %.20s is not a valid argument\n",
> > +                       mmc_hostname(card->host), name);
> > +               return -EINVAL;
> > +       }
> > +
> > +       /* setup FW data buffer */
> > +       err = request_firmware(&fw, name, &card->dev);
> > +       if (err) {
> > +               pr_err("FFU: %s: Firmware request failed %d\n",
> > +                       mmc_hostname(card->host), err);
> > +               return err;
> > +       }
> > +       if ((fw->size % CARD_BLOCK_SIZE)) {
> > +                       pr_warn("FFU: %s: Warning %zd firmware data size "
> > +                       "is not aligned!!!\n",  mmc_hostname(card->host),
> > +                       fw->size);
> > +       }
> > +
> > +       mmc_get_card(card);
> > +
> > +       /* set device to FFU mode */
> > +       err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> EXT_CSD_MODE_CONFIG,
> > +               MMC_FFU_MODE_SET, card->ext_csd.generic_cmd6_time);
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
> > +                       mmc_hostname(card->host), err);
> > +               goto exit;
> > +       }
> > +
> > +       /* Read the EXT_CSD */
> > +       err = mmc_send_ext_csd(card, ext_csd);
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d sending ext_csd\n",
> > +                       mmc_hostname(card->host), err);
> > +               goto exit;
> > +       }
> > +
> > +       /* set CMD ARG */
> > +       arg = ext_csd[EXT_CSD_FFU_ARG] |
> > +               ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
> > +               ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
> > +               ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
> > +
> > +       err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
> > +
> > +       /* host switch back to work in normal MMC Read/Write commands */
> > +       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> > +               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
> > +               card->ext_csd.generic_cmd6_time);
> > +       if (ret) {
> > +               err = ret;
> > +               goto exit;
> > +       }
> > +
> > +       /* Read the EXT_CSD */
> > +       err = mmc_send_ext_csd(card, ext_csd);
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d sending ext_csd\n",
> > +                       mmc_hostname(card->host), err);
> > +               goto exit;
> > +       }
> > +
> > +       /* check that the eMMC has received the payload */
> > +       fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
> > +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> > +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> > +               ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> > +
> > +       /* convert sector to bytes */
> > +        fw_prog_bytes *=
> > +               CARD_BLOCK_SIZE << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] *
> 3);
> > +       if (fw_prog_bytes != fw->size) {
> > +               err = -EINVAL;
> > +               pr_err("FFU: %s: error %d number of programmed fw sector\n",
> > +                       mmc_hostname(card->host), err);
> > +       }
> > +
> > +exit:
> > +       release_firmware(fw);
> > +       mmc_put_card(card);
> > +       return err;
> > +}
> > +EXPORT_SYMBOL(mmc_ffu_download);
> > +
> > +int mmc_ffu_install(struct mmc_card *card) {
> > +       u8 ext_csd[CARD_BLOCK_SIZE];
> > +       int err;
> > +       u32 ffu_data_len;
> > +       u32 timeout;
> > +
> > +       /* Check if FFU is supported */
> > +       if (!card->ext_csd.ffu_capable) {
> > +               pr_err("FFU: %s: error FFU is not supported\n",
> > +                       mmc_hostname(card->host));
> > +               return -EOPNOTSUPP;
> > +       }
> > +
> > +       err = mmc_send_ext_csd(card, ext_csd);
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d sending ext_csd\n",
> > +                       mmc_hostname(card->host), err);
> > +               return err;
> > +       }
> > +
> > +       /* check mode operation */
> > +       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
> > +               /* restart the eMMC */
> > +               err = mmc_ffu_restart(card);
> > +               if (err) {
> > +                       pr_err("FFU: %s: install error %d:\n",
> > +                               mmc_hostname(card->host), err);
> > +                       return err;
> > +               }
> > +       } else {
> > +
> > +               ffu_data_len = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG]|
> > +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
> > +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
> > +                       ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
> > +
> > +               if (!ffu_data_len) {
> > +                       err = -EPERM;
> > +                       return err;
> > +               }
> > +               /* set device to FFU mode */
> > +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> > +                       EXT_CSD_MODE_CONFIG, 0x1,
> > +                       card->ext_csd.generic_cmd6_time);
> > +
> > +               if (err) {
> > +                       pr_err("FFU: %s: error %d FFU is not supported\n",
> > +                               mmc_hostname(card->host), err);
> > +                       return err;
> > +               }
> > +
> > +               timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
> > +               if (timeout == 0 || timeout > 0x17) {
> > +                       timeout = 0x17;
> > +                       pr_warn("FFU: %s: operation code timeout is out "
> > +                               "of range. Using maximum timeout.\n",
> > +                               mmc_hostname(card->host));
> > +               }
> > +
> > +               /* timeout is at millisecond resolution */
> > +               timeout = (100 * (1 << timeout) / 1000) + 1;
> > +
> > +               /* set ext_csd to install mode */
> > +               err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
> > +                       EXT_CSD_MODE_OPERATION_CODES,
> > +                       MMC_FFU_INSTALL_SET, timeout);
> > +
> > +               if (err) {
> > +                       pr_err("FFU: %s: error %d setting install mode\n",
> > +                               mmc_hostname(card->host), err);
> > +                       return err;
> > +               }
> > +       }
> > +
> > +       /* read ext_csd */
> > +       err = mmc_send_ext_csd(card, ext_csd);
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d sending ext_csd\n",
> > +                       mmc_hostname(card->host), err);
> > +               return err;
> > +       }
> > +
> > +       /* return status */
> > +       err = ext_csd[EXT_CSD_FFU_STATUS];
> > +       if (err) {
> > +               pr_err("FFU: %s: error %d FFU install:\n",
> > +                       mmc_hostname(card->host), err);
> > +               return  -EINVAL;
> > +       }
> > +
> > +       return 0;
> > +}
> > +EXPORT_SYMBOL(mmc_ffu_install);
> > diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
> > 98e9eb0..a29065a 100644
> > --- a/drivers/mmc/core/mmc.c
> > +++ b/drivers/mmc/core/mmc.c
> > @@ -571,6 +571,14 @@ static int mmc_read_ext_csd(struct mmc_card
> *card, u8 *ext_csd)
> >                 card->ext_csd.data_sector_size = 512;
> >         }
> >
> > +       /* eMMC v5 or later */
> > +       if (card->ext_csd.rev >= 7) {
> > +               card->ext_csd.ffu_capable =
> > +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
> > +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
> > +       } else {
> > +               card->ext_csd.ffu_capable = false;
> > +       }
> 
> I would like to add the "ffu_capable" field from a standalone separate patch.

No problem. Let us know if you prefer us doing this.

> 
> >  out:
> >         return err;
> >  }
> > diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index
> > b730272..bc6f6d0 100644
> > --- a/include/linux/mmc/card.h
> > +++ b/include/linux/mmc/card.h
> > @@ -87,6 +87,7 @@ struct mmc_ext_csd {
> >         unsigned int            data_tag_unit_size;     /* DATA TAG UNIT size */
> >         unsigned int            boot_ro_lock;           /* ro lock support */
> >         bool                    boot_ro_lockable;
> > +       bool            ffu_capable;    /* FFU support */
> >         u8                      raw_exception_status;   /* 54 */
> >         u8                      raw_partition_support;  /* 160 */
> >         u8                      raw_rpmb_size_mult;     /* 168 */
> > diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new
> > file mode 100644 index 0000000..7e4133d
> > --- /dev/null
> > +++ b/include/linux/mmc/ffu.h
> > @@ -0,0 +1,51 @@
> > +/*
> > + *
> > + *  ffu.h
> > + *
> > + * Copyright (c) 2013 SanDisk Corp.
> > + *
> > + * This program is free software; you can redistribute it and/or
> > +modify
> > + * it under the terms of the GNU General Public License as published
> > +by
> > + * the Free Software Foundation; either version 2 of the License, or
> > +(at
> > + * your option) any later version.
> > + *
> > + * This program was created by SanDisk Corp
> > + * The ffu.h file is obtained under the GPL v2.0 license that is
> > + * available via http://www.gnu.org/licenses/,
> > + * or http://www.opensource.org/licenses/gpl-2.0.php
> > +*/
> > +
> > +#if !defined(_FFU_H_)
> > +#define _FFU_H_
> > +
> > +#include <linux/mmc/card.h>
> > +
> > +#define CARD_BLOCK_SIZE 512
> > +
> > +/*
> > + * eMMC5.0 Field Firmware Update (FFU) opcodes */ #define
> > +MMC_FFU_DOWNLOAD_OP 302 #define MMC_FFU_INSTALL_OP 303
> > +
> > +#define MMC_FFU_MODE_SET 0x1
> > +#define MMC_FFU_MODE_NORMAL 0x0
> > +#define MMC_FFU_INSTALL_SET 0x1
> > +
> > +#ifdef CONFIG_MMC_FFU
> > +#define MMC_FFU_FEATURES 0x1
> > +#define FFU_FEATURES(ffu_features) (ffu_features &
> MMC_FFU_FEATURES)
> > +
> > +int mmc_ffu_download(struct mmc_card *card, const char *name); int
> > +mmc_ffu_install(struct mmc_card *card); #else static inline int
> > +mmc_ffu_download(struct mmc_card *card, const char *name) {
> > +       return -ENOSYS;
> > +}
> > +static inline int mmc_ffu_install(struct mmc_card *card) {
> > +       return -ENOSYS;
> > +}
> > +#endif
> > +#endif /* FFU_H_ */
> > diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index
> > 50bcde3..3651449 100644
> > --- a/include/linux/mmc/mmc.h
> > +++ b/include/linux/mmc/mmc.h
> > @@ -272,6 +272,9 @@ struct _mmc_csd {
> >   * EXT_CSD fields
> >   */
> >
> > +#define EXT_CSD_FFU_STATUS             26      /* R */
> > +#define EXT_CSD_MODE_OPERATION_CODES   29      /* W */
> > +#define EXT_CSD_MODE_CONFIG            30      /* R/W */
> >  #define EXT_CSD_FLUSH_CACHE            32      /* W */
> >  #define EXT_CSD_CACHE_CTRL             33      /* R/W */
> >  #define EXT_CSD_POWER_OFF_NOTIFICATION 34      /* R/W */
> > @@ -290,6 +293,7 @@ struct _mmc_csd {
> >  #define EXT_CSD_SANITIZE_START         165     /* W */
> >  #define EXT_CSD_WR_REL_PARAM           166     /* RO */
> >  #define EXT_CSD_RPMB_MULT              168     /* RO */
> > +#define EXT_CSD_FW_CONFIG              169     /* R/W */
> >  #define EXT_CSD_BOOT_WP                        173     /* R/W */
> >  #define EXT_CSD_ERASE_GROUP_DEF                175     /* R/W */
> >  #define EXT_CSD_PART_CONFIG            179     /* R/W */
> > @@ -325,6 +329,11 @@ struct _mmc_csd {
> >  #define EXT_CSD_POWER_OFF_LONG_TIME    247     /* RO */
> >  #define EXT_CSD_GENERIC_CMD6_TIME      248     /* RO */
> >  #define EXT_CSD_CACHE_SIZE             249     /* RO, 4 bytes */
> > +#define EXT_CSD_NUM_OF_FW_SEC_PROG     302     /* RO, 4 bytes */
> > +#define EXT_CSD_FFU_ARG                        487     /* RO, 4 bytes */
> > +#define EXT_CSD_OPERATION_CODE_TIMEOUT 491     /* RO */
> > +#define EXT_CSD_FFU_FEATURES           492     /* RO */
> > +#define EXT_CSD_SUPPORTED_MODE         493     /* RO */
> >  #define EXT_CSD_TAG_UNIT_SIZE          498     /* RO */
> >  #define EXT_CSD_DATA_TAG_SUPPORT       499     /* RO */
> >  #define EXT_CSD_MAX_PACKED_WRITES      500     /* RO */
> > --
> > 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
> 
> Kind regards
> Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-14 12:37   ` Alex Lemberg
@ 2014-08-18 10:56     ` Ulf Hansson
  2014-08-21  9:13       ` Alex Lemberg
  0 siblings, 1 reply; 17+ messages in thread
From: Ulf Hansson @ 2014-08-18 10:56 UTC (permalink / raw)
  To: Alex Lemberg
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

On 14 August 2014 14:37, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> Hi Ulf,
>
>> -----Original Message-----
>> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]
>> Sent: Wednesday, August 13, 2014 3:47 PM
>> To: Avi Shchislowski; Grant Grundler; Alex Lemberg
>> Cc: linux-mmc@vger.kernel.org; cjb@laptop.org
>> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
>>
>> On 16 July 2014 17:47, Avi Shchislowski <Avi.Shchislowski@sandisk.com>
>> wrote:
>> > The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec (Jedec:
>> JESD84-B50.pdf)
>> >
>> > http://www.jedec.org/standards-documents/technology-focus-
>> areas/flash-
>> > memory-ssds-ufs-emmc/e-mmc
>> >
>> > An ioctl has been added to provide the new firmware image's file name to
>> the  mmc driver and udev is then used to retrieve its data.
>> >
>> > Two new ioctls have been added:
>> > 1. FFU download firmware - transfer the new firmware data from user
>> > space to the eMMC device 2. FFU install - initializes the new firmware
>> > update
>> >
>> >
>> > Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
>> > Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
>>
>> Hi guys,
>>
>> Sorry for the delay in providing feeback, but hey better late than never. :-)
>
> Thanks, we appreciate your comments!
>>
>> >
>> > ---
>> > V8:
>> > - Modified according to Gwendal Grignou comments and patch:
>> >    [PATCH] Fix on top of sandisk patches
>> >
>> > V7:
>> > - fixed mangled white space
>> >
>> > V5:
>> > - provides udev (request_firmware) implementation as advised in patch
>> > v2 comments.
>> >
>> > diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index
>> > 5562308..19ba729 100644
>> > --- a/drivers/mmc/card/Kconfig
>> > +++ b/drivers/mmc/card/Kconfig
>> > @@ -68,3 +68,11 @@ config MMC_TEST
>> >
>> >           This driver is only of interest to those developing or
>> >           testing a host driver. Most people should say N here.
>> > +
>> > +config MMC_FFU
>> > +       bool "FFU SUPPORT"
>> > +       depends on MMC != n
>> > +       help
>> > +         This is an option to run firmware update on eMMC 5.0.
>> > +         Field firmware updates (FFU) enables features enhancment
>> > +         in the field.
>>
>> Why do we need a new Kconfig for this? I think we can drop it.
>
> The motivation was to let vendor decide if FFU code should be included in his release or not.
> Some vendors prefer using this FFU functionality during production/testing/development only.

OK, I see.

>
>>
>> > diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
>> > index c73b406..1e9223b 100644
>> > --- a/drivers/mmc/card/Makefile
>> > +++ b/drivers/mmc/card/Makefile
>> > @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
>> >
>> >  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
>> >
>> > +obj-$(CONFIG_MMC_FFU)          += ffu.o
>> > diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index
>> > 7b5424f..3ed900b 100644
>> > --- a/drivers/mmc/card/block.c
>> > +++ b/drivers/mmc/card/block.c
>> > @@ -41,6 +41,7 @@
>> >  #include <linux/mmc/host.h>
>> >  #include <linux/mmc/mmc.h>
>> >  #include <linux/mmc/sd.h>
>> > +#include <linux/mmc/ffu.h>
>> >
>> >  #include <asm/uaccess.h>
>> >
>> > @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct block_device
>> > *bdev,
>> >
>> >         mrq.cmd = &cmd;
>> >
>> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>> > +                       err = mmc_ffu_download(card, idata->buf);
>> > +                       goto cmd_done;
>> > +               }
>> > +
>> >         mmc_get_card(card);
>> >
>> > +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
>> > +               err = mmc_ffu_install(card);
>> > +               goto cmd_rel_host;
>> > +       }
>> > +
>>
>> I think one FFU operation code should be enough. Overall that would also
>> simplify management and thus code.
>
> The reason for splitting it to two operation codes is providing flexibility to hosts
> due to different implementation of power_off routine.
> Some mmc hosts could not  support suspend/resume, so this split will allow to make
> "manual" power reset instead of running MMC_FFU_INSTALL_OP. (JEDEC spec allows doing power cycle).

To clarify the terms here. All hosts supports system PM suspend/resume.

Most of the suspend/resume operations are handled by the mmc core. The
host's ->set_ios callback will though be invoked by the core to give
the option for the host, to cut the power(s) to the card. Depending on
host and hw, it varies what power(s) will be cut.

Now, I don't understand how the upper layers will know which way it
should complete the FFU upgrade process?

>
>
>
>>
>> Additionally I think it's better to move these "if statements" prior a potential
>> data transfer is set up. You don't use the mrq struct anyway.
>
> Agree
>
>>
>> >         err = mmc_blk_part_switch(card, md);
>> >         if (err)
>> >                 goto cmd_rel_host;
>> > diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new file
>> > mode 100644 index 0000000..783673e
>> > --- /dev/null
>> > +++ b/drivers/mmc/card/ffu.c
>>
>> I am wondering whether this code should belong to
>> drivers/mmc/core/mmc.c instead. Or unless it becomes too big, we could
>> have a separate file called mmc_ffu.[c|h], but in the core directory.
>
> For easiest maintenance we prefer using separate file.
> mmc_ffu.[c|h] in core directory - will do.
>
>>
>> > @@ -0,0 +1,607 @@
>> > +/*
>> > + * *  ffu.c
>> > + *
>> > + *  Copyright 2007-2008 Pierre Ossman
>> > + *
>> > + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
>> > + *
>> > + * This program is free software; you can redistribute it and/or
>> > +modify
>> > + * it under the terms of the GNU General Public License as published
>> > +by
>> > + * the Free Software Foundation; either version 2 of the License, or
>> > +(at
>> > + * your option) any later version.
>> > + *
>> > + * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
>> > + * slab.h, ffu.h & swap.h header files
>> > + * The original, unmodified version of this program - the mmc_test.c
>> > + * file - is obtained under the GPL v2.0 license that is available
>> > +via
>> > + * http://www.gnu.org/licenses/,
>> > + * or http://www.opensource.org/licenses/gpl-2.0.php
>> > +*/
>> > +
>> > +#include <linux/bug.h>
>> > +#include <linux/errno.h>
>> > +#include <linux/mmc/card.h>
>> > +#include <linux/mmc/host.h>
>> > +#include <linux/mmc/mmc.h>
>> > +#include <linux/scatterlist.h>
>> > +#include <linux/slab.h>
>> > +#include <linux/swap.h>
>> > +#include <linux/mmc/ffu.h>
>> > +#include <linux/firmware.h>
>> > +
>> > +/**
>> > + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
>> > + * @page: first page in the allocation
>> > + * @order: order of the number of pages allocated  */ struct
>> > +mmc_ffu_pages {
>> > +       struct page *page;
>> > +       unsigned int order;
>> > +};
>> > +
>> > +/**
>> > + * struct mmc_ffu_mem - allocated memory.
>> > + * @arr: array of allocations
>> > + * @cnt: number of allocations
>> > + */
>> > +struct mmc_ffu_mem {
>> > +       struct mmc_ffu_pages *arr;
>> > +       unsigned int cnt;
>> > +};
>> > +
>> > +struct mmc_ffu_area {
>> > +       unsigned long max_sz;
>> > +       unsigned int max_tfr;
>> > +       unsigned int max_segs;
>> > +       unsigned int max_seg_sz;
>> > +       unsigned int blocks;
>> > +       unsigned int sg_len;
>> > +       struct mmc_ffu_mem *mem;
>> > +       struct scatterlist *sg;
>> > +};
>> > +
>> > +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
>> > +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
>> > +       u32 arg, unsigned int blocks, unsigned int blksz) {
>> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>> > +
>> > +       if (blocks > 1)
>> > +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>> > +       else
>> > +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
>> > +
>> > +       mrq->cmd->arg = arg;
>> > +       if (!mmc_card_blockaddr(card))
>> > +               mrq->cmd->arg <<= 9;
>> > +
>> > +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
>> > +       if (blocks == 1) {
>> > +               mrq->stop = NULL;
>> > +       } else {
>> > +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
>> > +               mrq->stop->arg = 0;
>> > +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
>> > +       }
>> > +
>> > +       mrq->data->blksz = blksz;
>> > +       mrq->data->blocks = blocks;
>> > +       mrq->data->flags = MMC_DATA_WRITE;
>> > +       mrq->data->sg = sg;
>> > +       mrq->data->sg_len = sg_len;
>> > +
>> > +       mmc_set_data_timeout(mrq->data, card); }
>> > +
>> > +/*
>> > + * Checks that a normal transfer didn't have any errors  */ static
>> > +int mmc_ffu_check_result(struct mmc_request *mrq) {
>> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
>> > +
>> > +       if (mrq->cmd->error != 0)
>> > +               return -EINVAL;
>> > +
>> > +       if (mrq->data->error != 0)
>> > +               return -EINVAL;
>> > +
>> > +       if (mrq->stop != NULL && mrq->stop->error != 0)
>> > +               return -1;
>> > +
>> > +       if (mrq->data->bytes_xfered != (mrq->data->blocks * mrq->data-
>> >blksz))
>> > +               return -EINVAL;
>> > +
>> > +       return 0;
>> > +}
>> > +
>> > +static int mmc_ffu_busy(struct mmc_command *cmd) {
>> > +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
>> > +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
>> > +
>> > +static int mmc_ffu_wait_busy(struct mmc_card *card) {
>> > +       int ret, busy = 0;
>> > +       struct mmc_command cmd = {0};
>> > +
>> > +       memset(&cmd, 0, sizeof(struct mmc_command));
>> > +       cmd.opcode = MMC_SEND_STATUS;
>> > +       cmd.arg = card->rca << 16;
>> > +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
>> > +
>> > +       do {
>> > +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
>> > +               if (ret)
>> > +                       break;
>> > +
>> > +               if (!busy && mmc_ffu_busy(&cmd)) {
>> > +                       busy = 1;
>> > +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
>> > +                               pr_warn("%s: Warning: Host did not "
>> > +                                       "wait for busy state to end.\n",
>> > +                                       mmc_hostname(card->host));
>> > +                       }
>> > +               }
>> > +
>> > +       } while (mmc_ffu_busy(&cmd));
>> > +
>> > +       return ret;
>> > +}
>>
>> By just browsing through the code for preparing and handling the mmc data
>> request in the above functions, I find quite some duplication of code from
>> the card/*.c files. Could we maybe share some code between these layers to
>> avoid duplications, and thus errors?
>
> Yes, we are aware of some duplications and tried to avoid it.
> Eventually, sharing code to avoid duplications will require changes in existing card/*c files.
> We preferred not changing existing code as much as possible.

Breaking out code and doing re-factoring in sometimes necessary. I
certainly think we should give it a try.

My concern is not just duplication of code, but also duplication of
errors as that follows with it.

>
>>
>> > +
>> > +/*
>> > + * transfer with certain parameters
>> > + */
>> > +static int mmc_ffu_simple_transfer(struct mmc_card *card,
>> > +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
>> > +       unsigned int blocks, unsigned int blksz) {
>> > +       struct mmc_request mrq = {0};
>> > +       struct mmc_command cmd = {0};
>> > +       struct mmc_command stop = {0};
>> > +       struct mmc_data data = {0};
>> > +
>> > +       mrq.cmd = &cmd;
>> > +       mrq.data = &data;
>> > +       mrq.stop = &stop;
>> > +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>> > +       mmc_wait_for_req(card->host, &mrq);
>> > +
>> > +       mmc_ffu_wait_busy(card);
>> > +
>> > +       return mmc_ffu_check_result(&mrq); }
>> > +
>> > +/*
>> > + * Map memory into a scatterlist.
>> > + */
>> > +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int
>> size,
>> > +       struct scatterlist *sglist, unsigned int max_segs,
>> > +       unsigned int max_seg_sz)
>> > +{
>> > +       struct scatterlist *sg = sglist;
>> > +       unsigned int i;
>> > +       unsigned long sz = size;
>> > +       unsigned int sctr_len = 0;
>> > +       unsigned long len;
>> > +
>> > +       sg_init_table(sglist, max_segs);
>> > +
>> > +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>> > +               len = PAGE_SIZE * (1 << mem->arr[i].order);
>> > +
>> > +               if (len > sz) {
>> > +                       len = sz;
>> > +                       sz = 0;
>> > +               }
>> > +
>> > +               sg_set_page(sg, mem->arr[i].page, len, 0);
>> > +               sg = sg_next(sg);
>> > +               sctr_len += 1;
>> > +       }
>> > +       sg_mark_end(sg);
>> > +
>> > +       return sctr_len;
>> > +}
>> > +
>> > +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
>> > +       if (!mem)
>> > +               return;
>> > +
>> > +       while (mem->cnt--)
>> > +               __free_pages(mem->arr[mem->cnt].page,
>> > + mem->arr[mem->cnt].order);
>> > +
>> > +       kfree(mem->arr);
>> > +       kfree(mem);
>> > +}
>> > +
>> > +/*
>> > + * Cleanup struct mmc_ffu_area.
>> > + */
>> > +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
>> > +       kfree(area->sg);
>> > +       mmc_ffu_free_mem(area->mem);
>> > +
>> > +       return 0;
>> > +}
>> > +
>> > +/*
>> > + * Allocate a lot of memory, preferably max_sz but at least min_sz.
>> > +In case
>> > + * there isn't much memory do not exceed 1/16th total low mem pages.
>> > +Also do
>> > + * not exceed a maximum number of segments and try not to make
>> > +segments much
>> > + * bigger than maximum segment size.
>> > + */
>> > +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
>> > +       unsigned long max_sz, unsigned int max_segs, unsigned int
>> > +max_seg_sz) {
>> > +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz, PAGE_SIZE);
>> > +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
>> > +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz,
>> PAGE_SIZE);
>> > +       unsigned long page_cnt = 0;
>> > +       /* we divide by 16 to ensure we will not allocate a big amount
>> > +        * of unnecessary pages */
>> > +       unsigned long limit = nr_free_buffer_pages() >> 4;
>> > +       struct mmc_ffu_mem *mem;
>> > +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
>> > +__GFP_NORETRY;
>> > +
>> > +       if (max_page_cnt > limit)
>> > +               max_page_cnt = limit;
>> > +
>> > +       if (min_page_cnt > max_page_cnt)
>> > +               min_page_cnt = max_page_cnt;
>> > +
>> > +       if (max_segs * max_seg_page_cnt > max_page_cnt)
>> > +               max_segs = DIV_ROUND_UP(max_page_cnt,
>> > + max_seg_page_cnt);
>> > +
>> > +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
>> > +       if (!mem)
>> > +               return NULL;
>> > +
>> > +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
>> > +               GFP_KERNEL);
>> > +       if (!mem->arr)
>> > +               goto out_free;
>> > +
>> > +       while (max_page_cnt) {
>> > +               struct page *page;
>> > +               unsigned int order;
>> > +
>> > +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
>> > +
>> > +               do {
>> > +                       page = alloc_pages(flags, order);
>> > +               } while (!page && order--);
>> > +
>> > +               if (!page)
>> > +                       goto out_free;
>> > +
>> > +               mem->arr[mem->cnt].page = page;
>> > +               mem->arr[mem->cnt].order = order;
>> > +               mem->cnt += 1;
>> > +               if (max_page_cnt <= (1UL << order))
>> > +                       break;
>> > +               max_page_cnt -= 1UL << order;
>> > +               page_cnt += 1UL << order;
>> > +       }
>> > +
>> > +       if (page_cnt < min_page_cnt)
>> > +               goto out_free;
>> > +
>> > +       return mem;
>> > +
>> > +out_free:
>> > +       mmc_ffu_free_mem(mem);
>> > +       return NULL;
>> > +}
>> > +
>> > +/*
>> > + * Initialize an area for data transfers.
>> > + * Copy the data to the allocated pages.
>> > + */
>> > +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct
>> mmc_card *card,
>> > +       const u8 *data, int size)
>> > +{
>> > +       int ret;
>> > +       int i;
>> > +       int length = 0;
>> > +
>> > +       area->max_tfr = size;
>> > +
>> > +       /* Try to allocate enough memory for a max. sized transfer. Less is OK
>> > +        * because the same memory can be mapped into the scatterlist more
>> than
>> > +        * once. Also, take into account the limits imposed on scatterlist
>> > +        * segments by the host driver.
>> > +        */
>> > +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area-
>> >max_segs,
>> > +               area->max_seg_sz);
>> > +       if (!area->mem)
>> > +               return -ENOMEM;
>> > +
>> > +       /* copy data to page */
>> > +       for (i = 0; i < area->mem->cnt; i++) {
>> > +               if (length > size) {
>> > +                       ret = -EINVAL;
>> > +                       goto out_free;
>> > +               }
>> > +
>> > +               memcpy(page_address(area->mem->arr[i].page), data + length,
>> > +                       min(size - length, (int)area->max_seg_sz));
>> > +               length += area->max_seg_sz;
>> > +       }
>> > +
>> > +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>> > +               GFP_KERNEL);
>> > +       if (!area->sg) {
>> > +               ret = -ENOMEM;
>> > +               goto out_free;
>> > +       }
>> > +
>> > +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
>> > +               area->max_segs, area->mem->cnt);
>> > +
>> > +       return 0;
>> > +
>> > +out_free:
>> > +       mmc_ffu_area_cleanup(area);
>> > +       return ret;
>> > +}
>> > +
>> > +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>> > +       int size)
>> > +{
>> > +       int rc;
>> > +       struct mmc_ffu_area area;
>> > +       int max_tfr;
>> > +
>> > +       area.sg = NULL;
>> > +       area.mem = NULL;
>> > +       area.max_segs = card->host->max_segs;
>> > +       area.max_seg_sz = card->host->max_seg_size & ~(CARD_BLOCK_SIZE
>> - 1);
>> > +       do {
>> > +               max_tfr = size;
>> > +               if (max_tfr >> 9 > card->host->max_blk_count)
>> > +                       max_tfr = card->host->max_blk_count << 9;
>> > +               if (max_tfr > card->host->max_req_size)
>> > +                       max_tfr = card->host->max_req_size;
>> > +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) > area.max_segs)
>> > +                       max_tfr = area.max_segs * area.max_seg_sz;
>> > +
>> > +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
>> > +               if (rc != 0)
>> > +                       goto exit;
>> > +
>> > +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>> > +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>> > +               if (rc != 0)
>> > +                       goto exit;
>> > +
>> > +               src += max_tfr;
>> > +               size -= max_tfr;
>> > +       } while (size > 0);
>> > +
>> > +exit:
>> > +       mmc_ffu_area_cleanup(&area);
>> > +       return rc;
>> > +}
>> > +
>> > +/* Flush all scheduled work from the MMC work queue.
>> > + * and initialize the MMC device */
>> > +static int mmc_ffu_restart(struct mmc_card *card) {
>> > +       struct mmc_host *host = card->host;
>> > +       int err = 0;
>> > +
>> > +       mmc_cache_ctrl(host, 0);
>>
>> Isn't cache flushing a bit late to consider here?
>
> I think we need to call mmc_flush_cache() instead of mmc_cache_ctrl().
>
>>
>> I would, as stated earlier, prefer one FFU operational code. Thus it would be
>> possible, before starting the FFU seuquence, to for example do cache
>> flushing, stopping BKOPS and suspend/flush the blk queue etc.
>> I think this would be more safe.
>
> Agree, it probably  would be more safe, but:
> - as mentioned before, we would like to split the process into two operational codes due
>    to different host power implementations
> - We implemented FFU as non-blocking procedure, which allows host sending regular R/W commands
>    between  downloading FW data process and FW install

Is there really a valid use case for that?

The reason we are considering to have this in-kernel is to get
robustness and to make sure the procedure don't get interrupted, at
least that is how I have interpreted the previous discussions.

Maybe you can elaborate on the reasons for in-kernel once more?

>
>
>>
>> > +       err = mmc_power_save_host(host);
>>
>> This is fragile. There are no guarantees the eMMC card will be properly
>> power cycled, I guess that is the requirement.
>
> The FFU requirement is not necessarily making power cycle, but performing "eMMC init"
> sequence (CMD0, CMD1,...).
> Once host cycling the power, it will run eMMC init sequence anyway.

Ah, that could simplify things a bit. In principle we could just run a
suspend/resume cycle to complete the operation.

>
>
>>
>> In some cases we can power cycle the card in some cases not, depending on
>> the hardware design. Are there other ways of telling the card to start using
>> the new firmware, but doing a full power cycle?
>>
>> Another concern I see, is if the card might change identity due to the
>> firmware upgrade. The card identity is validated each system resume cycle
>> and failed validation is an error we can't recover from.
>
> The current assumption is that device will change its identity during the FFU.
> But your concern is correct.
> In order to support this, the mmc_init_card() function call should be modified and assume that the card is not "oldcard".

This will be far more complex to support. It means we actually need to
remove the old card's struct device, it's corresponding blk device and
the blk device queue and for a NON_REMOVABLE host. Then trigger a new
mmc_rescan to detect and initialize the card again. I wondering what
upper mounted rootfs file systems thinks about that. :-)

So, as for now, we should aim for the simple approach and thus don't
do re-initiation/power-cycle. Instead let's leave that to be done by a
"manual" power cycle of the platform.

>
>>

[snip]

>> > +       /* eMMC v5 or later */
>> > +       if (card->ext_csd.rev >= 7) {
>> > +               card->ext_csd.ffu_capable =
>> > +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
>> > +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
>> > +       } else {
>> > +               card->ext_csd.ffu_capable = false;
>> > +       }
>>
>> I would like to add the "ffu_capable" field from a standalone separate patch.
>
> No problem. Let us know if you prefer us doing this.

Yes, please!


Kind regards
Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-18 10:56     ` Ulf Hansson
@ 2014-08-21  9:13       ` Alex Lemberg
  2014-08-21 12:17         ` Ulf Hansson
  0 siblings, 1 reply; 17+ messages in thread
From: Alex Lemberg @ 2014-08-21  9:13 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)



> -----Original Message-----
> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]
> Sent: Monday, August 18, 2014 1:57 PM
> To: Alex Lemberg
> Cc: linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Avi
> Shchislowski; Gwendal Grignou (gwendal@chromium.org)
> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
> 
> On 14 August 2014 14:37, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> > Hi Ulf,
> >
> >> -----Original Message-----
> >> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]
> >> Sent: Wednesday, August 13, 2014 3:47 PM
> >> To: Avi Shchislowski; Grant Grundler; Alex Lemberg
> >> Cc: linux-mmc@vger.kernel.org; cjb@laptop.org
> >> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
> >>
> >> On 16 July 2014 17:47, Avi Shchislowski
> >> <Avi.Shchislowski@sandisk.com>
> >> wrote:
> >> > The Field Firmware Update (FFU) feature is new for eMMC 5.0 spec
> (Jedec:
> >> JESD84-B50.pdf)
> >> >
> >> > http://www.jedec.org/standards-documents/technology-focus-
> >> areas/flash-
> >> > memory-ssds-ufs-emmc/e-mmc
> >> >
> >> > An ioctl has been added to provide the new firmware image's file
> >> > name to
> >> the  mmc driver and udev is then used to retrieve its data.
> >> >
> >> > Two new ioctls have been added:
> >> > 1. FFU download firmware - transfer the new firmware data from user
> >> > space to the eMMC device 2. FFU install - initializes the new
> >> > firmware update
> >> >
> >> >
> >> > Signed-off-by: Avi Shchislowski <avi.shchislowski@sandisk.com>
> >> > Signed-off-by: Alex Lemberg <alex.lemberg@sandisk.com>
> >>
> >> Hi guys,
> >>
> >> Sorry for the delay in providing feeback, but hey better late than
> >> never. :-)
> >
> > Thanks, we appreciate your comments!
> >>
> >> >
> >> > ---
> >> > V8:
> >> > - Modified according to Gwendal Grignou comments and patch:
> >> >    [PATCH] Fix on top of sandisk patches
> >> >
> >> > V7:
> >> > - fixed mangled white space
> >> >
> >> > V5:
> >> > - provides udev (request_firmware) implementation as advised in
> >> > patch
> >> > v2 comments.
> >> >
> >> > diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
> >> > index
> >> > 5562308..19ba729 100644
> >> > --- a/drivers/mmc/card/Kconfig
> >> > +++ b/drivers/mmc/card/Kconfig
> >> > @@ -68,3 +68,11 @@ config MMC_TEST
> >> >
> >> >           This driver is only of interest to those developing or
> >> >           testing a host driver. Most people should say N here.
> >> > +
> >> > +config MMC_FFU
> >> > +       bool "FFU SUPPORT"
> >> > +       depends on MMC != n
> >> > +       help
> >> > +         This is an option to run firmware update on eMMC 5.0.
> >> > +         Field firmware updates (FFU) enables features enhancment
> >> > +         in the field.
> >>
> >> Why do we need a new Kconfig for this? I think we can drop it.
> >
> > The motivation was to let vendor decide if FFU code should be included in his
> release or not.
> > Some vendors prefer using this FFU functionality during
> production/testing/development only.
> 
> OK, I see.
> 
> >
> >>
> >> > diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile
> >> > index c73b406..1e9223b 100644
> >> > --- a/drivers/mmc/card/Makefile
> >> > +++ b/drivers/mmc/card/Makefile
> >> > @@ -8,3 +8,4 @@ obj-$(CONFIG_MMC_TEST)          += mmc_test.o
> >> >
> >> >  obj-$(CONFIG_SDIO_UART)                += sdio_uart.o
> >> >
> >> > +obj-$(CONFIG_MMC_FFU)          += ffu.o
> >> > diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
> >> > index 7b5424f..3ed900b 100644
> >> > --- a/drivers/mmc/card/block.c
> >> > +++ b/drivers/mmc/card/block.c
> >> > @@ -41,6 +41,7 @@
> >> >  #include <linux/mmc/host.h>
> >> >  #include <linux/mmc/mmc.h>
> >> >  #include <linux/mmc/sd.h>
> >> > +#include <linux/mmc/ffu.h>
> >> >
> >> >  #include <asm/uaccess.h>
> >> >
> >> > @@ -523,8 +524,18 @@ static int mmc_blk_ioctl_cmd(struct
> >> > block_device *bdev,
> >> >
> >> >         mrq.cmd = &cmd;
> >> >
> >> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> >> > +                       err = mmc_ffu_download(card, idata->buf);
> >> > +                       goto cmd_done;
> >> > +               }
> >> > +
> >> >         mmc_get_card(card);
> >> >
> >> > +       if (cmd.opcode == MMC_FFU_INSTALL_OP) {
> >> > +               err = mmc_ffu_install(card);
> >> > +               goto cmd_rel_host;
> >> > +       }
> >> > +
> >>
> >> I think one FFU operation code should be enough. Overall that would
> >> also simplify management and thus code.
> >
> > The reason for splitting it to two operation codes is providing
> > flexibility to hosts due to different implementation of power_off routine.
> > Some mmc hosts could not  support suspend/resume, so this split will
> > allow to make "manual" power reset instead of running
> MMC_FFU_INSTALL_OP. (JEDEC spec allows doing power cycle).
> 
> To clarify the terms here. All hosts supports system PM suspend/resume.
> 
> Most of the suspend/resume operations are handled by the mmc core. The
> host's ->set_ios callback will though be invoked by the core to give the option
> for the host, to cut the power(s) to the card. Depending on host and hw, it
> varies what power(s) will be cut.
> 
> Now, I don't understand how the upper layers will know which way it should
> complete the FFU upgrade process?

We wanted to leave this choice to host vendors (also mobile) that implements the upper/user layers of FFU solution.
Can we assure that all hosts will perform eMMC card init routine on suspend/resume?
If so, we can obsolete this additional MMC_FFU_INSTALL_OP.

In case host is not calling eMMC card init routine on suspend/resume - it have to perform power cycle.

> 
> >
> >
> >
> >>
> >> Additionally I think it's better to move these "if statements" prior
> >> a potential data transfer is set up. You don't use the mrq struct anyway.
> >
> > Agree
> >
> >>
> >> >         err = mmc_blk_part_switch(card, md);
> >> >         if (err)
> >> >                 goto cmd_rel_host;
> >> > diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c new
> >> > file mode 100644 index 0000000..783673e
> >> > --- /dev/null
> >> > +++ b/drivers/mmc/card/ffu.c
> >>
> >> I am wondering whether this code should belong to
> >> drivers/mmc/core/mmc.c instead. Or unless it becomes too big, we
> >> could have a separate file called mmc_ffu.[c|h], but in the core directory.
> >
> > For easiest maintenance we prefer using separate file.
> > mmc_ffu.[c|h] in core directory - will do.
> >
> >>
> >> > @@ -0,0 +1,607 @@
> >> > +/*
> >> > + * *  ffu.c
> >> > + *
> >> > + *  Copyright 2007-2008 Pierre Ossman
> >> > + *
> >> > + *  Modified by SanDisk Corp., Copyright (c) 2013 SanDisk Corp.
> >> > + *
> >> > + * This program is free software; you can redistribute it and/or
> >> > +modify
> >> > + * it under the terms of the GNU General Public License as
> >> > +published by
> >> > + * the Free Software Foundation; either version 2 of the License,
> >> > +or (at
> >> > + * your option) any later version.
> >> > + *
> >> > + * This program includes bug.h, card.h, host.h, mmc.h,
> >> > +scatterlist.h,
> >> > + * slab.h, ffu.h & swap.h header files
> >> > + * The original, unmodified version of this program - the
> >> > +mmc_test.c
> >> > + * file - is obtained under the GPL v2.0 license that is available
> >> > +via
> >> > + * http://www.gnu.org/licenses/,
> >> > + * or http://www.opensource.org/licenses/gpl-2.0.php
> >> > +*/
> >> > +
> >> > +#include <linux/bug.h>
> >> > +#include <linux/errno.h>
> >> > +#include <linux/mmc/card.h>
> >> > +#include <linux/mmc/host.h>
> >> > +#include <linux/mmc/mmc.h>
> >> > +#include <linux/scatterlist.h>
> >> > +#include <linux/slab.h>
> >> > +#include <linux/swap.h>
> >> > +#include <linux/mmc/ffu.h>
> >> > +#include <linux/firmware.h>
> >> > +
> >> > +/**
> >> > + * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
> >> > + * @page: first page in the allocation
> >> > + * @order: order of the number of pages allocated  */ struct
> >> > +mmc_ffu_pages {
> >> > +       struct page *page;
> >> > +       unsigned int order;
> >> > +};
> >> > +
> >> > +/**
> >> > + * struct mmc_ffu_mem - allocated memory.
> >> > + * @arr: array of allocations
> >> > + * @cnt: number of allocations
> >> > + */
> >> > +struct mmc_ffu_mem {
> >> > +       struct mmc_ffu_pages *arr;
> >> > +       unsigned int cnt;
> >> > +};
> >> > +
> >> > +struct mmc_ffu_area {
> >> > +       unsigned long max_sz;
> >> > +       unsigned int max_tfr;
> >> > +       unsigned int max_segs;
> >> > +       unsigned int max_seg_sz;
> >> > +       unsigned int blocks;
> >> > +       unsigned int sg_len;
> >> > +       struct mmc_ffu_mem *mem;
> >> > +       struct scatterlist *sg;
> >> > +};
> >> > +
> >> > +static void mmc_ffu_prepare_mrq(struct mmc_card *card,
> >> > +       struct mmc_request *mrq, struct scatterlist *sg, unsigned int sg_len,
> >> > +       u32 arg, unsigned int blocks, unsigned int blksz) {
> >> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> >> > +
> >> > +       if (blocks > 1)
> >> > +               mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
> >> > +       else
> >> > +               mrq->cmd->opcode = MMC_WRITE_BLOCK;
> >> > +
> >> > +       mrq->cmd->arg = arg;
> >> > +       if (!mmc_card_blockaddr(card))
> >> > +               mrq->cmd->arg <<= 9;
> >> > +
> >> > +       mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
> >> > +       if (blocks == 1) {
> >> > +               mrq->stop = NULL;
> >> > +       } else {
> >> > +               mrq->stop->opcode = MMC_STOP_TRANSMISSION;
> >> > +               mrq->stop->arg = 0;
> >> > +               mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
> >> > +       }
> >> > +
> >> > +       mrq->data->blksz = blksz;
> >> > +       mrq->data->blocks = blocks;
> >> > +       mrq->data->flags = MMC_DATA_WRITE;
> >> > +       mrq->data->sg = sg;
> >> > +       mrq->data->sg_len = sg_len;
> >> > +
> >> > +       mmc_set_data_timeout(mrq->data, card); }
> >> > +
> >> > +/*
> >> > + * Checks that a normal transfer didn't have any errors  */ static
> >> > +int mmc_ffu_check_result(struct mmc_request *mrq) {
> >> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data);
> >> > +
> >> > +       if (mrq->cmd->error != 0)
> >> > +               return -EINVAL;
> >> > +
> >> > +       if (mrq->data->error != 0)
> >> > +               return -EINVAL;
> >> > +
> >> > +       if (mrq->stop != NULL && mrq->stop->error != 0)
> >> > +               return -1;
> >> > +
> >> > +       if (mrq->data->bytes_xfered != (mrq->data->blocks *
> >> > + mrq->data-
> >> >blksz))
> >> > +               return -EINVAL;
> >> > +
> >> > +       return 0;
> >> > +}
> >> > +
> >> > +static int mmc_ffu_busy(struct mmc_command *cmd) {
> >> > +       return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
> >> > +               (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG); }
> >> > +
> >> > +static int mmc_ffu_wait_busy(struct mmc_card *card) {
> >> > +       int ret, busy = 0;
> >> > +       struct mmc_command cmd = {0};
> >> > +
> >> > +       memset(&cmd, 0, sizeof(struct mmc_command));
> >> > +       cmd.opcode = MMC_SEND_STATUS;
> >> > +       cmd.arg = card->rca << 16;
> >> > +       cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
> >> > +
> >> > +       do {
> >> > +               ret = mmc_wait_for_cmd(card->host, &cmd, 0);
> >> > +               if (ret)
> >> > +                       break;
> >> > +
> >> > +               if (!busy && mmc_ffu_busy(&cmd)) {
> >> > +                       busy = 1;
> >> > +                       if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
> >> > +                               pr_warn("%s: Warning: Host did not "
> >> > +                                       "wait for busy state to end.\n",
> >> > +                                       mmc_hostname(card->host));
> >> > +                       }
> >> > +               }
> >> > +
> >> > +       } while (mmc_ffu_busy(&cmd));
> >> > +
> >> > +       return ret;
> >> > +}
> >>
> >> By just browsing through the code for preparing and handling the mmc
> >> data request in the above functions, I find quite some duplication of
> >> code from the card/*.c files. Could we maybe share some code between
> >> these layers to avoid duplications, and thus errors?
> >
> > Yes, we are aware of some duplications and tried to avoid it.
> > Eventually, sharing code to avoid duplications will require changes in existing
> card/*c files.
> > We preferred not changing existing code as much as possible.
> 
> Breaking out code and doing re-factoring in sometimes necessary. I certainly
> think we should give it a try.
> 
> My concern is not just duplication of code, but also duplication of errors as that
> follows with it.

Your concern is correct.
Can we suggest taking this significant step separately from adding this patch of FFU support?
eMMC 5.0 is already there and vendors would like to start using this feature in order to be able upgrading FW to their eMMC parts.

 
> 
> >
> >>
> >> > +
> >> > +/*
> >> > + * transfer with certain parameters  */ static int
> >> > +mmc_ffu_simple_transfer(struct mmc_card *card,
> >> > +       struct scatterlist *sg, unsigned int sg_len, u32 arg,
> >> > +       unsigned int blocks, unsigned int blksz) {
> >> > +       struct mmc_request mrq = {0};
> >> > +       struct mmc_command cmd = {0};
> >> > +       struct mmc_command stop = {0};
> >> > +       struct mmc_data data = {0};
> >> > +
> >> > +       mrq.cmd = &cmd;
> >> > +       mrq.data = &data;
> >> > +       mrq.stop = &stop;
> >> > +       mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
> >> > +       mmc_wait_for_req(card->host, &mrq);
> >> > +
> >> > +       mmc_ffu_wait_busy(card);
> >> > +
> >> > +       return mmc_ffu_check_result(&mrq); }
> >> > +
> >> > +/*
> >> > + * Map memory into a scatterlist.
> >> > + */
> >> > +static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int
> >> size,
> >> > +       struct scatterlist *sglist, unsigned int max_segs,
> >> > +       unsigned int max_seg_sz)
> >> > +{
> >> > +       struct scatterlist *sg = sglist;
> >> > +       unsigned int i;
> >> > +       unsigned long sz = size;
> >> > +       unsigned int sctr_len = 0;
> >> > +       unsigned long len;
> >> > +
> >> > +       sg_init_table(sglist, max_segs);
> >> > +
> >> > +       for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
> >> > +               len = PAGE_SIZE * (1 << mem->arr[i].order);
> >> > +
> >> > +               if (len > sz) {
> >> > +                       len = sz;
> >> > +                       sz = 0;
> >> > +               }
> >> > +
> >> > +               sg_set_page(sg, mem->arr[i].page, len, 0);
> >> > +               sg = sg_next(sg);
> >> > +               sctr_len += 1;
> >> > +       }
> >> > +       sg_mark_end(sg);
> >> > +
> >> > +       return sctr_len;
> >> > +}
> >> > +
> >> > +static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
> >> > +       if (!mem)
> >> > +               return;
> >> > +
> >> > +       while (mem->cnt--)
> >> > +               __free_pages(mem->arr[mem->cnt].page,
> >> > + mem->arr[mem->cnt].order);
> >> > +
> >> > +       kfree(mem->arr);
> >> > +       kfree(mem);
> >> > +}
> >> > +
> >> > +/*
> >> > + * Cleanup struct mmc_ffu_area.
> >> > + */
> >> > +static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area) {
> >> > +       kfree(area->sg);
> >> > +       mmc_ffu_free_mem(area->mem);
> >> > +
> >> > +       return 0;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Allocate a lot of memory, preferably max_sz but at least min_sz.
> >> > +In case
> >> > + * there isn't much memory do not exceed 1/16th total low mem pages.
> >> > +Also do
> >> > + * not exceed a maximum number of segments and try not to make
> >> > +segments much
> >> > + * bigger than maximum segment size.
> >> > + */
> >> > +static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long
> min_sz,
> >> > +       unsigned long max_sz, unsigned int max_segs, unsigned int
> >> > +max_seg_sz) {
> >> > +       unsigned long max_page_cnt = DIV_ROUND_UP(max_sz,
> PAGE_SIZE);
> >> > +       unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
> >> > +       unsigned long max_seg_page_cnt = DIV_ROUND_UP(max_seg_sz,
> >> PAGE_SIZE);
> >> > +       unsigned long page_cnt = 0;
> >> > +       /* we divide by 16 to ensure we will not allocate a big amount
> >> > +        * of unnecessary pages */
> >> > +       unsigned long limit = nr_free_buffer_pages() >> 4;
> >> > +       struct mmc_ffu_mem *mem;
> >> > +       gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN |
> >> > +__GFP_NORETRY;
> >> > +
> >> > +       if (max_page_cnt > limit)
> >> > +               max_page_cnt = limit;
> >> > +
> >> > +       if (min_page_cnt > max_page_cnt)
> >> > +               min_page_cnt = max_page_cnt;
> >> > +
> >> > +       if (max_segs * max_seg_page_cnt > max_page_cnt)
> >> > +               max_segs = DIV_ROUND_UP(max_page_cnt,
> >> > + max_seg_page_cnt);
> >> > +
> >> > +       mem = kzalloc(sizeof(struct mmc_ffu_mem), GFP_KERNEL);
> >> > +       if (!mem)
> >> > +               return NULL;
> >> > +
> >> > +       mem->arr = kzalloc(sizeof(struct mmc_ffu_pages) * max_segs,
> >> > +               GFP_KERNEL);
> >> > +       if (!mem->arr)
> >> > +               goto out_free;
> >> > +
> >> > +       while (max_page_cnt) {
> >> > +               struct page *page;
> >> > +               unsigned int order;
> >> > +
> >> > +               order = get_order(max_seg_page_cnt << PAGE_SHIFT);
> >> > +
> >> > +               do {
> >> > +                       page = alloc_pages(flags, order);
> >> > +               } while (!page && order--);
> >> > +
> >> > +               if (!page)
> >> > +                       goto out_free;
> >> > +
> >> > +               mem->arr[mem->cnt].page = page;
> >> > +               mem->arr[mem->cnt].order = order;
> >> > +               mem->cnt += 1;
> >> > +               if (max_page_cnt <= (1UL << order))
> >> > +                       break;
> >> > +               max_page_cnt -= 1UL << order;
> >> > +               page_cnt += 1UL << order;
> >> > +       }
> >> > +
> >> > +       if (page_cnt < min_page_cnt)
> >> > +               goto out_free;
> >> > +
> >> > +       return mem;
> >> > +
> >> > +out_free:
> >> > +       mmc_ffu_free_mem(mem);
> >> > +       return NULL;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Initialize an area for data transfers.
> >> > + * Copy the data to the allocated pages.
> >> > + */
> >> > +static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct
> >> mmc_card *card,
> >> > +       const u8 *data, int size)
> >> > +{
> >> > +       int ret;
> >> > +       int i;
> >> > +       int length = 0;
> >> > +
> >> > +       area->max_tfr = size;
> >> > +
> >> > +       /* Try to allocate enough memory for a max. sized transfer. Less is
> OK
> >> > +        * because the same memory can be mapped into the
> >> > + scatterlist more
> >> than
> >> > +        * once. Also, take into account the limits imposed on scatterlist
> >> > +        * segments by the host driver.
> >> > +        */
> >> > +       area->mem = mmc_ffu_alloc_mem(1, area->max_tfr, area-
> >> >max_segs,
> >> > +               area->max_seg_sz);
> >> > +       if (!area->mem)
> >> > +               return -ENOMEM;
> >> > +
> >> > +       /* copy data to page */
> >> > +       for (i = 0; i < area->mem->cnt; i++) {
> >> > +               if (length > size) {
> >> > +                       ret = -EINVAL;
> >> > +                       goto out_free;
> >> > +               }
> >> > +
> >> > +               memcpy(page_address(area->mem->arr[i].page), data + length,
> >> > +                       min(size - length, (int)area->max_seg_sz));
> >> > +               length += area->max_seg_sz;
> >> > +       }
> >> > +
> >> > +       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
> >> > +               GFP_KERNEL);
> >> > +       if (!area->sg) {
> >> > +               ret = -ENOMEM;
> >> > +               goto out_free;
> >> > +       }
> >> > +
> >> > +       area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg,
> >> > +               area->max_segs, area->mem->cnt);
> >> > +
> >> > +       return 0;
> >> > +
> >> > +out_free:
> >> > +       mmc_ffu_area_cleanup(area);
> >> > +       return ret;
> >> > +}
> >> > +
> >> > +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
> >> > +       int size)
> >> > +{
> >> > +       int rc;
> >> > +       struct mmc_ffu_area area;
> >> > +       int max_tfr;
> >> > +
> >> > +       area.sg = NULL;
> >> > +       area.mem = NULL;
> >> > +       area.max_segs = card->host->max_segs;
> >> > +       area.max_seg_sz = card->host->max_seg_size &
> >> > + ~(CARD_BLOCK_SIZE
> >> - 1);
> >> > +       do {
> >> > +               max_tfr = size;
> >> > +               if (max_tfr >> 9 > card->host->max_blk_count)
> >> > +                       max_tfr = card->host->max_blk_count << 9;
> >> > +               if (max_tfr > card->host->max_req_size)
> >> > +                       max_tfr = card->host->max_req_size;
> >> > +               if (DIV_ROUND_UP(max_tfr, area.max_seg_sz) >
> area.max_segs)
> >> > +                       max_tfr = area.max_segs * area.max_seg_sz;
> >> > +
> >> > +               rc = mmc_ffu_area_init(&area, card, src, max_tfr);
> >> > +               if (rc != 0)
> >> > +                       goto exit;
> >> > +
> >> > +               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
> >> > +                       max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
> >> > +               if (rc != 0)
> >> > +                       goto exit;
> >> > +
> >> > +               src += max_tfr;
> >> > +               size -= max_tfr;
> >> > +       } while (size > 0);
> >> > +
> >> > +exit:
> >> > +       mmc_ffu_area_cleanup(&area);
> >> > +       return rc;
> >> > +}
> >> > +
> >> > +/* Flush all scheduled work from the MMC work queue.
> >> > + * and initialize the MMC device */ static int
> >> > +mmc_ffu_restart(struct mmc_card *card) {
> >> > +       struct mmc_host *host = card->host;
> >> > +       int err = 0;
> >> > +
> >> > +       mmc_cache_ctrl(host, 0);
> >>
> >> Isn't cache flushing a bit late to consider here?
> >
> > I think we need to call mmc_flush_cache() instead of mmc_cache_ctrl().
> >
> >>
> >> I would, as stated earlier, prefer one FFU operational code. Thus it
> >> would be possible, before starting the FFU seuquence, to for example
> >> do cache flushing, stopping BKOPS and suspend/flush the blk queue etc.
> >> I think this would be more safe.
> >
> > Agree, it probably  would be more safe, but:
> > - as mentioned before, we would like to split the process into two operational
> codes due
> >    to different host power implementations
> > - We implemented FFU as non-blocking procedure, which allows host sending
> regular R/W commands
> >    between  downloading FW data process and FW install
> 
> Is there really a valid use case for that?
> 

As mentioned above, in case we can assure that all hosts will perform eMMC 
card init routine on suspend/resume, we can unify those operations.


> The reason we are considering to have this in-kernel is to get robustness and to
> make sure the procedure don't get interrupted, at least that is how I have
> interpreted the previous discussions.
> 
> Maybe you can elaborate on the reasons for in-kernel once more?

- In case FW data cannot be transferred by one singe transaction
- The host need to be claimed during the FFU process to avoid interruptions by other R/W IOs.
- Device is set to FFU mode during FFU download operations, and no Normal IOs are allowed during this process.


> 
> >
> >
> >>
> >> > +       err = mmc_power_save_host(host);
> >>
> >> This is fragile. There are no guarantees the eMMC card will be
> >> properly power cycled, I guess that is the requirement.
> >
> > The FFU requirement is not necessarily making power cycle, but performing
> "eMMC init"
> > sequence (CMD0, CMD1,...).
> > Once host cycling the power, it will run eMMC init sequence anyway.
> 
> Ah, that could simplify things a bit. In principle we could just run a
> suspend/resume cycle to complete the operation.
> 
> >
> >
> >>
> >> In some cases we can power cycle the card in some cases not,
> >> depending on the hardware design. Are there other ways of telling the
> >> card to start using the new firmware, but doing a full power cycle?
> >>
> >> Another concern I see, is if the card might change identity due to
> >> the firmware upgrade. The card identity is validated each system
> >> resume cycle and failed validation is an error we can't recover from.
> >
> > The current assumption is that device will change its identity during the FFU.
> > But your concern is correct.
> > In order to support this, the mmc_init_card() function call should be modified
> and assume that the card is not "oldcard".
> 
> This will be far more complex to support. It means we actually need to remove
> the old card's struct device, it's corresponding blk device and the blk device
> queue and for a NON_REMOVABLE host. Then trigger a new mmc_rescan to
> detect and initialize the card again. I wondering what upper mounted rootfs file
> systems thinks about that. :-)
> 
> So, as for now, we should aim for the simple approach and thus don't do re-
> initiation/power-cycle. Instead let's leave that to be done by a "manual" power
> cycle of the platform.

As mentioned above, we wanted to let host vendors to decide.
If eMMC init is called during suspend/resume - no Power Cycle is required.


> 
> >
> >>
> 
> [snip]
> 
> >> > +       /* eMMC v5 or later */
> >> > +       if (card->ext_csd.rev >= 7) {
> >> > +               card->ext_csd.ffu_capable =
> >> > +                       ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 1) == 1) &&
> >> > +                       ((ext_csd[EXT_CSD_FW_CONFIG] & 1) == 0);
> >> > +       } else {
> >> > +               card->ext_csd.ffu_capable = false;
> >> > +       }
> >>
> >> I would like to add the "ffu_capable" field from a standalone separate patch.
> >
> > No problem. Let us know if you prefer us doing this.
> 
> Yes, please!

Will do
> 
> 
> Kind regards
> Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-21  9:13       ` Alex Lemberg
@ 2014-08-21 12:17         ` Ulf Hansson
  2014-08-25 12:25           ` Alex Lemberg
  2014-09-01  9:26           ` Alex Lemberg
  0 siblings, 2 replies; 17+ messages in thread
From: Ulf Hansson @ 2014-08-21 12:17 UTC (permalink / raw)
  To: Alex Lemberg
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

[snip]

>> >> By just browsing through the code for preparing and handling the mmc
>> >> data request in the above functions, I find quite some duplication of
>> >> code from the card/*.c files. Could we maybe share some code between
>> >> these layers to avoid duplications, and thus errors?
>> >
>> > Yes, we are aware of some duplications and tried to avoid it.
>> > Eventually, sharing code to avoid duplications will require changes in existing
>> card/*c files.
>> > We preferred not changing existing code as much as possible.
>>
>> Breaking out code and doing re-factoring in sometimes necessary. I certainly
>> think we should give it a try.
>>
>> My concern is not just duplication of code, but also duplication of errors as that
>> follows with it.
>
> Your concern is correct.
> Can we suggest taking this significant step separately from adding this patch of FFU support?
> eMMC 5.0 is already there and vendors would like to start using this feature in order to be able upgrading FW to their eMMC parts.

No, I will only accept good quality code - sorry. But, I have a
suggestion which might simplify this patch significantly. :-) Read
further down.

[snip]

>> >>
>> >> I would, as stated earlier, prefer one FFU operational code. Thus it
>> >> would be possible, before starting the FFU seuquence, to for example
>> >> do cache flushing, stopping BKOPS and suspend/flush the blk queue etc.
>> >> I think this would be more safe.
>> >
>> > Agree, it probably  would be more safe, but:
>> > - as mentioned before, we would like to split the process into two operational
>> codes due
>> >    to different host power implementations
>> > - We implemented FFU as non-blocking procedure, which allows host sending
>> regular R/W commands
>> >    between  downloading FW data process and FW install
>>
>> Is there really a valid use case for that?
>>
>
> As mentioned above, in case we can assure that all hosts will perform eMMC
> card init routine on suspend/resume, we can unify those operations.
>
>
>> The reason we are considering to have this in-kernel is to get robustness and to
>> make sure the procedure don't get interrupted, at least that is how I have
>> interpreted the previous discussions.
>>
>> Maybe you can elaborate on the reasons for in-kernel once more?
>
> - In case FW data cannot be transferred by one singe transaction
> - The host need to be claimed during the FFU process to avoid interruptions by other R/W IOs.
> - Device is set to FFU mode during FFU download operations, and no Normal IOs are allowed during this process.

Thanks for clarifying this. So, clearly we need some additional
support in-kernel to handle the synchronization mechanism.

First, let's consider below snippet pasted from the eMMC5.0 spec:
--------------

Once in FFU Mode the host may send the new firmware bundle to the
device using one or more write
commands.

The host could regain regular functionally of write and read commands
by setting MODE_CONFIG field
in the EXT_CSD back to Normal state. Switching out of FFU Mode may
abort the firmware download
operation. When switched back-in to FFU Mode, the host should check
the FFU Status to get indication
about the number of sectors which were downloaded successfully by reading the
NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED in the extended CSD. In case the
number of sectors which were downloaded successfully is zero the host
should re-start downloading the
new firmware bundle from its first sector. In case the number of
sectors which were downloaded
successfully is positive the host should continue the download from
the next sector, which would resume
the firmware download operation.
-------------

My conclusion from that is (correct me if I am wrong, since it may be
vendor specific), that only minor modifications should be needed to
the mmc ioctl. We need to add special treatment for when "cmd.opcode
== MMC_FFU_DOWNLOAD_OP". In such scenario the following sequence needs
to be maintained.

1. Claim host etc.
2. Set FFU mode.
3. Write some sectors to the FFU area, but in the same way as any
other mmc ioctl WRITE is done.
4. Set normal mode.
5. Relase host etc.

That sequence then needs to be repeated to write the complete
firmware, depending on buffer size. Between each and every piece of
FFU data that is written, normal READ/WRITE operations can be served.
Right?

User space will thus also be responsible of verifying the FFU
download. It will also have to find out whether
SUPPORTED_MODE_OPERATION_CODES in EXTCSD supported - and take relevant
actions to complete the FFU upgrade process.

[snip]

>> >> Another concern I see, is if the card might change identity due to
>> >> the firmware upgrade. The card identity is validated each system
>> >> resume cycle and failed validation is an error we can't recover from.
>> >
>> > The current assumption is that device will change its identity during the FFU.
>> > But your concern is correct.
>> > In order to support this, the mmc_init_card() function call should be modified
>> and assume that the card is not "oldcard".
>>
>> This will be far more complex to support. It means we actually need to remove
>> the old card's struct device, it's corresponding blk device and the blk device
>> queue and for a NON_REMOVABLE host. Then trigger a new mmc_rescan to
>> detect and initialize the card again. I wondering what upper mounted rootfs file
>> systems thinks about that. :-)
>>
>> So, as for now, we should aim for the simple approach and thus don't do re-
>> initiation/power-cycle. Instead let's leave that to be done by a "manual" power
>> cycle of the platform.
>
> As mentioned above, we wanted to let host vendors to decide.
> If eMMC init is called during suspend/resume - no Power Cycle is required.

I think you missed my point here.

If the card identity changes and it's a NON_REMOVABLE host, we can't
easily just perform a suspend/resume cycle to complete the FFU
upgrade. The best and safest way is to power cycle the complete hw -
always.

Kind regards
Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-21 12:17         ` Ulf Hansson
@ 2014-08-25 12:25           ` Alex Lemberg
  2014-09-01  9:26           ` Alex Lemberg
  1 sibling, 0 replies; 17+ messages in thread
From: Alex Lemberg @ 2014-08-25 12:25 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org),
	Kees Cook (keescook@chromium.org)



> -----Original Message-----
> From: Ulf Hansson [mailto:ulf.hansson@linaro.org]
> Sent: Thursday, August 21, 2014 3:18 PM
> To: Alex Lemberg
> Cc: linux-mmc@vger.kernel.org; cjb@laptop.org; Grant Grundler; Avi
> Shchislowski; Gwendal Grignou (gwendal@chromium.org)
> Subject: Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
> 
> [snip]
> 
> >> >> By just browsing through the code for preparing and handling the
> >> >> mmc data request in the above functions, I find quite some
> >> >> duplication of code from the card/*.c files. Could we maybe share
> >> >> some code between these layers to avoid duplications, and thus errors?
> >> >
> >> > Yes, we are aware of some duplications and tried to avoid it.
> >> > Eventually, sharing code to avoid duplications will require changes
> >> > in existing
> >> card/*c files.
> >> > We preferred not changing existing code as much as possible.
> >>
> >> Breaking out code and doing re-factoring in sometimes necessary. I
> >> certainly think we should give it a try.
> >>
> >> My concern is not just duplication of code, but also duplication of
> >> errors as that follows with it.
> >
> > Your concern is correct.
> > Can we suggest taking this significant step separately from adding this patch
> of FFU support?
> > eMMC 5.0 is already there and vendors would like to start using this feature
> in order to be able upgrading FW to their eMMC parts.
> 
> No, I will only accept good quality code - sorry. But, I have a suggestion which
> might simplify this patch significantly. :-) Read further down.
> 
> [snip]
> 
> >> >>
> >> >> I would, as stated earlier, prefer one FFU operational code. Thus
> >> >> it would be possible, before starting the FFU seuquence, to for
> >> >> example do cache flushing, stopping BKOPS and suspend/flush the blk
> queue etc.
> >> >> I think this would be more safe.
> >> >
> >> > Agree, it probably  would be more safe, but:
> >> > - as mentioned before, we would like to split the process into two
> >> > operational
> >> codes due
> >> >    to different host power implementations
> >> > - We implemented FFU as non-blocking procedure, which allows host
> >> > sending
> >> regular R/W commands
> >> >    between  downloading FW data process and FW install
> >>
> >> Is there really a valid use case for that?
> >>
> >
> > As mentioned above, in case we can assure that all hosts will perform
> > eMMC card init routine on suspend/resume, we can unify those operations.
> >
> >
> >> The reason we are considering to have this in-kernel is to get
> >> robustness and to make sure the procedure don't get interrupted, at
> >> least that is how I have interpreted the previous discussions.
> >>
> >> Maybe you can elaborate on the reasons for in-kernel once more?
> >
> > - In case FW data cannot be transferred by one singe transaction
> > - The host need to be claimed during the FFU process to avoid interruptions
> by other R/W IOs.
> > - Device is set to FFU mode during FFU download operations, and no Normal
> IOs are allowed during this process.
> 
> Thanks for clarifying this. So, clearly we need some additional support in-kernel
> to handle the synchronization mechanism.
> 
> First, let's consider below snippet pasted from the eMMC5.0 spec:
> --------------
> 
> Once in FFU Mode the host may send the new firmware bundle to the device
> using one or more write commands.
> 
> The host could regain regular functionally of write and read commands by
> setting MODE_CONFIG field in the EXT_CSD back to Normal state. Switching
> out of FFU Mode may abort the firmware download operation. When switched
> back-in to FFU Mode, the host should check the FFU Status to get indication
> about the number of sectors which were downloaded successfully by reading
> the NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED in the extended
> CSD. In case the number of sectors which were downloaded successfully is zero
> the host should re-start downloading the new firmware bundle from its first
> sector. In case the number of sectors which were downloaded successfully is
> positive the host should continue the download from the next sector, which
> would resume the firmware download operation.
> -------------
> 
> My conclusion from that is (correct me if I am wrong, since it may be vendor
> specific), that only minor modifications should be needed to the mmc ioctl. We
> need to add special treatment for when "cmd.opcode ==
> MMC_FFU_DOWNLOAD_OP". In such scenario the following sequence needs to
> be maintained.
> 
> 1. Claim host etc.
> 2. Set FFU mode.
> 3. Write some sectors to the FFU area, but in the same way as any other mmc
> ioctl WRITE is done.
> 4. Set normal mode.
> 5. Relase host etc.
> 
> That sequence then needs to be repeated to write the complete firmware,
> depending on buffer size. Between each and every piece of FFU data that is
> written, normal READ/WRITE operations can be served.
> Right?

Right

> 
> User space will thus also be responsible of verifying the FFU download. It will
> also have to find out whether SUPPORTED_MODE_OPERATION_CODES in
> EXTCSD supported - and take relevant actions to complete the FFU upgrade
> process.

Right, and in this case user space will need to know exactly the way how FFU is work:
- verify each FFU download operation
- check that all FW data was downloaded successfully
- choose and proceed with FFU install according to SUPPORTED_MODE_OPERATION_CODES
Can we trust user space making the right implementation of FFU?

Also, we were asked by Chromium team to change FFU implementation to be more secure/trusted
using udev mechanism. Please see comments by Kees Cook in: 
"[RFC PATCH 1/1] mmc-utils: Support-sending-eMMC-5.0-FFU" thread.


> 
> [snip]
> 
> >> >> Another concern I see, is if the card might change identity due to
> >> >> the firmware upgrade. The card identity is validated each system
> >> >> resume cycle and failed validation is an error we can't recover from.
> >> >
> >> > The current assumption is that device will change its identity during the
> FFU.
> >> > But your concern is correct.
> >> > In order to support this, the mmc_init_card() function call should
> >> > be modified
> >> and assume that the card is not "oldcard".
> >>
> >> This will be far more complex to support. It means we actually need
> >> to remove the old card's struct device, it's corresponding blk device
> >> and the blk device queue and for a NON_REMOVABLE host. Then trigger a
> >> new mmc_rescan to detect and initialize the card again. I wondering
> >> what upper mounted rootfs file systems thinks about that. :-)
> >>
> >> So, as for now, we should aim for the simple approach and thus don't
> >> do re- initiation/power-cycle. Instead let's leave that to be done by
> >> a "manual" power cycle of the platform.
> >
> > As mentioned above, we wanted to let host vendors to decide.
> > If eMMC init is called during suspend/resume - no Power Cycle is required.
> 
> I think you missed my point here.
> 
> If the card identity changes and it's a NON_REMOVABLE host, we can't easily
> just perform a suspend/resume cycle to complete the FFU upgrade. The best
> and safest way is to power cycle the complete hw - always.
> 
> Kind regards
> Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* RE: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-08-21 12:17         ` Ulf Hansson
  2014-08-25 12:25           ` Alex Lemberg
@ 2014-09-01  9:26           ` Alex Lemberg
  2014-09-01 11:27             ` Ulf Hansson
  1 sibling, 1 reply; 17+ messages in thread
From: Alex Lemberg @ 2014-09-01  9:26 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

[snip]

> My conclusion from that is (correct me if I am wrong, since it may be vendor
> specific), that only minor modifications should be needed to the mmc ioctl. We
> need to add special treatment for when "cmd.opcode ==
> MMC_FFU_DOWNLOAD_OP". In such scenario the following sequence needs to
> be maintained.
> 
> 1. Claim host etc.
> 2. Set FFU mode.
> 3. Write some sectors to the FFU area, but in the same way as any other mmc
> ioctl WRITE is done.
> 4. Set normal mode.
> 5. Relase host etc.
> 
> That sequence then needs to be repeated to write the complete firmware,
> depending on buffer size. Between each and every piece of FFU data that is
> written, normal READ/WRITE operations can be served.
> Right?

We checked again the current IOCTL implementation, and we see issues
In implementing FFU as suggested above:

1. No support for multiple Write operations.
    For running Multiple I/O, user need to send CMD23 or CMD12.
    So structures "sbc" or "stop" of "mmc_blk_request" should be set accordingly.

2. There is a limit in chunk size in current implementation of IOCTL.
     From some reason we can't write more than 64KB on our platform,
     Although, the MMC_IOC_MAX_BYTES is defined to be 128KB

Based on this, we think that using existing IOCTL implementation is not 
a good solution for FFU.


Thanks,
Alex




^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-09-01  9:26           ` Alex Lemberg
@ 2014-09-01 11:27             ` Ulf Hansson
  2014-09-04  1:06               ` Hsin-Hsiang Tseng
  0 siblings, 1 reply; 17+ messages in thread
From: Ulf Hansson @ 2014-09-01 11:27 UTC (permalink / raw)
  To: Alex Lemberg
  Cc: linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

On 1 September 2014 11:26, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> [snip]
>
>> My conclusion from that is (correct me if I am wrong, since it may be vendor
>> specific), that only minor modifications should be needed to the mmc ioctl. We
>> need to add special treatment for when "cmd.opcode ==
>> MMC_FFU_DOWNLOAD_OP". In such scenario the following sequence needs to
>> be maintained.
>>
>> 1. Claim host etc.
>> 2. Set FFU mode.
>> 3. Write some sectors to the FFU area, but in the same way as any other mmc
>> ioctl WRITE is done.
>> 4. Set normal mode.
>> 5. Relase host etc.
>>
>> That sequence then needs to be repeated to write the complete firmware,
>> depending on buffer size. Between each and every piece of FFU data that is
>> written, normal READ/WRITE operations can be served.
>> Right?
>
> We checked again the current IOCTL implementation, and we see issues
> In implementing FFU as suggested above:
>
> 1. No support for multiple Write operations.
>     For running Multiple I/O, user need to send CMD23 or CMD12.
>     So structures "sbc" or "stop" of "mmc_blk_request" should be set accordingly.

You are right regarding multiple I/O!

I suppose using single block write option (CMD24) still can be used
for FFU, right?

Yes, we would suffer from unoptimized performance, but on the other
hand - how much data could we typically be expected to write? Do you
have some rough numbers that you can share?

>
> 2. There is a limit in chunk size in current implementation of IOCTL.
>      From some reason we can't write more than 64KB on our platform,
>      Although, the MMC_IOC_MAX_BYTES is defined to be 128KB

That is likely because the mmc host driver may limit the size, due to
it's controller HW.

>
> Based on this, we think that using existing IOCTL implementation is not
> a good solution for FFU.

Okay, let's for now continue with your current approach and try to
address those concerns I have raised while reviewing the code. I will
happily review new versions of the patch(es).

Kind regards
Uffe

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0
  2014-09-01 11:27             ` Ulf Hansson
@ 2014-09-04  1:06               ` Hsin-Hsiang Tseng
  0 siblings, 0 replies; 17+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-09-04  1:06 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Alex Lemberg, linux-mmc, cjb, Grant Grundler, Avi Shchislowski,
	Gwendal Grignou (gwendal@chromium.org)

Hi, Ulf

the data we expected to write in my case is about 288KB.
BTW, Alex's FFU flow already implemented on my platform which using
exynos SoC without problem.

Thanks,
Hsinhsiang

2014-09-01 19:27 GMT+08:00 Ulf Hansson <ulf.hansson@linaro.org>:
> On 1 September 2014 11:26, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
>> [snip]
>>
>>> My conclusion from that is (correct me if I am wrong, since it may be vendor
>>> specific), that only minor modifications should be needed to the mmc ioctl. We
>>> need to add special treatment for when "cmd.opcode ==
>>> MMC_FFU_DOWNLOAD_OP". In such scenario the following sequence needs to
>>> be maintained.
>>>
>>> 1. Claim host etc.
>>> 2. Set FFU mode.
>>> 3. Write some sectors to the FFU area, but in the same way as any other mmc
>>> ioctl WRITE is done.
>>> 4. Set normal mode.
>>> 5. Relase host etc.
>>>
>>> That sequence then needs to be repeated to write the complete firmware,
>>> depending on buffer size. Between each and every piece of FFU data that is
>>> written, normal READ/WRITE operations can be served.
>>> Right?
>>
>> We checked again the current IOCTL implementation, and we see issues
>> In implementing FFU as suggested above:
>>
>> 1. No support for multiple Write operations.
>>     For running Multiple I/O, user need to send CMD23 or CMD12.
>>     So structures "sbc" or "stop" of "mmc_blk_request" should be set accordingly.
>
> You are right regarding multiple I/O!
>
> I suppose using single block write option (CMD24) still can be used
> for FFU, right?
>
> Yes, we would suffer from unoptimized performance, but on the other
> hand - how much data could we typically be expected to write? Do you
> have some rough numbers that you can share?
>
>>
>> 2. There is a limit in chunk size in current implementation of IOCTL.
>>      From some reason we can't write more than 64KB on our platform,
>>      Although, the MMC_IOC_MAX_BYTES is defined to be 128KB
>
> That is likely because the mmc host driver may limit the size, due to
> it's controller HW.
>
>>
>> Based on this, we think that using existing IOCTL implementation is not
>> a good solution for FFU.
>
> Okay, let's for now continue with your current approach and try to
> address those concerns I have raised while reviewing the code. I will
> happily review new versions of the patch(es).
>
> Kind regards
> Uffe
> --
> 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

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2014-09-04  1:06 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-07-16 15:47 [RFC PATCH 1/1 v8]mmc: Support-FFU-for-eMMC-v5.0 Avi Shchislowski
2014-07-21 18:01 ` Gwendal Grignou
2014-07-30  0:14   ` Gwendal Grignou
2014-07-30  0:31     ` Gwendal Grignou
2014-07-30 14:08       ` Avi Shchislowski
2014-08-01  2:26         ` Hsin-Hsiang Tseng
2014-08-01  8:08           ` Hsin-Hsiang Tseng
2014-08-07 10:09             ` Avi Shchislowski
2014-08-13 12:46 ` Ulf Hansson
2014-08-14 12:37   ` Alex Lemberg
2014-08-18 10:56     ` Ulf Hansson
2014-08-21  9:13       ` Alex Lemberg
2014-08-21 12:17         ` Ulf Hansson
2014-08-25 12:25           ` Alex Lemberg
2014-09-01  9:26           ` Alex Lemberg
2014-09-01 11:27             ` Ulf Hansson
2014-09-04  1:06               ` Hsin-Hsiang Tseng

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.