Re: [PATCH] mmc-utils:add KSI eMMC 5.0 FFU support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Darren!
Did you understand what Puthikorn was asking for?

linux-mmc maintainers don't care about Chrome-OS branches
(stabilize-5784.0.B). Puthikorn was just pointing out the patch needs
to be against something that linux-mmc can build.

This Kingston support patch is a good example of why Avi's "[v7] mmc:
Support-FFU-for-eMMC-v5.0" patch should be included into linux-mmc
tree. Makes life simpler for everyone.

cheers,
grant

On Tue, Jun 17, 2014 at 2:00 PM, Puthikorn Voravootivat
<puthik@xxxxxxxxxxxx> wrote:
> Hi Darren
>
> Thanks for the patch. To make the reviewers job easier, could you send the patch
> against [RFC PATCH 1/1 v7]mmc: Support-FFU-for-eMMC-v5.0 by avi.
> You can find that patch at http://www.spinics.net/lists/linux-mmc/msg26519.html
>
> Also, please include the "Signed-off-by:" field in the patch.
>
> Thanks.
>
> On Tue, Jun 17, 2014 at 1:41 AM,  <Darren_Chen@xxxxxxxxxxxxxxxxxxxxxxxx> wrote:
>> By Field Firmware Update feature in JEDEC eMMC 5.0 spec.
>> Verified successfully in daisy.
>> Note,also has to patch kernel drivers.
>> Diff with stabilize-5784.0.B
>>
>> diff --git a/mmc.c b/mmc.c
>> index a30426d..98c1950 100644
>> --- a/mmc.c
>> +++ b/mmc.c
>> @@ -115,6 +115,11 @@ static struct Command commands[] = {
>>                  "Send Sanitize command to the <device>.\nThis will delete
>> the unmapped memory region of the device.",
>>            NULL
>>          },
>> +        { do_emmc50_ffu, -2,
>> +                  "ffu", "<image name> <device>\n"
>> +                        "run eMMC 5.0 Field firmware update.\n.",
>> +                  NULL
>> +        },
>>          { 0, 0, 0, 0 }
>>  };
>>
>> diff --git a/mmc.h b/mmc.h
>> index 9871d62..b338e1d 100644
>> --- a/mmc.h
>> +++ b/mmc.h
>> @@ -64,6 +64,7 @@
>>  #define EXT_CSD_ENH_START_ADDR_2        138
>>  #define EXT_CSD_ENH_START_ADDR_1        137
>>  #define EXT_CSD_ENH_START_ADDR_0        136
>> +#define EXT_CSD_REV                        192
>>  #define EXT_CSD_NATIVE_SECTOR_SIZE        63 /* R */
>>  #define EXT_CSD_USE_NATIVE_SECTOR        62 /* R/W */
>>  #define EXT_CSD_DATA_SECTOR_SIZE        61 /* R */
>> @@ -79,6 +80,9 @@
>>   */
>>  #define BKOPS_ENABLE        (1<<0)
>>
>> +#define MMC_FFU_DOWNLOAD_OP        302
>> +#define MMC_FFU_INSTALL_OP        303
>> +
>>  /*
>>   * EXT_CSD field definitions
>>   */
>> diff --git a/mmc_cmds.c b/mmc_cmds.c
>> index b61f6ae..6811ab4 100644
>> --- a/mmc_cmds.c
>> +++ b/mmc_cmds.c
>> @@ -1243,7 +1243,7 @@ int do_sanitize(int nargs, char **argv)
>>          char *device;
>>
>>          CHECK(nargs != 2, "Usage: mmc sanitize </path/to/mmcblkX>\n",
>> -                        exit(1));
>> +                exit(1));
>>
>>          device = argv[1];
>>
>> @@ -1264,3 +1264,94 @@ int do_sanitize(int nargs, char **argv)
>>
>>  }
>>
>> +static int ffu_download_image(char *fname, int mmc_fd, int blk_size)
>> +{
>> +        int ret = 0;
>> +        struct mmc_ioc_cmd mmc_ioc_cmd;
>> +        char firmware_name[512];
>> +
>> +        memset(firmware_name, 0, sizeof(firmware_name));
>> +        memcpy(firmware_name, fname, strlen(fname));
>> +
>> +        /* prepare and send ioctl */
>> +        memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
>> +        mmc_ioc_cmd.opcode = MMC_FFU_DOWNLOAD_OP;
>> +        mmc_ioc_cmd.blksz = blk_size;
>> +        mmc_ioc_cmd.blocks = 1;
>> +        mmc_ioc_cmd.arg = 0;
>> +        mmc_ioc_cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
>> +        mmc_ioc_cmd.write_flag = 1;
>> +        mmc_ioc_cmd_set_data(mmc_ioc_cmd, firmware_name);
>> +        ret = ioctl(mmc_fd, MMC_IOC_CMD, &mmc_ioc_cmd);
>> +        if (ret) {
>> +                fprintf(stderr, "ioctl FW download error%d\n", ret);
>> +                return ret;
>> +        }
>> +
>> +        return ret;
>> +}
>> +
>> +static int ffu_install(int mmc_fd, int blk_size)
>> +{
>> +        int ret;
>> +        struct mmc_ioc_cmd mmc_ioc_cmd;
>> +
>> +        memset(&mmc_ioc_cmd, 0, sizeof(mmc_ioc_cmd));
>> +        mmc_ioc_cmd.opcode = MMC_FFU_INSTALL_OP;
>> +        mmc_ioc_cmd.blksz = blk_size;
>> +        mmc_ioc_cmd.blocks = 0;
>> +        mmc_ioc_cmd.arg = 0;
>> +        mmc_ioc_cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
>> +        mmc_ioc_cmd.write_flag = 0;
>> +        ret = ioctl(mmc_fd, MMC_IOC_CMD, &mmc_ioc_cmd);
>> +
>> +        return ret;
>> +}
>> +
>> +int do_emmc50_ffu (int nargs, char **argv)
>> +{
>> +        int fd, ret;
>> +        char *device;
>> +        char *path;
>> +        __u8 ext_csd[512];
>> +        int blk_size;
>> +
>> +        CHECK(nargs != 3, "Usage: ffu <image name> </path/to/mmcblkX> \n",
>> +                exit(1));
>> +
>> +        device = argv[2];
>> +        fd = open(device, O_RDWR);
>> +        if (fd < 0) {
>> +                perror("open");
>> +                exit(1);
>> +        }
>> +
>> +        ret = read_extcsd(fd, ext_csd);
>> +        if (ret) {
>> +                fprintf(stderr, "Could not read EXT_CSD from %s\n",
>> device);
>> +                exit(1);
>> +        }
>> +
>> +        if (ext_csd[EXT_CSD_REV] < 6) {
>> +                fprintf(stderr, "FFU is not supported on device %s\n",
>> device);
>> +                exit(1);
>> +        }
>> +
>> +        blk_size = (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] == 0) ? 512 : 4096;
>> +        path = argv[1];
>> +        ret = ffu_download_image(path, fd, blk_size);
>> +        if (ret) {
>> +                fprintf(stderr, "FFU failed %d\n", ret);
>> +                exit(1);
>> +        }
>> +
>> +        ret = ffu_install(fd, blk_size);
>> +        if (ret) {
>> +                fprintf(stderr, "FFU failed %d\n", ret);
>> +                exit(1);
>> +        }
>> +
>> +        close(fd);
>> +        fprintf(stderr, "FFU finished with status %d \n", ret);
>> +        return ret;
>> +}
>> diff --git a/mmc_cmds.h b/mmc_cmds.h
>> index f023fb0..4c7b6ed 100644
>> --- a/mmc_cmds.h
>> +++ b/mmc_cmds.h
>> @@ -29,3 +29,4 @@ int do_sanitize(int nargs, char **argv);
>>  int do_status_get(int nargs, char **argv);
>>  int do_enh_area_set(int nargs, char **argv);
>>  int do_write_reliability_set(int nargs, char **argv);
>> +int do_emmc50_ffu(int nargs, char **argv);
>>
>>
>>
>>
>>
>>
>> diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
>> index 8f3194b..b616591 100644
>> --- a/drivers/base/Kconfig
>> +++ b/drivers/base/Kconfig
>> @@ -85,7 +85,7 @@ config PREVENT_FIRMWARE_BUILD
>>
>>  config FW_LOADER
>>          tristate "Userspace firmware loading support" if EXPERT
>> -        default y
>> +        default n
>>          ---help---
>>            This option is provided for the case where none of the in-tree
>> modules
>>            require userspace firmware loading support, but a module built
>> @@ -94,7 +94,7 @@ config FW_LOADER
>>  config FIRMWARE_IN_KERNEL
>>          bool "Include in-kernel firmware blobs in kernel binary"
>>          depends on FW_LOADER
>> -        default y
>> +        default n
>>          help
>>            The kernel source tree includes a number of firmware 'blobs'
>>            that are used by various drivers. The recommended way to
>> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
>> index 3b1f783..66f141f 100644
>> --- a/drivers/mmc/card/Kconfig
>> +++ b/drivers/mmc/card/Kconfig
>> @@ -67,3 +67,12 @@ 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
>> +        default y
>> +        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 7fcf041..b60cfaa 100644
>> --- a/drivers/mmc/card/block.c
>> +++ b/drivers/mmc/card/block.c
>> @@ -40,6 +40,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>
>>
>> @@ -487,6 +488,17 @@ static int mmc_blk_ioctl_cmd(struct block_device
>> *bdev,
>>
>>          mmc_claim_host(card->host);
>>
>> +        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..9e7a4ca
>> --- /dev/null
>> +++ b/drivers/mmc/card/ffu.c
>> @@ -0,0 +1,605 @@
>> +/*
>> + * *  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/init.h>
>> +#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>
>> +#include "../core/mmc_ops.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 < 6)
>> +                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;
>> +        }
>> +
>> +        /* 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 */
>> +        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;
>> +        }
>> +
>> +        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;
>> +
>> +        printk("FFU programmed data length in sector = %
>> d\r\n",ffu_data_len);
>> +
>> +        /* 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) {
>> +                        pr_err("EPERM FFU programmed data length in sector
>> = %d\r\n",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/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
>> index 428aca1..1ec14b2 100644
>> --- a/drivers/mmc/core/core.c
>> +++ b/drivers/mmc/core/core.c
>> @@ -2558,6 +2558,38 @@ int mmc_resume_host(struct mmc_host *host)
>>  }
>>  EXPORT_SYMBOL(mmc_resume_host);
>>
>> +int mmc_cache_ctrl(struct mmc_host *host, u8 enable)
>> +{
>> +        struct mmc_card *card = host->card;
>> +        unsigned int timeout;
>> +        int err = 0;
>> +
>> +        if (!(host->caps2 & MMC_CAP2_CACHE_CTRL) ||
>> +                        mmc_card_is_removable(host))
>> +                return err;
>> +
>> +        if (card && mmc_card_mmc(card) &&
>> +                        (card->ext_csd.cache_size > 0)) {
>> +                enable = !!enable;
>> +
>> +                if (card->ext_csd.cache_ctrl ^ enable) {
>> +                        timeout = enable ? card-
>>>ext_csd.generic_cmd6_time : 0;
>> +                        err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
>> +                                        EXT_CSD_CACHE_CTRL, enable,
>> timeout);
>> +                        if (err)
>> +                               pr_err("%s: cache %s error %d\n",
>> +                                                mmc_hostname(card->host),
>> +                                                enable ? "on" : "off",
>> +                                                err);
>> +                        else
>> +                                card->ext_csd.cache_ctrl = enable;
>> +                }
>> +        }
>> +
>> +        return err;
>> +}
>> +EXPORT_SYMBOL(mmc_cache_ctrl);
>> +
>>  /* Do the card removal on suspend if card is assumed removeable
>>   * Do that in pm notifier while userspace isn't yet frozen, so we will be
>> able
>>     to sync the card.
>> diff --git a/include/linux/mmc/ffu.h b/include/linux/mmc/ffu.h
>> new file mode 100644
>> index 0000000..4924b8e7
>> --- /dev/null
>> +++ b/include/linux/mmc/ffu.h
>> @@ -0,0 +1,56 @@
>> +/*
>> + *
>> + *  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/host.h b/include/linux/mmc/host.h
>> index 91caaa9..b589381 100644
>> --- a/include/linux/mmc/host.h
>> +++ b/include/linux/mmc/host.h
>> @@ -248,6 +248,7 @@ struct mmc_host {
>>          u32                        caps2;                /* More host
>> capabilities
>> */
>>
>>  #define MMC_CAP2_BOOTPART_NOACC        (1 << 0)        /* Boot partition
>> no access */
>> +#define MMC_CAP2_CACHE_CTRL        (1 << 1)        /* Allow cache control
>> */
>>  #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2)        /* Notify poweroff
>> supported */
>>  #define MMC_CAP2_NO_MULTI_READ        (1 << 3)        /* Multiblock reads
>> don't
>> work */
>>  #define MMC_CAP2_NO_SLEEP_CMD        (1 << 4)        /* Don't allow sleep
>> command */
>> @@ -372,6 +373,8 @@ extern int mmc_power_restore_host(struct mmc_host
>> *host);
>>  extern void mmc_detect_change(struct mmc_host *, unsigned long delay);
>>  extern void mmc_request_done(struct mmc_host *, struct mmc_request *);
>>
>> +extern int mmc_cache_ctrl(struct mmc_host *, u8);
>> +
>>  static inline void mmc_signal_sdio_irq(struct mmc_host *host)
>>  {
>>          host->ops->enable_sdio_irq(host, 0);
>> diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h
>> index 94d532e..d58e14b 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 */
>> @@ -287,6 +290,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 */
>> @@ -322,6 +326,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_BKOPS_SUPPORT                502        /* 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




[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux