Hi ,Avi, According to EXT_CSD_FFU_STATUS can report the status of the FFU process. If it necessary to check Error in downloading firmware(value: 0x12) before we continue to execute mmc_ffu_install? Thanks. 2014-07-30 22:08 GMT+08:00 Avi Shchislowski <Avi.Shchislowski@xxxxxxxxxxx>: > 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 -- 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