[U-Boot] [PATCH v3] NAND: environment offset in OOB (CONFIG_ENV_OFFSET_OOB)

Ben Gardiner
2010-06-30T21:32:08Z

This is a re-submission of the patch by Harald Welte
<laforge@openmoko.org> with minor modifications for rebase and changes
as suggested by Scott Wood <scottwood@freescale.com> [1] [2].

This patch enables the environment partition to have a run-time dynamic
location (offset) in the NAND flash. The reason for this is simply that
all NAND flashes have factory-default bad blocks, and a fixed compile
time offset would mean that sometimes the environment partition would
live inside factory bad blocks. Since the number of factory default
blocks can be quite high (easily 1.3MBytes in current standard
components), it is not economic to keep that many spare blocks inside
the environment partition.

With this patch and CONFIG_ENV_OFFSET_OOB enabled, the location of the
environment partition is stored in the out-of-band (OOB) data of the
first block in flash. Since the first block is where most systems boot
from, the vendors guarantee that the first block is not a factory
default block.

This patch introduces the 'nand env.oob' command, which can be called from
the u-boot command line. 'nand env.oob get' reads the address of the
environment partition from the OOB data,
'nand env.oob set {offset,partition-name}' allows the setting of the marker
by specifying a numeric offset or a partition name.

[1] http://article.gmane.org/gmane.comp.boot-loaders.u-boot/43916
[2] http://article.gmane.org/gmane.comp.boot-loaders.u-boot/79195

---

Changes in v3:
* updated commit message
* rebased to 39ddd10b046fb791f47281ffb2100be01909ad72 of
git://git.denx.de/u-boot.git
* tested using small config changes to include/configs/da850evm.h

Changes in v2:
* don't use generic names for the env-offset global and the comand
* make a sub-command of the nand command
* store the offset in units of eraseblocks
* allocate oob write/read buffers on stack
* verify write of new offset
* make setting new offset affect the live values
* update copyright of file
* use the global variable instead of the macro in address statements
* don't make the oob used bytes configurable
* don't undef CONFIG_ENV_OFFSET

Signed-off-by: Ben Gardiner <bengardiner@nanometrics.ca>

---
common/cmd_nand.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++-
common/env_nand.c | 45 ++++++++++++++++++++
include/environment.h | 20 ++++++---
include/nand.h | 9 ++++
4 files changed, 174 insertions(+), 7 deletions(-)

diff --git a/common/cmd_nand.c b/common/cmd_nand.c
index f611fd7..5af5b9e 100644
--- a/common/cmd_nand.c
+++ b/common/cmd_nand.c
@@ -6,6 +6,8 @@
*
* Added 16-bit nand support
* (C) 2004 Texas Instruments
+ * Added env offset in OOB
+ * (C) 2010 Nanometrics, Inc.
*/

#include <common.h>
@@ -193,6 +195,91 @@ static void do_nand_status(nand_info_t *nand)
}
#endif

