All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
@ 2014-05-19  8:01 Avi Shchislowski
  2014-06-23 17:14 ` Grant Grundler
  0 siblings, 1 reply; 20+ messages in thread
From: Avi Shchislowski @ 2014-05-19  8:01 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>

---
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..8311200 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>
 
@@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
 
 	mmc_get_card(card);
 
+	if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
+		err = mmc_ffu_download(card, &cmd , idata->buf,
+			idata->buf_bytes);
+		goto cmd_rel_host;
+	}
+
+	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..7d254fd
--- /dev/null
+++ b/drivers/mmc/card/ffu.c
@@ -0,0 +1,595 @@
+/*
+ * *  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, int write) {
+	BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
+
+	if (blocks > 1) {
+		mrq->cmd->opcode = write ?
+			MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
+	} else {
+		mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
+			MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+	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, int write) {
+	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,
+		write);
+	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,
+	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, 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, 1);
+		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, struct mmc_command *cmd,
+	u8 *data, int buf_bytes)
+{
+	u8 ext_csd[CARD_BLOCK_SIZE];
+	int err;
+	int ret;
+	u8 *buf = NULL;
+	const struct firmware *fw;
+
+	/* 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 if card is eMMC 5.0 or higher */
+	if (card->ext_csd.rev < 7)
+		return -EINVAL;
+
+	/* Check if FFU is supported */
+	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
+		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
+		err = -EINVAL;
+		pr_err("FFU: %s: error %d FFU is not supported\n",
+			mmc_hostname(card->host), err);
+		goto exit;
+	}
+
+	/* setup FW data buffer */
+	err = request_firmware(&fw, data, &card->dev);
+	if (err) {
+		pr_err("Firmware request failed %d\n", err);
+		goto exit_normal;
+	}
+
+	buf = kmalloc(fw->size, GFP_KERNEL);
+	if (buf == NULL) {
+		pr_err("Allocating memory for firmware failed!\n");
+		goto exit_normal;
+	}
+
+	if ((fw->size % CARD_BLOCK_SIZE)) {
+		pr_warn("FFU: %s: Warning %zd firmware data is not aligned!!!\n",
+			mmc_hostname(card->host), fw->size);
+	}
+
+	memcpy(buf, fw->data, fw->size);
+
+	/* 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_normal;
+	}
+
+	/* set CMD ARG */
+	cmd->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, buf, cmd->arg, (int)fw->size);
+
+exit_normal:
+	release_firmware(fw);
+	kfree(buf);
+
+	/* 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;
+exit:
+	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;
+
+	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 if FFU is supported */
+	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
+		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
+		err = -EINVAL;
+		pr_err("FFU: %s: error %d FFU is not supported\n",
+			mmc_hostname(card->host), err);
+		goto exit;
+	}
+
+	/* 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: error %d FFU install:\n",
+				mmc_hostname(card->host), 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);
+			goto exit;
+		}
+
+		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);
+			goto exit;
+		}
+	}
+
+	/* 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);
+		goto exit;
+	}
+
+	/* 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);
+		err = -EINVAL;
+		goto exit;
+	}
+
+exit:
+	return err;
+}
+EXPORT_SYMBOL(mmc_ffu_install);
+
diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..be70880
--- /dev/null
+++ b/include/linux/mmc/ffu.h
@@ -0,0 +1,63 @@
+/*
+ *
+ *  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_ENABLE 0x0
+#define MMC_FFU_CONFIG 0x1
+#define MMC_FFU_SUPPORTED_MODES 0x1
+#define MMC_FFU_FEATURES 0x1
+
+#define FFU_ENABLED(ffu_enable)	(ffu_enable & MMC_FFU_CONFIG)
+#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
+	(ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
+FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
+FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
+
+int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
+	u8 *data, int buf_bytes);
+int mmc_ffu_install(struct mmc_card *card); #else static inline int 
+mmc_ffu_download(struct mmc_card *card,
+	struct mmc_command *cmd, u8 *data, int buf_bytes) {
+	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..bf29e52 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 */
+#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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-05-19  8:01 [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0 Avi Shchislowski
@ 2014-06-23 17:14 ` Grant Grundler
       [not found]   ` <CAC1bqKg=qse6MkgYD3bJDcBg0f7eis3dZZ=e3SM7bxE2A-XKPA@mail.gmail.com>
  2014-06-30 23:35   ` Grant Grundler
  0 siblings, 2 replies; 20+ messages in thread
From: Grant Grundler @ 2014-06-23 17:14 UTC (permalink / raw)
  To: cjb
  Cc: linux-mmc, Grant Grundler, Alex Lemberg, Avi Shchislowski, Ulf Hansson

Chris, Ulf,
Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?

While I can't directly test this patch due to limitations of which HW
ChromeOS supports, I believe it's a big step in the right direction.

It would also make life easier for other vendors to add FFU support
once this "base support" patch has landed (for nboth eMMC 4.5 and 5.0
parts).

thanks,
grant


On Mon, May 19, 2014 at 1:01 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>
>
> ---
> 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..8311200 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>
>
> @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>
>         mmc_get_card(card);
>
> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> +               err = mmc_ffu_download(card, &cmd , idata->buf,
> +                       idata->buf_bytes);
> +               goto cmd_rel_host;
> +       }
> +
> +       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..7d254fd
> --- /dev/null
> +++ b/drivers/mmc/card/ffu.c
> @@ -0,0 +1,595 @@
> +/*
> + * *  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, int write) {
> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> +
> +       if (blocks > 1) {
> +               mrq->cmd->opcode = write ?
> +                       MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
> +       } else {
> +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
> +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
> +       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, int write) {
> +       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,
> +               write);
> +       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,
> +       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, 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, 1);
> +               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, struct mmc_command *cmd,
> +       u8 *data, int buf_bytes)
> +{
> +       u8 ext_csd[CARD_BLOCK_SIZE];
> +       int err;
> +       int ret;
> +       u8 *buf = NULL;
> +       const struct firmware *fw;
> +
> +       /* 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 if card is eMMC 5.0 or higher */
> +       if (card->ext_csd.rev < 7)
> +               return -EINVAL;
> +
> +       /* Check if FFU is supported */
> +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
> +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> +               err = -EINVAL;
> +               pr_err("FFU: %s: error %d FFU is not supported\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* setup FW data buffer */
> +       err = request_firmware(&fw, data, &card->dev);
> +       if (err) {
> +               pr_err("Firmware request failed %d\n", err);
> +               goto exit_normal;
> +       }
> +
> +       buf = kmalloc(fw->size, GFP_KERNEL);
> +       if (buf == NULL) {
> +               pr_err("Allocating memory for firmware failed!\n");
> +               goto exit_normal;
> +       }
> +
> +       if ((fw->size % CARD_BLOCK_SIZE)) {
> +               pr_warn("FFU: %s: Warning %zd firmware data is not aligned!!!\n",
> +                       mmc_hostname(card->host), fw->size);
> +       }
> +
> +       memcpy(buf, fw->data, fw->size);
> +
> +       /* 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_normal;
> +       }
> +
> +       /* set CMD ARG */
> +       cmd->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, buf, cmd->arg, (int)fw->size);
> +
> +exit_normal:
> +       release_firmware(fw);
> +       kfree(buf);
> +
> +       /* 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;
> +exit:
> +       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;
> +
> +       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 if FFU is supported */
> +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
> +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> +               err = -EINVAL;
> +               pr_err("FFU: %s: error %d FFU is not supported\n",
> +                       mmc_hostname(card->host), err);
> +               goto exit;
> +       }
> +
> +       /* 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: error %d FFU install:\n",
> +                               mmc_hostname(card->host), 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);
> +                       goto exit;
> +               }
> +
> +               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);
> +                       goto exit;
> +               }
> +       }
> +
> +       /* 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);
> +               goto exit;
> +       }
> +
> +       /* 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);
> +               err = -EINVAL;
> +               goto exit;
> +       }
> +
> +exit:
> +       return err;
> +}
> +EXPORT_SYMBOL(mmc_ffu_install);
> +
> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..be70880
> --- /dev/null
> +++ b/include/linux/mmc/ffu.h
> @@ -0,0 +1,63 @@
> +/*
> + *
> + *  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_ENABLE 0x0
> +#define MMC_FFU_CONFIG 0x1
> +#define MMC_FFU_SUPPORTED_MODES 0x1
> +#define MMC_FFU_FEATURES 0x1
> +
> +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
> +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
> +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
> +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
> +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
> +
> +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
> +       u8 *data, int buf_bytes);
> +int mmc_ffu_install(struct mmc_card *card); #else static inline int
> +mmc_ffu_download(struct mmc_card *card,
> +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
> +       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..bf29e52 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 */
> +#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] 20+ messages in thread

* Fwd: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
       [not found]     ` <CAC1bqKh59OTYOPNQqiwpr1c=P+KwCFDf53o_YZWqZ8Gs2VaCfA@mail.gmail.com>
@ 2014-06-24  1:48       ` Hsin-Hsiang Tseng
  0 siblings, 0 replies; 20+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-06-24  1:48 UTC (permalink / raw)
  To: linux-mmc

Hi, Grant,

I am interested in this topic too.

I have some issues want to discuss here. Should we check the firmware
bin file before we execute FFU? (for example, execute checksum to
verify firmware bin file)  Should eMMC controller vendors cover this
issue?

Another issue is about interrupt, because FFU is trigger by ioctl
therefore FFU will be execute on OS (like Android) environment. Should
OS (host) guarantee the FFU not to be interruptable or eMMC (device)
need to handle interrupt when execute FFU?

thanks,
hsinhsiang


> Chris, Ulf,
> Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?
>
> While I can't directly test this patch due to limitations of which HW
> ChromeOS supports, I believe it's a big step in the right direction.
>
> It would also make life easier for other vendors to add FFU support
> once this "base support" patch has landed (for nboth eMMC 4.5 and 5.0
> parts).
>
> thanks,
> grant
>
>
> On Mon, May 19, 2014 at 1:01 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>
> >
> > ---
> > 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..8311200 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>
> >
> > @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
> >
> >         mmc_get_card(card);
> >
> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> > +               err = mmc_ffu_download(card, &cmd , idata->buf,
> > +                       idata->buf_bytes);
> > +               goto cmd_rel_host;
> > +       }
> > +
> > +       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..7d254fd
> > --- /dev/null
> > +++ b/drivers/mmc/card/ffu.c
> > @@ -0,0 +1,595 @@
> > +/*
> > + * *  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, int write) {
> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> > +
> > +       if (blocks > 1) {
> > +               mrq->cmd->opcode = write ?
> > +                       MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
> > +       } else {
> > +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
> > +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
> > +       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, int write) {
> > +       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,
> > +               write);
> > +       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,
> > +       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, 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, 1);
> > +               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, struct mmc_command *cmd,
> > +       u8 *data, int buf_bytes)
> > +{
> > +       u8 ext_csd[CARD_BLOCK_SIZE];
> > +       int err;
> > +       int ret;
> > +       u8 *buf = NULL;
> > +       const struct firmware *fw;
> > +
> > +       /* 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 if card is eMMC 5.0 or higher */
> > +       if (card->ext_csd.rev < 7)
> > +               return -EINVAL;
> > +
> > +       /* Check if FFU is supported */
> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> > +               err = -EINVAL;
> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
> > +                       mmc_hostname(card->host), err);
> > +               goto exit;
> > +       }
> > +
> > +       /* setup FW data buffer */
> > +       err = request_firmware(&fw, data, &card->dev);
> > +       if (err) {
> > +               pr_err("Firmware request failed %d\n", err);
> > +               goto exit_normal;
> > +       }
> > +
> > +       buf = kmalloc(fw->size, GFP_KERNEL);
> > +       if (buf == NULL) {
> > +               pr_err("Allocating memory for firmware failed!\n");
> > +               goto exit_normal;
> > +       }
> > +
> > +       if ((fw->size % CARD_BLOCK_SIZE)) {
> > +               pr_warn("FFU: %s: Warning %zd firmware data is not aligned!!!\n",
> > +                       mmc_hostname(card->host), fw->size);
> > +       }
> > +
> > +       memcpy(buf, fw->data, fw->size);
> > +
> > +       /* 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_normal;
> > +       }
> > +
> > +       /* set CMD ARG */
> > +       cmd->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, buf, cmd->arg, (int)fw->size);
> > +
> > +exit_normal:
> > +       release_firmware(fw);
> > +       kfree(buf);
> > +
> > +       /* 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;
> > +exit:
> > +       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;
> > +
> > +       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 if FFU is supported */
> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> > +               err = -EINVAL;
> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
> > +                       mmc_hostname(card->host), err);
> > +               goto exit;
> > +       }
> > +
> > +       /* 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: error %d FFU install:\n",
> > +                               mmc_hostname(card->host), 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);
> > +                       goto exit;
> > +               }
> > +
> > +               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);
> > +                       goto exit;
> > +               }
> > +       }
> > +
> > +       /* 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);
> > +               goto exit;
> > +       }
> > +
> > +       /* 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);
> > +               err = -EINVAL;
> > +               goto exit;
> > +       }
> > +
> > +exit:
> > +       return err;
> > +}
> > +EXPORT_SYMBOL(mmc_ffu_install);
> > +
> > diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..be70880
> > --- /dev/null
> > +++ b/include/linux/mmc/ffu.h
> > @@ -0,0 +1,63 @@
> > +/*
> > + *
> > + *  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_ENABLE 0x0
> > +#define MMC_FFU_CONFIG 0x1
> > +#define MMC_FFU_SUPPORTED_MODES 0x1
> > +#define MMC_FFU_FEATURES 0x1
> > +
> > +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
> > +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
> > +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
> > +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
> > +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
> > +
> > +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
> > +       u8 *data, int buf_bytes);
> > +int mmc_ffu_install(struct mmc_card *card); #else static inline int
> > +mmc_ffu_download(struct mmc_card *card,
> > +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
> > +       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..bf29e52 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 */
> > +#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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
       [not found]   ` <CAC1bqKg=qse6MkgYD3bJDcBg0f7eis3dZZ=e3SM7bxE2A-XKPA@mail.gmail.com>
       [not found]     ` <CAC1bqKh59OTYOPNQqiwpr1c=P+KwCFDf53o_YZWqZ8Gs2VaCfA@mail.gmail.com>
@ 2014-06-27 18:33     ` Grant Grundler
  2014-06-28 13:45       ` Hsin-Hsiang Tseng
  1 sibling, 1 reply; 20+ messages in thread
From: Grant Grundler @ 2014-06-27 18:33 UTC (permalink / raw)
  To: Hsin-Hsiang Tseng
  Cc: Grant Grundler, cjb, linux-mmc, Alex Lemberg, Avi Shchislowski,
	Ulf Hansson

