A negative value of CONFIG_ENV_OFFSET is treated as a backwards offset from the end of the eMMC device/partition, rather than a forwards offset from the start. This is useful when a single board may be stuffed with different eMMC devices, each of which has a different capacity, and you always want the environment to be stored at the very end of the device (or eMMC boot partition for example). One example of this case is NVIDIA's Ventana reference board. Signed-off-by: Stephen Warren <swarren@nvidia.com> Signed-off-by: Andy Fleming <afleming@freescale.com>
324 lines
7 KiB
C
324 lines
7 KiB
C
/*
|
|
* (C) Copyright 2008-2011 Freescale Semiconductor, Inc.
|
|
*
|
|
* See file CREDITS for list of people who contributed to this
|
|
* project.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
|
* MA 02111-1307 USA
|
|
*/
|
|
|
|
/* #define DEBUG */
|
|
|
|
#include <common.h>
|
|
|
|
#include <command.h>
|
|
#include <environment.h>
|
|
#include <linux/stddef.h>
|
|
#include <malloc.h>
|
|
#include <mmc.h>
|
|
#include <search.h>
|
|
#include <errno.h>
|
|
|
|
#if defined(CONFIG_ENV_SIZE_REDUND) && \
|
|
(CONFIG_ENV_SIZE_REDUND != CONFIG_ENV_SIZE)
|
|
#error CONFIG_ENV_SIZE_REDUND should be the same as CONFIG_ENV_SIZE
|
|
#endif
|
|
|
|
char *env_name_spec = "MMC";
|
|
|
|
#ifdef ENV_IS_EMBEDDED
|
|
env_t *env_ptr = &environment;
|
|
#else /* ! ENV_IS_EMBEDDED */
|
|
env_t *env_ptr;
|
|
#endif /* ENV_IS_EMBEDDED */
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#if !defined(CONFIG_ENV_OFFSET)
|
|
#define CONFIG_ENV_OFFSET 0
|
|
#endif
|
|
|
|
__weak int mmc_get_env_addr(struct mmc *mmc, int copy, u32 *env_addr)
|
|
{
|
|
s64 offset;
|
|
|
|
offset = CONFIG_ENV_OFFSET;
|
|
#ifdef CONFIG_ENV_OFFSET_REDUND
|
|
if (copy)
|
|
offset = CONFIG_ENV_OFFSET_REDUND;
|
|
#endif
|
|
|
|
if (offset < 0)
|
|
offset += mmc->capacity;
|
|
|
|
*env_addr = offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int env_init(void)
|
|
{
|
|
/* use default */
|
|
gd->env_addr = (ulong)&default_environment[0];
|
|
gd->env_valid = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int init_mmc_for_env(struct mmc *mmc)
|
|
{
|
|
if (!mmc) {
|
|
puts("No MMC card found\n");
|
|
return -1;
|
|
}
|
|
|
|
if (mmc_init(mmc)) {
|
|
puts("MMC init failed\n");
|
|
return -1;
|
|
}
|
|
|
|
#ifdef CONFIG_SYS_MMC_ENV_PART
|
|
if (CONFIG_SYS_MMC_ENV_PART != mmc->part_num) {
|
|
if (mmc_switch_part(CONFIG_SYS_MMC_ENV_DEV,
|
|
CONFIG_SYS_MMC_ENV_PART)) {
|
|
puts("MMC partition switch failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fini_mmc_for_env(struct mmc *mmc)
|
|
{
|
|
#ifdef CONFIG_SYS_MMC_ENV_PART
|
|
if (CONFIG_SYS_MMC_ENV_PART != mmc->part_num)
|
|
mmc_switch_part(CONFIG_SYS_MMC_ENV_DEV,
|
|
mmc->part_num);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_CMD_SAVEENV
|
|
static inline int write_env(struct mmc *mmc, unsigned long size,
|
|
unsigned long offset, const void *buffer)
|
|
{
|
|
uint blk_start, blk_cnt, n;
|
|
|
|
blk_start = ALIGN(offset, mmc->write_bl_len) / mmc->write_bl_len;
|
|
blk_cnt = ALIGN(size, mmc->write_bl_len) / mmc->write_bl_len;
|
|
|
|
n = mmc->block_dev.block_write(CONFIG_SYS_MMC_ENV_DEV, blk_start,
|
|
blk_cnt, (u_char *)buffer);
|
|
|
|
return (n == blk_cnt) ? 0 : -1;
|
|
}
|
|
|
|
#ifdef CONFIG_ENV_OFFSET_REDUND
|
|
static unsigned char env_flags;
|
|
#endif
|
|
|
|
int saveenv(void)
|
|
{
|
|
ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
|
|
ssize_t len;
|
|
char *res;
|
|
struct mmc *mmc = find_mmc_device(CONFIG_SYS_MMC_ENV_DEV);
|
|
u32 offset;
|
|
int ret, copy = 0;
|
|
|
|
if (init_mmc_for_env(mmc))
|
|
return 1;
|
|
|
|
res = (char *)&env_new->data;
|
|
len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);
|
|
if (len < 0) {
|
|
error("Cannot export environment: errno = %d\n", errno);
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
env_new->crc = crc32(0, &env_new->data[0], ENV_SIZE);
|
|
|
|
#ifdef CONFIG_ENV_OFFSET_REDUND
|
|
env_new->flags = ++env_flags; /* increase the serial */
|
|
|
|
if (gd->env_valid == 1)
|
|
copy = 1;
|
|
#endif
|
|
|
|
if (mmc_get_env_addr(mmc, copy, &offset)) {
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
printf("Writing to %sMMC(%d)... ", copy ? "redundant " : "",
|
|
CONFIG_SYS_MMC_ENV_DEV);
|
|
if (write_env(mmc, CONFIG_ENV_SIZE, offset, (u_char *)env_new)) {
|
|
puts("failed\n");
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
puts("done\n");
|
|
ret = 0;
|
|
|
|
#ifdef CONFIG_ENV_OFFSET_REDUND
|
|
gd->env_valid = gd->env_valid == 2 ? 1 : 2;
|
|
#endif
|
|
|
|
fini:
|
|
fini_mmc_for_env(mmc);
|
|
return ret;
|
|
}
|
|
#endif /* CONFIG_CMD_SAVEENV */
|
|
|
|
static inline int read_env(struct mmc *mmc, unsigned long size,
|
|
unsigned long offset, const void *buffer)
|
|
{
|
|
uint blk_start, blk_cnt, n;
|
|
|
|
blk_start = ALIGN(offset, mmc->read_bl_len) / mmc->read_bl_len;
|
|
blk_cnt = ALIGN(size, mmc->read_bl_len) / mmc->read_bl_len;
|
|
|
|
n = mmc->block_dev.block_read(CONFIG_SYS_MMC_ENV_DEV, blk_start,
|
|
blk_cnt, (uchar *)buffer);
|
|
|
|
return (n == blk_cnt) ? 0 : -1;
|
|
}
|
|
|
|
#ifdef CONFIG_ENV_OFFSET_REDUND
|
|
void env_relocate_spec(void)
|
|
{
|
|
#if !defined(ENV_IS_EMBEDDED)
|
|
struct mmc *mmc = find_mmc_device(CONFIG_SYS_MMC_ENV_DEV);
|
|
u32 offset1, offset2;
|
|
int read1_fail = 0, read2_fail = 0;
|
|
int crc1_ok = 0, crc2_ok = 0;
|
|
env_t *ep, *tmp_env1, *tmp_env2;
|
|
int ret;
|
|
|
|
tmp_env1 = (env_t *)malloc(CONFIG_ENV_SIZE);
|
|
tmp_env2 = (env_t *)malloc(CONFIG_ENV_SIZE);
|
|
if (tmp_env1 == NULL || tmp_env2 == NULL) {
|
|
puts("Can't allocate buffers for environment\n");
|
|
ret = 1;
|
|
goto err;
|
|
}
|
|
|
|
if (init_mmc_for_env(mmc)) {
|
|
ret = 1;
|
|
goto err;
|
|
}
|
|
|
|
if (mmc_get_env_addr(mmc, 0, &offset1) ||
|
|
mmc_get_env_addr(mmc, 1, &offset2)) {
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
read1_fail = read_env(mmc, CONFIG_ENV_SIZE, offset1, tmp_env1);
|
|
read2_fail = read_env(mmc, CONFIG_ENV_SIZE, offset2, tmp_env2);
|
|
|
|
if (read1_fail && read2_fail)
|
|
puts("*** Error - No Valid Environment Area found\n");
|
|
else if (read1_fail || read2_fail)
|
|
puts("*** Warning - some problems detected "
|
|
"reading environment; recovered successfully\n");
|
|
|
|
crc1_ok = !read1_fail &&
|
|
(crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
|
|
crc2_ok = !read2_fail &&
|
|
(crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);
|
|
|
|
if (!crc1_ok && !crc2_ok) {
|
|
ret = 1;
|
|
goto fini;
|
|
} else if (crc1_ok && !crc2_ok) {
|
|
gd->env_valid = 1;
|
|
} else if (!crc1_ok && crc2_ok) {
|
|
gd->env_valid = 2;
|
|
} else {
|
|
/* both ok - check serial */
|
|
if (tmp_env1->flags == 255 && tmp_env2->flags == 0)
|
|
gd->env_valid = 2;
|
|
else if (tmp_env2->flags == 255 && tmp_env1->flags == 0)
|
|
gd->env_valid = 1;
|
|
else if (tmp_env1->flags > tmp_env2->flags)
|
|
gd->env_valid = 1;
|
|
else if (tmp_env2->flags > tmp_env1->flags)
|
|
gd->env_valid = 2;
|
|
else /* flags are equal - almost impossible */
|
|
gd->env_valid = 1;
|
|
}
|
|
|
|
free(env_ptr);
|
|
|
|
if (gd->env_valid == 1)
|
|
ep = tmp_env1;
|
|
else
|
|
ep = tmp_env2;
|
|
|
|
env_flags = ep->flags;
|
|
env_import((char *)ep, 0);
|
|
ret = 0;
|
|
|
|
fini:
|
|
fini_mmc_for_env(mmc);
|
|
err:
|
|
if (ret)
|
|
set_default_env(NULL);
|
|
|
|
free(tmp_env1);
|
|
free(tmp_env2);
|
|
#endif
|
|
}
|
|
#else /* ! CONFIG_ENV_OFFSET_REDUND */
|
|
void env_relocate_spec(void)
|
|
{
|
|
#if !defined(ENV_IS_EMBEDDED)
|
|
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
|
|
struct mmc *mmc = find_mmc_device(CONFIG_SYS_MMC_ENV_DEV);
|
|
u32 offset;
|
|
int ret;
|
|
|
|
if (init_mmc_for_env(mmc)) {
|
|
ret = 1;
|
|
goto err;
|
|
}
|
|
|
|
if (mmc_get_env_addr(mmc, 0, &offset)) {
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
if (read_env(mmc, CONFIG_ENV_SIZE, offset, buf)) {
|
|
ret = 1;
|
|
goto fini;
|
|
}
|
|
|
|
env_import(buf, 1);
|
|
ret = 0;
|
|
|
|
fini:
|
|
fini_mmc_for_env(mmc);
|
|
err:
|
|
if (ret)
|
|
set_default_env(NULL);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_ENV_OFFSET_REDUND */
|