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