
Add support for environment in NAND with automatic recognition, including unaligned environment, bad-block skipping, redundant environment copy.
Signed-off-by: Guennadi Liakhovetski lg@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;