mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-07 07:46:59 +02:00
When sect_size is greater than the CONFIG_ENV_SIZE, this wild
pointer will cause CPU halt or system crash.
Fixes: 03fb08d4ae
("env: Introduce support for MTD")
Signed-off-by: Shiji Yang <yangshiji66@outlook.com>
345 lines
6.1 KiB
C
345 lines
6.1 KiB
C
// 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:
|
|
put_mtd_device(mtd_env);
|
|
|
|
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:
|
|
put_mtd_device(mtd_env);
|
|
|
|
free(buf);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int env_mtd_erase(void)
|
|
{
|
|
struct mtd_info *mtd_env;
|
|
u32 sect_size, sect_num;
|
|
char *saved_buf = NULL, *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:
|
|
put_mtd_device(mtd_env);
|
|
|
|
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,
|
|
};
|