On Mon, Jun 23, 2014 at 6:17 PM, Hsin-Hsiang Tseng
<hsinhsiangtseng@gmail.com> wrote:
> Hi, Grant, I am interested in this topic too.
>
> I have some issues want to discuss here. Should we check the firmware bin
> file before we execute FFU? (for example, execute checksum to verify
> firmware bin file)  Should eMMC controller vendors cover this issue?

We _could_.  But I don't see a need for it now.

I expect the existing firmware to check that the new firmware is "valid".
(e.g. much stronger signatures). I've been suggesting this to HW
vendors for the past year.

> Another issue is about interrupt, because FFU is trigger by ioctl therefore
> FFU will be execute on OS (like Android) environment. Should OS (host)
> guarantee the FFU not to be interruptable or eMMC (device) need to handle
> interrupt when execute FFU?

I'm not sure I understand this question. My understand of the current
patch is the FFU upload is "atomic" (no other commands will get
interleaved) and the FFU "enable" step is also atomic.

If I misunderstood, SanDisk should be able to answer this question.

cheers
grant

>
> thanks,
> hsinhsiang
>
>
> 2014-06-24 1:14 GMT+08:00 Grant Grundler <grundler@chromium.org>:
>
>> Chris, Ulf,
>> Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?
>>
>> While I can't directly test this patch due to limitations of which HW
>> ChromeOS supports, I believe it's a big step in the right direction.
>>
>> It would also make life easier for other vendors to add FFU support
>> once this "base support" patch has landed (for nboth eMMC 4.5 and 5.0
>> parts).
>>
>> thanks,
>> grant
>>
>>
>> On Mon, May 19, 2014 at 1:01 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>
>> >
>> > ---
>> > 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..8311200 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>
>> >
>> > @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device
>> > *bdev,
>> >
>> >         mmc_get_card(card);
>> >
>> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>> > +               err = mmc_ffu_download(card, &cmd , idata->buf,
>> > +                       idata->buf_bytes);
>> > +               goto cmd_rel_host;
>> > +       }
>> > +
>> > +       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..7d254fd
>> > --- /dev/null
>> > +++ b/drivers/mmc/card/ffu.c
>> > @@ -0,0 +1,595 @@
>> > +/*
>> > + * *  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, int write) {
>> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>> > +
>> > +       if (blocks > 1) {
>> > +               mrq->cmd->opcode = write ?
>> > +                       MMC_WRITE_MULTIPLE_BLOCK :
>> > MMC_READ_MULTIPLE_BLOCK;
>> > +       } else {
>> > +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
>> > +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
>> > +       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, int write) {
>> > +       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,
>> > +               write);
>> > +       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,
>> > +       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, 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, 1);
>> > +               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, struct mmc_command *cmd,
>> > +       u8 *data, int buf_bytes)
>> > +{
>> > +       u8 ext_csd[CARD_BLOCK_SIZE];
>> > +       int err;
>> > +       int ret;
>> > +       u8 *buf = NULL;
>> > +       const struct firmware *fw;
>> > +
>> > +       /* 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 if card is eMMC 5.0 or higher */
>> > +       if (card->ext_csd.rev < 7)
>> > +               return -EINVAL;
>> > +
>> > +       /* Check if FFU is supported */
>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> > +               err = -EINVAL;
>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>> > +                       mmc_hostname(card->host), err);
>> > +               goto exit;
>> > +       }
>> > +
>> > +       /* setup FW data buffer */
>> > +       err = request_firmware(&fw, data, &card->dev);
>> > +       if (err) {
>> > +               pr_err("Firmware request failed %d\n", err);
>> > +               goto exit_normal;
>> > +       }
>> > +
>> > +       buf = kmalloc(fw->size, GFP_KERNEL);
>> > +       if (buf == NULL) {
>> > +               pr_err("Allocating memory for firmware failed!\n");
>> > +               goto exit_normal;
>> > +       }
>> > +
>> > +       if ((fw->size % CARD_BLOCK_SIZE)) {
>> > +               pr_warn("FFU: %s: Warning %zd firmware data is not
>> > aligned!!!\n",
>> > +                       mmc_hostname(card->host), fw->size);
>> > +       }
>> > +
>> > +       memcpy(buf, fw->data, fw->size);
>> > +
>> > +       /* 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_normal;
>> > +       }
>> > +
>> > +       /* set CMD ARG */
>> > +       cmd->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, buf, cmd->arg, (int)fw->size);
>> > +
>> > +exit_normal:
>> > +       release_firmware(fw);
>> > +       kfree(buf);
>> > +
>> > +       /* 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;
>> > +exit:
>> > +       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;
>> > +
>> > +       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 if FFU is supported */
>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> > +               err = -EINVAL;
>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>> > +                       mmc_hostname(card->host), err);
>> > +               goto exit;
>> > +       }
>> > +
>> > +       /* 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: error %d FFU install:\n",
>> > +                               mmc_hostname(card->host), 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);
>> > +                       goto exit;
>> > +               }
>> > +
>> > +               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);
>> > +                       goto exit;
>> > +               }
>> > +       }
>> > +
>> > +       /* 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);
>> > +               goto exit;
>> > +       }
>> > +
>> > +       /* 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);
>> > +               err = -EINVAL;
>> > +               goto exit;
>> > +       }
>> > +
>> > +exit:
>> > +       return err;
>> > +}
>> > +EXPORT_SYMBOL(mmc_ffu_install);
>> > +
>> > diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file
>> > mode 100644 index 0000000..be70880
>> > --- /dev/null
>> > +++ b/include/linux/mmc/ffu.h
>> > @@ -0,0 +1,63 @@
>> > +/*
>> > + *
>> > + *  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_ENABLE 0x0
>> > +#define MMC_FFU_CONFIG 0x1
>> > +#define MMC_FFU_SUPPORTED_MODES 0x1
>> > +#define MMC_FFU_FEATURES 0x1
>> > +
>> > +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
>> > +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
>> > +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
>> > +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
>> > +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
>> > +
>> > +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
>> > +       u8 *data, int buf_bytes);
>> > +int mmc_ffu_install(struct mmc_card *card); #else static inline int
>> > +mmc_ffu_download(struct mmc_card *card,
>> > +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
>> > +       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..bf29e52 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 */
>> > +#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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-06-27 18:33     ` Grant Grundler
@ 2014-06-28 13:45       ` Hsin-Hsiang Tseng
  2014-06-29  8:46         ` Avi Shchislowski
  0 siblings, 1 reply; 20+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-06-28 13:45 UTC (permalink / raw)
  To: Grant Grundler
  Cc: cjb, linux-mmc, Alex Lemberg, Avi Shchislowski, Ulf Hansson

2014-06-28 2:33 GMT+08:00 Grant Grundler <grundler@chromium.org>:
> On Mon, Jun 23, 2014 at 6:17 PM, Hsin-Hsiang Tseng
> <hsinhsiangtseng@gmail.com> wrote:
>> Hi, Grant, I am interested in this topic too.
>>
>> I have some issues want to discuss here. Should we check the firmware bin
>> file before we execute FFU? (for example, execute checksum to verify
>> firmware bin file)  Should eMMC controller vendors cover this issue?
>
> We _could_.  But I don't see a need for it now.
>
> I expect the existing firmware to check that the new firmware is "valid".
> (e.g. much stronger signatures). I've been suggesting this to HW
> vendors for the past year.
>
>> Another issue is about interrupt, because FFU is trigger by ioctl therefore
>> FFU will be execute on OS (like Android) environment. Should OS (host)
>> guarantee the FFU not to be interruptable or eMMC (device) need to handle
>> interrupt when execute FFU?
>
> I'm not sure I understand this question. My understand of the current
> patch is the FFU upload is "atomic" (no other commands will get
> interleaved) and the FFU "enable" step is also atomic.
>
Could you please tell me where could I find FFU upload is "atomic"?
This patch uses mmc_ffu_write to transfer data into eMMC, but i didn't
know how it guarantees there will not have other requests between this
task. For example, One thread is doing a eMMC burn-in test. Another
thread is doing FFU upload step.

By the way, Someone told me that eMMC will handle the interrupt during
FFU process.
If every HW vendors support this, hosts will no need to worry this issue.
But i think the more discuss for a flow to be robust, the less failure
will occur.


Thanks,
Hsinhsiang

> If I misunderstood, SanDisk should be able to answer this question.
>
> cheers
> grant
>
>>
>> thanks,
>> hsinhsiang
>>
>>
>> 2014-06-24 1:14 GMT+08:00 Grant Grundler <grundler@chromium.org>:
>>
>>> Chris, Ulf,
>>> Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?
>>>
>>> While I can't directly test this patch due to limitations of which HW
>>> ChromeOS supports, I believe it's a big step in the right direction.
>>>
>>> It would also make life easier for other vendors to add FFU support
>>> once this "base support" patch has landed (for nboth eMMC 4.5 and 5.0
>>> parts).
>>>
>>> thanks,
>>> grant
>>>
>>>
>>> On Mon, May 19, 2014 at 1:01 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>
>>> >
>>> > ---
>>> > 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..8311200 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>
>>> >
>>> > @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device
>>> > *bdev,
>>> >
>>> >         mmc_get_card(card);
>>> >
>>> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>> > +               err = mmc_ffu_download(card, &cmd , idata->buf,
>>> > +                       idata->buf_bytes);
>>> > +               goto cmd_rel_host;
>>> > +       }
>>> > +
>>> > +       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..7d254fd
>>> > --- /dev/null
>>> > +++ b/drivers/mmc/card/ffu.c
>>> > @@ -0,0 +1,595 @@
>>> > +/*
>>> > + * *  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, int write) {
>>> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>> > +
>>> > +       if (blocks > 1) {
>>> > +               mrq->cmd->opcode = write ?
>>> > +                       MMC_WRITE_MULTIPLE_BLOCK :
>>> > MMC_READ_MULTIPLE_BLOCK;
>>> > +       } else {
>>> > +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
>>> > +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
>>> > +       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, int write) {
>>> > +       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,
>>> > +               write);
>>> > +       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,
>>> > +       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, 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, 1);
>>> > +               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, struct mmc_command *cmd,
>>> > +       u8 *data, int buf_bytes)
>>> > +{
>>> > +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> > +       int err;
>>> > +       int ret;
>>> > +       u8 *buf = NULL;
>>> > +       const struct firmware *fw;
>>> > +
>>> > +       /* 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 if card is eMMC 5.0 or higher */
>>> > +       if (card->ext_csd.rev < 7)
>>> > +               return -EINVAL;
>>> > +
>>> > +       /* Check if FFU is supported */
>>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>>> > +               err = -EINVAL;
>>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> > +                       mmc_hostname(card->host), err);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* setup FW data buffer */
>>> > +       err = request_firmware(&fw, data, &card->dev);
>>> > +       if (err) {
>>> > +               pr_err("Firmware request failed %d\n", err);
>>> > +               goto exit_normal;
>>> > +       }
>>> > +
>>> > +       buf = kmalloc(fw->size, GFP_KERNEL);
>>> > +       if (buf == NULL) {
>>> > +               pr_err("Allocating memory for firmware failed!\n");
>>> > +               goto exit_normal;
>>> > +       }
>>> > +
>>> > +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>> > +               pr_warn("FFU: %s: Warning %zd firmware data is not
>>> > aligned!!!\n",
>>> > +                       mmc_hostname(card->host), fw->size);
>>> > +       }
>>> > +
>>> > +       memcpy(buf, fw->data, fw->size);
>>> > +
>>> > +       /* 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_normal;
>>> > +       }
>>> > +
>>> > +       /* set CMD ARG */
>>> > +       cmd->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, buf, cmd->arg, (int)fw->size);
>>> > +
>>> > +exit_normal:
>>> > +       release_firmware(fw);
>>> > +       kfree(buf);
>>> > +
>>> > +       /* 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;
>>> > +exit:
>>> > +       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;
>>> > +
>>> > +       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 if FFU is supported */
>>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>>> > +               err = -EINVAL;
>>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> > +                       mmc_hostname(card->host), err);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* 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: error %d FFU install:\n",
>>> > +                               mmc_hostname(card->host), 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);
>>> > +                       goto exit;
>>> > +               }
>>> > +
>>> > +               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);
>>> > +                       goto exit;
>>> > +               }
>>> > +       }
>>> > +
>>> > +       /* 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);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* 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);
>>> > +               err = -EINVAL;
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +exit:
>>> > +       return err;
>>> > +}
>>> > +EXPORT_SYMBOL(mmc_ffu_install);
>>> > +
>>> > diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file
>>> > mode 100644 index 0000000..be70880
>>> > --- /dev/null
>>> > +++ b/include/linux/mmc/ffu.h
>>> > @@ -0,0 +1,63 @@
>>> > +/*
>>> > + *
>>> > + *  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_ENABLE 0x0
>>> > +#define MMC_FFU_CONFIG 0x1
>>> > +#define MMC_FFU_SUPPORTED_MODES 0x1
>>> > +#define MMC_FFU_FEATURES 0x1
>>> > +
>>> > +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
>>> > +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
>>> > +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
>>> > +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
>>> > +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
>>> > +
>>> > +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
>>> > +       u8 *data, int buf_bytes);
>>> > +int mmc_ffu_install(struct mmc_card *card); #else static inline int
>>> > +mmc_ffu_download(struct mmc_card *card,
>>> > +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
>>> > +       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..bf29e52 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 */
>>> > +#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] 20+ messages in thread

* RE: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-06-28 13:45       ` Hsin-Hsiang Tseng
@ 2014-06-29  8:46         ` Avi Shchislowski
  0 siblings, 0 replies; 20+ messages in thread
From: Avi Shchislowski @ 2014-06-29  8:46 UTC (permalink / raw)
  To: Hsin-Hsiang Tseng, Grant Grundler
  Cc: cjb, linux-mmc, Alex Lemberg, Ulf Hansson

Hi Hsinhsiang

Before sending the data with mmc_ffu_write we are calling to the mmc_claim_host API in order to avoid any other writes to the device.

Let me know if you think otherwise
Thanks
Avi.

-----Original Message-----
From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com] 
Sent: Saturday, June 28, 2014 4:46 PM
To: Grant Grundler
Cc: cjb@laptop.org; linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Ulf Hansson
Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0

