[U-Boot] [PATCH 6/6] Support environment in NAND
Guennadi Liakhovetski
lg at denx.de
Wed Aug 27 17:52:57 CEST 2008
Add support for environment in NAND with automatic recognition, including
unaligned environment, bad-block skipping, redundant environment copy.
Signed-off-by: Guennadi Liakhovetski <lg at denx.de>
---
tools/env/fw_env.c | 344 +++++++++++++++++++++++++++++++++++-----------------
1 files changed, 231 insertions(+), 113 deletions(-)
diff --git a/tools/env/fw_env.c b/tools/env/fw_env.c
index 931e647..66422e3 100644
--- a/tools/env/fw_env.c
+++ b/tools/env/fw_env.c
@@ -44,6 +44,12 @@
#define CMD_GETENV "fw_printenv"
#define CMD_SETENV "fw_setenv"
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void) (&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+
typedef struct envdev_s {
char devname[16]; /* Device name */
ulong devoff; /* Device offset */
@@ -413,179 +419,290 @@ int fw_setenv (int argc, char *argv[])
return 0;
}
+static int flash_bad_block (int dev, int fd, struct mtd_info_user *mtdinfo,
+ loff_t *blockstart, size_t blocklen)
+{
+ if (mtdinfo->type == MTD_NANDFLASH) {
+ int badblock = ioctl (fd, MEMGETBADBLOCK, blockstart);
+
+ if (badblock < 0) {
+ perror ("Cannot read bad block mark");
+ return badblock;
+ }
+
+ if (badblock) {
+ fprintf (stderr, "Bad block at 0x%llx, "
+ "skipping\n", *blockstart);
+ *blockstart += blocklen;
+ return badblock;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * We are called with count == 0 for backing up as much data from the
+ * range as possible
+ */
static int flash_read_buf (int dev, int fd, void *buf, size_t count,
- off_t offset)
+ off_t offset, size_t range)
{
+ struct mtd_info_user mtdinfo;
+ size_t blocklen, processed = 0;
+ size_t readlen = count ? : range;
+ off_t erase_offset, block_seek;
+ loff_t blockstart;
int rc;
+ int backup_mode = !count;
- rc = lseek (fd, offset, SEEK_SET);
- if (rc == -1) {
- fprintf (stderr,
- "seek error on %s: %s\n",
- DEVNAME (dev), strerror (errno));
+ if (!count)
+ count = range;
+
+ rc = ioctl (fd, MEMGETINFO, &mtdinfo);
+ if (rc < 0) {
+ perror ("Cannot get MTD information");
return rc;
}
- rc = read (fd, buf, count);
- if (rc != count) {
- fprintf (stderr,
- "Read error on %s: %s\n",
- DEVNAME (dev), strerror (errno));
- return -1;
+ /* Erase sector size is always a power of 2 */
+ erase_offset = offset & ~(mtdinfo.erasesize - 1);
+
+ blockstart = erase_offset;
+ /* Offset inside a block */
+ block_seek = offset - erase_offset;
+
+ if (mtdinfo.type == MTD_NANDFLASH) {
+ /*
+ * NAND: calculate which blocks we are reading. We have
+ * to read one block at a time to skip bad blocks.
+ */
+ blocklen = mtdinfo.erasesize;
+ /* Limit to one block for the first read */
+ if (readlen > blocklen - block_seek)
+ readlen = blocklen - block_seek;
+ } else {
+ blocklen = 0;
}
- return rc;
+ /* This only runs once for NOR flash */
+ while (processed < count) {
+ rc = flash_bad_block (dev, fd, &mtdinfo, &blockstart, blocklen);
+ if (rc < 0)
+ return -1;
+ else if (blockstart + block_seek + readlen > offset + range) {
+ /* End of range is reached */
+ if (backup_mode) {
+ return processed;
+ } else {
+ fprintf (stderr,
+ "Too few good blocks within range\n");
+ return -1;
+ }
+ } else if (rc)
+ continue;
+
+ /*
+ * If a block is bad, we retry in the next block
+ * at the same offset - see common/env_nand.c::
+ * writeenv()
+ */
+ lseek (fd, blockstart + block_seek, SEEK_SET);
+
+ rc = read (fd, buf + processed, readlen);
+ if (rc != readlen) {
+ fprintf (stderr,
+ "Read error on %s: %s\n",
+ DEVNAME (dev), strerror (errno));
+ return -1;
+ }
+ processed += readlen;
+ readlen = min(blocklen, count - processed);
+ block_seek = 0;
+ blockstart += blocklen;
+ }
+
+ return processed;
}
-static int flash_write (void)
+static int flash_write_buf (int dev, int fd, void *buf, size_t count,
+ off_t offset)
{
- int fd_current, fd_target, rc, dev_target;
- erase_info_t erase_current = {}, erase_target;
char *data = NULL;
- off_t erase_offset;
- struct mtd_info_user mtdinfo_target;
+ erase_info_t erase;
+ struct mtd_info_user mtdinfo;
+ size_t blocklen, erase_len, processed = 0;
+ size_t writelen, write_total = DEVESIZE (dev);
+ off_t erase_offset, block_seek;
+ loff_t blockstart;
+ int rc;
- /* dev_current: fd_current, erase_current */
- if ((fd_current = open (DEVNAME (dev_current), O_RDWR)) < 0) {
- fprintf (stderr,
- "Can't open %s: %s\n",
- DEVNAME (dev_current), strerror (errno));
+ rc = ioctl (fd, MEMGETINFO, &mtdinfo);
+ if (rc < 0) {
+ perror ("Cannot get MTD information");
return -1;
}
- if (HaveRedundEnv) {
- /* switch to next partition for writing */
- dev_target = !dev_current;
- /* dev_target: fd_target, erase_target */
- if ((fd_target = open (DEVNAME (dev_target), O_RDWR)) < 0) {
- fprintf (stderr,
- "Can't open %s: %s\n",
- DEVNAME (dev_target),
- strerror (errno));
- return -1;
- }
- } else {
- dev_target = dev_current;
- fd_target = fd_current;
- }
+ /* Erase sector size is always a power of 2 */
+ erase_offset = offset & ~(mtdinfo.erasesize - 1);
+ /* Maximum area we may use */
+ erase_len = (offset - erase_offset + DEVESIZE (dev) +
+ mtdinfo.erasesize - 1) & ~(mtdinfo.erasesize - 1);
+
+ blockstart = erase_offset;
+ /* Offset inside a block */
+ block_seek = offset - erase_offset;
/*
* Support environment anywhere within erase sectors: read out the
* complete area to be erased, replace the environment image, write
* the whole block back again.
*/
- if (DEVESIZE (dev_target) > CFG_ENV_SIZE) {
- data = malloc (DEVESIZE (dev_target));
+ if (erase_len > DEVESIZE (dev)) {
+ data = malloc (erase_len);
if (!data) {
fprintf (stderr,
- "Cannot malloc %lu bytes: %s\n",
- DEVESIZE (dev_target),
- strerror (errno));
+ "Cannot malloc %u bytes: %s\n",
+ erase_len, strerror (errno));
return -1;
}
- rc = ioctl (fd_target, MEMGETINFO, &mtdinfo_target);
- if (rc < 0) {
- perror ("Cannot get MTD information");
+ /*
+ * This is different from a normal read. We have to read as much
+ * as we can from a certain area, and it should be at least X
+ * bytes, instead of having to read a fixed number of bytes as
+ * usual. This also tells us how much data "fits" in the good
+ * blocks in the area.
+ */
+ write_total = flash_read_buf (dev, fd, data, 0,
+ erase_offset, erase_len);
+ if (write_total < block_seek + CFG_ENV_SIZE)
return -1;
- }
-
- /* Erase sector size is always a power of 2 */
- erase_offset = DEVOFFSET (dev_target) &
- ~(mtdinfo_target.erasesize - 1);
-
- rc = flash_read_buf (dev_target, fd_target, data,
- DEVESIZE (dev_target), erase_offset);
- if (rc < 0)
- return rc;
/* Overwrite the old environment */
- memcpy(DEVOFFSET (dev_target) - erase_offset + data,
- environment.image, CFG_ENV_SIZE);
+ memcpy(data + block_seek, buf, count);
} else {
data = (char *)environment.image;
- erase_offset = DEVOFFSET (dev_target);
}
- printf ("Unlocking flash...\n");
- erase_target.length = DEVESIZE (dev_target);
- erase_target.start = DEVOFFSET (dev_target);
- ioctl (fd_target, MEMUNLOCK, &erase_target);
-
- if (HaveRedundEnv) {
- erase_current.length = DEVESIZE (dev_current);
- erase_current.start = DEVOFFSET (dev_current);
- ioctl (fd_current, MEMUNLOCK, &erase_current);
- ENV_FLAGS(environment) = active_flag;
+ if (mtdinfo.type == MTD_NANDFLASH) {
+ /*
+ * NAND: calculate which blocks we are writing. We have
+ * to write one block at a time to skip bad blocks.
+ */
+ blocklen = mtdinfo.erasesize;
+ /* Limit to one block */
+ writelen = blocklen;
+ } else {
+ blocklen = erase_len;
+ writelen = erase_len;
}
- printf ("Done\n");
+ erase.length = blocklen;
- printf ("Erasing old environment...\n");
+ while (processed < write_total) {
+ rc = flash_bad_block (dev, fd, &mtdinfo, &blockstart, blocklen);
+ if (rc < 0)
+ return rc;
+ else if (rc)
+ continue;
- if (ioctl (fd_target, MEMERASE, &erase_target) != 0) {
- fprintf (stderr, "MTD erase error on %s: %s\n",
- DEVNAME (dev_target),
- strerror (errno));
- return -1;
- }
+ printf ("Unlocking flash at %llx...", blockstart);
- printf ("Done\n");
+ erase.start = blockstart;
+ ioctl (fd, MEMUNLOCK, &erase);
- printf ("Writing environment to %s...\n", DEVNAME (dev_target));
- if (lseek (fd_target, erase_offset, SEEK_SET) == -1) {
- fprintf (stderr,
- "seek error on %s: %s\n",
- DEVNAME (dev_target), strerror (errno));
- return -1;
- }
+ printf ("Done\n");
- if (write (fd_target, data, DEVESIZE (dev_target)) !=
- DEVESIZE (dev_target)) {
- fprintf (stderr,
- "Write error on %s: %s\n",
- DEVNAME (dev_target), strerror (errno));
- return -1;
- }
+ printf ("Erasing old environment at %llx...", blockstart);
- if (DEVESIZE (dev_target) > CFG_ENV_SIZE)
- free (data);
+ if (ioctl (fd, MEMERASE, &erase) != 0) {
+ fprintf (stderr, "MTD erase error on %s: %s\n",
+ DEVNAME (dev),
+ strerror (errno));
+ return -1;
+ }
- if (HaveRedundEnv) {
- /* change flag on current active env partition */
- if (lseek (fd_current, DEVOFFSET (dev_current) + sizeof (ulong),
- SEEK_SET) == -1) {
- fprintf (stderr, "seek error on %s: %s\n",
- DEVNAME (dev_current), strerror (errno));
+ printf ("Done\n");
+
+ printf ("Writing %u bytes of environment to %s...", writelen,
+ DEVNAME (dev));
+ if (lseek (fd, blockstart, SEEK_SET) == -1) {
+ fprintf (stderr,
+ "Seek error on %s: %s\n",
+ DEVNAME (dev), strerror (errno));
return -1;
}
- if (write (fd_current, &obsolete_flag,
- sizeof (obsolete_flag)) != sizeof (obsolete_flag)) {
+
+ if (write (fd, data + processed, writelen) != writelen) {
fprintf (stderr,
"Write error on %s: %s\n",
- DEVNAME (dev_current), strerror (errno));
+ DEVNAME (dev), strerror (errno));
return -1;
}
+ printf ("Done\n");
+
+ printf ("Locking ...");
+ ioctl (fd, MEMLOCK, &erase);
+ printf ("Done\n");
+
+ processed += writelen;
+ writelen = min(blocklen, count - processed);
+ block_seek = 0;
+ blockstart += blocklen;
}
- printf ("Done\n");
- printf ("Locking ...\n");
- ioctl (fd_target, MEMLOCK, &erase_target);
+
+ if (erase_len > CFG_ENV_SIZE)
+ free (data);
+
+ return processed;
+}
+
+static int flash_write (void)
+{
+ int fd_current, fd_target, rc, dev_target;
+
+ /* dev_current: fd_current, erase_current */
+ if ((fd_current = open (DEVNAME (dev_current), O_RDWR)) < 0) {
+ fprintf (stderr,
+ "Can't open %s: %s\n",
+ DEVNAME (dev_current), strerror (errno));
+ return -1;
+ }
+
if (HaveRedundEnv) {
- ioctl (fd_current, MEMLOCK, &erase_current);
- if (close (fd_target)) {
+ /* switch to next partition for writing */
+ dev_target = !dev_current;
+ /* dev_target: fd_target, erase_target */
+ if ((fd_target = open (DEVNAME (dev_target), O_RDWR)) < 0) {
fprintf (stderr,
- "I/O error on %s: %s\n",
+ "Can't open %s: %s\n",
DEVNAME (dev_target),
strerror (errno));
return -1;
}
+ ENV_FLAGS(environment) = active_flag;
+ } else {
+ dev_target = dev_current;
+ fd_target = fd_current;
}
- printf ("Done\n");
- if (close (fd_current)) {
+ rc = flash_write_buf (dev_target, fd_target, environment.image,
+ CFG_ENV_SIZE, DEVOFFSET (dev_target));
+ if (rc < 0)
+ return rc;
+
+ if (HaveRedundEnv) {
+ off_t offset = DEVOFFSET (dev_current) +
+ offsetof(union env_image, redund.flags);
+ rc = flash_write_buf(dev_current, fd_current, &obsolete_flag,
+ sizeof (obsolete_flag), offset);
+ }
+
+ if (close (fd_target)) {
fprintf (stderr,
"I/O error on %s: %s\n",
- DEVNAME (dev_current), strerror (errno));
+ DEVNAME (dev_target), strerror (errno));
return -1;
}
@@ -604,8 +721,9 @@ static int flash_read (void)
return -1;
}
+ /* Only try within CFG_ENV_RANGE */
rc = flash_read_buf (dev_current, fd, environment.image, CFG_ENV_SIZE,
- DEVOFFSET (dev_current));
+ DEVOFFSET (dev_current), DEVESIZE (dev_current));
if (rc < 0)
return rc;
--
1.5.4
More information about the U-Boot
mailing list