Commit 56086921 authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Wolfgang Denk
Browse files

fw_env: add NAND support



Add support for environment in NAND with automatic NOR / NAND recognition,
including unaligned environment, bad-block skipping, redundant environment
copy.
Signed-off-by: default avatarGuennadi Liakhovetski <lg@denx.de>
parent dd794323
......@@ -22,9 +22,11 @@ following lines are relevant:
#define DEVICE1_OFFSET 0x0000
#define ENV1_SIZE 0x4000
#define DEVICE1_ESIZE 0x4000
#define DEVICE1_ENVSECTORS 2
#define DEVICE2_OFFSET 0x0000
#define ENV2_SIZE 0x4000
#define DEVICE2_ESIZE 0x4000
#define DEVICE2_ENVSECTORS 2
Current configuration matches the environment layout of the TRAB
board.
......@@ -46,3 +48,7 @@ then 1 sector.
DEVICEx_ESIZE defines the size of the first sector in the flash
partition where the environment resides.
DEVICEx_ENVSECTORS defines the number of sectors that may be used for
this environment instance. On NAND this is used to limit the range
within which bad blocks are skipped, on NOR it is not used.
......@@ -2,6 +2,9 @@
* (C) Copyright 2000-2008
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* (C) Copyright 2008
* Guennadi Liakhovetski, DENX Software Engineering, lg@denx.de.
*
* See file CREDITS for list of people who contributed to this
* project.
*
......@@ -45,36 +48,75 @@
#define CMD_GETENV "fw_printenv"
#define CMD_SETENV "fw_setenv"
typedef struct envdev_s {
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
struct envdev_s {
char devname[16]; /* Device name */
ulong devoff; /* Device offset */
ulong env_size; /* environment size */
ulong erase_size; /* device erase size */
} envdev_t;
ulong env_sectors; /* number of environment sectors */
uint8_t mtd_type; /* type of the MTD device */
};
static envdev_t envdevices[2];
static int curdev;
static struct envdev_s envdevices[2] =
{
{
.mtd_type = MTD_ABSENT,
}, {
.mtd_type = MTD_ABSENT,
},
};
static int dev_current;
#define DEVNAME(i) envdevices[(i)].devname
#define DEVOFFSET(i) envdevices[(i)].devoff
#define ENVSIZE(i) envdevices[(i)].env_size
#define DEVESIZE(i) envdevices[(i)].erase_size
#define ENVSECTORS(i) envdevices[(i)].env_sectors
#define DEVTYPE(i) envdevices[(i)].mtd_type
#define CFG_ENV_SIZE ENVSIZE(curdev)
#define CFG_ENV_SIZE ENVSIZE(dev_current)
#define ENV_SIZE getenvsize()
typedef struct environment_s {
ulong crc; /* CRC32 over data bytes */
unsigned char flags; /* active or obsolete */
char *data;
} env_t;
struct env_image_single {
uint32_t crc; /* CRC32 over data bytes */
char data[];
};
static env_t environment;
struct env_image_redundant {
uint32_t crc; /* CRC32 over data bytes */
unsigned char flags; /* active or obsolete */
char data[];
};
enum flag_scheme {
FLAG_NONE,
FLAG_BOOLEAN,
FLAG_INCREMENTAL,
};
struct environment {
void *image;
uint32_t *crc;
unsigned char *flags;
char *data;
enum flag_scheme flag_scheme;
};
static struct environment environment = {
.flag_scheme = FLAG_NONE,
};
static int HaveRedundEnv = 0;
static unsigned char active_flag = 1;
/* obsolete_flag must be 0 to efficiently set it on NOR flash without erasing */
static unsigned char obsolete_flag = 0;
......@@ -157,7 +199,7 @@ static char default_environment[] = {
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0" /* Termimate env_t data with 2 NULs */
"\0" /* Termimate struct environment data with 2 NULs */
};
static int flash_io (int mode);
......@@ -186,7 +228,7 @@ char *fw_getenv (char *name)
char *env, *nxt;
if (env_init ())
return (NULL);
return NULL;
for (env = environment.data; *env; env = nxt + 1) {
char *val;
......@@ -195,15 +237,15 @@ char *fw_getenv (char *name)
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf (stderr, "## Error: "
"environment not terminated\n");
return (NULL);
return NULL;
}
}
val = envmatch (name, env);
if (!val)
continue;
return (val);
return val;
}
return (NULL);
return NULL;
}
/*
......@@ -217,7 +259,7 @@ int fw_printenv (int argc, char *argv[])
int rc = 0;
if (env_init ())
return (-1);
return -1;
if (argc == 1) { /* Print all env variables */
for (env = environment.data; *env; env = nxt + 1) {
......@@ -225,13 +267,13 @@ int fw_printenv (int argc, char *argv[])
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf (stderr, "## Error: "
"environment not terminated\n");
return (-1);
return -1;
}
}
printf ("%s\n", env);
}
return (0);
return 0;
}
if (strcmp (argv[1], "-n") == 0) {
......@@ -241,7 +283,7 @@ int fw_printenv (int argc, char *argv[])
if (argc != 2) {
fprintf (stderr, "## Error: "
"`-n' option requires exactly one argument\n");
return (-1);
return -1;
}
} else {
n_flag = 0;
......@@ -257,7 +299,7 @@ int fw_printenv (int argc, char *argv[])
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf (stderr, "## Error: "
"environment not terminated\n");
return (-1);
return -1;
}
}
val = envmatch (name, env);
......@@ -276,11 +318,11 @@ int fw_printenv (int argc, char *argv[])
}
}
return (rc);
return rc;
}
/*
* Deletes or sets environment variables. Returns errno style error codes:
* Deletes or sets environment variables. Returns -1 and sets errno error codes:
* 0 - OK
* EINVAL - need at least 1 argument
* EROFS - certain variables ("ethaddr", "serial#") cannot be
......@@ -295,11 +337,12 @@ int fw_setenv (int argc, char *argv[])
char *name;
if (argc < 2) {
return (EINVAL);
errno = EINVAL;
return -1;
}
if (env_init ())
return (errno);
return -1;
name = argv[1];
......@@ -311,7 +354,8 @@ int fw_setenv (int argc, char *argv[])
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf (stderr, "## Error: "
"environment not terminated\n");
return (EINVAL);
errno = EINVAL;
return -1;
}
}
if ((oldval = envmatch (name, env)) != NULL)
......@@ -328,7 +372,8 @@ int fw_setenv (int argc, char *argv[])
if ((strcmp (name, "ethaddr") == 0) ||
(strcmp (name, "serial#") == 0)) {
fprintf (stderr, "Can't overwrite \"%s\"\n", name);
return (EROFS);
errno = EROFS;
return -1;
}
if (*++nxt == '\0') {
......@@ -367,7 +412,7 @@ int fw_setenv (int argc, char *argv[])
fprintf (stderr,
"Error: environment overflow, \"%s\" deleted\n",
name);
return (-1);
return -1;
}
while ((*env = *name++) != '\0')
env++;
......@@ -383,195 +428,430 @@ int fw_setenv (int argc, char *argv[])
WRITE_FLASH:
/* Update CRC */
environment.crc = crc32 (0, (uint8_t*) environment.data, ENV_SIZE);
/*
* Update CRC
*/
*environment.crc = crc32 (0, (uint8_t *) environment.data, ENV_SIZE);
/* write environment back to flash */
if (flash_io (O_RDWR)) {
fprintf (stderr, "Error: can't write fw_env to flash\n");
return (-1);
return -1;
}
return (0);
return 0;
}
static int flash_io (int mode)
/*
* Test for bad block on NAND, just returns 0 on NOR, on NAND:
* 0 - block is good
* > 0 - block is bad
* < 0 - failed to test
*/
static int flash_bad_block (int fd, uint8_t mtd_type, loff_t *blockstart)
{
int fd, fdr, rc, otherdev, len, resid;
struct erase_info_user erase;
char *data = NULL;
if (mtd_type == MTD_NANDFLASH) {
int badblock = ioctl (fd, MEMGETBADBLOCK, blockstart);
if ((fd = open (DEVNAME (curdev), mode)) < 0) {
fprintf (stderr,
"Can't open %s: %s\n",
DEVNAME (curdev), strerror (errno));
return (-1);
if (badblock < 0) {
perror ("Cannot read bad block mark");
return badblock;
}
if (badblock) {
#ifdef DEBUG
fprintf (stderr, "Bad block at 0x%llx, "
"skipping\n", *blockstart);
#endif
return badblock;
}
}
len = sizeof (environment.crc);
if (HaveRedundEnv) {
len += sizeof (environment.flags);
return 0;
}
/*
* Read data from flash at an offset into a provided buffer. On NAND it skips
* bad blocks but makes sure it stays within ENVSECTORS (dev) starting from
* the DEVOFFSET (dev) block. On NOR the loop is only run once.
*/
static int flash_read_buf (int dev, int fd, void *buf, size_t count,
off_t offset, uint8_t mtd_type)
{
size_t blocklen; /* erase / write length - one block on NAND,
0 on NOR */
size_t processed = 0; /* progress counter */
size_t readlen = count; /* current read length */
off_t top_of_range; /* end of the last block we may use */
off_t block_seek; /* offset inside the current block to the start
of the data */
loff_t blockstart; /* running start of the current block -
MEMGETBADBLOCK needs 64 bits */
int rc;
/*
* Start of the first block to be read, relies on the fact, that
* erase sector size is always a power of 2
*/
blockstart = offset & ~(DEVESIZE (dev) - 1);
/* Offset inside a block */
block_seek = offset - blockstart;
if (mtd_type == MTD_NANDFLASH) {
/*
* NAND: calculate which blocks we are reading. We have
* to read one block at a time to skip bad blocks.
*/
blocklen = DEVESIZE (dev);
/*
* To calculate the top of the range, we have to use the
* global DEVOFFSET (dev), which can be different from offset
*/
top_of_range = (DEVOFFSET (dev) & ~(blocklen - 1)) +
ENVSECTORS (dev) * blocklen;
/* Limit to one block for the first read */
if (readlen > blocklen - block_seek)
readlen = blocklen - block_seek;
} else {
blocklen = 0;
top_of_range = offset + count;
}
if (mode == O_RDWR) {
if (HaveRedundEnv) {
/* switch to next partition for writing */
otherdev = !curdev;
if ((fdr = open (DEVNAME (otherdev), mode)) < 0) {
fprintf (stderr,
"Can't open %s: %s\n",
DEVNAME (otherdev),
strerror (errno));
return (-1);
}
} else {
otherdev = curdev;
fdr = fd;
}
printf ("Unlocking flash...\n");
erase.length = DEVESIZE (otherdev);
erase.start = DEVOFFSET (otherdev);
ioctl (fdr, MEMUNLOCK, &erase);
/* This only runs once on NOR flash */
while (processed < count) {
rc = flash_bad_block (fd, mtd_type, &blockstart);
if (rc < 0) /* block test failed */
return -1;
if (HaveRedundEnv) {
erase.length = DEVESIZE (curdev);
erase.start = DEVOFFSET (curdev);
ioctl (fd, MEMUNLOCK, &erase);
environment.flags = active_flag;
if (blockstart + block_seek + readlen > top_of_range) {
/* End of range is reached */
fprintf (stderr,
"Too few good blocks within range\n");
return -1;
}
printf ("Done\n");
resid = DEVESIZE (otherdev) - CFG_ENV_SIZE;
if (resid) {
if ((data = malloc (resid)) == NULL) {
fprintf (stderr,
"Cannot malloc %d bytes: %s\n",
resid,
strerror (errno));
return (-1);
}
if (lseek (fdr, DEVOFFSET (otherdev) + CFG_ENV_SIZE, SEEK_SET)
== -1) {
fprintf (stderr, "seek error on %s: %s\n",
DEVNAME (otherdev),
strerror (errno));
return (-1);
}
if ((rc = read (fdr, data, resid)) != resid) {
fprintf (stderr,
"read error on %s: %s\n",
DEVNAME (otherdev),
strerror (errno));
return (-1);
}
if (rc) { /* block is bad */
blockstart += blocklen;
continue;
}
printf ("Erasing old environment...\n");
/*
* 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);
erase.length = DEVESIZE (otherdev);
erase.start = DEVOFFSET (otherdev);
if (ioctl (fdr, MEMERASE, &erase) != 0) {
fprintf (stderr, "MTD erase error on %s: %s\n",
DEVNAME (otherdev),
strerror (errno));
return (-1);
rc = read (fd, buf + processed, readlen);
if (rc != readlen) {
fprintf (stderr, "Read error on %s: %s\n",
DEVNAME (dev), strerror (errno));
return -1;
}
#ifdef DEBUG
fprintf (stderr, "Read 0x%x bytes at 0x%llx\n",
rc, blockstart + block_seek);
#endif
processed += readlen;
readlen = min (blocklen, count - processed);
block_seek = 0;
blockstart += blocklen;
}
return processed;
}
/*
* Write count bytes at offset, but stay within ENVSETCORS (dev) sectors of
* DEVOFFSET (dev). Similar to the read case above, on NOR we erase and write
* the whole data at once.
*/
static int flash_write_buf (int dev, int fd, void *buf, size_t count,
off_t offset, uint8_t mtd_type)
{
void *data;
struct erase_info_user erase;
size_t blocklen; /* length of NAND block / NOR erase sector */
size_t erase_len; /* whole area that can be erased - may include
bad blocks */
size_t erasesize; /* erase / write length - one block on NAND,
whole area on NOR */
size_t processed = 0; /* progress counter */
size_t write_total; /* total size to actually write - excludinig
bad blocks */
off_t erase_offset; /* offset to the first erase block (aligned)
below offset */
off_t block_seek; /* offset inside the erase block to the start
of the data */
off_t top_of_range; /* end of the last block we may use */
loff_t blockstart; /* running start of the current block -
MEMGETBADBLOCK needs 64 bits */
int rc;
blocklen = DEVESIZE (dev);
/* Erase sector size is always a power of 2 */
top_of_range = (DEVOFFSET (dev) & ~(blocklen - 1)) +
ENVSECTORS (dev) * blocklen;
printf ("Done\n");
erase_offset = offset & ~(blocklen - 1);
printf ("Writing environment to %s...\n", DEVNAME (otherdev));
if (lseek (fdr, DEVOFFSET (otherdev), SEEK_SET) == -1) {
/* Maximum area we may use */
erase_len = top_of_range - erase_offset;
blockstart = erase_offset;
/* Offset inside a block */
block_seek = offset - erase_offset;
/*
* Data size we actually have to write: from the start of the block
* to the start of the data, then count bytes of data, and to the
* end of the block
*/
write_total = (block_seek + count + blocklen - 1) & ~(blocklen - 1);
/*
* Support data anywhere within erase sectors: read out the complete
* area to be erased, replace the environment image, write the whole
* block back again.
*/
if (write_total > count) {
data = malloc (erase_len);
if (!data) {
fprintf (stderr,
"seek error on %s: %s\n",
DEVNAME (otherdev), strerror (errno));
return (-1);
"Cannot malloc %u bytes: %s\n",
erase_len, strerror (errno));
return -1;
}
if (write (fdr, &environment, len) != len) {
fprintf (stderr,
"CRC write error on %s: %s\n",
DEVNAME (otherdev), strerror (errno));
return (-1);
rc = flash_read_buf (dev, fd, data, write_total, erase_offset,
mtd_type);
if (write_total != rc)
return -1;
/* Overwrite the old environment */
memcpy (data + block_seek, buf, count);
} else {
/*
* We get here, iff offset is block-aligned and count is a
* multiple of blocklen - see write_total calculation above
*/
data = buf;
}
if (mtd_type == MTD_NANDFLASH) {
/*
* NAND: calculate which blocks we are writing. We have
* to write one block at a time to skip bad blocks.
*/
erasesize = blocklen;
} else {
erasesize = erase_len;
}
erase.length = erasesize;
/* This only runs once on NOR flash */
while (processed < write_total) {
rc = flash_bad_block (fd, mtd_type, &blockstart);
if (rc < 0) /* block test failed */
return rc;
if (blockstart + erasesize > top_of_range) {
fprintf (stderr, "End of range reached, aborting\n");
return -1;
}
if (write (fdr, environment.data, ENV_SIZE) != ENV_SIZE) {
if (rc) { /* block is bad */
blockstart += blocklen;
continue;
}
erase.start = blockstart;
ioctl (fd, MEMUNLOCK, &erase);
if (ioctl (fd, MEMERASE, &erase) != 0) {
fprintf (stderr, "MTD erase error on %s: %s\n",
DEVNAME (dev),
strerror (errno));
return -1;
}
if (lseek (fd, blockstart, SEEK_SET) == -1) {
fprintf (stderr,
"Write error on %s: %s\n",
DEVNAME (otherdev), strerror (errno));
return (-1);
"Seek error on %s: %s\n",
DEVNAME (dev), strerror (errno));
return -1;
}
if (resid) {
if (write (fdr, data, resid) != resid) {
fprintf (stderr,
"write error on %s: %s\n",
DEVNAME (curdev), strerror (errno));
return (-1);
}
free (data);
#ifdef DEBUG
printf ("Write 0x%x bytes at 0x%llx\n", erasesize, blockstart);
#endif
if (write (fd, data + processed, erasesize) != erasesize) {
fprintf (stderr, "Write error on %s: %s\n",
DEVNAME (dev), strerror (errno));
return -1;
}
ioctl (fd, MEMLOCK, &erase);
processed += blocklen;
block_seek = 0;
blockstart += blocklen;
}
if (write_total > count)
free (data);
return processed;
}
/*
* Set obsolete flag at offset - NOR flash only
*/
static int flash_flag_obsolete (int dev, int fd, off_t offset)
{