2014-06-28 2:33 GMT+08:00 Grant Grundler <grundler@chromium.org>:
> On Mon, Jun 23, 2014 at 6:17 PM, Hsin-Hsiang Tseng 
> <hsinhsiangtseng@gmail.com> wrote:
>> Hi, Grant, I am interested in this topic too.
>>
>> I have some issues want to discuss here. Should we check the firmware 
>> bin file before we execute FFU? (for example, execute checksum to 
>> verify firmware bin file)  Should eMMC controller vendors cover this issue?
>
> We _could_.  But I don't see a need for it now.
>
> I expect the existing firmware to check that the new firmware is "valid".
> (e.g. much stronger signatures). I've been suggesting this to HW 
> vendors for the past year.
>
>> Another issue is about interrupt, because FFU is trigger by ioctl 
>> therefore FFU will be execute on OS (like Android) environment. 
>> Should OS (host) guarantee the FFU not to be interruptable or eMMC 
>> (device) need to handle interrupt when execute FFU?
>
> I'm not sure I understand this question. My understand of the current 
> patch is the FFU upload is "atomic" (no other commands will get
> interleaved) and the FFU "enable" step is also atomic.
>
Could you please tell me where could I find FFU upload is "atomic"?
This patch uses mmc_ffu_write to transfer data into eMMC, but i didn't know how it guarantees there will not have other requests between this task. For example, One thread is doing a eMMC burn-in test. Another thread is doing FFU upload step.

By the way, Someone told me that eMMC will handle the interrupt during FFU process.
If every HW vendors support this, hosts will no need to worry this issue.
But i think the more discuss for a flow to be robust, the less failure will occur.


Thanks,
Hsinhsiang

> If I misunderstood, SanDisk should be able to answer this question.
>
> cheers
> grant
>
>>
>> thanks,
>> hsinhsiang
>>
>>
>> 2014-06-24 1:14 GMT+08:00 Grant Grundler <grundler@chromium.org>:
>>
>>> Chris, Ulf,
>>> Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?
>>>
>>> While I can't directly test this patch due to limitations of which 
>>> HW ChromeOS supports, I believe it's a big step in the right direction.
>>>
>>> It would also make life easier for other vendors to add FFU support 
>>> once this "base support" patch has landed (for nboth eMMC 4.5 and 
>>> 5.0 parts).
>>>
>>> thanks,
>>> grant
>>>
>>>
>>> On Mon, May 19, 2014 at 1:01 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
>>> > ash-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>
>>> >
>>> > ---
>>> > 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..8311200 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>
>>> >
>>> > @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct 
>>> > block_device *bdev,
>>> >
>>> >         mmc_get_card(card);
>>> >
>>> > +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>>> > +               err = mmc_ffu_download(card, &cmd , idata->buf,
>>> > +                       idata->buf_bytes);
>>> > +               goto cmd_rel_host;
>>> > +       }
>>> > +
>>> > +       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..7d254fd
>>> > --- /dev/null
>>> > +++ b/drivers/mmc/card/ffu.c
>>> > @@ -0,0 +1,595 @@
>>> > +/*
>>> > + * *  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, int write) {
>>> > +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>> > +
>>> > +       if (blocks > 1) {
>>> > +               mrq->cmd->opcode = write ?
>>> > +                       MMC_WRITE_MULTIPLE_BLOCK :
>>> > MMC_READ_MULTIPLE_BLOCK;
>>> > +       } else {
>>> > +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
>>> > +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
>>> > +       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, int write) {
>>> > +       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,
>>> > +               write);
>>> > +       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,
>>> > +       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, 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, 1);
>>> > +               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, struct mmc_command *cmd,
>>> > +       u8 *data, int buf_bytes)
>>> > +{
>>> > +       u8 ext_csd[CARD_BLOCK_SIZE];
>>> > +       int err;
>>> > +       int ret;
>>> > +       u8 *buf = NULL;
>>> > +       const struct firmware *fw;
>>> > +
>>> > +       /* 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 if card is eMMC 5.0 or higher */
>>> > +       if (card->ext_csd.rev < 7)
>>> > +               return -EINVAL;
>>> > +
>>> > +       /* Check if FFU is supported */
>>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>>> > +               err = -EINVAL;
>>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> > +                       mmc_hostname(card->host), err);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* setup FW data buffer */
>>> > +       err = request_firmware(&fw, data, &card->dev);
>>> > +       if (err) {
>>> > +               pr_err("Firmware request failed %d\n", err);
>>> > +               goto exit_normal;
>>> > +       }
>>> > +
>>> > +       buf = kmalloc(fw->size, GFP_KERNEL);
>>> > +       if (buf == NULL) {
>>> > +               pr_err("Allocating memory for firmware failed!\n");
>>> > +               goto exit_normal;
>>> > +       }
>>> > +
>>> > +       if ((fw->size % CARD_BLOCK_SIZE)) {
>>> > +               pr_warn("FFU: %s: Warning %zd firmware data is not
>>> > aligned!!!\n",
>>> > +                       mmc_hostname(card->host), fw->size);
>>> > +       }
>>> > +
>>> > +       memcpy(buf, fw->data, fw->size);
>>> > +
>>> > +       /* 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_normal;
>>> > +       }
>>> > +
>>> > +       /* set CMD ARG */
>>> > +       cmd->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, buf, cmd->arg, (int)fw->size);
>>> > +
>>> > +exit_normal:
>>> > +       release_firmware(fw);
>>> > +       kfree(buf);
>>> > +
>>> > +       /* 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;
>>> > +exit:
>>> > +       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;
>>> > +
>>> > +       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 if FFU is supported */
>>> > +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>>> > +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>>> > +               err = -EINVAL;
>>> > +               pr_err("FFU: %s: error %d FFU is not supported\n",
>>> > +                       mmc_hostname(card->host), err);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* 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: error %d FFU install:\n",
>>> > +                               mmc_hostname(card->host), 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);
>>> > +                       goto exit;
>>> > +               }
>>> > +
>>> > +               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);
>>> > +                       goto exit;
>>> > +               }
>>> > +       }
>>> > +
>>> > +       /* 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);
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +       /* 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);
>>> > +               err = -EINVAL;
>>> > +               goto exit;
>>> > +       }
>>> > +
>>> > +exit:
>>> > +       return err;
>>> > +}
>>> > +EXPORT_SYMBOL(mmc_ffu_install);
>>> > +
>>> > diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new 
>>> > file mode 100644 index 0000000..be70880
>>> > --- /dev/null
>>> > +++ b/include/linux/mmc/ffu.h
>>> > @@ -0,0 +1,63 @@
>>> > +/*
>>> > + *
>>> > + *  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_ENABLE 0x0
>>> > +#define MMC_FFU_CONFIG 0x1
>>> > +#define MMC_FFU_SUPPORTED_MODES 0x1 #define MMC_FFU_FEATURES 0x1
>>> > +
>>> > +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
>>> > +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
>>> > +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
>>> > +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
>>> > +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
>>> > +
>>> > +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
>>> > +       u8 *data, int buf_bytes);
>>> > +int mmc_ffu_install(struct mmc_card *card); #else static inline 
>>> > +int mmc_ffu_download(struct mmc_card *card,
>>> > +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
>>> > +       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..bf29e52 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 */
>>> > +#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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-06-23 17:14 ` Grant Grundler
       [not found]   ` <CAC1bqKg=qse6MkgYD3bJDcBg0f7eis3dZZ=e3SM7bxE2A-XKPA@mail.gmail.com>
@ 2014-06-30 23:35   ` Grant Grundler
  2014-07-01  0:34     ` Chris Ball
  1 sibling, 1 reply; 20+ messages in thread
From: Grant Grundler @ 2014-06-30 23:35 UTC (permalink / raw)
  To: cjb, Ulf Hansson
  Cc: linux-mmc, Alex Lemberg, Avi Shchislowski, Olof Johansson

ping? Another week is past.

I know you guys are busy with other stuff. But please at least publish
what you think the next steps for this patch are.

This is a critical patch for supporting eMMC products and adding
additional support for other vendors. I'd like to continue working
with "upstream" and the lack of response in this case is making this
unnecessarily difficult.

thanks,
grant

On Mon, Jun 23, 2014 at 10:14 AM, Grant Grundler <grundler@chromium.org> wrote:
> Chris, Ulf,
> Any reason the eMMC 5.0 FFU patch can't be integrated into linux-mmc tree?
>
> While I can't directly test this patch due to limitations of which HW
> ChromeOS supports, I believe it's a big step in the right direction.
>
> It would also make life easier for other vendors to add FFU support
> once this "base support" patch has landed (for nboth eMMC 4.5 and 5.0
> parts).
>
> thanks,
> grant
>
>
> On Mon, May 19, 2014 at 1:01 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>
>>
>> ---
>> 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..8311200 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>
>>
>> @@ -525,6 +526,17 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
>>
>>         mmc_get_card(card);
>>
>> +       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>> +               err = mmc_ffu_download(card, &cmd , idata->buf,
>> +                       idata->buf_bytes);
>> +               goto cmd_rel_host;
>> +       }
>> +
>> +       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..7d254fd
>> --- /dev/null
>> +++ b/drivers/mmc/card/ffu.c
>> @@ -0,0 +1,595 @@
>> +/*
>> + * *  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, int write) {
>> +       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>> +
>> +       if (blocks > 1) {
>> +               mrq->cmd->opcode = write ?
>> +                       MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
>> +       } else {
>> +               mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
>> +                       MMC_READ_SINGLE_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 = write ? MMC_DATA_WRITE : MMC_DATA_READ;
>> +       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, int write) {
>> +       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,
>> +               write);
>> +       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,
>> +       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, 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, 1);
>> +               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, struct mmc_command *cmd,
>> +       u8 *data, int buf_bytes)
>> +{
>> +       u8 ext_csd[CARD_BLOCK_SIZE];
>> +       int err;
>> +       int ret;
>> +       u8 *buf = NULL;
>> +       const struct firmware *fw;
>> +
>> +       /* 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 if card is eMMC 5.0 or higher */
>> +       if (card->ext_csd.rev < 7)
>> +               return -EINVAL;
>> +
>> +       /* Check if FFU is supported */
>> +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>> +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> +               err = -EINVAL;
>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>> +                       mmc_hostname(card->host), err);
>> +               goto exit;
>> +       }
>> +
>> +       /* setup FW data buffer */
>> +       err = request_firmware(&fw, data, &card->dev);
>> +       if (err) {
>> +               pr_err("Firmware request failed %d\n", err);
>> +               goto exit_normal;
>> +       }
>> +
>> +       buf = kmalloc(fw->size, GFP_KERNEL);
>> +       if (buf == NULL) {
>> +               pr_err("Allocating memory for firmware failed!\n");
>> +               goto exit_normal;
>> +       }
>> +
>> +       if ((fw->size % CARD_BLOCK_SIZE)) {
>> +               pr_warn("FFU: %s: Warning %zd firmware data is not aligned!!!\n",
>> +                       mmc_hostname(card->host), fw->size);
>> +       }
>> +
>> +       memcpy(buf, fw->data, fw->size);
>> +
>> +       /* 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_normal;
>> +       }
>> +
>> +       /* set CMD ARG */
>> +       cmd->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, buf, cmd->arg, (int)fw->size);
>> +
>> +exit_normal:
>> +       release_firmware(fw);
>> +       kfree(buf);
>> +
>> +       /* 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;
>> +exit:
>> +       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;
>> +
>> +       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 if FFU is supported */
>> +       if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
>> +               FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> +               err = -EINVAL;
>> +               pr_err("FFU: %s: error %d FFU is not supported\n",
>> +                       mmc_hostname(card->host), err);
>> +               goto exit;
>> +       }
>> +
>> +       /* 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: error %d FFU install:\n",
>> +                               mmc_hostname(card->host), 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);
>> +                       goto exit;
>> +               }
>> +
>> +               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);
>> +                       goto exit;
>> +               }
>> +       }
>> +
>> +       /* 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);
>> +               goto exit;
>> +       }
>> +
>> +       /* 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);
>> +               err = -EINVAL;
>> +               goto exit;
>> +       }
>> +
>> +exit:
>> +       return err;
>> +}
>> +EXPORT_SYMBOL(mmc_ffu_install);
>> +
>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h new file mode 100644 index 0000000..be70880
>> --- /dev/null
>> +++ b/include/linux/mmc/ffu.h
>> @@ -0,0 +1,63 @@
>> +/*
>> + *
>> + *  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_ENABLE 0x0
>> +#define MMC_FFU_CONFIG 0x1
>> +#define MMC_FFU_SUPPORTED_MODES 0x1
>> +#define MMC_FFU_FEATURES 0x1
>> +
>> +#define FFU_ENABLED(ffu_enable)        (ffu_enable & MMC_FFU_CONFIG)
>> +#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
>> +       (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES) #define
>> +FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) #define
>> +FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
>> +
>> +int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
>> +       u8 *data, int buf_bytes);
>> +int mmc_ffu_install(struct mmc_card *card); #else static inline int
>> +mmc_ffu_download(struct mmc_card *card,
>> +       struct mmc_command *cmd, u8 *data, int buf_bytes) {
>> +       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..bf29e52 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 */
>> +#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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-06-30 23:35   ` Grant Grundler
@ 2014-07-01  0:34     ` Chris Ball
  2014-07-01  6:29       ` Hsin-Hsiang Tseng
  2014-07-01 15:47       ` Grant Grundler
  0 siblings, 2 replies; 20+ messages in thread
From: Chris Ball @ 2014-07-01  0:34 UTC (permalink / raw)
  To: Grant Grundler
  Cc: cjb, Ulf Hansson, linux-mmc, Alex Lemberg, Avi Shchislowski,
	Olof Johansson

Hi Grant, sorry about the delay.

On Tue, Jul 01 2014, Grant Grundler wrote:
> ping? Another week is past.
>
> I know you guys are busy with other stuff. But please at least publish
> what you think the next steps for this patch are.
>
> This is a critical patch for supporting eMMC products and adding
> additional support for other vendors. I'd like to continue working
> with "upstream" and the lack of response in this case is making this
> unnecessarily difficult.

It's tough to merge a significant patch that you haven't tested and we
haven't tested.  We could do it, though -- I'd like to hear what Ulf
thinks.  Please could you give a quick explanation of why doing this
in kernel-space makes more sense than using your mmc-utils version?

Thanks,

- Chris.
-- 
Chris Ball   <http://printf.net/>

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

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01  0:34     ` Chris Ball
@ 2014-07-01  6:29       ` Hsin-Hsiang Tseng
  2014-07-01 11:39         ` Avi Shchislowski
  2014-07-01 15:47       ` Grant Grundler
  1 sibling, 1 reply; 20+ messages in thread
From: Hsin-Hsiang Tseng @ 2014-07-01  6:29 UTC (permalink / raw)
  To: Chris Ball
  Cc: Grant Grundler, cjb, Ulf Hansson, linux-mmc, Alex Lemberg,
	Avi Shchislowski, Olof Johansson

Hi, Chris,
I think there are two reasons why doing FFU in kernel-space makes more sense.

First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to
restart eMMC after FFU.

Second, if host want to vouch "atmoic" in FFU process, we need to call
mmc_claim_host API, and this API is the kernel-space code. I said "if"
is because eMMC vendors may take care this. So, host may not need to
worry about this.

Thanks,
Hsinhsiang

2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
> Hi Grant, sorry about the delay.
>
> On Tue, Jul 01 2014, Grant Grundler wrote:
>> ping? Another week is past.
>>
>> I know you guys are busy with other stuff. But please at least publish
>> what you think the next steps for this patch are.
>>
>> This is a critical patch for supporting eMMC products and adding
>> additional support for other vendors. I'd like to continue working
>> with "upstream" and the lack of response in this case is making this
>> unnecessarily difficult.
>
> It's tough to merge a significant patch that you haven't tested and we
> haven't tested.  We could do it, though -- I'd like to hear what Ulf
> thinks.  Please could you give a quick explanation of why doing this
> in kernel-space makes more sense than using your mmc-utils version?
>
> Thanks,
>
> - Chris.
> --
> Chris Ball   <http://printf.net/>
> --
> 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] 20+ messages in thread

* RE: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01  6:29       ` Hsin-Hsiang Tseng
@ 2014-07-01 11:39         ` Avi Shchislowski
  2014-07-02 20:35           ` Gwendal Grignou
  0 siblings, 1 reply; 20+ messages in thread
