| // 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 = NULL, *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 = write_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, |
| }; |