[U-Boot] [PATCH] NAND: Support dynamic location of enviromnent (CONFIG_ENV_OFFSET_OOB)

Ben Gardiner
2010-05-17T21:04:30Z

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> in
http://article.gmane.org/gmane.comp.boot-loaders.u-boot/43916.

This patch was made against master branch of
git://git.denx.de/u-boot-nand-flash.git with the recent patch from Wolfgang Denk
to fix the undefined __aeabi_unwind_cpp_pr0() problem:
http://download.gmane.org/gmane.comp.boot-loaders.u-boot/78612/78613

Testing was performed with the patch applied to the master branch of
git://arago-project.org/git/people/sekhar/u-boot-omapl1.git , which is forked from
uboot's v2009.11 a200a7c04d89853d2a1395b96d8ca5e3dd754551 tag -- this is because
the board I am actually using -- the da850 / OMAP L138 -- does not have config.

This patch enables the environment parttion 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 'dynenv' command, which can be called from the u-boot
command line. 'dynenv get' reads the address of the environment partition from
the OOB data, 'dynenv set {offset,partition-name}' allows the setting of the
marker by specifying a numeric offset or a partition name.

Signed-off-by: Ben Gardiner <bengardiner@nanometrics.ca>
---
common/Makefile | 1 +
common/cmd_dynenv.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++
common/cmd_nand.c | 10 ++--
common/cmd_nand.h | 33 ++++++++++++++
common/env_nand.c | 52 +++++++++++++++++++++++
include/environment.h | 21 +++++++---
include/nand.h | 6 +++
7 files changed, 224 insertions(+), 11 deletions(-)
create mode 100644 common/cmd_dynenv.c
create mode 100644 common/cmd_nand.h

diff --git a/common/Makefile b/common/Makefile
index dbf7a05..83520d7 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -85,6 +85,7 @@ COBJS-$(CONFIG_CMD_DIAG) += cmd_diag.o
endif
COBJS-$(CONFIG_CMD_DISPLAY) += cmd_display.o
COBJS-$(CONFIG_CMD_DTT) += cmd_dtt.o
+COBJS-$(CONFIG_ENV_OFFSET_OOB) += cmd_dynenv.o
COBJS-$(CONFIG_CMD_ECHO) += cmd_echo.o
COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += cmd_eeprom.o
COBJS-$(CONFIG_CMD_EEPROM) += cmd_eeprom.o
diff --git a/common/cmd_dynenv.c b/common/cmd_dynenv.c
new file mode 100644
index 0000000..5167875
--- /dev/null
+++ b/common/cmd_dynenv.c
@@ -0,0 +1,112 @@
+/*
+ * (C) Copyright 2006-2007 OpenMoko, Inc.
+ * Author: Harald Welte <laforge@openmoko.org>
+ * (C) Copyright 2008 Harald Welte <laforge@openmoko.org>
+ *
+ * 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
+ */
+
+#include <common.h>
+#include <command.h>
+#include <malloc.h>
+#include <environment.h>
+#include <nand.h>
+#include "cmd_nand.h"
+#include <asm/errno.h>
+
+unsigned long env_offset;
+
+int do_dynenv(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
+{
+ int ret;
+ uint32_t *oob_buf;
+
+ char *cmd = argv[1];
+
+ if (!strcmp(cmd, "get")) {
+ ret = get_dynenv(&CONFIG_ENV_OFFSET);
+ if(!ret) {
+ printf("0x%08lx\n", CONFIG_ENV_OFFSET);
+ }
+ else {
+ return 1;
+ }
+ }
+ else if (!strcmp(cmd, "set")) {
+ ulong addr, dummy_size;
+ struct mtd_info *mtd = &nand_info[0];
+ struct mtd_oob_ops ops;
+
+ if (argc < 3)
+ goto usage;
+
+ if (nand_arg_off_size(argc-2, argv + 2, mtd, &addr, &dummy_size, 1) < 0) {
+ printf("Offset or partition name expected\n");
+ return 1;
+ }
+
+ if(mtd->oobavail < CONFIG_ENV_OFFSET_SIZE){
+ printf("Insufficient available OOB bytes: %d OOB bytes available but %d required for dynenv support\n",mtd->oobavail,8);
+ }
+
+ oob_buf = malloc(mtd->oobsize);
+ if(!oob_buf)
+ return -ENOMEM;
+
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = CONFIG_ENV_OFFSET_SIZE;
+ ops.oobbuf = (void *) oob_buf;
+
+ ret = mtd->read_oob(mtd, CONFIG_ENV_OFFSET_SIZE, &ops);
+ oob_buf[0] = ENV_OOB_MARKER;
+
+ if (!ret) {
+ if(addr & ~oob_buf[1]) {
+ printf("ERROR: erase OOB block 0 to "
+ "write this value\n");
+ goto fail;
+ }
+ }
+ oob_buf[1] = addr;
+
+ ret = mtd->write_oob(mtd, CONFIG_ENV_OFFSET_SIZE, &ops);
+ if (!ret)
+ CONFIG_ENV_OFFSET = addr;
+ else {
+ printf("Error writing OOB block 0\n");
+ goto fail;
+ }
+
+ free(oob_buf);
+ } else
+ goto usage;
+
+ return ret;
+
+fail:
+ free(oob_buf);
+ return 1;
+usage:
+ cmd_usage(cmdtp);
+ return 1;
+}
+
+U_BOOT_CMD(dynenv, 4, 1, do_dynenv,
+ "dynenv - dynamically placed (NAND) environment",
+ "set off - set enviromnent offset\n"
+ "dynenv get - get environment offset");
\ No newline at end of file
diff --git a/common/cmd_nand.c b/common/cmd_nand.c
index 9b0c930..7575904 100644
--- a/common/cmd_nand.c
+++ b/common/cmd_nand.c
@@ -89,8 +89,8 @@ static inline int str2long(char *p, ulong *num)
return (*p != '\0' && *endptr == '\0') ? 1 : 0;
}

-static int
-arg_off_size(int argc, char *argv[], nand_info_t *nand, ulong *off, size_t *size)
+int
+nand_arg_off_size(int argc, char *argv[], nand_info_t *nand, ulong *off, size_t *size)
{
int idx = nand_curr_device;
#if defined(CONFIG_CMD_MTDPARTS)
@@ -305,7 +305,7 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])

printf("\nNAND %s: ", scrub ? "scrub" : "erase");
/* skip first two or three arguments, look for offset and size */
- if (arg_off_size(argc - o, argv + o, nand, &off, &size) != 0)
+ if (nand_arg_off_size(argc - o, argv + o, nand, &off, &size) != 0)
return 1;

memset(&opts, 0, sizeof(opts));
@@ -372,7 +372,7 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])

read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
printf("\nNAND %s: ", read ? "read" : "write");
- if (arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
+ if (nand_arg_off_size(argc - 3, argv + 3, nand, &off, &size) != 0)
return 1;

s = strchr(cmd, '.');
@@ -462,7 +462,7 @@ int do_nand(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
}

if (strcmp(cmd, "unlock") == 0) {
- if (arg_off_size(argc - 2, argv + 2, nand, &off, &size) < 0)
+ if (nand_arg_off_size(argc - 2, argv + 2, nand, &off, &size) < 0)
return 1;

if (!nand_unlock(nand, off, size)) {
diff --git a/common/cmd_nand.h b/common/cmd_nand.h
new file mode 100644
index 0000000..023ed4f
--- /dev/null
+++ b/common/cmd_nand.h
@@ -0,0 +1,33 @@
+/*
+ * cmd_nand.h - Convenience functions
+ *
+ * (C) Copyright 2006-2007 OpenMoko, Inc.
+ * Author: Werner Almesberger <werner@openmoko.org>
+ *
+ * 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
+ */
+
+#ifndef CMD_NAND_H
+#define CMD_NAND_H
+
+#include <nand.h>
+
+
+/* common/cmd_nand.c */
+int nand_arg_off_size(int argc, char *argv[], nand_info_t *nand, ulong *off,
+ ulong *size, int net);
+
+#endif /* CMD_NAND_H */
diff --git a/common/env_nand.c b/common/env_nand.c
index a15a950..ee55877 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,46 @@ int readenv (size_t offset, u_char * buf)
return 0;
}

+#ifdef CONFIG_ENV_OFFSET_OOB
+int get_dynenv(unsigned long *result)
+{
+ struct mtd_info *mtd = &nand_info[0];
+ struct mtd_oob_ops ops;
+ uint32_t *oob_buf;
+ int ret;
+
+ oob_buf = malloc(mtd->oobsize);
+ if(!oob_buf)
+ return -ENOMEM;
+
+ ops.datbuf = NULL;
+ ops.mode = MTD_OOB_AUTO;
+ ops.ooboffs = 0;
+ ops.ooblen = CONFIG_ENV_OFFSET_SIZE;
+ ops.oobbuf = (void *) oob_buf;
+
+ ret = mtd->read_oob(mtd, CONFIG_ENV_OFFSET_SIZE, &ops);
+
+ if(!ret) {
+ if(oob_buf[0] == ENV_OOB_MARKER) {
+ *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:
+ free(oob_buf);
+
+ return ret;
+}
+#endif
+
#ifdef CONFIG_ENV_OFFSET_REDUND
void env_relocate_spec (void)
{
@@ -357,12 +398,23 @@ void env_relocate_spec (void)
#if !defined(ENV_IS_EMBEDDED)
int ret;

+#if defined(CONFIG_ENV_OFFSET_OOB)
+ ret = get_dynenv(&CONFIG_ENV_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();

if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();
+
#endif /* ! ENV_IS_EMBEDDED */
}
#endif /* CONFIG_ENV_OFFSET_REDUND */
diff --git a/include/environment.h b/include/environment.h
index b9924fd..03b6c92 100644
--- a/include/environment.h
+++ b/include/environment.h
@@ -74,15 +74,24 @@
#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 env_offset;
+# undef CONFIG_ENV_OFFSET
+# define CONFIG_ENV_OFFSET env_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..29238c8 100644
--- a/include/nand.h
+++ b/include/nand.h
@@ -130,3 +130,9 @@ 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 0x30564e45 //"ENV0" in little-endian
+#define CONFIG_ENV_OFFSET_SIZE 8
+int get_dynenv(unsigned long *result);
+#endif
--
1.7.0.4