From: Avi Shchislowski @ 2014-07-01 11:39 UTC (permalink / raw)
  To: Hsin-Hsiang Tseng, Chris Ball
  Cc: Grant Grundler, cjb, Ulf Hansson, linux-mmc, Alex Lemberg,
	Olof Johansson

Hi Chris,

Re your concern that the patch was not tested – some of SanDisk’s OEM customers have implemented the patch, and they report that it is working for them.

Thanks
Avi

-----Original Message-----
From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com] 
Sent: Tuesday, July 01, 2014 9:29 AM
To: Chris Ball
Cc: Grant Grundler; cjb@laptop.org; Ulf Hansson; linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Olof Johansson
Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0

Hi, Chris,
I think there are two reasons why doing FFU in kernel-space makes more sense.

First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to restart eMMC after FFU.

Second, if host want to vouch "atmoic" in FFU process, we need to call mmc_claim_host API, and this API is the kernel-space code. I said "if"
is because eMMC vendors may take care this. So, host may not need to worry about this.

Thanks,
Hsinhsiang

2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
> Hi Grant, sorry about the delay.
>
> On Tue, Jul 01 2014, Grant Grundler wrote:
>> ping? Another week is past.
>>
>> I know you guys are busy with other stuff. But please at least 
>> publish what you think the next steps for this patch are.
>>
>> This is a critical patch for supporting eMMC products and adding 
>> additional support for other vendors. I'd like to continue working 
>> with "upstream" and the lack of response in this case is making this 
>> unnecessarily difficult.
>
> It's tough to merge a significant patch that you haven't tested and we 
> haven't tested.  We could do it, though -- I'd like to hear what Ulf 
> thinks.  Please could you give a quick explanation of why doing this 
> in kernel-space makes more sense than using your mmc-utils version?
>
> Thanks,
>
> - Chris.
> --
> Chris Ball   <http://printf.net/>
> --
> 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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01  0:34     ` Chris Ball
  2014-07-01  6:29       ` Hsin-Hsiang Tseng
@ 2014-07-01 15:47       ` Grant Grundler
  2014-07-01 17:07         ` Ulf Hansson
  1 sibling, 1 reply; 20+ messages in thread
From: Grant Grundler @ 2014-07-01 15:47 UTC (permalink / raw)
  To: Chris Ball
  Cc: Grant Grundler, cjb, Ulf Hansson, linux-mmc, Alex Lemberg,
	Avi Shchislowski, Olof Johansson

On Mon, Jun 30, 2014 at 5:34 PM, Chris Ball <chris@printf.net> wrote:
> Hi Grant, sorry about the delay.
>
> On Tue, Jul 01 2014, Grant Grundler wrote:
>> ping? Another week is past.
>>
>> I know you guys are busy with other stuff. But please at least publish
>> what you think the next steps for this patch are.
>>
>> This is a critical patch for supporting eMMC products and adding
>> additional support for other vendors. I'd like to continue working
>> with "upstream" and the lack of response in this case is making this
>> unnecessarily difficult.
>
> It's tough to merge a significant patch that you haven't tested and we
> haven't tested.

Hi Chris!
Thanks for the response.

You are correct that neither of us has tested the patch. But I know
SanDisk, Samsung, and Kingston (IIRC) have.  And in the case that FFU
doesn't work, contracts with these HW vendors require FFU works. So
they have financial incentives to make it work. I'm comfortable with
support that ChromeOS has for this patch in the long term.

Most of the magic (for FFU and general flash operation) is in the
Vendor Firmware. We depend on those vendors entirely for this to work
correctly already. This is not a new dependency and looks more like
collaboration when they supply the patch to support "industry
standard" features (like FFU).

>  We could do it, though -- I'd like to hear what Ulf thinks.

Sure - me too. :)

>  Please could you give a quick explanation of why doing this
> in kernel-space makes more sense than using your mmc-utils version?

Only the kernel can guarantee no other commands are sent to the device
during FFU. User space can attempt to hold this promise but the
constraints on when it can are pretty onerous. SanDisk is pushing this
patch entirely for this reason and I agree it's a better solution.

I don't know about extCSD[492] FFU_FEATURES bits that Hsin-Hsiang mentions.

cheers,
grant

>
> Thanks,
>
> - Chris.
> --
> Chris Ball   <http://printf.net/>

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

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01 15:47       ` Grant Grundler
@ 2014-07-01 17:07         ` Ulf Hansson
  2014-07-09 17:27           ` Grant Grundler
  0 siblings, 1 reply; 20+ messages in thread
From: Ulf Hansson @ 2014-07-01 17:07 UTC (permalink / raw)
  To: Grant Grundler
  Cc: Chris Ball, cjb, linux-mmc, Alex Lemberg, Avi Shchislowski,
	Olof Johansson

On 1 July 2014 17:47, Grant Grundler <grundler@chromium.org> wrote:
> On Mon, Jun 30, 2014 at 5:34 PM, Chris Ball <chris@printf.net> wrote:
>> Hi Grant, sorry about the delay.
>>
>> On Tue, Jul 01 2014, Grant Grundler wrote:
>>> ping? Another week is past.
>>>
>>> I know you guys are busy with other stuff. But please at least publish
>>> what you think the next steps for this patch are.
>>>
>>> This is a critical patch for supporting eMMC products and adding
>>> additional support for other vendors. I'd like to continue working
>>> with "upstream" and the lack of response in this case is making this
>>> unnecessarily difficult.
>>
>> It's tough to merge a significant patch that you haven't tested and we
>> haven't tested.
>
> Hi Chris!
> Thanks for the response.
>
> You are correct that neither of us has tested the patch. But I know
> SanDisk, Samsung, and Kingston (IIRC) have.  And in the case that FFU
> doesn't work, contracts with these HW vendors require FFU works. So
> they have financial incentives to make it work. I'm comfortable with
> support that ChromeOS has for this patch in the long term.
>
> Most of the magic (for FFU and general flash operation) is in the
> Vendor Firmware. We depend on those vendors entirely for this to work
> correctly already. This is not a new dependency and looks more like
> collaboration when they supply the patch to support "industry
> standard" features (like FFU).
>
>>  We could do it, though -- I'd like to hear what Ulf thinks.
>
> Sure - me too. :)

Hi Grant,

Sorry for lack of input!

Unfortunate I am travelling and will have to come back to this next week.

>
>>  Please could you give a quick explanation of why doing this
>> in kernel-space makes more sense than using your mmc-utils version?
>
> Only the kernel can guarantee no other commands are sent to the device
> during FFU. User space can attempt to hold this promise but the
> constraints on when it can are pretty onerous. SanDisk is pushing this
> patch entirely for this reason and I agree it's a better solution.

I am open to consider this patch. From my point of view, I need some
additional time to review the code.

Kind regards
Uffe

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

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01 11:39         ` Avi Shchislowski
@ 2014-07-02 20:35           ` Gwendal Grignou
  2014-07-03  0:15             ` Gwendal Grignou
  2014-07-03 12:32             ` Alex Lemberg
  0 siblings, 2 replies; 20+ messages in thread
From: Gwendal Grignou @ 2014-07-02 20:35 UTC (permalink / raw)
  To: Avi Shchislowski
  Cc: Hsin-Hsiang Tseng, Chris Ball, Grant Grundler, cjb, Ulf Hansson,
	linux-mmc, Alex Lemberg, Olof Johansson, Puthikorn Voravootivat

[resent in text mode]
Avi,
+ I am testing the patch on a system where the firmware image is on
the emmc itself. It fails most of the time. The problem is
mmc_ffu_download has been call with mmc_claim_host called, preventing
mmcqd from pulling the firmware from the disk. The dead lock situation
cause the machine to panic. It the file is in the cache, the firmware
upgrade goes further.

Looking at stacks:

mmc:
...
sleep_on_buffer+0xe/0x12
...
bh_submit_read+0x49/0x5b
...
_request_firmware+0x3b0/0x84e
request_firmware+0x16/0x18
mmc_ffu_download+0xc7/0x8d7

and  mmcqd/0:
schedule+0x69/0x6b
__mmc_claim_host+0xc6/0x19a
...
mmc_get_card+0x25/0x28
mmc_blk_issue_rq+0x41/0x403

+ in mmc_ffu_write(), don't you want to check that the eMMC has
received the payload by looking at
NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED ext_csd register?

+ did you test with eMMC that do not support MODE_OPERATION_CODES?
In mmc_ffu_restart, you call
mmc_power_save_host/mmc_power_restore_host. Can the reset be achieve
with mmc_hw_reset() which has the advantage to be more atomic?

Ulf, Chris,
+ One extra advantage of using request_firmware is if we can trust
udev (or whoever is serving the firmware uevent request), we are
guaranteed that we can trust the firmware as well. If we can send the
firmware payload via ioctl, we can not be sure the caller of that
ioctl is not an attacker that wants to put a bad firmware in the eMMC.

On Tue, Jul 1, 2014 at 4:39 AM, Avi Shchislowski
<Avi.Shchislowski@sandisk.com> wrote:
> Hi Chris,
>
> Re your concern that the patch was not tested – some of SanDisk’s OEM customers have implemented the patch, and they report that it is working for them.
>
> Thanks
> Avi
>
> -----Original Message-----
> From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com]
> Sent: Tuesday, July 01, 2014 9:29 AM
> To: Chris Ball
> Cc: Grant Grundler; cjb@laptop.org; Ulf Hansson; linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Olof Johansson
> Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
>
> Hi, Chris,
> I think there are two reasons why doing FFU in kernel-space makes more sense.
>
> First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to restart eMMC after FFU.
>
> Second, if host want to vouch "atmoic" in FFU process, we need to call mmc_claim_host API, and this API is the kernel-space code. I said "if"
> is because eMMC vendors may take care this. So, host may not need to worry about this.
>
> Thanks,
> Hsinhsiang
>
> 2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
>> Hi Grant, sorry about the delay.
>>
>> On Tue, Jul 01 2014, Grant Grundler wrote:
>>> ping? Another week is past.
>>>
>>> I know you guys are busy with other stuff. But please at least
>>> publish what you think the next steps for this patch are.
>>>
>>> This is a critical patch for supporting eMMC products and adding
>>> additional support for other vendors. I'd like to continue working
>>> with "upstream" and the lack of response in this case is making this
>>> unnecessarily difficult.
>>
>> It's tough to merge a significant patch that you haven't tested and we
>> haven't tested.  We could do it, though -- I'd like to hear what Ulf
>> thinks.  Please could you give a quick explanation of why doing this
>> in kernel-space makes more sense than using your mmc-utils version?
>>
>> Thanks,
>>
>> - Chris.
>> --
>> Chris Ball   <http://printf.net/>
>> --
>> 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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-02 20:35           ` Gwendal Grignou
@ 2014-07-03  0:15             ` Gwendal Grignou
  2014-07-16 15:54               ` Avi Shchislowski
  2014-07-03 12:32             ` Alex Lemberg
  1 sibling, 1 reply; 20+ messages in thread
From: Gwendal Grignou @ 2014-07-03  0:15 UTC (permalink / raw)
  To: Gwendal Grignou
  Cc: Avi Shchislowski, Hsin-Hsiang Tseng, Chris Ball, Grant Grundler,
	cjb, Ulf Hansson, linux-mmc, Alex Lemberg, Olof Johansson,
	Puthikorn Voravootivat

Also:
- you don't need to allocate a buffer buf in mmc_ffu_download, you can
directly call mmc_ffu_write with fw->data; you are memcopying the data
again in mmc_ffu_area_init().
- mmc_ffu_prepare_mrq() is a copy of mmc_test_prepare_mrq(), but in
our case, we don't need the write arg, it will be always 1. Is there a
way to use an existing function instead of recopying what is in
mmc_test.c?

Gwendal.


On Wed, Jul 2, 2014 at 1:35 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
> [resent in text mode]
> Avi,
> + I am testing the patch on a system where the firmware image is on
> the emmc itself. It fails most of the time. The problem is
> mmc_ffu_download has been call with mmc_claim_host called, preventing
> mmcqd from pulling the firmware from the disk. The dead lock situation
> cause the machine to panic. It the file is in the cache, the firmware
> upgrade goes further.
>
> Looking at stacks:
>
> mmc:
> ...
> sleep_on_buffer+0xe/0x12
> ...
> bh_submit_read+0x49/0x5b
> ...
> _request_firmware+0x3b0/0x84e
> request_firmware+0x16/0x18
> mmc_ffu_download+0xc7/0x8d7
>
> and  mmcqd/0:
> schedule+0x69/0x6b
> __mmc_claim_host+0xc6/0x19a
> ...
> mmc_get_card+0x25/0x28
> mmc_blk_issue_rq+0x41/0x403
>
> + in mmc_ffu_write(), don't you want to check that the eMMC has
> received the payload by looking at
> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED ext_csd register?
>
> + did you test with eMMC that do not support MODE_OPERATION_CODES?
> In mmc_ffu_restart, you call
> mmc_power_save_host/mmc_power_restore_host. Can the reset be achieve
> with mmc_hw_reset() which has the advantage to be more atomic?
>
> Ulf, Chris,
> + One extra advantage of using request_firmware is if we can trust
> udev (or whoever is serving the firmware uevent request), we are
> guaranteed that we can trust the firmware as well. If we can send the
> firmware payload via ioctl, we can not be sure the caller of that
> ioctl is not an attacker that wants to put a bad firmware in the eMMC.
>
> On Tue, Jul 1, 2014 at 4:39 AM, Avi Shchislowski
> <Avi.Shchislowski@sandisk.com> wrote:
>> Hi Chris,
>>
>> Re your concern that the patch was not tested – some of SanDisk’s OEM customers have implemented the patch, and they report that it is working for them.
>>
>> Thanks
>> Avi
>>
>> -----Original Message-----
>> From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com]
>> Sent: Tuesday, July 01, 2014 9:29 AM
>> To: Chris Ball
>> Cc: Grant Grundler; cjb@laptop.org; Ulf Hansson; linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Olof Johansson
>> Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
>>
>> Hi, Chris,
>> I think there are two reasons why doing FFU in kernel-space makes more sense.
>>
>> First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to restart eMMC after FFU.
>>
>> Second, if host want to vouch "atmoic" in FFU process, we need to call mmc_claim_host API, and this API is the kernel-space code. I said "if"
>> is because eMMC vendors may take care this. So, host may not need to worry about this.
>>
>> Thanks,
>> Hsinhsiang
>>
>> 2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
>>> Hi Grant, sorry about the delay.
>>>
>>> On Tue, Jul 01 2014, Grant Grundler wrote:
>>>> ping? Another week is past.
>>>>
>>>> I know you guys are busy with other stuff. But please at least
>>>> publish what you think the next steps for this patch are.
>>>>
>>>> This is a critical patch for supporting eMMC products and adding
>>>> additional support for other vendors. I'd like to continue working
>>>> with "upstream" and the lack of response in this case is making this
>>>> unnecessarily difficult.
>>>
>>> It's tough to merge a significant patch that you haven't tested and we
>>> haven't tested.  We could do it, though -- I'd like to hear what Ulf
>>> thinks.  Please could you give a quick explanation of why doing this
>>> in kernel-space makes more sense than using your mmc-utils version?
>>>
>>> Thanks,
>>>
>>> - Chris.
>>> --
>>> Chris Ball   <http://printf.net/>
>>> --
>>> 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] 20+ messages in thread

* RE: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-02 20:35           ` Gwendal Grignou
  2014-07-03  0:15             ` Gwendal Grignou
@ 2014-07-03 12:32             ` Alex Lemberg
  1 sibling, 0 replies; 20+ messages in thread
