diff options
Diffstat (limited to 'env/mtd.c')
-rw-r--r-- | env/mtd.c | 338 |
1 files changed, 338 insertions, 0 deletions
diff --git a/env/mtd.c b/env/mtd.c new file mode 100644 index 00000000000..721faebd8f2 --- /dev/null +++ b/env/mtd.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Author: Christian Marangi <ansuelsmth@gmail.com> + */ +#include <env_internal.h> +#include <errno.h> +#include <malloc.h> +#include <mtd.h> +#include <asm/cache.h> +#include <asm/global_data.h> +#include <linux/mtd/mtd.h> +#include <u-boot/crc.h> + +DECLARE_GLOBAL_DATA_PTR; + +static int setup_mtd_device(struct mtd_info **mtd_env) +{ + struct mtd_info *mtd; + + mtd_probe_devices(); + + mtd = get_mtd_device_nm(CONFIG_ENV_MTD_DEV); + if (IS_ERR_OR_NULL(mtd)) { + env_set_default("get_mtd_device_nm() failed", 0); + return mtd ? PTR_ERR(mtd) : -EINVAL; + } + + *mtd_env = mtd; + + return 0; +} + +static int env_mtd_save(void) +{ + char *saved_buf, *write_buf, *tmp; + struct erase_info ei = { }; + struct mtd_info *mtd_env; + u32 sect_size, sect_num; + size_t ret_len = 0; + u32 write_size; + env_t env_new; + int remaining; + u32 offset; + int ret; + + ret = setup_mtd_device(&mtd_env); + if (ret) + return ret; + + sect_size = mtd_env->erasesize; + + /* Is the sector larger than the env (i.e. embedded) */ + if (sect_size > CONFIG_ENV_SIZE) { + saved_buf = malloc(sect_size); + if (!saved_buf) { + ret = -ENOMEM; + goto done; + } + + offset = CONFIG_ENV_OFFSET; + remaining = sect_size; + tmp = saved_buf; + + while (remaining) { + /* Skip the block if it is bad */ + if (!(offset % sect_size) && + mtd_block_isbad(mtd_env, offset)) { + offset += sect_size; + continue; + } + + ret = mtd_read(mtd_env, offset, mtd_env->writesize, + &ret_len, tmp); + if (ret) + goto done; + + tmp += ret_len; + offset += ret_len; + remaining -= ret_len; + } + } + + ret = env_export(&env_new); + if (ret) + goto done; + + sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size); + + ei.mtd = mtd_env; + ei.addr = CONFIG_ENV_OFFSET; + ei.len = sect_num * sect_size; + + puts("Erasing MTD..."); + ret = mtd_erase(mtd_env, &ei); + if (ret) + goto done; + + if (sect_size > CONFIG_ENV_SIZE) { + memcpy(saved_buf, &env_new, CONFIG_ENV_SIZE); + write_size = sect_size; + write_buf = saved_buf; + } else { + write_size = sect_num * sect_size; + write_buf = (char *)&env_new; + } + + offset = CONFIG_ENV_OFFSET; + remaining = sect_size; + tmp = write_buf; + + puts("Writing to MTD..."); + while (remaining) { + /* Skip the block if it is bad */ + if (!(offset % sect_size) && + mtd_block_isbad(mtd_env, offset)) { + offset += sect_size; + continue; + } + + ret = mtd_write(mtd_env, offset, mtd_env->writesize, + &ret_len, tmp); + if (ret) + goto done; + + offset += mtd_env->writesize; + remaining -= ret_len; + tmp += ret_len; + } + + ret = 0; + puts("done\n"); + +done: + if (saved_buf) + free(saved_buf); + + return ret; +} + +static int env_mtd_load(void) +{ + struct mtd_info *mtd_env; + char *buf, *tmp; + size_t ret_len; + int remaining; + u32 sect_size; + u32 offset; + int ret; + + buf = (char *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE); + if (!buf) { + env_set_default("memalign() failed", 0); + return -EIO; + } + + ret = setup_mtd_device(&mtd_env); + if (ret) + goto out; + + sect_size = mtd_env->erasesize; + + offset = CONFIG_ENV_OFFSET; + remaining = CONFIG_ENV_SIZE; + tmp = buf; + + while (remaining) { + /* Skip the block if it is bad */ + if (!(offset % sect_size) && + mtd_block_isbad(mtd_env, offset)) { + offset += sect_size; + continue; + } + + ret = mtd_read(mtd_env, offset, mtd_env->writesize, + &ret_len, tmp); + if (ret) { + env_set_default("mtd_read() failed", 1); + goto out; + } + + tmp += ret_len; + offset += ret_len; + remaining -= ret_len; + } + + ret = env_import(buf, 1, H_EXTERNAL); + if (!ret) + gd->env_valid = ENV_VALID; + +out: + free(buf); + + return ret; +} + +static int env_mtd_erase(void) +{ + struct mtd_info *mtd_env; + u32 sect_size, sect_num; + char *saved_buf, *tmp; + struct erase_info ei; + size_t ret_len; + int remaining; + u32 offset; + int ret; + + ret = setup_mtd_device(&mtd_env); + if (ret) + return ret; + + sect_size = mtd_env->erasesize; + + /* Is the sector larger than the env (i.e. embedded) */ + if (sect_size > CONFIG_ENV_SIZE) { + saved_buf = malloc(sect_size); + if (!saved_buf) { + ret = -ENOMEM; + goto done; + } + + offset = CONFIG_ENV_OFFSET; + remaining = sect_size; + tmp = saved_buf; + + while (remaining) { + /* Skip the block if it is bad */ + if (!(offset % sect_size) && + mtd_block_isbad(mtd_env, offset)) { + offset += sect_size; + continue; + } + + ret = mtd_read(mtd_env, offset, mtd_env->writesize, + &ret_len, tmp); + if (ret) + goto done; + + tmp += ret_len; + offset += ret_len; + remaining -= ret_len; + } + } + + sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size); + + ei.mtd = mtd_env; + ei.addr = CONFIG_ENV_OFFSET; + ei.len = sect_num * sect_size; + + ret = mtd_erase(mtd_env, &ei); + if (ret) + goto done; + + if (sect_size > CONFIG_ENV_SIZE) { + memset(saved_buf, 0, CONFIG_ENV_SIZE); + + offset = CONFIG_ENV_OFFSET; + remaining = sect_size; + tmp = saved_buf; + + while (remaining) { + /* Skip the block if it is bad */ + if (!(offset % sect_size) && + mtd_block_isbad(mtd_env, offset)) { + offset += sect_size; + continue; + } + + ret = mtd_write(mtd_env, offset, mtd_env->writesize, + &ret_len, tmp); + if (ret) + goto done; + + offset += mtd_env->writesize; + remaining -= ret_len; + tmp += ret_len; + } + } + + ret = 0; + +done: + if (saved_buf) + free(saved_buf); + + return ret; +} + +__weak void *env_mtd_get_env_addr(void) +{ + return (void *)CONFIG_ENV_ADDR; +} + +/* + * Check if Environment on CONFIG_ENV_ADDR is valid. + */ +static int env_mtd_init_addr(void) +{ + env_t *env_ptr = (env_t *)env_mtd_get_env_addr(); + + if (!env_ptr) + return -ENOENT; + + if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) { + gd->env_addr = (ulong)&env_ptr->data; + gd->env_valid = ENV_VALID; + } else { + gd->env_valid = ENV_INVALID; + } + + return 0; +} + +static int env_mtd_init(void) +{ + int ret; + + ret = env_mtd_init_addr(); + if (ret != -ENOENT) + return ret; + + /* + * return here -ENOENT, so env_init() + * can set the init bit and later if no + * other Environment storage is defined + * can set the default environment + */ + return -ENOENT; +} + +U_BOOT_ENV_LOCATION(mtd) = { + .location = ENVL_MTD, + ENV_NAME("MTD") + .load = env_mtd_load, + .save = ENV_SAVE_PTR(env_mtd_save), + .erase = ENV_ERASE_PTR(env_mtd_erase), + .init = env_mtd_init, +}; |