[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