From: Alex Lemberg @ 2014-07-03 12:32 UTC (permalink / raw)
  To: Gwendal Grignou, Avi Shchislowski
  Cc: Hsin-Hsiang Tseng, Chris Ball, Grant Grundler, cjb, Ulf Hansson,
	linux-mmc, Olof Johansson, Puthikorn Voravootivat

Hi Gwendal,

> -----Original Message-----
> From: linux-mmc-owner@vger.kernel.org [mailto:linux-mmc-
> owner@vger.kernel.org] On Behalf Of Gwendal Grignou
> Sent: Wednesday, July 02, 2014 11:35 PM
> To: Avi Shchislowski
> Cc: Hsin-Hsiang Tseng; Chris Ball; Grant Grundler; cjb@laptop.org; Ulf
> Hansson; linux-mmc@vger.kernel.org; Alex Lemberg; Olof Johansson;
> Puthikorn Voravootivat
> Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
> 
> [resent in text mode]
> Avi,
> + I am testing the patch on a system where the firmware image is on
> the emmc itself. It fails most of the time. The problem is mmc_ffu_download
> has been call with mmc_claim_host called, preventing mmcqd from pulling
> the firmware from the disk. The dead lock situation cause the machine to
> panic. It the file is in the cache, the firmware upgrade goes further.
> 
> Looking at stacks:
> 
> mmc:
> ...
> sleep_on_buffer+0xe/0x12
> ...
> bh_submit_read+0x49/0x5b
> ...
> _request_firmware+0x3b0/0x84e
> request_firmware+0x16/0x18
> mmc_ffu_download+0xc7/0x8d7
> 
> and  mmcqd/0:
> schedule+0x69/0x6b
> __mmc_claim_host+0xc6/0x19a
> ...
> mmc_get_card+0x25/0x28
> mmc_blk_issue_rq+0x41/0x403
> 

Let us check this issue. We will try to reproduce it. 
We probably will need to call request_firmware before claiming the host - i.e. call it separately in mmc_blk_ioctl_cmd


> + in mmc_ffu_write(), don't you want to check that the eMMC has
> received the payload by looking at
> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED ext_csd register?

We haven't use this register previously due to IOCTL chunk size limitation, but can use it now with request_firmware

> 
> + did you test with eMMC that do not support MODE_OPERATION_CODES?

Yes
 
> In mmc_ffu_restart, you call
> mmc_power_save_host/mmc_power_restore_host. Can the reset be
> achieve with mmc_hw_reset() which has the advantage to be more atomic?

Will check this...

> 
> Ulf, Chris,
> + One extra advantage of using request_firmware is if we can trust
> udev (or whoever is serving the firmware uevent request), we are guaranteed
> that we can trust the firmware as well. If we can send the firmware payload
> via ioctl, we can not be sure the caller of that ioctl is not an attacker that
> wants to put a bad firmware in the eMMC.
> 
> On Tue, Jul 1, 2014 at 4:39 AM, Avi Shchislowski
> <Avi.Shchislowski@sandisk.com> wrote:
> > Hi Chris,
> >
> > Re your concern that the patch was not tested – some of SanDisk’s OEM
> customers have implemented the patch, and they report that it is working for
> them.
> >
> > Thanks
> > Avi
> >
> > -----Original Message-----
> > From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com]
> > Sent: Tuesday, July 01, 2014 9:29 AM
> > To: Chris Ball
> > Cc: Grant Grundler; cjb@laptop.org; Ulf Hansson;
> > linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Olof
> > Johansson
> > Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
> >
> > Hi, Chris,
> > I think there are two reasons why doing FFU in kernel-space makes more
> sense.
> >
> > First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to
> restart eMMC after FFU.
> >
> > Second, if host want to vouch "atmoic" in FFU process, we need to call
> mmc_claim_host API, and this API is the kernel-space code. I said "if"
> > is because eMMC vendors may take care this. So, host may not need to
> worry about this.
> >
> > Thanks,
> > Hsinhsiang
> >
> > 2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
> >> Hi Grant, sorry about the delay.
> >>
> >> On Tue, Jul 01 2014, Grant Grundler wrote:
> >>> ping? Another week is past.
> >>>
> >>> I know you guys are busy with other stuff. But please at least
> >>> publish what you think the next steps for this patch are.
> >>>
> >>> This is a critical patch for supporting eMMC products and adding
> >>> additional support for other vendors. I'd like to continue working
> >>> with "upstream" and the lack of response in this case is making this
> >>> unnecessarily difficult.
> >>
> >> It's tough to merge a significant patch that you haven't tested and
> >> we haven't tested.  We could do it, though -- I'd like to hear what
> >> Ulf thinks.  Please could you give a quick explanation of why doing
> >> this in kernel-space makes more sense than using your mmc-utils version?
> >>
> >> Thanks,
> >>
> >> - Chris.
> >> --
> >> Chris Ball   <http://printf.net/>
> >> --
> >> 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] 20+ messages in thread

* Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-01 17:07         ` Ulf Hansson
@ 2014-07-09 17:27           ` Grant Grundler
  2014-07-10 23:08             ` [PATCH] Fix on top of sandisk patches Gwendal Grignou
  0 siblings, 1 reply; 20+ messages in thread
From: Grant Grundler @ 2014-07-09 17:27 UTC (permalink / raw)
  To: Ulf Hansson
  Cc: Grant Grundler, Chris Ball, cjb, linux-mmc, Alex Lemberg,
	Avi Shchislowski, Olof Johansson

PIng. :)  Just a friendly reminder.

I am aware of additional issues that Gwendal pointed out. But I still
think it's better to take this patch now, knowing there are issues
(which is better than NOT knowing there are issues), and then review
additional patches for the outstand issues.

thanks,
grant

On Tue, Jul 1, 2014 at 10:07 AM, Ulf Hansson <ulf.hansson@linaro.org> wrote:
> On 1 July 2014 17:47, Grant Grundler <grundler@chromium.org> wrote:
>> On Mon, Jun 30, 2014 at 5:34 PM, Chris Ball <chris@printf.net> wrote:
>>> Hi Grant, sorry about the delay.
>>>
>>> On Tue, Jul 01 2014, Grant Grundler wrote:
>>>> ping? Another week is past.
>>>>
>>>> I know you guys are busy with other stuff. But please at least publish
>>>> what you think the next steps for this patch are.
>>>>
>>>> This is a critical patch for supporting eMMC products and adding
>>>> additional support for other vendors. I'd like to continue working
>>>> with "upstream" and the lack of response in this case is making this
>>>> unnecessarily difficult.
>>>
>>> It's tough to merge a significant patch that you haven't tested and we
>>> haven't tested.
>>
>> Hi Chris!
>> Thanks for the response.
>>
>> You are correct that neither of us has tested the patch. But I know
>> SanDisk, Samsung, and Kingston (IIRC) have.  And in the case that FFU
>> doesn't work, contracts with these HW vendors require FFU works. So
>> they have financial incentives to make it work. I'm comfortable with
>> support that ChromeOS has for this patch in the long term.
>>
>> Most of the magic (for FFU and general flash operation) is in the
>> Vendor Firmware. We depend on those vendors entirely for this to work
>> correctly already. This is not a new dependency and looks more like
>> collaboration when they supply the patch to support "industry
>> standard" features (like FFU).
>>
>>>  We could do it, though -- I'd like to hear what Ulf thinks.
>>
>> Sure - me too. :)
>
> Hi Grant,
>
> Sorry for lack of input!
>
> Unfortunate I am travelling and will have to come back to this next week.
>
>>
>>>  Please could you give a quick explanation of why doing this
>>> in kernel-space makes more sense than using your mmc-utils version?
>>
>> Only the kernel can guarantee no other commands are sent to the device
>> during FFU. User space can attempt to hold this promise but the
>> constraints on when it can are pretty onerous. SanDisk is pushing this
>> patch entirely for this reason and I agree it's a better solution.
>
> I am open to consider this patch. From my point of view, I need some
> additional time to review the code.
>
> Kind regards
> Uffe

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

* [PATCH] Fix on top of sandisk patches.
  2014-07-09 17:27           ` Grant Grundler
@ 2014-07-10 23:08             ` Gwendal Grignou
  2014-07-10 23:40               ` Alex Lemberg
  0 siblings, 1 reply; 20+ messages in thread
From: Gwendal Grignou @ 2014-07-10 23:08 UTC (permalink / raw)
  To: Avi.Shchislowski, chris, ulf.hansson
  Cc: olofj, Alex.Lemberg, linux-mmc, cjb, grundler, Yaniv.Iarovici,
	Gwendal Grignou

To apply on top of 
[RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0

Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
---

Avi,

As I mentioned earlier, I found several problems with your patch.
Here is a patch that allows to go further.
It is still not working reliably. The eMMC I am using has a problem
being reseted after upgrade, and I am still hitting what appears to
be memory corruptions. I believe the issues I am still having are due to
the kernel code, not the eMMC I am testing.

Gwendal.

 drivers/mmc/card/block.c |   3 +-
 drivers/mmc/card/ffu.c   | 121 +++++++++++++++++++++++------------------------
 drivers/mmc/core/mmc.c   |   8 ++++
 include/linux/mmc/card.h |   1 +
 include/linux/mmc/ffu.h  |  13 +----
 5 files changed, 71 insertions(+), 75 deletions(-)

diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index fc65a6c..9d866fb 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -504,8 +504,7 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
 	mmc_get_card(card);
 
 	if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
-		err = mmc_ffu_download(card, &cmd , idata->buf,
-			idata->buf_bytes);
+		err = mmc_ffu_download(card, idata->buf);
 		goto cmd_rel_host;
 	}
 
diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c
index 04e07a5..0e85b2b 100644
--- a/drivers/mmc/card/ffu.c
+++ b/drivers/mmc/card/ffu.c
@@ -63,15 +63,13 @@ struct mmc_ffu_area {
 
 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, int write) {
+	u32 arg, unsigned int blocks, unsigned int blksz) {
 	BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
 
 	if (blocks > 1) {
-		mrq->cmd->opcode = write ?
-			MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
+		mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
 	} else {
-		mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
-			MMC_READ_SINGLE_BLOCK;
+		mrq->cmd->opcode = MMC_WRITE_BLOCK;
 	}
 
 	mrq->cmd->arg = arg;
@@ -89,11 +87,12 @@ static void mmc_ffu_prepare_mrq(struct mmc_card *card,
 
 	mrq->data->blksz = blksz;
 	mrq->data->blocks = blocks;
-	mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+	mrq->data->flags = MMC_DATA_WRITE;
 	mrq->data->sg = sg;
 	mrq->data->sg_len = sg_len;
 
-	mmc_set_data_timeout(mrq->data, card); }
+	mmc_set_data_timeout(mrq->data, card);
+}
 
 /*
  * Checks that a normal transfer didn't have any errors
@@ -153,7 +152,7 @@ static int mmc_ffu_wait_busy(struct mmc_card *card) {
  */
 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, int write) {
+	unsigned int blocks, unsigned int blksz) {
 	struct mmc_request mrq = {0};
 	struct mmc_command cmd = {0};
 	struct mmc_command stop = {0};
@@ -162,8 +161,7 @@ static int mmc_ffu_simple_transfer(struct mmc_card *card,
 	mrq.cmd = &cmd;
 	mrq.data = &data;
 	mrq.stop = &stop;
-	mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz,
-		write);
+	mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
 	mmc_wait_for_req(card->host, &mrq);
 
 	mmc_ffu_wait_busy(card);
@@ -175,19 +173,17 @@ static int mmc_ffu_simple_transfer(struct mmc_card *card,
  * 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 *sglist)
 {
 	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);
+	sg_init_table(sglist, mem->cnt);
 
-	for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
-		len = PAGE_SIZE * (1 << mem->arr[i].order);
+	for (i = 0; i < mem->cnt && sz; i++, sg++, sz -= len) {
+		len = PAGE_SIZE << mem->arr[i].order;
 
 		if (len > sz) {
 			len = sz;
@@ -195,12 +191,10 @@ static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
 		}
 
 		sg_set_page(sg, mem->arr[i].page, len, 0);
-		sg = sg_next(sg);
-		sctr_len += 1;
 	}
 	sg_mark_end(sg);
 
-	return sctr_len;
+	return mem->cnt;
 }
 
 static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) {
@@ -277,6 +271,8 @@ static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned long min_sz,
 		mem->arr[mem->cnt].page = page;
 		mem->arr[mem->cnt].order = order;
 		mem->cnt += 1;
+		pr_debug("FFU: cnt: %d - order %d\n", mem->cnt, order);
+
 		if (max_page_cnt <= (1UL << order))
 			break;
 		max_page_cnt -= 1UL << order;
@@ -298,11 +294,11 @@ out_free:
  * Copy the data to the allocated pages.
  */
 static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
-	u8 *data, int size)
+	const u8 *data, int size)
 {
 	int ret;
 	int i;
-	int length = 0;
+	int length = 0, page_length;
 
 	area->max_tfr = size;
 
@@ -323,20 +319,20 @@ static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
 			goto out_free;
 		}
 
