// SPDX-License-Identifier: GPL-2.0+ /* * Author: Christian Marangi */ #include #include #include #include #include #include #include #include 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, };