From 5608692104efa8d56df803dc79ea41ac3607eee5 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 4 Sep 2008 13:01:49 +0200 Subject: [PATCH] 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: Guennadi Liakhovetski --- tools/env/README | 6 + tools/env/fw_env.c | 863 ++++++++++++++++++++++++++++------------ tools/env/fw_env.config | 6 +- 3 files changed, 611 insertions(+), 264 deletions(-) diff --git a/tools/env/README b/tools/env/README index f8a644e0c2..91e679a324 100644 --- a/tools/env/README +++ b/tools/env/README @@ -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. diff --git a/tools/env/fw_env.c b/tools/env/fw_env.c index e4fc02d29b..b6036c8ae2 100644 --- a/tools/env/fw_env.c +++ b/tools/env/fw_env.c @@ -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) +{ + int rc; + + /* This relies on the fact, that obsolete_flag == 0 */ + rc = lseek (fd, offset, SEEK_SET); + if (rc < 0) { + fprintf (stderr, "Cannot seek to set the flag on %s \n", + DEVNAME (dev)); + return rc; + } + rc = write (fd, &obsolete_flag, sizeof (obsolete_flag)); + if (rc < 0) + perror ("Could not set obsolete flag"); + + return rc; +} + +static int flash_write (int fd_current, int fd_target, int dev_target) +{ + int rc; + + switch (environment.flag_scheme) { + case FLAG_NONE: + break; + case FLAG_INCREMENTAL: + (*environment.flags)++; + break; + case FLAG_BOOLEAN: + *environment.flags = active_flag; + break; + default: + fprintf (stderr, "Unimplemented flash scheme %u \n", + environment.flag_scheme); + return -1; + } + +#ifdef DEBUG + printf ("Writing new environment at 0x%lx on %s\n", + DEVOFFSET (dev_target), DEVNAME (dev_target)); +#endif + rc = flash_write_buf (dev_target, fd_target, environment.image, + CFG_ENV_SIZE, DEVOFFSET (dev_target), + DEVTYPE(dev_target)); + if (rc < 0) + return rc; + + if (environment.flag_scheme == FLAG_BOOLEAN) { + /* Have to set obsolete flag */ + off_t offset = DEVOFFSET (dev_current) + + offsetof (struct env_image_redundant, flags); +#ifdef DEBUG + printf ("Setting obsolete flag in environment at 0x%lx on %s\n", + DEVOFFSET (dev_current), DEVNAME (dev_current)); +#endif + flash_flag_obsolete (dev_current, fd_current, offset); + } + + return 0; +} + +static int flash_read (int fd) +{ + struct mtd_info_user mtdinfo; + int rc; + + rc = ioctl (fd, MEMGETINFO, &mtdinfo); + if (rc < 0) { + perror ("Cannot get MTD information"); + return -1; + } + + if (mtdinfo.type != MTD_NORFLASH && mtdinfo.type != MTD_NANDFLASH) { + fprintf (stderr, "Unsupported flash type %u\n", mtdinfo.type); + return -1; + } + + DEVTYPE(dev_current) = mtdinfo.type; + + rc = flash_read_buf (dev_current, fd, environment.image, CFG_ENV_SIZE, + DEVOFFSET (dev_current), mtdinfo.type); + + return (rc != CFG_ENV_SIZE) ? -1 : 0; +} + +static int flash_io (int mode) +{ + int fd_current, fd_target, rc, dev_target; + + /* dev_current: fd_current, erase_current */ + fd_current = open (DEVNAME (dev_current), mode); + if (fd_current < 0) { + fprintf (stderr, + "Can't open %s: %s\n", + DEVNAME (dev_current), strerror (errno)); + return -1; + } + + if (mode == O_RDWR) { if (HaveRedundEnv) { - /* change flag on current active env partition */ - if (lseek (fd, DEVOFFSET (curdev) + sizeof (ulong), SEEK_SET) - == -1) { - fprintf (stderr, "seek error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); - } - if (write (fd, &obsolete_flag, sizeof (obsolete_flag)) != - sizeof (obsolete_flag)) { + /* switch to next partition for writing */ + dev_target = !dev_current; + /* dev_target: fd_target, erase_target */ + fd_target = open (DEVNAME (dev_target), mode); + if (fd_target < 0) { fprintf (stderr, - "Write error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); + "Can't open %s: %s\n", + DEVNAME (dev_target), + strerror (errno)); + rc = -1; + goto exit; } + } else { + dev_target = dev_current; + fd_target = fd_current; } - printf ("Done\n"); - printf ("Locking ...\n"); - erase.length = DEVESIZE (otherdev); - erase.start = DEVOFFSET (otherdev); - ioctl (fdr, MEMLOCK, &erase); + + rc = flash_write (fd_current, fd_target, dev_target); + if (HaveRedundEnv) { - erase.length = DEVESIZE (curdev); - erase.start = DEVOFFSET (curdev); - ioctl (fd, MEMLOCK, &erase); - if (close (fdr)) { + if (close (fd_target)) { fprintf (stderr, "I/O error on %s: %s\n", - DEVNAME (otherdev), + DEVNAME (dev_target), strerror (errno)); - return (-1); + rc = -1; } } - printf ("Done\n"); } else { - - if (lseek (fd, DEVOFFSET (curdev), SEEK_SET) == -1) { - fprintf (stderr, - "seek error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); - } - if (read (fd, &environment, len) != len) { - fprintf (stderr, - "CRC read error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); - } - if ((rc = read (fd, environment.data, ENV_SIZE)) != ENV_SIZE) { - fprintf (stderr, - "Read error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); - } + rc = flash_read (fd_current); } - if (close (fd)) { +exit: + if (close (fd_current)) { fprintf (stderr, - "I/O error on %s: %s\n", - DEVNAME (curdev), strerror (errno)); - return (-1); + "I/O error on %s: %s\n", + DEVNAME (dev_current), strerror (errno)); + return -1; } - /* everything ok */ - return (0); + return rc; } /* @@ -585,10 +865,10 @@ static char *envmatch (char * s1, char * s2) while (*s1 == *s2++) if (*s1++ == '=') - return (s2); + return s2; if (*s1 == '\0' && *(s2 - 1) == '=') - return (s2); - return (NULL); + return s2; + return NULL; } /* @@ -596,108 +876,156 @@ static char *envmatch (char * s1, char * s2) */ static int env_init (void) { + int crc0, crc0_ok; + char flag0; + void *addr0; + int crc1, crc1_ok; - char *addr1; + char flag1; + void *addr1; - int crc2, crc2_ok; - char flag1, flag2, *addr2; + struct env_image_single *single; + struct env_image_redundant *redundant; if (parse_config ()) /* should fill envdevices */ - return 1; + return -1; - if ((addr1 = calloc (1, ENV_SIZE)) == NULL) { + addr0 = calloc (1, CFG_ENV_SIZE); + if (addr0 == NULL) { fprintf (stderr, "Not enough memory for environment (%ld bytes)\n", - ENV_SIZE); - return (errno); + CFG_ENV_SIZE); + return -1; } /* read environment from FLASH to local buffer */ - environment.data = addr1; - curdev = 0; - if (flash_io (O_RDONLY)) { - return (errno); + environment.image = addr0; + + if (HaveRedundEnv) { + redundant = addr0; + environment.crc = &redundant->crc; + environment.flags = &redundant->flags; + environment.data = redundant->data; + } else { + single = addr0; + environment.crc = &single->crc; + environment.flags = NULL; + environment.data = single->data; } - crc1_ok = ((crc1 = crc32 (0, (uint8_t *) environment.data, ENV_SIZE)) - == environment.crc); + dev_current = 0; + if (flash_io (O_RDONLY)) + return -1; + + crc0 = crc32 (0, (uint8_t *) environment.data, ENV_SIZE); + crc0_ok = (crc0 == *environment.crc); if (!HaveRedundEnv) { - if (!crc1_ok) { + if (!crc0_ok) { fprintf (stderr, "Warning: Bad CRC, using default environment\n"); memcpy(environment.data, default_environment, sizeof default_environment); } } else { - flag1 = environment.flags; + flag0 = *environment.flags; - curdev = 1; - if ((addr2 = calloc (1, ENV_SIZE)) == NULL) { + dev_current = 1; + addr1 = calloc (1, CFG_ENV_SIZE); + if (addr1 == NULL) { fprintf (stderr, "Not enough memory for environment (%ld bytes)\n", - ENV_SIZE); - return (errno); + CFG_ENV_SIZE); + return -1; } - environment.data = addr2; + redundant = addr1; - if (flash_io (O_RDONLY)) { - return (errno); + /* + * have to set environment.image for flash_read(), careful - + * other pointers in environment still point inside addr0 + */ + environment.image = addr1; + if (flash_io (O_RDONLY)) + return -1; + + /* Check flag scheme compatibility */ + if (DEVTYPE(dev_current) == MTD_NORFLASH && + DEVTYPE(!dev_current) == MTD_NORFLASH) { + environment.flag_scheme = FLAG_BOOLEAN; + } else if (DEVTYPE(dev_current) == MTD_NANDFLASH && + DEVTYPE(!dev_current) == MTD_NANDFLASH) { + environment.flag_scheme = FLAG_INCREMENTAL; + } else { + fprintf (stderr, "Incompatible flash types!\n"); + return -1; } - crc2_ok = ((crc2 = crc32 (0, (uint8_t *) environment.data, ENV_SIZE)) - == environment.crc); - flag2 = environment.flags; - - if (crc1_ok && !crc2_ok) { - environment.data = addr1; - environment.flags = flag1; - environment.crc = crc1; - curdev = 0; - free (addr2); - } else if (!crc1_ok && crc2_ok) { - environment.data = addr2; - environment.flags = flag2; - environment.crc = crc2; - curdev = 1; - free (addr1); - } else if (!crc1_ok && !crc2_ok) { + crc1 = crc32 (0, (uint8_t *) redundant->data, ENV_SIZE); + crc1_ok = (crc1 == redundant->crc); + flag1 = redundant->flags; + + if (crc0_ok && !crc1_ok) { + dev_current = 0; + } else if (!crc0_ok && crc1_ok) { + dev_current = 1; + } else if (!crc0_ok && !crc1_ok) { fprintf (stderr, "Warning: Bad CRC, using default environment\n"); - memcpy(environment.data, default_environment, sizeof default_environment); - curdev = 0; - free (addr1); - } else if (flag1 == active_flag && flag2 == obsolete_flag) { - environment.data = addr1; - environment.flags = flag1; - environment.crc = crc1; - curdev = 0; - free (addr2); - } else if (flag1 == obsolete_flag && flag2 == active_flag) { - environment.data = addr2; - environment.flags = flag2; - environment.crc = crc2; - curdev = 1; - free (addr1); - } else if (flag1 == flag2) { - environment.data = addr1; - environment.flags = flag1; - environment.crc = crc1; - curdev = 0; - free (addr2); - } else if (flag1 == 0xFF) { - environment.data = addr1; - environment.flags = flag1; - environment.crc = crc1; - curdev = 0; - free (addr2); - } else if (flag2 == 0xFF) { - environment.data = addr2; - environment.flags = flag2; - environment.crc = crc2; - curdev = 1; + memcpy (environment.data, default_environment, + sizeof default_environment); + dev_current = 0; + } else { + switch (environment.flag_scheme) { + case FLAG_BOOLEAN: + if (flag0 == active_flag && + flag1 == obsolete_flag) { + dev_current = 0; + } else if (flag0 == obsolete_flag && + flag1 == active_flag) { + dev_current = 1; + } else if (flag0 == flag1) { + dev_current = 0; + } else if (flag0 == 0xFF) { + dev_current = 0; + } else if (flag1 == 0xFF) { + dev_current = 1; + } else { + dev_current = 0; + } + break; + case FLAG_INCREMENTAL: + if ((flag0 == 255 && flag1 == 0) || + flag1 > flag0) + dev_current = 1; + else if ((flag1 == 255 && flag0 == 0) || + flag0 > flag1) + dev_current = 0; + else /* flags are equal - almost impossible */ + dev_current = 0; + break; + default: + fprintf (stderr, "Unknown flag scheme %u \n", + environment.flag_scheme); + return -1; + } + } + + /* + * If we are reading, we don't need the flag and the CRC any + * more, if we are writing, we will re-calculate CRC and update + * flags before writing out + */ + if (dev_current) { + environment.image = addr1; + environment.crc = &redundant->crc; + environment.flags = &redundant->flags; + environment.data = redundant->data; + free (addr0); + } else { + environment.image = addr0; + /* Other pointers are already set */ free (addr1); } } - return (0); + return 0; } @@ -710,18 +1038,20 @@ static int parse_config () if (get_config (CONFIG_FILE)) { fprintf (stderr, "Cannot parse config file: %s\n", strerror (errno)); - return 1; + return -1; } #else strcpy (DEVNAME (0), DEVICE1_NAME); DEVOFFSET (0) = DEVICE1_OFFSET; ENVSIZE (0) = ENV1_SIZE; DEVESIZE (0) = DEVICE1_ESIZE; + ENVSECTORS (0) = DEVICE1_ENVSECTORS; #ifdef HAVE_REDUND strcpy (DEVNAME (1), DEVICE2_NAME); DEVOFFSET (1) = DEVICE2_OFFSET; ENVSIZE (1) = ENV2_SIZE; DEVESIZE (1) = DEVICE2_ESIZE; + ENVSECTORS (1) = DEVICE2_ENVSECTORS; HaveRedundEnv = 1; #endif #endif @@ -729,14 +1059,14 @@ static int parse_config () fprintf (stderr, "Cannot access MTD device %s: %s\n", DEVNAME (0), strerror (errno)); - return 1; + return -1; } if (HaveRedundEnv && stat (DEVNAME (1), &st)) { fprintf (stderr, "Cannot access MTD device %s: %s\n", DEVNAME (1), strerror (errno)); - return 1; + return -1; } return 0; } @@ -749,21 +1079,28 @@ static int get_config (char *fname) int rc; char dump[128]; - if ((fp = fopen (fname, "r")) == NULL) { - return 1; - } - - while ((i < 2) && ((rc = fscanf (fp, "%s %lx %lx %lx", - DEVNAME (i), - &DEVOFFSET (i), - &ENVSIZE (i), - &DEVESIZE (i) )) != EOF)) { + fp = fopen (fname, "r"); + if (fp == NULL) + return -1; + while (i < 2 && fgets (dump, sizeof (dump), fp)) { /* Skip incomplete conversions and comment strings */ - if ((rc < 3) || (*DEVNAME (i) == '#')) { - fgets (dump, sizeof (dump), fp); /* Consume till end */ + if (dump[0] == '#') continue; - } + + rc = sscanf (dump, "%s %lx %lx %lx %lx", + DEVNAME (i), + &DEVOFFSET (i), + &ENVSIZE (i), + &DEVESIZE (i), + &ENVSECTORS (i)); + + if (rc < 4) + continue; + + if (rc < 5) + /* Default - 1 sector */ + ENVSECTORS (i) = 1; i++; } @@ -772,7 +1109,7 @@ static int get_config (char *fname) HaveRedundEnv = i - 1; if (!i) { /* No valid entries found */ errno = EINVAL; - return 1; + return -1; } else return 0; } diff --git a/tools/env/fw_env.config b/tools/env/fw_env.config index 2432bd866c..0fe37c959a 100644 --- a/tools/env/fw_env.config +++ b/tools/env/fw_env.config @@ -1,7 +1,11 @@ # Configuration file for fw_(printenv/saveenv) utility. # Up to two entries are valid, in this case the redundand # environment sector is assumed present. +# Notice, that the "Number of sectors" is ignored on NOR. -# MTD device name Device offset Env. size Flash sector size +# MTD device name Device offset Env. size Flash sector size Number of sectors /dev/mtd1 0x0000 0x4000 0x4000 /dev/mtd2 0x0000 0x4000 0x4000 + +# NAND example +#/dev/mtd0 0x4000 0x4000 0x20000 2 -- 2.39.2