+#ifdef CONFIG_ENV_OFFSET_OOB
+unsigned long nand_env_oob_offset;
+
+int do_nand_env_oob(cmd_tbl_t *cmdtp, nand_info_t *nand,
+ int argc, char *argv[])
+{
+ int ret;
+ uint32_t oob_buf[ENV_OFFSET_SIZE/sizeof(uint32_t)];
+
+ char *cmd = argv[1];
+
+ if (!strcmp(cmd, "get")) {
+ ret = get_nand_env_oob(nand, &nand_env_oob_offset);
+ if (!ret) {
+ printf("0x%08lx\n", nand_env_oob_offset);
+ }
+ else {
+ return 1;
+ }
+ } else if (!strcmp(cmd, "set")) {
+ ulong addr;
+ size_t dummy_size;
+ struct mtd_oob_ops ops;
+
+ if (argc < 3)
+ goto usage;
+
+ if (arg_off_size(argc-2, argv + 2, nand, &addr,
+ &dummy_size) < 0) {
+ printf("Offset or partition name expected\n");
+ return 1;
+ }
+
+ if (nand->oobavail < ENV_OFFSET_SIZE) {
+ printf("Insufficient available OOB bytes: %d OOB bytes"
+ " available but %d required for env.oob support\n"
+ ,nand->oobavail,ENV_OFFSET_SIZE);
+ return 1;
+ }
+
+ if ((addr & (nand->erasesize - 1)) != 0) {
+ printf("Environment offset must be block-aligned\n");
+ return 1;
+ }
+
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = ENV_OFFSET_SIZE;
+ ops.oobbuf = (void *) oob_buf;
+
+ oob_buf[0] = ENV_OOB_MARKER;
+ oob_buf[1] = addr / nand->erasesize;
+
+ ret = nand->write_oob(nand, ENV_OFFSET_SIZE, &ops);
+ if (!ret) {
+ ret = get_nand_env_oob(nand, &nand_env_oob_offset);
+ if (ret) {
+ printf("Error reading env offset in OOB\n");
+ return ret;
+ }
+
+ if (addr != nand_env_oob_offset) {
+ printf("Verification of env offset in OOB "
+ "failed: 0x%08lx expected but got "
+ "0x%08lx\n", addr, nand_env_oob_offset);
+ return 1;
+ }
+ } else {
+ printf("Error writing OOB block 0\n");
+ return ret;
+ }
+ } else {
+ goto usage;
+ }
+
+ return ret;
+
+usage:
+ cmd_usage(cmdtp);
+ return 1;
+}
+
+#endif
+
static void nand_print_info(int idx)
{
nand_info_t *nand = &nand_info[idx];
@@ -272,9 +359,20 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
strncmp(cmd, "read", 4) != 0 && strncmp(cmd, "write", 5) != 0 &&
strcmp(cmd, "scrub") != 0 && strcmp(cmd, "markbad") != 0 &&
strcmp(cmd, "biterr") != 0 &&
- strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0 )
+ strcmp(cmd, "lock") != 0 && strcmp(cmd, "unlock") != 0
+#ifdef CONFIG_ENV_OFFSET_OOB
+ && strcmp(cmd, "env.oob") != 0
+#endif
+ )
goto usage;