+		page_length = PAGE_SIZE << area->mem->arr[i].order;
 		memcpy(page_address(area->mem->arr[i].page), data + length,
-			min(size - length, (int)area->max_seg_sz));
-		length += area->max_seg_sz;
+			min(size - length, page_length));
+		length += page_length;
 	}
 
 	area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
-		GFP_KERNEL);
+			   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);
+	area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg);
 
 	return 0;
 
@@ -345,7 +341,7 @@ out_free:
 	return ret;
 }
 
-static int mmc_ffu_write(struct mmc_card *card, u8 *src, u32 arg,
+static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
 	int size)
 {
 	int rc;
@@ -370,7 +366,8 @@ static int mmc_ffu_write(struct mmc_card *card, u8 *src, u32 arg,
 			goto exit;
 
 		rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
-			max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE, 1);
+			max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
+		mmc_ffu_area_cleanup(&area);
 		if (rc != 0)
 			goto exit;
 
@@ -379,7 +376,6 @@ static int mmc_ffu_write(struct mmc_card *card, u8 *src, u32 arg,
 	} while (size > 0);
 
 exit:
-	mmc_ffu_area_cleanup(&area);
 	return rc;
 }
 
@@ -406,47 +402,39 @@ exit:
 	return err;
 }
 
-int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
-	u8 *data, int buf_bytes)
+int mmc_ffu_download(struct mmc_card *card, const char *name)
 {
 	u8 ext_csd[CARD_BLOCK_SIZE];
 	int err;
 	int ret;
-	u8 *buf = NULL;
+	u32 arg;
 	const struct firmware *fw;
 
-	/* 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);
+	pr_debug("FFU: %s uploading firmware %.20s to device\n",
+		 mmc_hostname(card->host), name);
+
+	if (strlen(name) > 512) {
+		err = -EINVAL;
+		pr_err("FFU: %s: %.20s is not a valid argument\n",
+		       mmc_hostname(card->host), name);
 		goto exit;
 	}
 
-	/* check if card is eMMC 5.0 or higher */
-	if (card->ext_csd.rev < 7)
-		return -EINVAL;
-
 	/* Check if FFU is supported */
-	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
-		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
-		err = -EINVAL;
+	if (!card->ext_csd.ffu_capable) {
+		err = -EOPNOTSUPP;
 		pr_err("FFU: %s: error %d FFU is not supported\n",
 			mmc_hostname(card->host), err);
 		goto exit;
 	}
 
-	/* setup FW data buffer */
-	err = request_firmware(&fw, data, &card->dev);
+	/* setup FW name buffer */
+	mmc_put_card(card);
+	err = request_firmware(&fw, name, &card->dev);
+	mmc_get_card(card);
 	if (err) {
 		pr_err("Firmware request failed %d\n", err);
-		goto exit_normal;
-	}
-
-	buf = kmalloc(fw->size, GFP_KERNEL);
-	if (buf == NULL) {
-		pr_err("Allocating memory for firmware failed!\n");
-		goto exit_normal;
+		goto exit;
 	}
 
 	if ((fw->size % CARD_BLOCK_SIZE)) {
@@ -454,9 +442,16 @@ int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
 			mmc_hostname(card->host), fw->size);
 	}
 
-	memcpy(buf, fw->data, fw->size);
+	/* 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 device to FFU mode */
+	pr_debug("FFU: %s switch to FFU mode\n", mmc_hostname(card->host));
 	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) {
@@ -466,18 +461,18 @@ int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
 	}
 
 	/* set CMD ARG */
-	cmd->arg = ext_csd[EXT_CSD_FFU_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, buf, cmd->arg, (int)fw->size);
+	err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
 
 exit_normal:
 	release_firmware(fw);
-	kfree(buf);
 
 	/* host switch back to work in normal MMC Read/Write commands */
+	pr_debug("FFU: %s switch to normal mode\n", mmc_hostname(card->host));
 	ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
 		EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
 		card->ext_csd.generic_cmd6_time);
@@ -495,6 +490,8 @@ int mmc_ffu_install(struct mmc_card *card)
 	u32 ffu_data_len;
 	u32 timeout;
 
