blob: d7ec30e183acd75aeb00d34a38de5f9ddda55680 [file] [log] [blame]
// 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,
};