+#ifdef CONFIG_ENV_OFFSET_OOB
+ /* this command operates only on the first nand device */
+ if (strcmp(cmd,"env.oob") == 0) {
+ return do_nand_env_oob(cmdtp, &nand_info[0], argc - 1, argv + 1);
+ }
+#endif
+
/* the following commands operate on the current device */
if (nand_curr_device < 0 || nand_curr_device >= CONFIG_SYS_MAX_NAND_DEVICE ||
!nand_info[nand_curr_device].name) {
@@ -502,6 +600,13 @@ U_BOOT_CMD(nand, CONFIG_SYS_MAXARGS, 1, do_nand,
" bring nand to lock state or display locked pages\n"
"nand unlock [offset] [size] - unlock section"
#endif
+#ifdef CONFIG_ENV_OFFSET_OOB
+ "\n"
+ "nand env.oob - environment offset in OOB of block 0 of"
+ " first device.\n"
+ "nand env.oob set off|partition - set enviromnent offset\n"
+ "nand env.oob get - get environment offset"
+#endif
);

static int nand_load_image(cmd_tbl_t *cmdtp, nand_info_t *nand,
diff --git a/common/env_nand.c b/common/env_nand.c
index a15a950..2289eb2 100644
--- a/common/env_nand.c
+++ b/common/env_nand.c
@@ -38,6 +38,7 @@
#include <linux/stddef.h>
#include <malloc.h>
#include <nand.h>
+#include <asm/errno.h>

#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_CMD_NAND)
#define CMD_SAVEENV
@@ -288,6 +289,40 @@ int readenv (size_t offset, u_char * buf)
return 0;
}

+#ifdef CONFIG_ENV_OFFSET_OOB
+int get_nand_env_oob(nand_info_t *nand, unsigned long *result)
+{
+ struct mtd_oob_ops ops;
+ uint32_t oob_buf[ENV_OFFSET_SIZE/sizeof(uint32_t)];
+ int ret;
+
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = ENV_OFFSET_SIZE;
+ ops.oobbuf = (void *) oob_buf;
+
+ ret = nand->read_oob(nand, ENV_OFFSET_SIZE, &ops);
+
+ if (!ret) {
+ if (oob_buf[0] == ENV_OOB_MARKER) {
+ *result = oob_buf[1] * nand->erasesize;
+ } else if (oob_buf[0] == ENV_OOB_MARKER_OLD) {
+ *result = oob_buf[1];
+ } else {
+ printf("No dynamic environment marker in OOB block 0\n");
+ ret = -ENOENT;
+ goto fail;
+ }
+ }
+ else {
+ printf("error reading OOB block 0\n");
+ }
+fail:
+ return ret;
+}
+#endif
+
#ifdef CONFIG_ENV_OFFSET_REDUND
void env_relocate_spec (void)
{
@@ -357,6 +392,16 @@ void env_relocate_spec (void)
#if !defined(ENV_IS_EMBEDDED)
int ret;

+#if defined(CONFIG_ENV_OFFSET_OOB)
+ ret = get_nand_env_oob(&nand_info[0], &nand_env_oob_offset);
+ if(!ret) {
+ /* fall through to the normal environment reading code below */
+ printf("Found Environment offset in OOB..\n");
+ } else {
+ return use_default();
+ }
+#endif
+
ret = readenv(CONFIG_ENV_OFFSET, (u_char *) env_ptr);
if (ret)
return use_default();
diff --git a/include/environment.h b/include/environment.h
index b9924fd..9820d23 100644
--- a/include/environment.h
+++ b/include/environment.h
@@ -74,15 +74,23 @@
#endif /* CONFIG_ENV_IS_IN_FLASH */

#if defined(CONFIG_ENV_IS_IN_NAND)
-# ifndef CONFIG_ENV_OFFSET
-# error "Need to define CONFIG_ENV_OFFSET when using CONFIG_ENV_IS_IN_NAND"
-# endif
+# if defined(CONFIG_ENV_OFFSET_OOB)
+# ifdef CONFIG_ENV_OFFSET_REDUND
+# error "CONFIG_ENV_OFFSET_REDUND is not supported when CONFIG_ENV_OFFSET_OOB is set"
+# endif
+extern unsigned long nand_env_oob_offset;
+# define CONFIG_ENV_OFFSET nand_env_oob_offset
+# else
+# ifndef CONFIG_ENV_OFFSET
+# error "Need to define CONFIG_ENV_OFFSET when using CONFIG_ENV_IS_IN_NAND"
+# endif
+# ifdef CONFIG_ENV_OFFSET_REDUND
+# define CONFIG_SYS_REDUNDAND_ENVIRONMENT
+# endif
+# endif /* CONFIG_ENV_OFFSET_OOB */
# ifndef CONFIG_ENV_SIZE
# error "Need to define CONFIG_ENV_SIZE when using CONFIG_ENV_IS_IN_NAND"
# endif
-# ifdef CONFIG_ENV_OFFSET_REDUND
-# define CONFIG_SYS_REDUNDAND_ENVIRONMENT
-# endif
#endif /* CONFIG_ENV_IS_IN_NAND */

#if defined(CONFIG_ENV_IS_IN_MG_DISK)
diff --git a/include/nand.h b/include/nand.h
index 2a81597..8bdf419 100644
--- a/include/nand.h
+++ b/include/nand.h
@@ -130,3 +130,12 @@ void board_nand_select_device(struct nand_chip *nand, int chip);
__attribute__((noreturn)) void nand_boot(void);

#endif
+
+#ifdef CONFIG_ENV_OFFSET_OOB
+#define ENV_OOB_MARKER 0x30425645 /*"EVB0" in little-endian -- offset is stored
+ as block number*/
+#define ENV_OOB_MARKER_OLD 0x30564e45 /*"ENV0" in little-endian -- offset is
+ stored as byte number */
+#define ENV_OFFSET_SIZE 8
+int get_nand_env_oob(nand_info_t *nand, unsigned long *result);
+#endif
--
1.7.0.4