On Mon, Jun 23, 2014 at 6:17 PM, Hsin-Hsiang Tseng <hsinhsiangtseng@xxxxxxxxx> 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@xxxxxxxxxxxx>: > >> 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@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/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@xxxxxxxxxxx> >> > Signed-off-by: Alex Lemberg <alex.lemberg@xxxxxxxxxxx> >> > >> > --- >> > 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@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