[U-Boot] [PATCH v5 2/9] dfu: Support larger than memory transfers.

Lukasz Majewski
2013-03-11T09:38:12Z

Hi Tom,

> From: Pantelis Antoniou <panto@antoniou-consulting.com>
>
> Previously we didn't support upload/download larger than available
> memory. This is pretty bad when you have to update your root
> filesystem for example.
>
> This patch removes that limitation (and the crashes when you
> transfered any file larger than 4MB) by making raw image writes be
> done in chunks and making file maximum size be configurable.
>
> The sequence number is a 16 bit counter; make sure we handle rollover
> correctly. This fixes the wrong transfers for large (> 256MB) images.
>
> Also utilize a variable to handle initialization, so that we don't
> rely on just the counter sent by the host.
>
> Signed-off-by: Pantelis Antoniou <panto@antoniou-consulting.com>
> Signed-off-by: Tom Rini <trini@ti.com>

Acked-by: Lukasz Majewski <l.majewski@samsung.com>

Test-hw: Exynos 4210 (Trats)

Tested-by: Lukasz Majewski <l.majewski@samsung.com>

> ---
> Changes in v5:
> - Rework Pantelis' "dfu: Support larger than memory transfers" to not
> break filesystem writing
>
> Changes in v4: None
> Changes in v3: None
> Changes in v2: None
>
> README | 7 ++
> drivers/dfu/dfu.c | 245
> ++++++++++++++++++++++++++++++++++++++-----------
> drivers/dfu/dfu_mmc.c | 116 +++++++++++++++++------
> include/dfu.h | 26 +++++- 4 files changed, 310
> insertions(+), 84 deletions(-)
>
> diff --git a/README b/README
> index 900ae5f..154b82f 100644
> --- a/README
> +++ b/README
> @@ -1338,6 +1338,13 @@ The following options need to be configured:
> CONFIG_DFU_MMC
> This enables support for exposing (e)MMC devices via
> DFU.
> + CONFIG_SYS_DFU_MAX_FILE_SIZE
> + When updating files rather than the raw storage
> device,
> + we use a static buffer to copy the file into and
> then write
> + the buffer once we've been given the whole file.
> Define
> + this to the maximum filesize (in bytes) for the
> buffer.
> + Default is 4 MiB if undefined.
> +
> - Journaling Flash filesystem support:
> CONFIG_JFFS2_NAND, CONFIG_JFFS2_NAND_OFF,
> CONFIG_JFFS2_NAND_SIZE, CONFIG_JFFS2_NAND_DEV
> diff --git a/drivers/dfu/dfu.c b/drivers/dfu/dfu.c
> index e8477fb..2fecf77 100644
> --- a/drivers/dfu/dfu.c
> +++ b/drivers/dfu/dfu.c
> @@ -44,90 +44,229 @@ static int dfu_find_alt_num(const char *s)
> static unsigned char __aligned(CONFIG_SYS_CACHELINE_SIZE)
> dfu_buf[DFU_DATA_BUF_SIZE];
>
> +static int dfu_write_buffer_drain(struct dfu_entity *dfu)
> +{
> + long w_size;
> + int ret;
> +
> + /* flush size? */
> + w_size = dfu->i_buf - dfu->i_buf_start;
> + if (w_size == 0)
> + return 0;
> +
> + /* update CRC32 */
> + dfu->crc = crc32(dfu->crc, dfu->i_buf_start, w_size);
> +
> + ret = dfu->write_medium(dfu, dfu->offset, dfu->i_buf_start,
> &w_size);
> + if (ret)
> + debug("%s: Write error!\n", __func__);
> +
> + /* point back */
> + dfu->i_buf = dfu->i_buf_start;
> +
> + /* update offset */
> + dfu->offset += w_size;
> +
> + puts("#");
> +
> + return ret;
> +}
> +
> int dfu_write(struct dfu_entity *dfu, void *buf, int size, int
> blk_seq_num) {
> - static unsigned char *i_buf;
> - static int i_blk_seq_num;
> - long w_size = 0;
> int ret = 0;
> + int tret;
> +
> + debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x "
> + "offset: 0x%llx bufoffset: 0x%x\n",
> + __func__, dfu->name, buf, size, blk_seq_num,
> dfu->offset,
> + dfu->i_buf - dfu->i_buf_start);
> +
> + if (!dfu->inited) {
> + /* initial state */
> + dfu->crc = 0;
> + dfu->offset = 0;
> + dfu->i_blk_seq_num = 0;
> + dfu->i_buf_start = dfu_buf;
> + dfu->i_buf_end = dfu_buf + sizeof(dfu_buf);
> + dfu->i_buf = dfu->i_buf_start;
> +
> + dfu->inited = 1;
> + }
>
> - debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x i_buf:
> 0x%p\n",
> - __func__, dfu->name, buf, size, blk_seq_num, i_buf);
> + if (dfu->i_blk_seq_num != blk_seq_num) {
> + printf("%s: Wrong sequence number! [%d] [%d]\n",
> + __func__, dfu->i_blk_seq_num, blk_seq_num);
> + return -1;
> + }
>
> - if (blk_seq_num == 0) {
> - i_buf = dfu_buf;
> - i_blk_seq_num = 0;
> + /* DFU 1.1 standard says:
> + * The wBlockNum field is a block sequence number. It
> increments each
> + * time a block is transferred, wrapping to zero from
> 65,535. It is used
> + * to provide useful context to the DFU loader in the
> device."
> + *
> + * This means that it's a 16 bit counter that roll-overs at
> + * 0xffff -> 0x0000. By having a typical 4K transfer block
> + * we roll-over at exactly 256MB. Not very fun to debug.
> + *
> + * Handling rollover, and having an inited variable,
> + * makes things work.
> + */
> +
> + /* handle rollover */
> + dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff;
> +
> + /* flush buffer if overflow */
> + if ((dfu->i_buf + size) > dfu->i_buf_end) {
> + tret = dfu_write_buffer_drain(dfu);
> + if (ret == 0)
> + ret = tret;
> }
>
> - if (i_blk_seq_num++ != blk_seq_num) {
> - printf("%s: Wrong sequence number! [%d] [%d]\n",
> - __func__, i_blk_seq_num, blk_seq_num);
> + /* we should be in buffer now (if not then size too large) */
> + if ((dfu->i_buf + size) > dfu->i_buf_end) {
> + printf("%s: Wrong size! [%d] [%d] - %d\n",
> + __func__, dfu->i_blk_seq_num, blk_seq_num,
> size); return -1;
> }
>
> - memcpy(i_buf, buf, size);
> - i_buf += size;
> + memcpy(dfu->i_buf, buf, size);
> + dfu->i_buf += size;
>
> + /* if end or if buffer full flush */
> + if (size == 0 || (dfu->i_buf + size) > dfu->i_buf_end) {
> + tret = dfu_write_buffer_drain(dfu);
> + if (ret == 0)
> + ret = tret;
> + }
> +
> + /* end? */
> if (size == 0) {
> - /* Integrity check (if needed) */
> - debug("%s: %s %d [B] CRC32: 0x%x\n", __func__,
> dfu->name,
> - i_buf - dfu_buf, crc32(0, dfu_buf, i_buf -
> dfu_buf));
> + /* Now try and flush to the medium if needed. */
> + if (dfu->flush_medium)
> + ret = dfu->flush_medium(dfu);
> + printf("\nDFU complete CRC32: 0x%08x\n", dfu->crc);
>
> - w_size = i_buf - dfu_buf;
> - ret = dfu->write_medium(dfu, dfu_buf, &w_size);
> - if (ret)
> - debug("%s: Write error!\n", __func__);
> + /* clear everything */
> + dfu->crc = 0;
> + dfu->offset = 0;
> + dfu->i_blk_seq_num = 0;
> + dfu->i_buf_start = dfu_buf;
> + dfu->i_buf_end = dfu_buf + sizeof(dfu_buf);
> + dfu->i_buf = dfu->i_buf_start;
> +
> + dfu->inited = 0;
>
> - i_blk_seq_num = 0;
> - i_buf = NULL;
> - return ret;
> }
>
> - return ret;
> + return ret = 0 ? size : ret;
> +}
> +
> +static int dfu_read_buffer_fill(struct dfu_entity *dfu, void *buf,
> int size) +{
> + long chunk;
> + int ret, readn;
> +
> + readn = 0;
> + while (size > 0) {
> +
> + /* get chunk that can be read */
> + chunk = min(size, dfu->b_left);
> + /* consume */
> + if (chunk > 0) {
> + memcpy(buf, dfu->i_buf, chunk);
> + dfu->crc = crc32(dfu->crc, buf, chunk);
> + dfu->i_buf += chunk;
> + dfu->b_left -= chunk;
> + size -= chunk;
> + buf += chunk;
> + readn += chunk;
> + }
> +
> + /* all done */
> + if (size > 0) {
> + /* no more to read */
> + if (dfu->r_left == 0)
> + break;
> +
> + dfu->i_buf = dfu->i_buf_start;
> + dfu->b_left = dfu->i_buf_end -
> dfu->i_buf_start; +
> + /* got to read, but buffer is empty */
> + if (dfu->b_left > dfu->r_left)
> + dfu->b_left = dfu->r_left;
> + ret = dfu->read_medium(dfu, dfu->offset,
> dfu->i_buf,
> + &dfu->b_left);
> + if (ret != 0) {
> + debug("%s: Read error!\n", __func__);
> + return ret;
> + }
> + dfu->offset += dfu->b_left;
> + dfu->r_left -= dfu->b_left;
> +
> + puts("#");
> + }
> + }
> +
> + return readn;
> }
>
> int dfu_read(struct dfu_entity *dfu, void *buf, int size, int
> blk_seq_num) {
> - static unsigned char *i_buf;
> - static int i_blk_seq_num;
> - static long r_size;
> - static u32 crc;
> int ret = 0;
>
> debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x i_buf:
> 0x%p\n",
> - __func__, dfu->name, buf, size, blk_seq_num, i_buf);
> -
> - if (blk_seq_num == 0) {
> - i_buf = dfu_buf;
> - ret = dfu->read_medium(dfu, i_buf, &r_size);
> - debug("%s: %s %ld [B]\n", __func__, dfu->name,
> r_size);
> - i_blk_seq_num = 0;
> - /* Integrity check (if needed) */
> - crc = crc32(0, dfu_buf, r_size);
> + __func__, dfu->name, buf, size, blk_seq_num,
> dfu->i_buf); +
> + if (!dfu->inited) {
> + ret = dfu->read_medium(dfu, 0, NULL, &dfu->r_left);
> + if (ret != 0) {
> + debug("%s: failed to get r_left\n",
> __func__);
> + return ret;
> + }
> +
> + debug("%s: %s %ld [B]\n", __func__, dfu->name,
> dfu->r_left); +
> + dfu->i_blk_seq_num = 0;
> + dfu->crc = 0;
> + dfu->offset = 0;
> + dfu->i_buf_start = dfu_buf;
> + dfu->i_buf_end = dfu_buf + sizeof(dfu_buf);
> + dfu->i_buf = dfu->i_buf_start;
> + dfu->b_left = 0;
> +
> + dfu->inited = 1;
> }
>
> - if (i_blk_seq_num++ != blk_seq_num) {
> + if (dfu->i_blk_seq_num != blk_seq_num) {
> printf("%s: Wrong sequence number! [%d] [%d]\n",
> - __func__, i_blk_seq_num, blk_seq_num);
> + __func__, dfu->i_blk_seq_num, blk_seq_num);
> return -1;
> }
> + /* handle rollover */
> + dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff;
>
> - if (r_size >= size) {
> - memcpy(buf, i_buf, size);
> - i_buf += size;
> - r_size -= size;
> - return size;
> - } else {
> - memcpy(buf, i_buf, r_size);
> - i_buf += r_size;
> - debug("%s: %s CRC32: 0x%x\n", __func__, dfu->name,
> crc);
> - puts("UPLOAD ... done\nCtrl+C to exit ...\n");
> + ret = dfu_read_buffer_fill(dfu, buf, size);
> + if (ret < 0) {
> + printf("%s: Failed to fill buffer\n", __func__);
> + return -1;
> + }
> +
> + if (ret < size) {
> + debug("%s: %s CRC32: 0x%x\n", __func__, dfu->name,
> dfu->crc);
> + puts("\nUPLOAD ... done\nCtrl+C to exit ...\n");
>
> - i_buf = NULL;
> - i_blk_seq_num = 0;
> - crc = 0;
> - return r_size;
> + dfu->i_blk_seq_num = 0;
> + dfu->crc = 0;
> + dfu->offset = 0;
> + dfu->i_buf_start = dfu_buf;
> + dfu->i_buf_end = dfu_buf + sizeof(dfu_buf);
> + dfu->i_buf = dfu->i_buf_start;
> + dfu->b_left = 0;
> +
> + dfu->inited = 0;
> }
> +
> return ret;
> }
>
> diff --git a/drivers/dfu/dfu_mmc.c b/drivers/dfu/dfu_mmc.c
> index 083d745..2d5ffd8 100644
> --- a/drivers/dfu/dfu_mmc.c
> +++ b/drivers/dfu/dfu_mmc.c
> @@ -22,6 +22,7 @@
> #include <common.h>
> #include <malloc.h>
> #include <errno.h>
> +#include <div64.h>
> #include <dfu.h>
>
> enum dfu_mmc_op {
> @@ -29,36 +30,67 @@ enum dfu_mmc_op {
> DFU_OP_WRITE,
> };
>
> +static unsigned char __aligned(CONFIG_SYS_CACHELINE_SIZE)
> +
> dfu_file_buf[CONFIG_SYS_DFU_MAX_FILE_SIZE]; +static long
> dfu_file_buf_len; +
> static int mmc_block_op(enum dfu_mmc_op op, struct dfu_entity *dfu,
> - void *buf, long *len)
> + u64 offset, void *buf, long *len)
> {
> char cmd_buf[DFU_CMD_BUF_SIZE];
> + u32 blk_start, blk_count;
> +
> + /*
> + * We must ensure that we work in lba_blk_size chunks, so
> ALIGN
> + * this value.
> + */
> + *len = ALIGN(*len, dfu->data.mmc.lba_blk_size);
> +
> + blk_start = dfu->data.mmc.lba_start +
> + (u32)lldiv(offset,
> dfu->data.mmc.lba_blk_size);
> + blk_count = *len / dfu->data.mmc.lba_blk_size;
> + if (*len + blk_start >
> + dfu->data.mmc.lba_size *
> dfu->data.mmc.lba_size) {
> + puts("Request would exceed designated area!\n");
> + return -EINVAL;
> + }
>
> - sprintf(cmd_buf, "mmc %s 0x%x %x %x",
> + sprintf(cmd_buf, "mmc %s %p %x %x",
> op == DFU_OP_READ ? "read" : "write",
> - (unsigned int) buf,
> - dfu->data.mmc.lba_start,
> - dfu->data.mmc.lba_size);
> -
> - if (op == DFU_OP_READ)
> - *len = dfu->data.mmc.lba_blk_size *
> dfu->data.mmc.lba_size;
> + buf, blk_start, blk_count);
>
> debug("%s: %s 0x%p\n", __func__, cmd_buf, cmd_buf);
> return run_command(cmd_buf, 0);
> }
>
> -static inline int mmc_block_write(struct dfu_entity *dfu, void *buf,
> long *len) +static inline int mmc_block_write(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len)
> +{
> + return mmc_block_op(DFU_OP_WRITE, dfu, offset, buf, len);
> +}
> +
> +static inline int mmc_block_read(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len)
> {
> - return mmc_block_op(DFU_OP_WRITE, dfu, buf, len);
> + return mmc_block_op(DFU_OP_READ, dfu, offset, buf, len);
> }
>
> -static inline int mmc_block_read(struct dfu_entity *dfu, void *buf,
> long *len) +static int mmc_file_buffer(struct dfu_entity *dfu, void
> *buf, long *len) {
> - return mmc_block_op(DFU_OP_READ, dfu, buf, len);
> + if (dfu_file_buf_len + *len > CONFIG_SYS_DFU_MAX_FILE_SIZE) {
> + dfu_file_buf_len = 0;
> + return -EINVAL;
> + }
> +
> + /* Add to the current buffer. */
> + memcpy(dfu_file_buf + dfu_file_buf_len, buf, *len);
> + dfu_file_buf_len += *len;
> +
> + return 0;
> }
>
> static int mmc_file_op(enum dfu_mmc_op op, struct dfu_entity *dfu,
> - void *buf, long *len)
> + u64 offset, void *buf, long *len)
> {
> char cmd_buf[DFU_CMD_BUF_SIZE];
> char *str_env;
> @@ -70,8 +102,15 @@ static int mmc_file_op(enum dfu_mmc_op op, struct
> dfu_entity *dfu, op == DFU_OP_READ ? "load" : "write",
> dfu->data.mmc.dev, dfu->data.mmc.part,
> (unsigned int) buf, dfu->name, *len);
> + if (op == DFU_OP_READ && offset != 0)
> + sprintf(cmd_buf + strlen(cmd_buf), " %llx",
> offset); break;
> case DFU_FS_EXT4:
> + if (offset != 0) {
> + debug("%s: Offset value %llx != 0 not
> supported!\n",
> + __func__, offset);
> + return -1;
> + }
> sprintf(cmd_buf, "ext4%s mmc %d:%d /%s 0x%x %ld",
> op == DFU_OP_READ ? "load" : "write",
> dfu->data.mmc.dev, dfu->data.mmc.part,
> @@ -80,6 +119,7 @@ static int mmc_file_op(enum dfu_mmc_op op, struct
> dfu_entity *dfu, default:
> printf("%s: Layout (%s) not (yet) supported!\n",
> __func__, dfu_get_layout(dfu->layout));
> + return -1;
> }
>
> debug("%s: %s 0x%p\n", __func__, cmd_buf, cmd_buf);
> @@ -102,27 +142,24 @@ static int mmc_file_op(enum dfu_mmc_op op,
> struct dfu_entity *dfu, return ret;
> }
>
> -static inline int mmc_file_write(struct dfu_entity *dfu, void *buf,
> long *len) +static inline int mmc_file_read(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len)
> {
> - return mmc_file_op(DFU_OP_WRITE, dfu, buf, len);
> + return mmc_file_op(DFU_OP_READ, dfu, offset, buf, len);
> }
>
> -static inline int mmc_file_read(struct dfu_entity *dfu, void *buf,
> long *len) -{
> - return mmc_file_op(DFU_OP_READ, dfu, buf, len);
> -}
> -
> -int dfu_write_medium_mmc(struct dfu_entity *dfu, void *buf, long
> *len) +int dfu_write_medium_mmc(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len)
> {
> int ret = -1;
>
> switch (dfu->layout) {
> case DFU_RAW_ADDR:
> - ret = mmc_block_write(dfu, buf, len);
> + ret = mmc_block_write(dfu, offset, buf, len);
> break;
> case DFU_FS_FAT:
> case DFU_FS_EXT4:
> - ret = mmc_file_write(dfu, buf, len);
> + ret = mmc_file_buffer(dfu, buf, len);
> break;
> default:
> printf("%s: Layout (%s) not (yet) supported!\n",
> __func__, @@ -132,17 +169,34 @@ int dfu_write_medium_mmc(struct
> dfu_entity *dfu, void *buf, long *len) return ret;
> }
>
> -int dfu_read_medium_mmc(struct dfu_entity *dfu, void *buf, long *len)
> +int dfu_flush_medium_mmc(struct dfu_entity *dfu)
> +{
> + int ret = 0;
> +
> + if (dfu->layout != DFU_RAW_ADDR) {
> + /* Do stuff here. */
> + ret = mmc_file_op(DFU_OP_WRITE, dfu, 0,
> &dfu_file_buf,
> + &dfu_file_buf_len);
> +
> + /* Now that we're done */
> + dfu_file_buf_len = 0;
> + }
> +
> + return ret;
> +}
> +
> +int dfu_read_medium_mmc(struct dfu_entity *dfu, u64 offset, void
> *buf,
> + long *len)
> {
> int ret = -1;
>
> switch (dfu->layout) {
> case DFU_RAW_ADDR:
> - ret = mmc_block_read(dfu, buf, len);
> + ret = mmc_block_read(dfu, offset, buf, len);
> break;
> case DFU_FS_FAT:
> case DFU_FS_EXT4:
> - ret = mmc_file_read(dfu, buf, len);
> + ret = mmc_file_read(dfu, offset, buf, len);
> break;
> default:
> printf("%s: Layout (%s) not (yet) supported!\n",
> __func__, @@ -181,13 +235,15 @@ int dfu_fill_entity_mmc(struct
> dfu_entity *dfu, char *s)
> mmc = find_mmc_device(dev);
> if (mmc == NULL || mmc_init(mmc)) {
> - printf("%s: could not find mmc device
> #%d!\n", __func__, dev);
> + printf("%s: could not find mmc device
> #%d!\n",
> + __func__, dev);
> return -ENODEV;
> }
>
> blk_dev = &mmc->block_dev;
> if (get_partition_info(blk_dev, part, &partinfo) !=
> 0) {
> - printf("%s: could not find partition #%d on
> mmc device #%d!\n",
> + printf("%s: could not find partition #%d "
> + "on mmc device #%d!\n",
> __func__, part, dev);
> return -ENODEV;
> }
> @@ -208,6 +264,10 @@ int dfu_fill_entity_mmc(struct dfu_entity *dfu,
> char *s)
> dfu->read_medium = dfu_read_medium_mmc;
> dfu->write_medium = dfu_write_medium_mmc;
> + dfu->flush_medium = dfu_flush_medium_mmc;
> +
> + /* initial state */
> + dfu->inited = 0;
>
> return 0;
> }
> diff --git a/include/dfu.h b/include/dfu.h
> index 5350d79..5182c6c 100644
> --- a/include/dfu.h
> +++ b/include/dfu.h
> @@ -59,7 +59,10 @@ static inline unsigned int get_mmc_blk_size(int
> dev)
> #define DFU_NAME_SIZE 32
> #define DFU_CMD_BUF_SIZE 128
> -#define DFU_DATA_BUF_SIZE (1024*1024*4) /* 4 MiB */
> +#define DFU_DATA_BUF_SIZE (64 << 10) /* 64 KiB
> */ +#ifndef CONFIG_SYS_DFU_MAX_FILE_SIZE
> +#define CONFIG_SYS_DFU_MAX_FILE_SIZE (4 << 20) /* 4
> MiB */ +#endif
>
> struct dfu_entity {
> char name[DFU_NAME_SIZE];
> @@ -73,10 +76,27 @@ struct dfu_entity {
> struct mmc_internal_data mmc;
> } data;
>
> - int (*read_medium)(struct dfu_entity *dfu, void *buf, long
> *len);
> - int (*write_medium)(struct dfu_entity *dfu, void *buf, long
> *len);
> + int (*read_medium)(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len);
> +
> + int (*write_medium)(struct dfu_entity *dfu,
> + u64 offset, void *buf, long *len);
> +
> + int (*flush_medium)(struct dfu_entity *dfu);
>
> struct list_head list;
> +
> + /* on the fly state */
> + u32 crc;
> + u64 offset;
> + int i_blk_seq_num;
> + u8 *i_buf;
> + u8 *i_buf_start;
> + u8 *i_buf_end;
> + long r_left;
> + long b_left;
> +
> + unsigned int inited:1;
> };
>
> int dfu_config_entities(char *s, char *interface, int num);

--
Best regards,

Lukasz Majewski

Samsung R&D Poland (SRPOL) | Linux Platform Group