From ea2453d56b8860dbd18a3c517531ffc8dcb5c839 Mon Sep 17 00:00:00 2001 From: Pantelis Antoniou Date: Thu, 14 Mar 2013 05:32:48 +0000 Subject: dfu: Support larger than memory transfers. 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 Signed-off-by: Tom Rini --- drivers/dfu/dfu.c | 243 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 190 insertions(+), 53 deletions(-) (limited to 'drivers/dfu/dfu.c') diff --git a/drivers/dfu/dfu.c b/drivers/dfu/dfu.c index e8477fb7c..609061dfa 100644 --- a/drivers/dfu/dfu.c +++ b/drivers/dfu/dfu.c @@ -44,90 +44,227 @@ 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, buf, &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; } -- cgit v1.2.3-70-g09d2 From c6631764c2a64efc91c84077ca65f4fee153f133 Mon Sep 17 00:00:00 2001 From: Pantelis Antoniou Date: Thu, 14 Mar 2013 05:32:52 +0000 Subject: dfu: NAND specific routines for DFU operation Support for NAND storage devices to work with the DFU framework. Signed-off-by: Pantelis Antoniou Signed-off-by: Tom Rini Acked-by: Scott Wood --- README | 3 + drivers/dfu/Makefile | 1 + drivers/dfu/dfu.c | 8 +++ drivers/dfu/dfu_nand.c | 187 +++++++++++++++++++++++++++++++++++++++++++++++++ include/dfu.h | 23 ++++++ 5 files changed, 222 insertions(+) create mode 100644 drivers/dfu/dfu_nand.c (limited to 'drivers/dfu/dfu.c') diff --git a/README b/README index 7879eef16..276fac9c6 100644 --- a/README +++ b/README @@ -1357,6 +1357,9 @@ The following options need to be configured: CONFIG_DFU_MMC This enables support for exposing (e)MMC devices via DFU. + CONFIG_DFU_NAND + This enables support for exposing NAND 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 diff --git a/drivers/dfu/Makefile b/drivers/dfu/Makefile index 7b717bce2..153095d71 100644 --- a/drivers/dfu/Makefile +++ b/drivers/dfu/Makefile @@ -27,6 +27,7 @@ LIB = $(obj)libdfu.o COBJS-$(CONFIG_DFU_FUNCTION) += dfu.o COBJS-$(CONFIG_DFU_MMC) += dfu_mmc.o +COBJS-$(CONFIG_DFU_NAND) += dfu_nand.o SRCS := $(COBJS-y:.o=.c) OBJS := $(addprefix $(obj),$(COBJS-y)) diff --git a/drivers/dfu/dfu.c b/drivers/dfu/dfu.c index 609061dfa..6af6890d0 100644 --- a/drivers/dfu/dfu.c +++ b/drivers/dfu/dfu.c @@ -85,6 +85,7 @@ int dfu_write(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) /* initial state */ dfu->crc = 0; dfu->offset = 0; + dfu->bad_skip = 0; dfu->i_blk_seq_num = 0; dfu->i_buf_start = dfu_buf; dfu->i_buf_end = dfu_buf + sizeof(dfu_buf); @@ -233,6 +234,8 @@ int dfu_read(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) dfu->i_buf = dfu->i_buf_start; dfu->b_left = 0; + dfu->bad_skip = 0; + dfu->inited = 1; } @@ -262,6 +265,8 @@ int dfu_read(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) dfu->i_buf = dfu->i_buf_start; dfu->b_left = 0; + dfu->bad_skip = 0; + dfu->inited = 0; } @@ -284,6 +289,9 @@ static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt, if (strcmp(interface, "mmc") == 0) { if (dfu_fill_entity_mmc(dfu, s)) return -1; + } else if (strcmp(interface, "nand") == 0) { + if (dfu_fill_entity_nand(dfu, s)) + return -1; } else { printf("%s: Device %s not (yet) supported!\n", __func__, interface); diff --git a/drivers/dfu/dfu_nand.c b/drivers/dfu/dfu_nand.c new file mode 100644 index 000000000..7dc89b2f2 --- /dev/null +++ b/drivers/dfu/dfu_nand.c @@ -0,0 +1,187 @@ +/* + * dfu_nand.c -- DFU for NAND routines. + * + * Copyright (C) 2012-2013 Texas Instruments, Inc. + * + * Based on dfu_mmc.c which is: + * Copyright (C) 2012 Samsung Electronics + * author: Lukasz Majewski + * + * 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 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum dfu_nand_op { + DFU_OP_READ = 1, + DFU_OP_WRITE, +}; + +static int nand_block_op(enum dfu_nand_op op, struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + loff_t start, lim; + size_t count, actual; + int ret; + nand_info_t *nand; + + /* if buf == NULL return total size of the area */ + if (buf == NULL) { + *len = dfu->data.nand.size; + return 0; + } + + start = dfu->data.nand.start + offset + dfu->bad_skip; + lim = dfu->data.nand.start + dfu->data.nand.size - start; + count = *len; + + if (nand_curr_device < 0 || + nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || + !nand_info[nand_curr_device].name) { + printf("%s: invalid nand device\n", __func__); + return -1; + } + + nand = &nand_info[nand_curr_device]; + + if (op == DFU_OP_READ) + ret = nand_read_skip_bad(nand, start, &count, &actual, + lim, buf); + else + ret = nand_write_skip_bad(nand, start, &count, &actual, + lim, buf, 0); + + if (ret != 0) { + printf("%s: nand_%s_skip_bad call failed at %llx!\n", + __func__, op == DFU_OP_READ ? "read" : "write", + start); + return ret; + } + + /* + * Find out where we stopped writing data. This can be deeper into + * the NAND than we expected due to having to skip bad blocks. So + * we must take this into account for the next write, if any. + */ + if (actual > count) + dfu->bad_skip += actual - count; + + return ret; +} + +static inline int nand_block_write(struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + return nand_block_op(DFU_OP_WRITE, dfu, offset, buf, len); +} + +static inline int nand_block_read(struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + return nand_block_op(DFU_OP_READ, dfu, offset, buf, len); +} + +static int dfu_write_medium_nand(struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + int ret = -1; + + switch (dfu->layout) { + case DFU_RAW_ADDR: + ret = nand_block_write(dfu, offset, buf, len); + break; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + } + + return ret; +} + +static int dfu_read_medium_nand(struct dfu_entity *dfu, u64 offset, void *buf, + long *len) +{ + int ret = -1; + + switch (dfu->layout) { + case DFU_RAW_ADDR: + ret = nand_block_read(dfu, offset, buf, len); + break; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + } + + return ret; +} + +int dfu_fill_entity_nand(struct dfu_entity *dfu, char *s) +{ + char *st; + int ret, dev, part; + + dfu->dev_type = DFU_DEV_NAND; + st = strsep(&s, " "); + if (!strcmp(st, "raw")) { + dfu->layout = DFU_RAW_ADDR; + dfu->data.nand.start = simple_strtoul(s, &s, 16); + s++; + dfu->data.nand.size = simple_strtoul(s, &s, 16); + } else if (!strcmp(st, "part")) { + char mtd_id[32]; + struct mtd_device *mtd_dev; + u8 part_num; + struct part_info *pi; + + dfu->layout = DFU_RAW_ADDR; + + dev = simple_strtoul(s, &s, 10); + s++; + part = simple_strtoul(s, &s, 10); + + sprintf(mtd_id, "%s%d,%d", "nand", dev, part - 1); + printf("using id '%s'\n", mtd_id); + + mtdparts_init(); + + ret = find_dev_and_part(mtd_id, &mtd_dev, &part_num, &pi); + if (ret != 0) { + printf("Could not locate '%s'\n", mtd_id); + return -1; + } + + dfu->data.nand.start = pi->offset; + dfu->data.nand.size = pi->size; + + } else { + printf("%s: Memory layout (%s) not supported!\n", __func__, st); + return -1; + } + + dfu->read_medium = dfu_read_medium_nand; + dfu->write_medium = dfu_write_medium_nand; + + /* initial state */ + dfu->inited = 0; + + return 0; +} diff --git a/include/dfu.h b/include/dfu.h index cc9926861..a107f4b13 100644 --- a/include/dfu.h +++ b/include/dfu.h @@ -52,6 +52,15 @@ struct mmc_internal_data { unsigned int part; }; +struct nand_internal_data { + /* RAW programming */ + u64 start; + u64 size; + + unsigned int dev; + unsigned int part; +}; + static inline unsigned int get_mmc_blk_size(int dev) { return find_mmc_device(dev)->read_bl_len; @@ -74,6 +83,7 @@ struct dfu_entity { union { struct mmc_internal_data mmc; + struct nand_internal_data nand; } data; int (*read_medium)(struct dfu_entity *dfu, @@ -96,6 +106,8 @@ struct dfu_entity { long r_left; long b_left; + u32 bad_skip; /* for nand use */ + unsigned int inited:1; }; @@ -120,4 +132,15 @@ static inline int dfu_fill_entity_mmc(struct dfu_entity *dfu, char *s) return -1; } #endif + +#ifdef CONFIG_DFU_NAND +extern int dfu_fill_entity_nand(struct dfu_entity *dfu, char *s); +#else +static inline int dfu_fill_entity_nand(struct dfu_entity *dfu, char *s) +{ + puts("NAND support not available!\n"); + return -1; +} +#endif + #endif /* __DFU_ENTITY_H_ */ -- cgit v1.2.3-70-g09d2