+	pr_debug("FFU: %s installing firmware to device\n",
+		 mmc_hostname(card->host));
 	err = mmc_send_ext_csd(card, ext_csd);
 	if (err) {
 		pr_err("FFU: %s: error %d sending ext_csd\n",
@@ -503,9 +500,8 @@ int mmc_ffu_install(struct mmc_card *card)
 	}
 
 	/* Check if FFU is supported */
-	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE]) ||
-		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
-		err = -EINVAL;
+	if (!card->ext_csd.ffu_capable) {
+		err = -EOPNOTSUPP;
 		pr_err("FFU: %s: error %d FFU is not supported\n",
 			mmc_hostname(card->host), err);
 		goto exit;
@@ -514,19 +510,20 @@ int mmc_ffu_install(struct mmc_card *card)
 	/* check mode operation */
 	if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
 		/* restart the eMMC */
+		mmc_put_card(card);
 		err = mmc_ffu_restart(card);
+		mmc_get_card(card);
 		if (err) {
 			pr_err("FFU: %s: error %d FFU install:\n",
 				mmc_hostname(card->host), 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) {
+		if (ffu_data_len == 0) {
 			err = -EPERM;
 			return err;
 		}
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 4099424..ace9123 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -552,6 +552,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 6a5c754..6a7de8d 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;  /* support Firmware Field Upgrade */
 	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
index f5dcedb..a0ade1b 100644
--- a/include/linux/mmc/ffu.h
+++ b/include/linux/mmc/ffu.h
@@ -34,19 +34,10 @@
 #define MMC_FFU_INSTALL_SET 0x1
 
 #ifdef CONFIG_MMC_FFU
-#define MMC_FFU_ENABLE 0x0
-#define MMC_FFU_CONFIG 0x1
-#define MMC_FFU_SUPPORTED_MODES 0x1
 #define MMC_FFU_FEATURES 0x1
+#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
 
-#define FFU_ENABLED(ffu_enable)	(ffu_enable & MMC_FFU_CONFIG)
-#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
-	(ffu_sup_mode && MMC_FFU_SUPPORTED_MODES)
-#define FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG)
-#define FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
-
-int mmc_ffu_download(struct mmc_card *card, struct mmc_command *cmd,
-	u8 *data, int buf_bytes);
+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,
-- 
2.0.0.526.g5318336


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

* RE: [PATCH] Fix on top of sandisk patches.
  2014-07-10 23:08             ` [PATCH] Fix on top of sandisk patches Gwendal Grignou
@ 2014-07-10 23:40               ` Alex Lemberg
  2014-07-10 23:46                 ` Gwendal Grignou
  0 siblings, 1 reply; 20+ messages in thread
From: Alex Lemberg @ 2014-07-10 23:40 UTC (permalink / raw)
  To: Gwendal Grignou, Avi Shchislowski, chris, ulf.hansson
  Cc: olofj, linux-mmc, cjb, grundler, Yaniv Iarovici

Thanks Gwendal!
We will test your changes.

Are you still experiencing the "request_firmware" failure?

Alex

> -----Original Message-----
> From: Gwendal Grignou [mailto:gwendal@chromium.org]
> Sent: Thursday, July 10, 2014 4:08 PM
> To: Avi Shchislowski; chris@printf.net; ulf.hansson@linaro.org
> Cc: olofj@chromium.org; Alex Lemberg; linux-mmc@vger.kernel.org;
> cjb@laptop.org; grundler@chromium.org; Yaniv Iarovici; Gwendal Grignou
> Subject: [PATCH] Fix on top of sandisk patches.
> 
> To apply on top of
> [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
> 
> Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
> ---
> 
> Avi,
> 
> As I mentioned earlier, I found several problems with your patch.
> Here is a patch that allows to go further.
> It is still not working reliably. The eMMC I am using has a problem being
> reseted after upgrade, and I am still hitting what appears to be memory
> corruptions. I believe the issues I am still having are due to the kernel code,
> not the eMMC I am testing.
> 
> Gwendal.
> 
>  drivers/mmc/card/block.c |   3 +-
>  drivers/mmc/card/ffu.c   | 121 +++++++++++++++++++++++--------------------
> ----
>  drivers/mmc/core/mmc.c   |   8 ++++
>  include/linux/mmc/card.h |   1 +
>  include/linux/mmc/ffu.h  |  13 +----
>  5 files changed, 71 insertions(+), 75 deletions(-)
> 
> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index
> fc65a6c..9d866fb 100644
> --- a/drivers/mmc/card/block.c
> +++ b/drivers/mmc/card/block.c
> @@ -504,8 +504,7 @@ static int mmc_blk_ioctl_cmd(struct block_device
> *bdev,
>  	mmc_get_card(card);
> 
>  	if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
> -		err = mmc_ffu_download(card, &cmd , idata->buf,
> -			idata->buf_bytes);
> +		err = mmc_ffu_download(card, idata->buf);
>  		goto cmd_rel_host;
>  	}
> 
> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c index
> 04e07a5..0e85b2b 100644
> --- a/drivers/mmc/card/ffu.c
> +++ b/drivers/mmc/card/ffu.c
> @@ -63,15 +63,13 @@ struct mmc_ffu_area {
> 
>  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, int write) {
> +	u32 arg, unsigned int blocks, unsigned int blksz) {
>  	BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
> 
>  	if (blocks > 1) {
> -		mrq->cmd->opcode = write ?
> -			MMC_WRITE_MULTIPLE_BLOCK :
> MMC_READ_MULTIPLE_BLOCK;
> +		mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>  	} else {
> -		mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
> -			MMC_READ_SINGLE_BLOCK;
> +		mrq->cmd->opcode = MMC_WRITE_BLOCK;
>  	}
> 
>  	mrq->cmd->arg = arg;
> @@ -89,11 +87,12 @@ static void mmc_ffu_prepare_mrq(struct mmc_card
> *card,
> 
>  	mrq->data->blksz = blksz;
>  	mrq->data->blocks = blocks;
> -	mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
> +	mrq->data->flags = MMC_DATA_WRITE;
>  	mrq->data->sg = sg;
>  	mrq->data->sg_len = sg_len;
> 
> -	mmc_set_data_timeout(mrq->data, card); }
> +	mmc_set_data_timeout(mrq->data, card); }
> 
>  /*
>   * Checks that a normal transfer didn't have any errors @@ -153,7 +152,7
> @@ static int mmc_ffu_wait_busy(struct mmc_card *card) {
>   */
>  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, int write) {
> +	unsigned int blocks, unsigned int blksz) {
>  	struct mmc_request mrq = {0};
>  	struct mmc_command cmd = {0};
>  	struct mmc_command stop = {0};
> @@ -162,8 +161,7 @@ static int mmc_ffu_simple_transfer(struct mmc_card
> *card,
>  	mrq.cmd = &cmd;
>  	mrq.data = &data;
>  	mrq.stop = &stop;
> -	mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz,
> -		write);
> +	mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>  	mmc_wait_for_req(card->host, &mrq);
> 
>  	mmc_ffu_wait_busy(card);
> @@ -175,19 +173,17 @@ static int mmc_ffu_simple_transfer(struct
> mmc_card *card,
>   * 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 *sglist)
>  {
>  	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);
> +	sg_init_table(sglist, mem->cnt);
> 
> -	for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
> -		len = PAGE_SIZE * (1 << mem->arr[i].order);
> +	for (i = 0; i < mem->cnt && sz; i++, sg++, sz -= len) {
> +		len = PAGE_SIZE << mem->arr[i].order;
> 
>  		if (len > sz) {
>  			len = sz;
> @@ -195,12 +191,10 @@ static unsigned int mmc_ffu_map_sg(struct
> mmc_ffu_mem *mem, int size,
>  		}
> 
>  		sg_set_page(sg, mem->arr[i].page, len, 0);
> -		sg = sg_next(sg);
> -		sctr_len += 1;
>  	}
>  	sg_mark_end(sg);
> 
> -	return sctr_len;
> +	return mem->cnt;
>  }
> 
>  static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) { @@ -277,6
> +271,8 @@ static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned
> long min_sz,
>  		mem->arr[mem->cnt].page = page;
>  		mem->arr[mem->cnt].order = order;
>  		mem->cnt += 1;
> +		pr_debug("FFU: cnt: %d - order %d\n", mem->cnt, order);
> +
>  		if (max_page_cnt <= (1UL << order))
>  			break;
>  		max_page_cnt -= 1UL << order;
> @@ -298,11 +294,11 @@ out_free:
>   * Copy the data to the allocated pages.
>   */
>  static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card
> *card,
> -	u8 *data, int size)
> +	const u8 *data, int size)
>  {
>  	int ret;
>  	int i;
> -	int length = 0;
> +	int length = 0, page_length;
> 
>  	area->max_tfr = size;
> 
> @@ -323,20 +319,20 @@ static int mmc_ffu_area_init(struct mmc_ffu_area
> *area, struct mmc_card *card,
>  			goto out_free;
>  		}
> 
> +		page_length = PAGE_SIZE << area->mem->arr[i].order;
>  		memcpy(page_address(area->mem->arr[i].page), data +
> length,
> -			min(size - length, (int)area->max_seg_sz));
> -		length += area->max_seg_sz;
> +			min(size - length, page_length));
> +		length += page_length;
>  	}
> 
>  	area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
> -		GFP_KERNEL);
> +			   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);
> +	area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg);
> 
>  	return 0;
> 
> @@ -345,7 +341,7 @@ out_free:
>  	return ret;
>  }
> 
> -static int mmc_ffu_write(struct mmc_card *card, u8 *src, u32 arg,
> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>  	int size)
>  {
>  	int rc;
> @@ -370,7 +366,8 @@ static int mmc_ffu_write(struct mmc_card *card, u8
> *src, u32 arg,
>  			goto exit;
> 
>  		rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
> -			max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE, 1);
> +			max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
> +		mmc_ffu_area_cleanup(&area);
>  		if (rc != 0)
>  			goto exit;
> 
> @@ -379,7 +376,6 @@ static int mmc_ffu_write(struct mmc_card *card, u8
> *src, u32 arg,
>  	} while (size > 0);
> 
>  exit:
> -	mmc_ffu_area_cleanup(&area);
>  	return rc;
>  }
> 
> @@ -406,47 +402,39 @@ exit:
>  	return err;
>  }
> 
> -int mmc_ffu_download(struct mmc_card *card, struct mmc_command
> *cmd,
> -	u8 *data, int buf_bytes)
> +int mmc_ffu_download(struct mmc_card *card, const char *name)
>  {
>  	u8 ext_csd[CARD_BLOCK_SIZE];
>  	int err;
>  	int ret;
> -	u8 *buf = NULL;
> +	u32 arg;
>  	const struct firmware *fw;
> 
> -	/* 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);
> +	pr_debug("FFU: %s uploading firmware %.20s to device\n",
> +		 mmc_hostname(card->host), name);
> +
> +	if (strlen(name) > 512) {
> +		err = -EINVAL;
> +		pr_err("FFU: %s: %.20s is not a valid argument\n",
> +		       mmc_hostname(card->host), name);
>  		goto exit;
>  	}
> 
> -	/* check if card is eMMC 5.0 or higher */
> -	if (card->ext_csd.rev < 7)
> -		return -EINVAL;
> -
>  	/* Check if FFU is supported */
> -	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE])
> ||
> -		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> -		err = -EINVAL;
> +	if (!card->ext_csd.ffu_capable) {
> +		err = -EOPNOTSUPP;
>  		pr_err("FFU: %s: error %d FFU is not supported\n",
>  			mmc_hostname(card->host), err);
>  		goto exit;
>  	}
> 
> -	/* setup FW data buffer */
> -	err = request_firmware(&fw, data, &card->dev);
> +	/* setup FW name buffer */
> +	mmc_put_card(card);
> +	err = request_firmware(&fw, name, &card->dev);
> +	mmc_get_card(card);
>  	if (err) {
>  		pr_err("Firmware request failed %d\n", err);
> -		goto exit_normal;
> -	}
> -
> -	buf = kmalloc(fw->size, GFP_KERNEL);
> -	if (buf == NULL) {
> -		pr_err("Allocating memory for firmware failed!\n");
> -		goto exit_normal;
> +		goto exit;
>  	}
> 
>  	if ((fw->size % CARD_BLOCK_SIZE)) {
> @@ -454,9 +442,16 @@ int mmc_ffu_download(struct mmc_card *card,
> struct mmc_command *cmd,
>  			mmc_hostname(card->host), fw->size);
>  	}
> 
> -	memcpy(buf, fw->data, fw->size);
> +	/* 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 device to FFU mode */
> +	pr_debug("FFU: %s switch to FFU mode\n", mmc_hostname(card-
> >host));
>  	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) {
> @@ -466,18 +461,18 @@ int mmc_ffu_download(struct mmc_card *card,
> struct mmc_command *cmd,
>  	}
> 
>  	/* set CMD ARG */
> -	cmd->arg = ext_csd[EXT_CSD_FFU_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, buf, cmd->arg, (int)fw->size);
> +	err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
> 
>  exit_normal:
>  	release_firmware(fw);
> -	kfree(buf);
> 
>  	/* host switch back to work in normal MMC Read/Write commands
> */
> +	pr_debug("FFU: %s switch to normal mode\n", mmc_hostname(card-
> >host));
>  	ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>  		EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>  		card->ext_csd.generic_cmd6_time);
> @@ -495,6 +490,8 @@ int mmc_ffu_install(struct mmc_card *card)
>  	u32 ffu_data_len;
>  	u32 timeout;
> 
> +	pr_debug("FFU: %s installing firmware to device\n",
> +		 mmc_hostname(card->host));
>  	err = mmc_send_ext_csd(card, ext_csd);
>  	if (err) {
>  		pr_err("FFU: %s: error %d sending ext_csd\n", @@ -503,9
> +500,8 @@ int mmc_ffu_install(struct mmc_card *card)
>  	}
> 
>  	/* Check if FFU is supported */
> -	if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE])
> ||
> -		FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
> -		err = -EINVAL;
> +	if (!card->ext_csd.ffu_capable) {
> +		err = -EOPNOTSUPP;
>  		pr_err("FFU: %s: error %d FFU is not supported\n",
>  			mmc_hostname(card->host), err);
>  		goto exit;
> @@ -514,19 +510,20 @@ int mmc_ffu_install(struct mmc_card *card)
>  	/* check mode operation */
>  	if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>  		/* restart the eMMC */
> +		mmc_put_card(card);
>  		err = mmc_ffu_restart(card);
> +		mmc_get_card(card);
>  		if (err) {
>  			pr_err("FFU: %s: error %d FFU install:\n",
>  				mmc_hostname(card->host), 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) {
> +		if (ffu_data_len == 0) {
>  			err = -EPERM;
>  			return err;
>  		}
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
> 4099424..ace9123 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -552,6 +552,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
> 6a5c754..6a7de8d 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;  /* support Firmware Field
> Upgrade */
>  	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 index
> f5dcedb..a0ade1b 100644
> --- a/include/linux/mmc/ffu.h
> +++ b/include/linux/mmc/ffu.h
> @@ -34,19 +34,10 @@
>  #define MMC_FFU_INSTALL_SET 0x1
> 
>  #ifdef CONFIG_MMC_FFU
> -#define MMC_FFU_ENABLE 0x0
> -#define MMC_FFU_CONFIG 0x1
> -#define MMC_FFU_SUPPORTED_MODES 0x1
>  #define MMC_FFU_FEATURES 0x1
> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
> 
> -#define FFU_ENABLED(ffu_enable)	(ffu_enable & MMC_FFU_CONFIG)
> -#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
> -	(ffu_sup_mode && MMC_FFU_SUPPORTED_MODES)
> -#define FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) -
> #define FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
> -
> -int mmc_ffu_download(struct mmc_card *card, struct mmc_command
> *cmd,
> -	u8 *data, int buf_bytes);
> +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,
> --
> 2.0.0.526.g5318336


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

* Re: [PATCH] Fix on top of sandisk patches.
  2014-07-10 23:40               ` Alex Lemberg
@ 2014-07-10 23:46                 ` Gwendal Grignou
  0 siblings, 0 replies; 20+ messages in thread
From: Gwendal Grignou @ 2014-07-10 23:46 UTC (permalink / raw)
  To: Alex Lemberg
  Cc: Gwendal Grignou, Avi Shchislowski, chris, ulf.hansson, olofj,
	linux-mmc, cjb, grundler, Yaniv Iarovici

On Thu, Jul 10, 2014 at 4:40 PM, Alex Lemberg <Alex.Lemberg@sandisk.com> wrote:
> Thanks Gwendal!
> We will test your changes.
>
> Are you still experiencing the "request_firmware" failure?
no, since I added the mmc_put_card(). But enclosing request_firmware()
betwen put()/get() is not to my opinion not the right thing to do: the
caller called us with the lock held, we should not release it behind
its back.
We also need to revisit the eMMC reset, I had to drop the lock there as well.
>
> Alex
>
>> -----Original Message-----
>> From: Gwendal Grignou [mailto:gwendal@chromium.org]
>> Sent: Thursday, July 10, 2014 4:08 PM
>> To: Avi Shchislowski; chris@printf.net; ulf.hansson@linaro.org
>> Cc: olofj@chromium.org; Alex Lemberg; linux-mmc@vger.kernel.org;
>> cjb@laptop.org; grundler@chromium.org; Yaniv Iarovici; Gwendal Grignou
>> Subject: [PATCH] Fix on top of sandisk patches.
>>
>> To apply on top of
>> [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
>>
>> Signed-off-by: Gwendal Grignou <gwendal@chromium.org>
>> ---
>>
>> Avi,
>>
>> As I mentioned earlier, I found several problems with your patch.
>> Here is a patch that allows to go further.
>> It is still not working reliably. The eMMC I am using has a problem being
>> reseted after upgrade, and I am still hitting what appears to be memory
>> corruptions. I believe the issues I am still having are due to the kernel code,
>> not the eMMC I am testing.
>>
>> Gwendal.
>>
>>  drivers/mmc/card/block.c |   3 +-
>>  drivers/mmc/card/ffu.c   | 121 +++++++++++++++++++++++--------------------
>> ----
>>  drivers/mmc/core/mmc.c   |   8 ++++
>>  include/linux/mmc/card.h |   1 +
>>  include/linux/mmc/ffu.h  |  13 +----
>>  5 files changed, 71 insertions(+), 75 deletions(-)
>>
>> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index
>> fc65a6c..9d866fb 100644
>> --- a/drivers/mmc/card/block.c
>> +++ b/drivers/mmc/card/block.c
>> @@ -504,8 +504,7 @@ static int mmc_blk_ioctl_cmd(struct block_device
>> *bdev,
>>       mmc_get_card(card);
>>
>>       if (cmd.opcode == MMC_FFU_DOWNLOAD_OP) {
>> -             err = mmc_ffu_download(card, &cmd , idata->buf,
>> -                     idata->buf_bytes);
>> +             err = mmc_ffu_download(card, idata->buf);
>>               goto cmd_rel_host;
>>       }
>>
>> diff --git a/drivers/mmc/card/ffu.c b/drivers/mmc/card/ffu.c index
>> 04e07a5..0e85b2b 100644
>> --- a/drivers/mmc/card/ffu.c
>> +++ b/drivers/mmc/card/ffu.c
>> @@ -63,15 +63,13 @@ struct mmc_ffu_area {
>>
>>  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, int write) {
>> +     u32 arg, unsigned int blocks, unsigned int blksz) {
>>       BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
>>
>>       if (blocks > 1) {
>> -             mrq->cmd->opcode = write ?
>> -                     MMC_WRITE_MULTIPLE_BLOCK :
>> MMC_READ_MULTIPLE_BLOCK;
>> +             mrq->cmd->opcode = MMC_WRITE_MULTIPLE_BLOCK;
>>       } else {
>> -             mrq->cmd->opcode = write ? MMC_WRITE_BLOCK :
>> -                     MMC_READ_SINGLE_BLOCK;
>> +             mrq->cmd->opcode = MMC_WRITE_BLOCK;
>>       }
>>
>>       mrq->cmd->arg = arg;
>> @@ -89,11 +87,12 @@ static void mmc_ffu_prepare_mrq(struct mmc_card
>> *card,
>>
>>       mrq->data->blksz = blksz;
>>       mrq->data->blocks = blocks;
>> -     mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
>> +     mrq->data->flags = MMC_DATA_WRITE;
>>       mrq->data->sg = sg;
>>       mrq->data->sg_len = sg_len;
>>
>> -     mmc_set_data_timeout(mrq->data, card); }
>> +     mmc_set_data_timeout(mrq->data, card); }
>>
>>  /*
>>   * Checks that a normal transfer didn't have any errors @@ -153,7 +152,7
>> @@ static int mmc_ffu_wait_busy(struct mmc_card *card) {
>>   */
>>  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, int write) {
>> +     unsigned int blocks, unsigned int blksz) {
>>       struct mmc_request mrq = {0};
>>       struct mmc_command cmd = {0};
>>       struct mmc_command stop = {0};
>> @@ -162,8 +161,7 @@ static int mmc_ffu_simple_transfer(struct mmc_card
>> *card,
>>       mrq.cmd = &cmd;
>>       mrq.data = &data;
>>       mrq.stop = &stop;
>> -     mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz,
>> -             write);
>> +     mmc_ffu_prepare_mrq(card, &mrq, sg, sg_len, arg, blocks, blksz);
>>       mmc_wait_for_req(card->host, &mrq);
>>
>>       mmc_ffu_wait_busy(card);
>> @@ -175,19 +173,17 @@ static int mmc_ffu_simple_transfer(struct
>> mmc_card *card,
>>   * 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 *sglist)
>>  {
>>       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);
>> +     sg_init_table(sglist, mem->cnt);
>>
>> -     for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
>> -             len = PAGE_SIZE * (1 << mem->arr[i].order);
>> +     for (i = 0; i < mem->cnt && sz; i++, sg++, sz -= len) {
>> +             len = PAGE_SIZE << mem->arr[i].order;
>>
>>               if (len > sz) {
>>                       len = sz;
>> @@ -195,12 +191,10 @@ static unsigned int mmc_ffu_map_sg(struct
>> mmc_ffu_mem *mem, int size,
>>               }
>>
>>               sg_set_page(sg, mem->arr[i].page, len, 0);
>> -             sg = sg_next(sg);
>> -             sctr_len += 1;
>>       }
>>       sg_mark_end(sg);
>>
>> -     return sctr_len;
>> +     return mem->cnt;
>>  }
>>
>>  static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem) { @@ -277,6
>> +271,8 @@ static struct mmc_ffu_mem *mmc_ffu_alloc_mem(unsigned
>> long min_sz,
>>               mem->arr[mem->cnt].page = page;
>>               mem->arr[mem->cnt].order = order;
>>               mem->cnt += 1;
>> +             pr_debug("FFU: cnt: %d - order %d\n", mem->cnt, order);
>> +
>>               if (max_page_cnt <= (1UL << order))
>>                       break;
>>               max_page_cnt -= 1UL << order;
>> @@ -298,11 +294,11 @@ out_free:
>>   * Copy the data to the allocated pages.
>>   */
>>  static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card
>> *card,
>> -     u8 *data, int size)
>> +     const u8 *data, int size)
>>  {
>>       int ret;
>>       int i;
>> -     int length = 0;
>> +     int length = 0, page_length;
>>
>>       area->max_tfr = size;
>>
>> @@ -323,20 +319,20 @@ static int mmc_ffu_area_init(struct mmc_ffu_area
>> *area, struct mmc_card *card,
>>                       goto out_free;
>>               }
>>
>> +             page_length = PAGE_SIZE << area->mem->arr[i].order;
>>               memcpy(page_address(area->mem->arr[i].page), data +
>> length,
>> -                     min(size - length, (int)area->max_seg_sz));
>> -             length += area->max_seg_sz;
>> +                     min(size - length, page_length));
>> +             length += page_length;
>>       }
>>
>>       area->sg = kmalloc(sizeof(struct scatterlist) * area->mem->cnt,
>> -             GFP_KERNEL);
>> +                        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);
>> +     area->sg_len = mmc_ffu_map_sg(area->mem, size, area->sg);
>>
>>       return 0;
>>
>> @@ -345,7 +341,7 @@ out_free:
>>       return ret;
>>  }
>>
>> -static int mmc_ffu_write(struct mmc_card *card, u8 *src, u32 arg,
>> +static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
>>       int size)
>>  {
>>       int rc;
>> @@ -370,7 +366,8 @@ static int mmc_ffu_write(struct mmc_card *card, u8
>> *src, u32 arg,
>>                       goto exit;
>>
>>               rc = mmc_ffu_simple_transfer(card, area.sg, area.sg_len, arg,
>> -                     max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE, 1);
>> +                     max_tfr / CARD_BLOCK_SIZE, CARD_BLOCK_SIZE);
>> +             mmc_ffu_area_cleanup(&area);
>>               if (rc != 0)
>>                       goto exit;
>>
>> @@ -379,7 +376,6 @@ static int mmc_ffu_write(struct mmc_card *card, u8
>> *src, u32 arg,
>>       } while (size > 0);
>>
>>  exit:
>> -     mmc_ffu_area_cleanup(&area);
>>       return rc;
>>  }
>>
>> @@ -406,47 +402,39 @@ exit:
>>       return err;
>>  }
>>
>> -int mmc_ffu_download(struct mmc_card *card, struct mmc_command
>> *cmd,
>> -     u8 *data, int buf_bytes)
>> +int mmc_ffu_download(struct mmc_card *card, const char *name)
>>  {
>>       u8 ext_csd[CARD_BLOCK_SIZE];
>>       int err;
>>       int ret;
>> -     u8 *buf = NULL;
>> +     u32 arg;
>>       const struct firmware *fw;
>>
>> -     /* 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);
>> +     pr_debug("FFU: %s uploading firmware %.20s to device\n",
>> +              mmc_hostname(card->host), name);
>> +
>> +     if (strlen(name) > 512) {
>> +             err = -EINVAL;
>> +             pr_err("FFU: %s: %.20s is not a valid argument\n",
>> +                    mmc_hostname(card->host), name);
>>               goto exit;
>>       }
>>
>> -     /* check if card is eMMC 5.0 or higher */
>> -     if (card->ext_csd.rev < 7)
>> -             return -EINVAL;
>> -
>>       /* Check if FFU is supported */
>> -     if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE])
>> ||
>> -             FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> -             err = -EINVAL;
>> +     if (!card->ext_csd.ffu_capable) {
>> +             err = -EOPNOTSUPP;
>>               pr_err("FFU: %s: error %d FFU is not supported\n",
>>                       mmc_hostname(card->host), err);
>>               goto exit;
>>       }
>>
>> -     /* setup FW data buffer */
>> -     err = request_firmware(&fw, data, &card->dev);
>> +     /* setup FW name buffer */
>> +     mmc_put_card(card);
>> +     err = request_firmware(&fw, name, &card->dev);
>> +     mmc_get_card(card);
>>       if (err) {
>>               pr_err("Firmware request failed %d\n", err);
>> -             goto exit_normal;
>> -     }
>> -
>> -     buf = kmalloc(fw->size, GFP_KERNEL);
>> -     if (buf == NULL) {
>> -             pr_err("Allocating memory for firmware failed!\n");
>> -             goto exit_normal;
>> +             goto exit;
>>       }
>>
>>       if ((fw->size % CARD_BLOCK_SIZE)) {
>> @@ -454,9 +442,16 @@ int mmc_ffu_download(struct mmc_card *card,
>> struct mmc_command *cmd,
>>                       mmc_hostname(card->host), fw->size);
>>       }
>>
>> -     memcpy(buf, fw->data, fw->size);
>> +     /* 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 device to FFU mode */
>> +     pr_debug("FFU: %s switch to FFU mode\n", mmc_hostname(card-
>> >host));
>>       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) {
>> @@ -466,18 +461,18 @@ int mmc_ffu_download(struct mmc_card *card,
>> struct mmc_command *cmd,
>>       }
>>
>>       /* set CMD ARG */
>> -     cmd->arg = ext_csd[EXT_CSD_FFU_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, buf, cmd->arg, (int)fw->size);
>> +     err = mmc_ffu_write(card, fw->data, arg, (int)fw->size);
>>
>>  exit_normal:
>>       release_firmware(fw);
>> -     kfree(buf);
>>
>>       /* host switch back to work in normal MMC Read/Write commands
>> */
>> +     pr_debug("FFU: %s switch to normal mode\n", mmc_hostname(card-
>> >host));
>>       ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>>               EXT_CSD_MODE_CONFIG, MMC_FFU_MODE_NORMAL,
>>               card->ext_csd.generic_cmd6_time);
>> @@ -495,6 +490,8 @@ int mmc_ffu_install(struct mmc_card *card)
>>       u32 ffu_data_len;
>>       u32 timeout;
>>
>> +     pr_debug("FFU: %s installing firmware to device\n",
>> +              mmc_hostname(card->host));
>>       err = mmc_send_ext_csd(card, ext_csd);
>>       if (err) {
>>               pr_err("FFU: %s: error %d sending ext_csd\n", @@ -503,9
>> +500,8 @@ int mmc_ffu_install(struct mmc_card *card)
>>       }
>>
>>       /* Check if FFU is supported */
>> -     if (!FFU_SUPPORTED_MODE(ext_csd[EXT_CSD_SUPPORTED_MODE])
>> ||
>> -             FFU_CONFIG(ext_csd[EXT_CSD_FW_CONFIG])) {
>> -             err = -EINVAL;
>> +     if (!card->ext_csd.ffu_capable) {
>> +             err = -EOPNOTSUPP;
>>               pr_err("FFU: %s: error %d FFU is not supported\n",
>>                       mmc_hostname(card->host), err);
>>               goto exit;
>> @@ -514,19 +510,20 @@ int mmc_ffu_install(struct mmc_card *card)
>>       /* check mode operation */
>>       if (!FFU_FEATURES(ext_csd[EXT_CSD_FFU_FEATURES])) {
>>               /* restart the eMMC */
>> +             mmc_put_card(card);
>>               err = mmc_ffu_restart(card);
>> +             mmc_get_card(card);
>>               if (err) {
>>                       pr_err("FFU: %s: error %d FFU install:\n",
>>                               mmc_hostname(card->host), 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) {
>> +             if (ffu_data_len == 0) {
>>                       err = -EPERM;
>>                       return err;
>>               }
>> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
>> 4099424..ace9123 100644
>> --- a/drivers/mmc/core/mmc.c
>> +++ b/drivers/mmc/core/mmc.c
>> @@ -552,6 +552,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
>> 6a5c754..6a7de8d 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;  /* support Firmware Field
>> Upgrade */
>>       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 index
>> f5dcedb..a0ade1b 100644
>> --- a/include/linux/mmc/ffu.h
>> +++ b/include/linux/mmc/ffu.h
>> @@ -34,19 +34,10 @@
>>  #define MMC_FFU_INSTALL_SET 0x1
>>
>>  #ifdef CONFIG_MMC_FFU
>> -#define MMC_FFU_ENABLE 0x0
>> -#define MMC_FFU_CONFIG 0x1
>> -#define MMC_FFU_SUPPORTED_MODES 0x1
>>  #define MMC_FFU_FEATURES 0x1
>> +#define FFU_FEATURES(ffu_features) (ffu_features & MMC_FFU_FEATURES)
>>
>> -#define FFU_ENABLED(ffu_enable)      (ffu_enable & MMC_FFU_CONFIG)
>> -#define FFU_SUPPORTED_MODE(ffu_sup_mode) \
>> -     (ffu_sup_mode && MMC_FFU_SUPPORTED_MODES)
>> -#define FFU_CONFIG(ffu_config) (ffu_config & MMC_FFU_CONFIG) -
>> #define FFU_FEATURES(ffu_fetures) (ffu_fetures & MMC_FFU_FEATURES)
>> -
>> -int mmc_ffu_download(struct mmc_card *card, struct mmc_command
>> *cmd,
>> -     u8 *data, int buf_bytes);
>> +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,
>> --
>> 2.0.0.526.g5318336
>

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

* RE: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
  2014-07-03  0:15             ` Gwendal Grignou
@ 2014-07-16 15:54               ` Avi Shchislowski
  0 siblings, 0 replies; 20+ messages in thread
From: Avi Shchislowski @ 2014-07-16 15:54 UTC (permalink / raw)
  To: Gwendal Grignou, Gwendal Grignou
  Cc: Hsin-Hsiang Tseng, Chris Ball, Grant Grundler, cjb, Ulf Hansson,
	linux-mmc, Alex Lemberg, Olof Johansson, Puthikorn Voravootivat

Hi Gwandal

-----Original Message-----
From: Gwendal Grignou [mailto:gwendal@google.com] 
Sent: Thursday, July 03, 2014 3:16 AM
To: Gwendal Grignou
Cc: Avi Shchislowski; Hsin-Hsiang Tseng; Chris Ball; Grant Grundler; cjb@laptop.org; Ulf Hansson; linux-mmc@vger.kernel.org; Alex Lemberg; Olof Johansson; Puthikorn Voravootivat
Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0

Also:
- you don't need to allocate a buffer buf in mmc_ffu_download, you can directly call mmc_ffu_write with fw->data; you are memcopying the data again in mmc_ffu_area_init().
- mmc_ffu_prepare_mrq() is a copy of mmc_test_prepare_mrq(), but in our case, we don't need the write arg, it will be always 1. Is there a way to use an existing function instead of recopying what is in mmc_test.c?

Gwendal.


On Wed, Jul 2, 2014 at 1:35 PM, Gwendal Grignou <gwendal@chromium.org> wrote:
> [resent in text mode]
> Avi,
> + I am testing the patch on a system where the firmware image is on
> the emmc itself. It fails most of the time. The problem is 
> mmc_ffu_download has been call with mmc_claim_host called, preventing 
> mmcqd from pulling the firmware from the disk. The dead lock situation 
> cause the machine to panic. It the file is in the cache, the firmware 
> upgrade goes further.
>
> Looking at stacks:
>
> mmc:
> ...
> sleep_on_buffer+0xe/0x12
> ...
> bh_submit_read+0x49/0x5b
> ...
> _request_firmware+0x3b0/0x84e
> request_firmware+0x16/0x18
> mmc_ffu_download+0xc7/0x8d7
>
> and  mmcqd/0:
> schedule+0x69/0x6b
> __mmc_claim_host+0xc6/0x19a
> ...
> mmc_get_card+0x25/0x28
> mmc_blk_issue_rq+0x41/0x403
>
> + in mmc_ffu_write(), don't you want to check that the eMMC has
> received the payload by looking at
> NUMBER_OF_FW_SECTORS_CORRECTLY_PROGRAMMED ext_csd register?
In patch v8 we are lo0oking at it and use it to compare the payload
>
> + did you test with eMMC that do not support MODE_OPERATION_CODES?
> In mmc_ffu_restart, you call
> mmc_power_save_host/mmc_power_restore_host. Can the reset be achieve 
> with mmc_hw_reset() which has the advantage to be more atomic?
We found out that there platforms that do not support this API
>
> Ulf, Chris,
> + One extra advantage of using request_firmware is if we can trust
> udev (or whoever is serving the firmware uevent request), we are 
> guaranteed that we can trust the firmware as well. If we can send the 
> firmware payload via ioctl, we can not be sure the caller of that 
> ioctl is not an attacker that wants to put a bad firmware in the eMMC.
>
> On Tue, Jul 1, 2014 at 4:39 AM, Avi Shchislowski 
> <Avi.Shchislowski@sandisk.com> wrote:
>> Hi Chris,
>>
>> Re your concern that the patch was not tested – some of SanDisk’s OEM customers have implemented the patch, and they report that it is working for them.
>>
>> Thanks
>> Avi
>>
>> -----Original Message-----
>> From: Hsin-Hsiang Tseng [mailto:hsinhsiangtseng@gmail.com]
>> Sent: Tuesday, July 01, 2014 9:29 AM
>> To: Chris Ball
>> Cc: Grant Grundler; cjb@laptop.org; Ulf Hansson; 
>> linux-mmc@vger.kernel.org; Alex Lemberg; Avi Shchislowski; Olof 
>> Johansson
>> Subject: Re: [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0
>>
>> Hi, Chris,
>> I think there are two reasons why doing FFU in kernel-space makes more sense.
>>
>> First, some eMMC (according to extCSD[492] FFU_FEATURES) will need to restart eMMC after FFU.
>>
>> Second, if host want to vouch "atmoic" in FFU process, we need to call mmc_claim_host API, and this API is the kernel-space code. I said "if"
>> is because eMMC vendors may take care this. So, host may not need to worry about this.
>>
>> Thanks,
>> Hsinhsiang
>>
>> 2014-07-01 8:34 GMT+08:00 Chris Ball <chris@printf.net>:
>>> Hi Grant, sorry about the delay.
>>>
>>> On Tue, Jul 01 2014, Grant Grundler wrote:
>>>> ping? Another week is past.
>>>>
>>>> I know you guys are busy with other stuff. But please at least 
>>>> publish what you think the next steps for this patch are.
>>>>
>>>> This is a critical patch for supporting eMMC products and adding 
>>>> additional support for other vendors. I'd like to continue working 
>>>> with "upstream" and the lack of response in this case is making 
>>>> this unnecessarily difficult.
>>>
>>> It's tough to merge a significant patch that you haven't tested and 
>>> we haven't tested.  We could do it, though -- I'd like to hear what 
>>> Ulf thinks.  Please could you give a quick explanation of why doing 
>>> this in kernel-space makes more sense than using your mmc-utils version?
>>>
>>> Thanks,
>>>
>>> - Chris.
>>> --
>>> Chris Ball   <http://printf.net/>
>>> --
>>> 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] 20+ messages in thread

end of thread, other threads:[~2014-07-16 15:54 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-05-19  8:01 [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0 Avi Shchislowski
2014-06-23 17:14 ` Grant Grundler
     [not found]   ` <CAC1bqKg=qse6MkgYD3bJDcBg0f7eis3dZZ=e3SM7bxE2A-XKPA@mail.gmail.com>
     [not found]     ` <CAC1bqKh59OTYOPNQqiwpr1c=P+KwCFDf53o_YZWqZ8Gs2VaCfA@mail.gmail.com>
2014-06-24  1:48       ` Fwd: " Hsin-Hsiang Tseng
2014-06-27 18:33     ` Grant Grundler
2014-06-28 13:45       ` Hsin-Hsiang Tseng
2014-06-29  8:46         ` Avi Shchislowski
2014-06-30 23:35   ` Grant Grundler
2014-07-01  0:34     ` Chris Ball
2014-07-01  6:29       ` Hsin-Hsiang Tseng
2014-07-01 11:39         ` Avi Shchislowski
2014-07-02 20:35           ` Gwendal Grignou
2014-07-03  0:15             ` Gwendal Grignou
2014-07-16 15:54               ` Avi Shchislowski
2014-07-03 12:32             ` Alex Lemberg
2014-07-01 15:47       ` Grant Grundler
2014-07-01 17:07         ` Ulf Hansson
2014-07-09 17:27           ` Grant Grundler
2014-07-10 23:08             ` [PATCH] Fix on top of sandisk patches Gwendal Grignou
2014-07-10 23:40               ` Alex Lemberg
2014-07-10 23:46                 ` Gwendal Grignou

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.