diff options
Diffstat (limited to 'roms/u-boot/drivers/dfu')
-rw-r--r-- | roms/u-boot/drivers/dfu/Kconfig | 96 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/Makefile | 13 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu.c | 728 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_alt.c | 125 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_mmc.c | 462 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_mtd.c | 328 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_nand.c | 252 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_ram.c | 87 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_sf.c | 222 | ||||
-rw-r--r-- | roms/u-boot/drivers/dfu/dfu_virt.c | 50 |
10 files changed, 2363 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/dfu/Kconfig b/roms/u-boot/drivers/dfu/Kconfig new file mode 100644 index 000000000..121dc54f5 --- /dev/null +++ b/roms/u-boot/drivers/dfu/Kconfig @@ -0,0 +1,96 @@ +menu "DFU support" + +config DFU + bool + imply DFU_OVER_USB if USB_GADGET + +config DFU_OVER_USB + bool + select HASH + depends on USB_GADGET + +config DFU_OVER_TFTP + bool + depends on NET + +if DFU +config DFU_WRITE_ALT + bool + default n + +config DFU_TFTP + bool "DFU via TFTP" + select UPDATE_COMMON + select DFU_OVER_TFTP + help + This option allows performing update of DFU-managed medium with data + sent via TFTP boot. + + Detailed description of this feature can be found at ./doc/README.dfutftp + +config DFU_TIMEOUT + bool "Timeout waiting for DFU" + help + This option adds an optional timeout parameter for DFU which, if set, + will cause DFU to only wait for that many seconds before exiting. + +config DFU_MMC + bool "MMC back end for DFU" + help + This option enables using DFU to read and write to MMC based storage. + +config DFU_NAND + bool "NAND back end for DFU" + depends on CMD_MTDPARTS + depends on MTD_RAW_NAND + help + This option enables using DFU to read and write to NAND based + storage. + +config DFU_NAND_TRIMFFS + bool "Skip empty pages when flashing UBI images to NAND" + depends on DFU_NAND + help + When flashing UBI images to NAND, enable the DROP_FFS flag to drop + trailing all-0xff pages. + +config DFU_RAM + bool "RAM back end for DFU" + help + This option enables using DFU to read and write RAM on the target. + +config DFU_SF + bool "SPI flash back end for DFU" + help + This option enables using DFU to read and write to SPI flash based + storage. + +config DFU_SF_PART + bool "MTD partition support for SPI flash back end" + depends on DFU_SF && CMD_MTDPARTS + default y + help + This option enables the support of "part" and "partubi" target in + SPI flash DFU back end. + +config DFU_MTD + bool "MTD back end for DFU" + depends on DM_MTD + depends on CMD_MTDPARTS + help + This option enables using DFU to read and write to on any MTD device. + +config DFU_VIRT + bool "VIRTUAL flash back end for DFU" + help + This option enables using DFU to read and write to VIRTUAL device + used at board level to manage specific behavior + (OTP update for example). + +config SET_DFU_ALT_INFO + bool "Dynamic set of DFU alternate information" + help + This option allows to call the function set_dfu_alt_info to + dynamically build dfu_alt_info in board. +endif +endmenu diff --git a/roms/u-boot/drivers/dfu/Makefile b/roms/u-boot/drivers/dfu/Makefile new file mode 100644 index 000000000..dfbf64da6 --- /dev/null +++ b/roms/u-boot/drivers/dfu/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (C) 2012 Samsung Electronics +# Lukasz Majewski <l.majewski@samsung.com> + +obj-$(CONFIG_$(SPL_)DFU) += dfu.o +obj-$(CONFIG_$(SPL_)DFU_MMC) += dfu_mmc.o +obj-$(CONFIG_$(SPL_)DFU_MTD) += dfu_mtd.o +obj-$(CONFIG_$(SPL_)DFU_NAND) += dfu_nand.o +obj-$(CONFIG_$(SPL_)DFU_RAM) += dfu_ram.o +obj-$(CONFIG_$(SPL_)DFU_SF) += dfu_sf.o +obj-$(CONFIG_$(SPL_)DFU_WRITE_ALT) += dfu_alt.o +obj-$(CONFIG_$(SPL_)DFU_VIRT) += dfu_virt.o diff --git a/roms/u-boot/drivers/dfu/dfu.c b/roms/u-boot/drivers/dfu/dfu.c new file mode 100644 index 000000000..213a20e7b --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu.c @@ -0,0 +1,728 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dfu.c -- DFU back-end routines + * + * Copyright (C) 2012 Samsung Electronics + * author: Lukasz Majewski <l.majewski@samsung.com> + */ + +#include <common.h> +#include <env.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> +#include <mmc.h> +#include <fat.h> +#include <dfu.h> +#include <hash.h> +#include <linux/list.h> +#include <linux/compiler.h> + +LIST_HEAD(dfu_list); +static int dfu_alt_num; +static int alt_num_cnt; +static struct hash_algo *dfu_hash_algo; +#ifdef CONFIG_DFU_TIMEOUT +static unsigned long dfu_timeout = 0; +#endif + +bool dfu_reinit_needed = false; + +/* + * The purpose of the dfu_flush_callback() function is to + * provide callback for dfu user + */ +__weak void dfu_flush_callback(struct dfu_entity *dfu) +{ +} + +/* + * The purpose of the dfu_initiated_callback() function is to + * provide callback for dfu user + */ +__weak void dfu_initiated_callback(struct dfu_entity *dfu) +{ +} + +/* + * The purpose of the dfu_usb_get_reset() function is to + * provide information if after USB_DETACH request + * being sent the dfu-util performed reset of USB + * bus. + * + * Described behaviour is the only way to distinct if + * user has typed -e (detach) or -R (reset) when invoking + * dfu-util command. + * + */ +__weak bool dfu_usb_get_reset(void) +{ +#ifdef CONFIG_SPL_DFU_NO_RESET + return false; +#else + return true; +#endif +} + +#ifdef CONFIG_DFU_TIMEOUT +void dfu_set_timeout(unsigned long timeout) +{ + dfu_timeout = timeout; +} + +unsigned long dfu_get_timeout(void) +{ + return dfu_timeout; +} +#endif + +static int dfu_find_alt_num(const char *s) +{ + int i = 0; + + for (; *s; s++) + if (*s == ';') + i++; + + return ++i; +} + +/* + * treat dfu_alt_info with several interface information + * to allow DFU on several device with one command, + * the string format is + * interface devstring'='alternate list (';' separated) + * and each interface separated by '&' + */ +int dfu_config_interfaces(char *env) +{ + struct dfu_entity *dfu; + char *s, *i, *d, *a, *part; + int ret = -EINVAL; + int n = 1; + + s = env; + for (; *s; s++) { + if (*s == ';') + n++; + if (*s == '&') + n++; + } + ret = dfu_alt_init(n, &dfu); + if (ret) + return ret; + + s = env; + while (s) { + ret = -EINVAL; + i = strsep(&s, " "); + if (!i) + break; + d = strsep(&s, "="); + if (!d) + break; + a = strsep(&s, "&"); + if (!a) + a = s; + do { + part = strsep(&a, ";"); + ret = dfu_alt_add(dfu, i, d, part); + if (ret) + return ret; + } while (a); + } + + return ret; +} + +int dfu_init_env_entities(char *interface, char *devstr) +{ + const char *str_env; + char *env_bkp; + int ret = 0; + + dfu_reinit_needed = false; + +#ifdef CONFIG_SET_DFU_ALT_INFO + set_dfu_alt_info(interface, devstr); +#endif + str_env = env_get("dfu_alt_info"); + if (!str_env) { + pr_err("\"dfu_alt_info\" env variable not defined!\n"); + return -EINVAL; + } + + env_bkp = strdup(str_env); + if (!interface && !devstr) + ret = dfu_config_interfaces(env_bkp); + else + ret = dfu_config_entities(env_bkp, interface, devstr); + + if (ret) { + pr_err("DFU entities configuration failed!\n"); + pr_err("(partition table does not match dfu_alt_info?)\n"); + goto done; + } + +done: + free(env_bkp); + return ret; +} + +static unsigned char *dfu_buf; +static unsigned long dfu_buf_size; +static enum dfu_device_type dfu_buf_device_type; + +unsigned char *dfu_free_buf(void) +{ + free(dfu_buf); + dfu_buf = NULL; + return dfu_buf; +} + +unsigned long dfu_get_buf_size(void) +{ + return dfu_buf_size; +} + +unsigned char *dfu_get_buf(struct dfu_entity *dfu) +{ + char *s; + + /* manage several entity with several contraint */ + if (dfu_buf && dfu->dev_type != dfu_buf_device_type) + dfu_free_buf(); + + if (dfu_buf != NULL) + return dfu_buf; + + s = env_get("dfu_bufsiz"); + if (s) + dfu_buf_size = (unsigned long)simple_strtol(s, NULL, 0); + + if (!s || !dfu_buf_size) + dfu_buf_size = CONFIG_SYS_DFU_DATA_BUF_SIZE; + + if (dfu->max_buf_size && dfu_buf_size > dfu->max_buf_size) + dfu_buf_size = dfu->max_buf_size; + + dfu_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, dfu_buf_size); + if (dfu_buf == NULL) + printf("%s: Could not memalign 0x%lx bytes\n", + __func__, dfu_buf_size); + + dfu_buf_device_type = dfu->dev_type; + return dfu_buf; +} + +static char *dfu_get_hash_algo(void) +{ + char *s; + + s = env_get("dfu_hash_algo"); + if (!s) + return NULL; + + if (!strcmp(s, "crc32")) { + debug("%s: DFU hash method: %s\n", __func__, s); + return s; + } + + pr_err("DFU hash method: %s not supported!\n", s); + return NULL; +} + +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; + + if (dfu_hash_algo) + dfu_hash_algo->hash_update(dfu_hash_algo, &dfu->crc, + dfu->i_buf_start, w_size, 0); + + 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; +} + +void dfu_transaction_cleanup(struct dfu_entity *dfu) +{ + /* clear everything */ + dfu->crc = 0; + dfu->offset = 0; + dfu->i_blk_seq_num = 0; + dfu->i_buf_start = dfu_get_buf(dfu); + dfu->i_buf_end = dfu->i_buf_start; + dfu->i_buf = dfu->i_buf_start; + dfu->r_left = 0; + dfu->b_left = 0; + dfu->bad_skip = 0; + + dfu->inited = 0; +} + +int dfu_transaction_initiate(struct dfu_entity *dfu, bool read) +{ + int ret = 0; + + if (dfu->inited) + return 0; + + dfu_transaction_cleanup(dfu); + + if (dfu->i_buf_start == NULL) + return -ENOMEM; + + dfu->i_buf_end = dfu->i_buf_start + dfu_get_buf_size(); + + if (read) { + ret = dfu->get_medium_size(dfu, &dfu->r_left); + if (ret < 0) + return ret; + debug("%s: %s %lld [B]\n", __func__, dfu->name, dfu->r_left); + } + + dfu->inited = 1; + dfu_initiated_callback(dfu); + + return 0; +} + +int dfu_flush(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) +{ + int ret = 0; + + ret = dfu_write_buffer_drain(dfu); + if (ret) + return ret; + + if (dfu->flush_medium) + ret = dfu->flush_medium(dfu); + + if (dfu_hash_algo) + printf("\nDFU complete %s: 0x%08x\n", dfu_hash_algo->name, + dfu->crc); + + dfu_flush_callback(dfu); + + dfu_transaction_cleanup(dfu); + + return ret; +} + +int dfu_write(struct dfu_entity *dfu, void *buf, int size, int blk_seq_num) +{ + int ret; + + debug("%s: name: %s buf: 0x%p size: 0x%x p_num: 0x%x offset: 0x%llx bufoffset: 0x%lx\n", + __func__, dfu->name, buf, size, blk_seq_num, dfu->offset, + (unsigned long)(dfu->i_buf - dfu->i_buf_start)); + + ret = dfu_transaction_initiate(dfu, false); + if (ret < 0) + return ret; + + 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); + dfu_transaction_cleanup(dfu); + return -1; + } + + /* 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) { + ret = dfu_write_buffer_drain(dfu); + if (ret) { + dfu_transaction_cleanup(dfu); + return ret; + } + } + + /* we should be in buffer now (if not then size too large) */ + if ((dfu->i_buf + size) > dfu->i_buf_end) { + pr_err("Buffer overflow! (0x%p + 0x%x > 0x%p)\n", dfu->i_buf, + size, dfu->i_buf_end); + dfu_transaction_cleanup(dfu); + return -1; + } + + 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) { + ret = dfu_write_buffer_drain(dfu); + if (ret) { + dfu_transaction_cleanup(dfu); + return ret; + } + } + + return 0; +} + +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((long)size, dfu->b_left); + /* consume */ + if (chunk > 0) { + memcpy(buf, dfu->i_buf, chunk); + if (dfu_hash_algo) + dfu_hash_algo->hash_update(dfu_hash_algo, + &dfu->crc, buf, + chunk, 0); + + 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; + } + if (dfu->b_left == 0) + break; + 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) +{ + 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, dfu->i_buf); + + ret = dfu_transaction_initiate(dfu, true); + if (ret < 0) + return ret; + + 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; + } + /* handle rollover */ + dfu->i_blk_seq_num = (dfu->i_blk_seq_num + 1) & 0xffff; + + ret = dfu_read_buffer_fill(dfu, buf, size); + if (ret < 0) { + printf("%s: Failed to fill buffer\n", __func__); + return -1; + } + + if (ret < size) { + if (dfu_hash_algo) + debug("%s: %s %s: 0x%x\n", __func__, dfu->name, + dfu_hash_algo->name, dfu->crc); + puts("\nUPLOAD ... done\nCtrl+C to exit ...\n"); + + dfu_transaction_cleanup(dfu); + } + + return ret; +} + +static int dfu_fill_entity(struct dfu_entity *dfu, char *s, int alt, + char *interface, char *devstr) +{ + char *st; + + debug("%s: %s interface: %s dev: %s\n", __func__, s, interface, devstr); + st = strsep(&s, " "); + strcpy(dfu->name, st); + + dfu->alt = alt; + dfu->max_buf_size = 0; + dfu->free_entity = NULL; + + /* Specific for mmc device */ + if (strcmp(interface, "mmc") == 0) { + if (dfu_fill_entity_mmc(dfu, devstr, s)) + return -1; + } else if (strcmp(interface, "mtd") == 0) { + if (dfu_fill_entity_mtd(dfu, devstr, s)) + return -1; + } else if (strcmp(interface, "nand") == 0) { + if (dfu_fill_entity_nand(dfu, devstr, s)) + return -1; + } else if (strcmp(interface, "ram") == 0) { + if (dfu_fill_entity_ram(dfu, devstr, s)) + return -1; + } else if (strcmp(interface, "sf") == 0) { + if (dfu_fill_entity_sf(dfu, devstr, s)) + return -1; + } else if (strcmp(interface, "virt") == 0) { + if (dfu_fill_entity_virt(dfu, devstr, s)) + return -1; + } else { + printf("%s: Device %s not (yet) supported!\n", + __func__, interface); + return -1; + } + dfu_get_buf(dfu); + + return 0; +} + +void dfu_free_entities(void) +{ + struct dfu_entity *dfu, *p, *t = NULL; + + dfu_free_buf(); + list_for_each_entry_safe_reverse(dfu, p, &dfu_list, list) { + list_del(&dfu->list); + if (dfu->free_entity) + dfu->free_entity(dfu); + t = dfu; + } + if (t) + free(t); + INIT_LIST_HEAD(&dfu_list); + + alt_num_cnt = 0; +} + +int dfu_alt_init(int num, struct dfu_entity **dfu) +{ + char *s; + int ret; + + dfu_alt_num = num; + debug("%s: dfu_alt_num=%d\n", __func__, dfu_alt_num); + + dfu_hash_algo = NULL; + s = dfu_get_hash_algo(); + if (s) { + ret = hash_lookup_algo(s, &dfu_hash_algo); + if (ret) + pr_err("Hash algorithm %s not supported\n", s); + } + + *dfu = calloc(sizeof(struct dfu_entity), dfu_alt_num); + if (!*dfu) + return -1; + + return 0; +} + +int dfu_alt_add(struct dfu_entity *dfu, char *interface, char *devstr, char *s) +{ + struct dfu_entity *p_dfu; + int ret; + + if (alt_num_cnt >= dfu_alt_num) + return -1; + + p_dfu = &dfu[alt_num_cnt]; + ret = dfu_fill_entity(p_dfu, s, alt_num_cnt, interface, devstr); + if (ret) + return -1; + + list_add_tail(&p_dfu->list, &dfu_list); + alt_num_cnt++; + + return 0; +} + +int dfu_config_entities(char *env, char *interface, char *devstr) +{ + struct dfu_entity *dfu; + int i, ret; + char *s; + + ret = dfu_alt_init(dfu_find_alt_num(env), &dfu); + if (ret) + return -1; + + for (i = 0; i < dfu_alt_num; i++) { + s = strsep(&env, ";"); + ret = dfu_alt_add(dfu, interface, devstr, s); + if (ret) { + /* We will free "dfu" in dfu_free_entities() */ + return -1; + } + } + + return 0; +} + +const char *dfu_get_dev_type(enum dfu_device_type t) +{ + const char *const dev_t[] = {NULL, "eMMC", "OneNAND", "NAND", "RAM", + "SF", "MTD", "VIRT"}; + return dev_t[t]; +} + +const char *dfu_get_layout(enum dfu_layout l) +{ + const char *const dfu_layout[] = {NULL, "RAW_ADDR", "FAT", "EXT2", + "EXT3", "EXT4", "RAM_ADDR", "SKIP", + "SCRIPT" }; + return dfu_layout[l]; +} + +void dfu_show_entities(void) +{ + struct dfu_entity *dfu; + + puts("DFU alt settings list:\n"); + + list_for_each_entry(dfu, &dfu_list, list) { + printf("dev: %s alt: %d name: %s layout: %s\n", + dfu_get_dev_type(dfu->dev_type), dfu->alt, + dfu->name, dfu_get_layout(dfu->layout)); + } +} + +int dfu_get_alt_number(void) +{ + return dfu_alt_num; +} + +struct dfu_entity *dfu_get_entity(int alt) +{ + struct dfu_entity *dfu; + + list_for_each_entry(dfu, &dfu_list, list) { + if (dfu->alt == alt) + return dfu; + } + + return NULL; +} + +int dfu_get_alt(char *name) +{ + struct dfu_entity *dfu; + char *str; + + list_for_each_entry(dfu, &dfu_list, list) { + if (dfu->name[0] != '/') { + if (!strncmp(dfu->name, name, strlen(dfu->name))) + return dfu->alt; + } else { + /* + * One must also consider absolute path + * (/boot/bin/uImage) available at dfu->name when + * compared "plain" file name (uImage) + * + * It is the case for e.g. thor gadget where lthor SW + * sends only the file name, so only the very last part + * of path must be checked for equality + */ + + str = strstr(dfu->name, name); + if (!str) + continue; + + /* + * Check if matching substring is the last element of + * dfu->name (uImage) + */ + if (strlen(dfu->name) == + ((str - dfu->name) + strlen(name))) + return dfu->alt; + } + } + + return -ENODEV; +} + +int dfu_write_from_mem_addr(struct dfu_entity *dfu, void *buf, int size) +{ + unsigned long dfu_buf_size, write, left = size; + int i, ret = 0; + void *dp = buf; + + /* + * Here we must call dfu_get_buf(dfu) first to be sure that dfu_buf_size + * has been properly initialized - e.g. if "dfu_bufsiz" has been taken + * into account. + */ + dfu_get_buf(dfu); + dfu_buf_size = dfu_get_buf_size(); + debug("%s: dfu buf size: %lu\n", __func__, dfu_buf_size); + + for (i = 0; left > 0; i++) { + write = min(dfu_buf_size, left); + + debug("%s: dp: 0x%p left: %lu write: %lu\n", __func__, + dp, left, write); + ret = dfu_write(dfu, dp, write, i); + if (ret) { + pr_err("DFU write failed\n"); + return ret; + } + + dp += write; + left -= write; + } + + ret = dfu_flush(dfu, NULL, 0, i); + if (ret) + pr_err("DFU flush failed!"); + + return ret; +} diff --git a/roms/u-boot/drivers/dfu/dfu_alt.c b/roms/u-boot/drivers/dfu/dfu_alt.c new file mode 100644 index 000000000..ece3d2236 --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_alt.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 + * Lukasz Majewski <l.majewski@majess.pl> + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <errno.h> +#include <dfu.h> + +/** + * dfu_write_by_name() - write data to DFU medium + * @dfu_entity_name: Name of DFU entity to write + * @addr: Address of data buffer to write + * @len: Number of bytes + * @interface: Destination DFU medium (e.g. "mmc") + * @devstring: Instance number of destination DFU medium (e.g. "1") + * + * This function is storing data received on DFU supported medium which + * is specified by @dfu_entity_name. + * + * Return: 0 - on success, error code - otherwise + */ +int dfu_write_by_name(char *dfu_entity_name, void *addr, + unsigned int len, char *interface, char *devstring) +{ + char *s, *sb; + int alt_setting_num, ret; + struct dfu_entity *dfu; + + debug("%s: name: %s addr: 0x%p len: %d device: %s:%s\n", __func__, + dfu_entity_name, addr, len, interface, devstring); + + ret = dfu_init_env_entities(interface, devstring); + if (ret) + goto done; + + /* + * We need to copy name pointed by *dfu_entity_name since this text + * is the integral part of the FDT image. + * Any implicit modification (i.e. done by strsep()) will corrupt + * the FDT image and prevent other images to be stored. + */ + s = strdup(dfu_entity_name); + sb = s; + if (!s) { + ret = -ENOMEM; + goto done; + } + + strsep(&s, "@"); + debug("%s: image name: %s strlen: %zd\n", __func__, sb, strlen(sb)); + + alt_setting_num = dfu_get_alt(sb); + free(sb); + if (alt_setting_num < 0) { + pr_err("Alt setting [%d] to write not found!", + alt_setting_num); + ret = -ENODEV; + goto done; + } + + dfu = dfu_get_entity(alt_setting_num); + if (!dfu) { + pr_err("DFU entity for alt: %d not found!", alt_setting_num); + ret = -ENODEV; + goto done; + } + + ret = dfu_write_from_mem_addr(dfu, (void *)addr, len); + +done: + dfu_free_entities(); + + return ret; +} + +/** + * dfu_write_by_alt() - write data to DFU medium + * @dfu_alt_num: DFU alt setting number + * @addr: Address of data buffer to write + * @len: Number of bytes + * @interface: Destination DFU medium (e.g. "mmc") + * @devstring: Instance number of destination DFU medium (e.g. "1") + * + * This function is storing data received on DFU supported medium which + * is specified by @dfu_alt_name. + * + * Return: 0 - on success, error code - otherwise + */ +int dfu_write_by_alt(int dfu_alt_num, void *addr, unsigned int len, + char *interface, char *devstring) +{ + struct dfu_entity *dfu; + int ret; + + debug("%s: alt: %d addr: 0x%p len: %d device: %s:%s\n", __func__, + dfu_alt_num, addr, len, interface, devstring); + + ret = dfu_init_env_entities(interface, devstring); + if (ret) + goto done; + + if (dfu_alt_num < 0) { + pr_err("Invalid alt number: %d", dfu_alt_num); + ret = -ENODEV; + goto done; + } + + dfu = dfu_get_entity(dfu_alt_num); + if (!dfu) { + pr_err("DFU entity for alt: %d not found!", dfu_alt_num); + ret = -ENODEV; + goto done; + } + + ret = dfu_write_from_mem_addr(dfu, (void *)(uintptr_t)addr, len); + +done: + dfu_free_entities(); + + return ret; +} diff --git a/roms/u-boot/drivers/dfu/dfu_mmc.c b/roms/u-boot/drivers/dfu/dfu_mmc.c new file mode 100644 index 000000000..e63fa84ce --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_mmc.c @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dfu.c -- DFU back-end routines + * + * Copyright (C) 2012 Samsung Electronics + * author: Lukasz Majewski <l.majewski@samsung.com> + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <errno.h> +#include <div64.h> +#include <dfu.h> +#include <ext4fs.h> +#include <fat.h> +#include <mmc.h> +#include <part.h> +#include <command.h> + +static unsigned char *dfu_file_buf; +static u64 dfu_file_buf_len; +static u64 dfu_file_buf_offset; + +static int mmc_block_op(enum dfu_op op, struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + struct mmc *mmc; + u32 blk_start, blk_count, n = 0; + int ret, part_num_bkp = 0; + + mmc = find_mmc_device(dfu->data.mmc.dev_num); + if (!mmc) { + pr_err("Device MMC %d - not found!", dfu->data.mmc.dev_num); + return -ENODEV; + } + + /* + * 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 (blk_start + blk_count > + dfu->data.mmc.lba_start + dfu->data.mmc.lba_size) { + puts("Request would exceed designated area!\n"); + return -EINVAL; + } + + if (dfu->data.mmc.hw_partition >= 0) { + part_num_bkp = mmc_get_blk_desc(mmc)->hwpart; + ret = blk_select_hwpart_devnum(IF_TYPE_MMC, + dfu->data.mmc.dev_num, + dfu->data.mmc.hw_partition); + if (ret) + return ret; + } + + debug("%s: %s dev: %d start: %d cnt: %d buf: 0x%p\n", __func__, + op == DFU_OP_READ ? "MMC READ" : "MMC WRITE", + dfu->data.mmc.dev_num, blk_start, blk_count, buf); + switch (op) { + case DFU_OP_READ: + n = blk_dread(mmc_get_blk_desc(mmc), blk_start, blk_count, buf); + break; + case DFU_OP_WRITE: + n = blk_dwrite(mmc_get_blk_desc(mmc), blk_start, blk_count, + buf); + break; + default: + pr_err("Operation not supported\n"); + } + + if (n != blk_count) { + pr_err("MMC operation failed"); + if (dfu->data.mmc.hw_partition >= 0) + blk_select_hwpart_devnum(IF_TYPE_MMC, + dfu->data.mmc.dev_num, + part_num_bkp); + return -EIO; + } + + if (dfu->data.mmc.hw_partition >= 0) { + ret = blk_select_hwpart_devnum(IF_TYPE_MMC, + dfu->data.mmc.dev_num, + part_num_bkp); + if (ret) + return ret; + } + + return 0; +} + +static int mmc_file_op(enum dfu_op op, struct dfu_entity *dfu, + u64 offset, void *buf, u64 *len) +{ + char dev_part_str[8]; + int ret; + int fstype; + loff_t size = 0; + + switch (dfu->layout) { + case DFU_FS_FAT: + fstype = FS_TYPE_FAT; + break; + case DFU_FS_EXT4: + fstype = FS_TYPE_EXT; + break; + case DFU_SKIP: + return 0; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + return -1; + } + + snprintf(dev_part_str, sizeof(dev_part_str), "%d:%d", + dfu->data.mmc.dev, dfu->data.mmc.part); + + ret = fs_set_blk_dev("mmc", dev_part_str, fstype); + if (ret) { + puts("dfu: fs_set_blk_dev error!\n"); + return ret; + } + + switch (op) { + case DFU_OP_READ: + ret = fs_read(dfu->name, (size_t)buf, offset, *len, &size); + if (ret) { + puts("dfu: fs_read error!\n"); + return ret; + } + *len = size; + break; + case DFU_OP_WRITE: + ret = fs_write(dfu->name, (size_t)buf, offset, *len, &size); + if (ret) { + puts("dfu: fs_write error!\n"); + return ret; + } + break; + case DFU_OP_SIZE: + ret = fs_size(dfu->name, &size); + if (ret) { + puts("dfu: fs_size error!\n"); + return ret; + } + *len = size; + break; + default: + return -1; + } + + return ret; +} + +static int mmc_file_buf_write(struct dfu_entity *dfu, u64 offset, void *buf, long *len) +{ + int ret = 0; + + if (offset == 0) { + dfu_file_buf_len = 0; + dfu_file_buf_offset = 0; + } + + /* Add to the current buffer. */ + if (dfu_file_buf_len + *len > CONFIG_SYS_DFU_MAX_FILE_SIZE) + *len = CONFIG_SYS_DFU_MAX_FILE_SIZE - dfu_file_buf_len; + memcpy(dfu_file_buf + dfu_file_buf_len, buf, *len); + dfu_file_buf_len += *len; + + if (dfu_file_buf_len == CONFIG_SYS_DFU_MAX_FILE_SIZE) { + ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf_offset, + dfu_file_buf, &dfu_file_buf_len); + dfu_file_buf_offset += dfu_file_buf_len; + dfu_file_buf_len = 0; + } + + return ret; +} + +static int mmc_file_buf_write_finish(struct dfu_entity *dfu) +{ + int ret = mmc_file_op(DFU_OP_WRITE, dfu, dfu_file_buf_offset, + dfu_file_buf, &dfu_file_buf_len); + + /* Now that we're done */ + dfu_file_buf_len = 0; + dfu_file_buf_offset = 0; + + return ret; +} + +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_op(DFU_OP_WRITE, dfu, offset, buf, len); + break; + case DFU_FS_FAT: + case DFU_FS_EXT4: + ret = mmc_file_buf_write(dfu, offset, buf, len); + break; + case DFU_SCRIPT: + ret = run_command_list(buf, *len, 0); + break; + case DFU_SKIP: + ret = 0; + break; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + } + + return ret; +} + +int dfu_flush_medium_mmc(struct dfu_entity *dfu) +{ + int ret = 0; + + switch (dfu->layout) { + case DFU_FS_FAT: + case DFU_FS_EXT4: + ret = mmc_file_buf_write_finish(dfu); + break; + case DFU_SCRIPT: + /* script may have changed the dfu_alt_info */ + dfu_reinit_needed = true; + break; + case DFU_RAW_ADDR: + case DFU_SKIP: + break; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + } + + return ret; +} + +int dfu_get_medium_size_mmc(struct dfu_entity *dfu, u64 *size) +{ + int ret; + + switch (dfu->layout) { + case DFU_RAW_ADDR: + *size = dfu->data.mmc.lba_size * dfu->data.mmc.lba_blk_size; + return 0; + case DFU_FS_FAT: + case DFU_FS_EXT4: + ret = mmc_file_op(DFU_OP_SIZE, dfu, 0, NULL, size); + if (ret < 0) + return ret; + return 0; + case DFU_SCRIPT: + case DFU_SKIP: + return 0; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + return -1; + } +} + + +static int mmc_file_buf_read(struct dfu_entity *dfu, u64 offset, void *buf, + long *len) +{ + int ret; + + if (offset == 0 || offset >= dfu_file_buf_offset + dfu_file_buf_len || + offset + *len < dfu_file_buf_offset) { + u64 file_len = CONFIG_SYS_DFU_MAX_FILE_SIZE; + + ret = mmc_file_op(DFU_OP_READ, dfu, offset, dfu_file_buf, + &file_len); + if (ret < 0) + return ret; + dfu_file_buf_len = file_len; + dfu_file_buf_offset = offset; + } + if (offset + *len > dfu_file_buf_offset + dfu_file_buf_len) + return -EINVAL; + + /* Add to the current buffer. */ + memcpy(buf, dfu_file_buf + offset - dfu_file_buf_offset, *len); + + return 0; +} + +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_op(DFU_OP_READ, dfu, offset, buf, len); + break; + case DFU_FS_FAT: + case DFU_FS_EXT4: + ret = mmc_file_buf_read(dfu, offset, buf, len); + break; + default: + printf("%s: Layout (%s) not (yet) supported!\n", __func__, + dfu_get_layout(dfu->layout)); + } + + return ret; +} + +void dfu_free_entity_mmc(struct dfu_entity *dfu) +{ + if (dfu_file_buf) { + free(dfu_file_buf); + dfu_file_buf = NULL; + } +} + +/* + * @param s Parameter string containing space-separated arguments: + * 1st: + * raw (raw read/write) + * fat (files) + * ext4 (^) + * part (partition image) + * 2nd and 3rd: + * lba_start and lba_size, for raw write + * mmc_dev and mmc_part, for filesystems and part + * 4th (optional): + * mmcpart <num> (access to HW eMMC partitions) + */ +int dfu_fill_entity_mmc(struct dfu_entity *dfu, char *devstr, char *s) +{ + const char *entity_type; + ssize_t second_arg; + size_t third_arg; + + struct mmc *mmc; + + const char *argv[3]; + const char **parg = argv; + + dfu->data.mmc.dev_num = simple_strtoul(devstr, NULL, 10); + + for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) { + *parg = strsep(&s, " "); + if (*parg == NULL) { + pr_err("Invalid number of arguments.\n"); + return -ENODEV; + } + } + + entity_type = argv[0]; + /* + * Base 0 means we'll accept (prefixed with 0x or 0) base 16, 8, + * with default 10. + */ + second_arg = simple_strtol(argv[1], NULL, 0); + third_arg = simple_strtoul(argv[2], NULL, 0); + + mmc = find_mmc_device(dfu->data.mmc.dev_num); + if (mmc == NULL) { + pr_err("Couldn't find MMC device no. %d.\n", + dfu->data.mmc.dev_num); + return -ENODEV; + } + + if (mmc_init(mmc)) { + pr_err("Couldn't init MMC device.\n"); + return -ENODEV; + } + + dfu->data.mmc.hw_partition = -EINVAL; + if (!strcmp(entity_type, "raw")) { + dfu->layout = DFU_RAW_ADDR; + dfu->data.mmc.lba_start = second_arg; + dfu->data.mmc.lba_size = third_arg; + dfu->data.mmc.lba_blk_size = mmc->read_bl_len; + + /* + * Check for an extra entry at dfu_alt_info env variable + * specifying the mmc HW defined partition number + */ + if (s) + if (!strcmp(strsep(&s, " "), "mmcpart")) + dfu->data.mmc.hw_partition = + simple_strtoul(s, NULL, 0); + + } else if (!strcmp(entity_type, "part")) { + struct disk_partition partinfo; + struct blk_desc *blk_dev = mmc_get_blk_desc(mmc); + int mmcdev = second_arg; + int mmcpart = third_arg; + int offset = 0; + + if (part_get_info(blk_dev, mmcpart, &partinfo) != 0) { + pr_err("Couldn't find part #%d on mmc device #%d\n", + mmcpart, mmcdev); + return -ENODEV; + } + + /* + * Check for an extra entry at dfu_alt_info env variable + * specifying the mmc HW defined partition number + */ + if (s) + if (!strcmp(strsep(&s, " "), "offset")) + offset = simple_strtoul(s, NULL, 0); + + dfu->layout = DFU_RAW_ADDR; + dfu->data.mmc.lba_start = partinfo.start + offset; + dfu->data.mmc.lba_size = partinfo.size-offset; + dfu->data.mmc.lba_blk_size = partinfo.blksz; + } else if (!strcmp(entity_type, "fat")) { + dfu->layout = DFU_FS_FAT; + } else if (!strcmp(entity_type, "ext4")) { + dfu->layout = DFU_FS_EXT4; + } else if (!strcmp(entity_type, "skip")) { + dfu->layout = DFU_SKIP; + } else if (!strcmp(entity_type, "script")) { + dfu->layout = DFU_SCRIPT; + } else { + pr_err("Memory layout (%s) not supported!\n", entity_type); + return -ENODEV; + } + + /* if it's NOT a raw write */ + if (strcmp(entity_type, "raw")) { + dfu->data.mmc.dev = (second_arg != -1) ? second_arg : + dfu->data.mmc.dev_num; + dfu->data.mmc.part = third_arg; + } + + dfu->dev_type = DFU_DEV_MMC; + dfu->get_medium_size = dfu_get_medium_size_mmc; + dfu->read_medium = dfu_read_medium_mmc; + dfu->write_medium = dfu_write_medium_mmc; + dfu->flush_medium = dfu_flush_medium_mmc; + dfu->inited = 0; + dfu->free_entity = dfu_free_entity_mmc; + + /* Check if file buffer is ready */ + if (!dfu_file_buf) { + dfu_file_buf = memalign(CONFIG_SYS_CACHELINE_SIZE, + CONFIG_SYS_DFU_MAX_FILE_SIZE); + if (!dfu_file_buf) { + pr_err("Could not memalign 0x%x bytes", + CONFIG_SYS_DFU_MAX_FILE_SIZE); + return -ENOMEM; + } + } + + return 0; +} diff --git a/roms/u-boot/drivers/dfu/dfu_mtd.c b/roms/u-boot/drivers/dfu/dfu_mtd.c new file mode 100644 index 000000000..ec40b8f6b --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_mtd.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * dfu_mtd.c -- DFU for MTD device. + * + * Copyright (C) 2019,STMicroelectronics - All Rights Reserved + * + * Based on dfu_nand.c + */ + +#include <common.h> +#include <dfu.h> +#include <mtd.h> +#include <jffs2/load_kernel.h> +#include <linux/err.h> + +static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) +{ + return !do_div(size, mtd->erasesize); +} + +static int mtd_block_op(enum dfu_op op, struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + u64 off, lim, remaining, lock_ofs, lock_len; + struct mtd_info *mtd = dfu->data.mtd.info; + struct mtd_oob_ops io_op = {}; + int ret = 0; + bool has_pages = mtd->type == MTD_NANDFLASH || + mtd->type == MTD_MLCNANDFLASH; + + /* if buf == NULL return total size of the area */ + if (!buf) { + *len = dfu->data.mtd.size; + return 0; + } + + off = lock_ofs = dfu->data.mtd.start + offset + dfu->bad_skip; + lim = dfu->data.mtd.start + dfu->data.mtd.size; + + if (off >= lim) { + printf("Limit reached 0x%llx\n", lim); + *len = 0; + return op == DFU_OP_READ ? 0 : -EIO; + } + /* limit request with the available size */ + if (off + *len >= lim) + *len = lim - off; + + if (!mtd_is_aligned_with_block_size(mtd, off)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + return 0; + } + + /* first erase */ + if (op == DFU_OP_WRITE) { + struct erase_info erase_op = {}; + + remaining = lock_len = round_up(*len, mtd->erasesize); + erase_op.mtd = mtd; + erase_op.addr = off; + erase_op.len = mtd->erasesize; + erase_op.scrub = 0; + + debug("Unlocking the mtd device\n"); + ret = mtd_unlock(mtd, lock_ofs, lock_len); + if (ret && ret != -EOPNOTSUPP) { + printf("MTD device unlock failed\n"); + return 0; + } + + while (remaining) { + if (erase_op.addr + remaining > lim) { + printf("Limit reached 0x%llx while erasing at offset 0x%llx\n", + lim, off); + return -EIO; + } + + ret = mtd_erase(mtd, &erase_op); + + if (ret) { + /* Abort if its not a bad block error */ + if (ret != -EIO) { + printf("Failure while erasing at offset 0x%llx\n", + erase_op.fail_addr); + return 0; + } + printf("Skipping bad block at 0x%08llx\n", + erase_op.addr); + } else { + remaining -= mtd->erasesize; + } + + /* Continue erase behind bad block */ + erase_op.addr += mtd->erasesize; + } + } + + io_op.mode = MTD_OPS_AUTO_OOB; + io_op.len = *len; + if (has_pages && io_op.len > mtd->writesize) + io_op.len = mtd->writesize; + io_op.ooblen = 0; + io_op.datbuf = buf; + io_op.oobbuf = NULL; + + /* Loop over to do the actual read/write */ + remaining = *len; + while (remaining) { + if (off + remaining > lim) { + printf("Limit reached 0x%llx while %s at offset 0x%llx\n", + lim, op == DFU_OP_READ ? "reading" : "writing", + off); + if (op == DFU_OP_READ) { + *len -= remaining; + return 0; + } else { + return -EIO; + } + } + + /* Skip the block if it is bad */ + if (mtd_is_aligned_with_block_size(mtd, off) && + mtd_block_isbad(mtd, off)) { + off += mtd->erasesize; + dfu->bad_skip += mtd->erasesize; + continue; + } + + if (op == DFU_OP_READ) + ret = mtd_read_oob(mtd, off, &io_op); + else + ret = mtd_write_oob(mtd, off, &io_op); + + if (ret) { + printf("Failure while %s at offset 0x%llx\n", + op == DFU_OP_READ ? "reading" : "writing", off); + return -EIO; + } + + off += io_op.retlen; + remaining -= io_op.retlen; + io_op.datbuf += io_op.retlen; + io_op.len = remaining; + if (has_pages && io_op.len > mtd->writesize) + io_op.len = mtd->writesize; + } + + if (op == DFU_OP_WRITE) { + /* Write done, lock again */ + debug("Locking the mtd device\n"); + ret = mtd_lock(mtd, lock_ofs, lock_len); + if (ret == -EOPNOTSUPP) + ret = 0; + else if (ret) + printf("MTD device lock failed\n"); + } + return ret; +} + +static int dfu_get_medium_size_mtd(struct dfu_entity *dfu, u64 *size) +{ + *size = dfu->data.mtd.info->size; + + return 0; +} + +static int dfu_read_medium_mtd(struct dfu_entity *dfu, u64 offset, void *buf, + long *len) +{ + int ret = -1; + + switch (dfu->layout) { + case DFU_RAW_ADDR: + ret = mtd_block_op(DFU_OP_READ, 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_write_medium_mtd(struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + int ret = -1; + + switch (dfu->layout) { + case DFU_RAW_ADDR: + ret = mtd_block_op(DFU_OP_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_flush_medium_mtd(struct dfu_entity *dfu) +{ + struct mtd_info *mtd = dfu->data.mtd.info; + u64 remaining; + int ret; + + /* in case of ubi partition, erase rest of the partition */ + if (dfu->data.mtd.ubi) { + struct erase_info erase_op = {}; + + erase_op.mtd = dfu->data.mtd.info; + erase_op.addr = round_up(dfu->data.mtd.start + dfu->offset + + dfu->bad_skip, mtd->erasesize); + erase_op.len = mtd->erasesize; + erase_op.scrub = 0; + + remaining = dfu->data.mtd.start + dfu->data.mtd.size - + erase_op.addr; + + while (remaining) { + ret = mtd_erase(mtd, &erase_op); + + if (ret) { + /* Abort if its not a bad block error */ + if (ret != -EIO) + break; + printf("Skipping bad block at 0x%08llx\n", + erase_op.addr); + } + + /* Skip bad block and continue behind it */ + erase_op.addr += mtd->erasesize; + remaining -= mtd->erasesize; + } + } + return 0; +} + +static unsigned int dfu_polltimeout_mtd(struct dfu_entity *dfu) +{ + /* + * Currently, Poll Timeout != 0 is only needed on nand + * ubi partition, as sectors which are not used need + * to be erased + */ + if (dfu->data.mtd.ubi) + return DFU_MANIFEST_POLL_TIMEOUT; + + return DFU_DEFAULT_POLL_TIMEOUT; +} + +int dfu_fill_entity_mtd(struct dfu_entity *dfu, char *devstr, char *s) +{ + char *st; + struct mtd_info *mtd; + bool has_pages; + int ret, part; + + mtd = get_mtd_device_nm(devstr); + if (IS_ERR_OR_NULL(mtd)) + return -ENODEV; + put_mtd_device(mtd); + + dfu->dev_type = DFU_DEV_MTD; + dfu->data.mtd.info = mtd; + + has_pages = mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH; + dfu->max_buf_size = has_pages ? mtd->erasesize : 0; + + st = strsep(&s, " "); + if (!strcmp(st, "raw")) { + dfu->layout = DFU_RAW_ADDR; + dfu->data.mtd.start = simple_strtoul(s, &s, 16); + s++; + dfu->data.mtd.size = simple_strtoul(s, &s, 16); + } else if ((!strcmp(st, "part")) || (!strcmp(st, "partubi"))) { + char mtd_id[32]; + struct mtd_device *mtd_dev; + u8 part_num; + struct part_info *pi; + + dfu->layout = DFU_RAW_ADDR; + + part = simple_strtoul(s, &s, 10); + + sprintf(mtd_id, "%s,%d", devstr, 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.mtd.start = pi->offset; + dfu->data.mtd.size = pi->size; + if (!strcmp(st, "partubi")) + dfu->data.mtd.ubi = 1; + } else { + printf("%s: Memory layout (%s) not supported!\n", __func__, st); + return -1; + } + + if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.start)) { + printf("Offset not aligned with a block (0x%x)\n", + mtd->erasesize); + return -EINVAL; + } + if (!mtd_is_aligned_with_block_size(mtd, dfu->data.mtd.size)) { + printf("Size not aligned with a block (0x%x)\n", + mtd->erasesize); + return -EINVAL; + } + + dfu->get_medium_size = dfu_get_medium_size_mtd; + dfu->read_medium = dfu_read_medium_mtd; + dfu->write_medium = dfu_write_medium_mtd; + dfu->flush_medium = dfu_flush_medium_mtd; + dfu->poll_timeout = dfu_polltimeout_mtd; + + /* initial state */ + dfu->inited = 0; + + return 0; +} diff --git a/roms/u-boot/drivers/dfu/dfu_nand.c b/roms/u-boot/drivers/dfu/dfu_nand.c new file mode 100644 index 000000000..b8d24d203 --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_nand.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * 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 <l.majewski@samsung.com> + */ + +#include <common.h> +#include <log.h> +#include <malloc.h> +#include <errno.h> +#include <div64.h> +#include <dfu.h> +#include <linux/mtd/mtd.h> +#include <jffs2/load_kernel.h> +#include <nand.h> + +static int nand_block_op(enum dfu_op op, struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + loff_t start, lim; + size_t count, actual; + int ret; + struct mtd_info *mtd; + + /* 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; + + mtd = get_nand_dev_by_index(nand_curr_device); + + if (nand_curr_device < 0 || + nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || + !mtd) { + printf("%s: invalid nand device\n", __func__); + return -1; + } + + if (op == DFU_OP_READ) { + ret = nand_read_skip_bad(mtd, start, &count, &actual, + lim, buf); + } else { + nand_erase_options_t opts; + int write_flags = WITH_WR_VERIFY; + + memset(&opts, 0, sizeof(opts)); + opts.offset = start; + opts.length = count; + opts.spread = 1; + opts.quiet = 1; + opts.lim = lim; + /* first erase */ + ret = nand_erase_opts(mtd, &opts); + if (ret) + return ret; + /* then write */ +#ifdef CONFIG_DFU_NAND_TRIMFFS + if (dfu->data.nand.ubi) + write_flags |= WITH_DROP_FFS; +#endif + ret = nand_write_skip_bad(mtd, start, &count, &actual, + lim, buf, write_flags); + } + + 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; +} + +int dfu_get_medium_size_nand(struct dfu_entity *dfu, u64 *size) +{ + *size = dfu->data.nand.size; + + return 0; +} + +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; +} + +static int dfu_flush_medium_nand(struct dfu_entity *dfu) +{ + int ret = 0; + u64 off; + + /* in case of ubi partition, erase rest of the partition */ + if (dfu->data.nand.ubi) { + struct mtd_info *mtd = get_nand_dev_by_index(nand_curr_device); + nand_erase_options_t opts; + + if (nand_curr_device < 0 || + nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE || + !mtd) { + printf("%s: invalid nand device\n", __func__); + return -1; + } + + memset(&opts, 0, sizeof(opts)); + off = dfu->offset; + if ((off & (mtd->erasesize - 1)) != 0) { + /* + * last write ended with unaligned length + * sector is erased, jump to next + */ + off = off & ~((mtd->erasesize - 1)); + off += mtd->erasesize; + } + opts.offset = dfu->data.nand.start + off + + dfu->bad_skip; + opts.length = dfu->data.nand.start + + dfu->data.nand.size - opts.offset; + ret = nand_erase_opts(mtd, &opts); + if (ret != 0) + printf("Failure erase: %d\n", ret); + } + + return ret; +} + +unsigned int dfu_polltimeout_nand(struct dfu_entity *dfu) +{ + /* + * Currently, Poll Timeout != 0 is only needed on nand + * ubi partition, as the not used sectors need an erase + */ + if (dfu->data.nand.ubi) + return DFU_MANIFEST_POLL_TIMEOUT; + + return DFU_DEFAULT_POLL_TIMEOUT; +} + +int dfu_fill_entity_nand(struct dfu_entity *dfu, char *devstr, char *s) +{ + char *st; + int ret, dev, part; + + dfu->data.nand.ubi = 0; + 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")) || (!strcmp(st, "partubi"))) { + 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); + debug("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; + if (!strcmp(st, "partubi")) + dfu->data.nand.ubi = 1; + } else { + printf("%s: Memory layout (%s) not supported!\n", __func__, st); + return -1; + } + + dfu->get_medium_size = dfu_get_medium_size_nand; + dfu->read_medium = dfu_read_medium_nand; + dfu->write_medium = dfu_write_medium_nand; + dfu->flush_medium = dfu_flush_medium_nand; + dfu->poll_timeout = dfu_polltimeout_nand; + + /* initial state */ + dfu->inited = 0; + + return 0; +} diff --git a/roms/u-boot/drivers/dfu/dfu_ram.c b/roms/u-boot/drivers/dfu/dfu_ram.c new file mode 100644 index 000000000..ab0ce9e6f --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_ram.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2013 + * Afzal Mohammed <afzal.mohd.ma@gmail.com> + * + * Reference: dfu_mmc.c + * Copyright (C) 2012 Samsung Electronics + * author: Lukasz Majewski <l.majewski@samsung.com> + */ + +#include <common.h> +#include <malloc.h> +#include <mapmem.h> +#include <errno.h> +#include <dfu.h> + +static int dfu_transfer_medium_ram(enum dfu_op op, struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + if (dfu->layout != DFU_RAM_ADDR) { + pr_err("unsupported layout: %s\n", dfu_get_layout(dfu->layout)); + return -EINVAL; + } + + if (offset > dfu->data.ram.size) { + pr_err("request exceeds allowed area\n"); + return -EINVAL; + } + + if (op == DFU_OP_WRITE) + memcpy(map_sysmem(dfu->data.ram.start + offset, 0), buf, *len); + else + memcpy(buf, map_sysmem(dfu->data.ram.start + offset, 0), *len); + + return 0; +} + +static int dfu_write_medium_ram(struct dfu_entity *dfu, u64 offset, + void *buf, long *len) +{ + return dfu_transfer_medium_ram(DFU_OP_WRITE, dfu, offset, buf, len); +} + +int dfu_get_medium_size_ram(struct dfu_entity *dfu, u64 *size) +{ + *size = dfu->data.ram.size; + + return 0; +} + +static int dfu_read_medium_ram(struct dfu_entity *dfu, u64 offset, + void *buf, long *len) +{ + return dfu_transfer_medium_ram(DFU_OP_READ, dfu, offset, buf, len); +} + +int dfu_fill_entity_ram(struct dfu_entity *dfu, char *devstr, char *s) +{ + const char *argv[3]; + const char **parg = argv; + + for (; parg < argv + sizeof(argv) / sizeof(*argv); ++parg) { + *parg = strsep(&s, " "); + if (*parg == NULL) { + pr_err("Invalid number of arguments.\n"); + return -ENODEV; + } + } + + dfu->dev_type = DFU_DEV_RAM; + if (strcmp(argv[0], "ram")) { + pr_err("unsupported device: %s\n", argv[0]); + return -ENODEV; + } + + dfu->layout = DFU_RAM_ADDR; + dfu->data.ram.start = simple_strtoul(argv[1], NULL, 16); + dfu->data.ram.size = simple_strtoul(argv[2], NULL, 16); + + dfu->write_medium = dfu_write_medium_ram; + dfu->get_medium_size = dfu_get_medium_size_ram; + dfu->read_medium = dfu_read_medium_ram; + + dfu->inited = 0; + + return 0; +} diff --git a/roms/u-boot/drivers/dfu/dfu_sf.c b/roms/u-boot/drivers/dfu/dfu_sf.c new file mode 100644 index 000000000..8f8c42597 --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_sf.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. + */ + +#include <common.h> +#include <flash.h> +#include <malloc.h> +#include <errno.h> +#include <div64.h> +#include <dfu.h> +#include <spi.h> +#include <spi_flash.h> +#include <jffs2/load_kernel.h> +#include <linux/mtd/mtd.h> + +static int dfu_get_medium_size_sf(struct dfu_entity *dfu, u64 *size) +{ + *size = dfu->data.sf.size; + + return 0; +} + +static int dfu_read_medium_sf(struct dfu_entity *dfu, u64 offset, void *buf, + long *len) +{ + return spi_flash_read(dfu->data.sf.dev, dfu->data.sf.start + offset, + *len, buf); +} + +static u64 find_sector(struct dfu_entity *dfu, u64 start, u64 offset) +{ + return (lldiv((start + offset), dfu->data.sf.dev->sector_size)) * + dfu->data.sf.dev->sector_size; +} + +static int dfu_write_medium_sf(struct dfu_entity *dfu, + u64 offset, void *buf, long *len) +{ + int ret; + + ret = spi_flash_erase(dfu->data.sf.dev, + find_sector(dfu, dfu->data.sf.start, offset), + dfu->data.sf.dev->sector_size); + if (ret) + return ret; + + ret = spi_flash_write(dfu->data.sf.dev, dfu->data.sf.start + offset, + *len, buf); + if (ret) + return ret; + + return 0; +} + +static int dfu_flush_medium_sf(struct dfu_entity *dfu) +{ + u64 off, length; + + if (!CONFIG_IS_ENABLED(DFU_SF_PART) || !dfu->data.sf.ubi) + return 0; + + /* in case of ubi partition, erase rest of the partition */ + off = find_sector(dfu, dfu->data.sf.start, dfu->offset); + /* last write ended with unaligned length jump to next */ + if (off != dfu->data.sf.start + dfu->offset) + off += dfu->data.sf.dev->sector_size; + length = dfu->data.sf.start + dfu->data.sf.size - off; + if (length) + return spi_flash_erase(dfu->data.sf.dev, off, length); + + return 0; +} + +static unsigned int dfu_polltimeout_sf(struct dfu_entity *dfu) +{ + /* + * Currently, Poll Timeout != 0 is only needed on nand + * ubi partition, as sectors which are not used need + * to be erased + */ + if (CONFIG_IS_ENABLED(DFU_SF_PART) && dfu->data.sf.ubi) + return DFU_MANIFEST_POLL_TIMEOUT; + + return DFU_DEFAULT_POLL_TIMEOUT; +} + +static void dfu_free_entity_sf(struct dfu_entity *dfu) +{ + /* + * In the DM case it is not necessary to free the SPI device. + * For the non-DM case we must ensure that the the SPI device is only + * freed once. + */ + if (!CONFIG_IS_ENABLED(DM_SPI_FLASH)) { + struct spi_flash *dev = dfu->data.sf.dev; + + if (!dev) + return; + dfu->data.sf.dev = NULL; + list_for_each_entry(dfu, &dfu_list, list) { + if (dfu->data.sf.dev == dev) + dfu->data.sf.dev = NULL; + } + spi_flash_free(dev); + } +} + +static struct spi_flash *parse_dev(char *devstr) +{ + unsigned int bus; + unsigned int cs; + unsigned int speed = CONFIG_SF_DEFAULT_SPEED; + unsigned int mode = CONFIG_SF_DEFAULT_MODE; + char *s, *endp; + struct spi_flash *dev; + + s = strsep(&devstr, ":"); + if (!s || !*s || (bus = simple_strtoul(s, &endp, 0), *endp)) { + printf("Invalid SPI bus %s\n", s); + return NULL; + } + + s = strsep(&devstr, ":"); + if (!s || !*s || (cs = simple_strtoul(s, &endp, 0), *endp)) { + printf("Invalid SPI chip-select %s\n", s); + return NULL; + } + + s = strsep(&devstr, ":"); + if (s && *s) { + speed = simple_strtoul(s, &endp, 0); + if (*endp || !speed) { + printf("Invalid SPI speed %s\n", s); + return NULL; + } + } + + s = strsep(&devstr, ":"); + if (s && *s) { + mode = simple_strtoul(s, &endp, 0); + if (*endp || mode > 3) { + printf("Invalid SPI mode %s\n", s); + return NULL; + } + } + + dev = spi_flash_probe(bus, cs, speed, mode); + if (!dev) { + printf("Failed to create SPI flash at %u:%u:%u:%u\n", + bus, cs, speed, mode); + return NULL; + } + + return dev; +} + +int dfu_fill_entity_sf(struct dfu_entity *dfu, char *devstr, char *s) +{ + char *st; + char *devstr_bkup = strdup(devstr); + + dfu->data.sf.dev = parse_dev(devstr_bkup); + free(devstr_bkup); + if (!dfu->data.sf.dev) + return -ENODEV; + + dfu->dev_type = DFU_DEV_SF; + dfu->max_buf_size = dfu->data.sf.dev->sector_size; + + st = strsep(&s, " "); + if (!strcmp(st, "raw")) { + dfu->layout = DFU_RAW_ADDR; + dfu->data.sf.start = simple_strtoul(s, &s, 16); + s++; + dfu->data.sf.size = simple_strtoul(s, &s, 16); + } else if (CONFIG_IS_ENABLED(DFU_SF_PART) && + (!strcmp(st, "part") || !strcmp(st, "partubi"))) { + char mtd_id[32]; + struct mtd_device *mtd_dev; + u8 part_num; + struct part_info *pi; + int ret, dev, part; + + dfu->layout = DFU_RAW_ADDR; + + dev = simple_strtoul(s, &s, 10); + s++; + part = simple_strtoul(s, &s, 10); + + sprintf(mtd_id, "%s%d,%d", "nor", 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.sf.start = pi->offset; + dfu->data.sf.size = pi->size; + if (!strcmp(st, "partubi")) + dfu->data.sf.ubi = 1; + } else { + printf("%s: Memory layout (%s) not supported!\n", __func__, st); + spi_flash_free(dfu->data.sf.dev); + return -1; + } + + dfu->get_medium_size = dfu_get_medium_size_sf; + dfu->read_medium = dfu_read_medium_sf; + dfu->write_medium = dfu_write_medium_sf; + dfu->flush_medium = dfu_flush_medium_sf; + dfu->poll_timeout = dfu_polltimeout_sf; + dfu->free_entity = dfu_free_entity_sf; + + /* initial state */ + dfu->inited = 0; + + return 0; +} diff --git a/roms/u-boot/drivers/dfu/dfu_virt.c b/roms/u-boot/drivers/dfu/dfu_virt.c new file mode 100644 index 000000000..62605bcde --- /dev/null +++ b/roms/u-boot/drivers/dfu/dfu_virt.c @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0+ OR BSD-3-Clause +/* + * Copyright (C) 2019, STMicroelectronics - All Rights Reserved + */ +#include <common.h> +#include <dfu.h> +#include <errno.h> +#include <log.h> +#include <malloc.h> + +int __weak dfu_write_medium_virt(struct dfu_entity *dfu, u64 offset, + void *buf, long *len) +{ + debug("%s: off=0x%llx, len=0x%x\n", __func__, offset, (u32)*len); + + return 0; +} + +int __weak dfu_get_medium_size_virt(struct dfu_entity *dfu, u64 *size) +{ + *size = 0; + + return 0; +} + +int __weak dfu_read_medium_virt(struct dfu_entity *dfu, u64 offset, + void *buf, long *len) +{ + debug("%s: off=0x%llx, len=0x%x\n", __func__, offset, (u32)*len); + *len = 0; + + return 0; +} + +int dfu_fill_entity_virt(struct dfu_entity *dfu, char *devstr, char *s) +{ + debug("%s: devstr = %s\n", __func__, devstr); + + dfu->dev_type = DFU_DEV_VIRT; + dfu->layout = DFU_RAW_ADDR; + dfu->data.virt.dev_num = simple_strtoul(devstr, NULL, 10); + + dfu->write_medium = dfu_write_medium_virt; + dfu->get_medium_size = dfu_get_medium_size_virt; + dfu->read_medium = dfu_read_medium_virt; + + dfu->inited = 0; + + return 0; +} |