diff options
Diffstat (limited to 'tools/env/fw_env.c')
-rw-r--r-- | tools/env/fw_env.c | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/tools/env/fw_env.c b/tools/env/fw_env.c new file mode 100644 index 0000000..5bf75ac --- /dev/null +++ b/tools/env/fw_env.c @@ -0,0 +1,662 @@ +/* + * (C) Copyright 2000 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> +#include <linux/mtd/mtd.h> +#include "fw_env.h" + +typedef unsigned char uchar; + +#define CMD_GETENV "fw_printenv" +#define CMD_SETENV "fw_setenv" + +typedef struct envdev_s { + uchar devname[16]; /* Device name */ + ulong env_size; /* environment size */ + ulong erase_size; /* device erase size */ +} envdev_t; + +static envdev_t envdevices[2]; +static int curdev; + +#define DEVNAME(i) envdevices[(i)].devname +#define ENVSIZE(i) envdevices[(i)].env_size +#define DEVESIZE(i) envdevices[(i)].erase_size + +#define CFG_ENV_SIZE ENVSIZE(curdev) + +#ifdef HAVE_REDUND +#define ENV_SIZE (CFG_ENV_SIZE - sizeof(long) - 1) +#else +#define ENV_SIZE (CFG_ENV_SIZE - sizeof(long)) +#endif + +typedef struct environment_s { + ulong crc; /* CRC32 over data bytes */ + uchar flags; /* active or obsolete */ + uchar *data; +} env_t; + +static env_t environment; +static int valid = 0; + +#ifdef HAVE_REDUND +static uchar active_flag = 1; +static uchar obsolete_flag = 0; +#endif + +#define XMK_STR(x) #x +#define MK_STR(x) XMK_STR(x) + +static uchar default_environment[] = { +#ifdef CONFIG_BOOTARGS + "bootargs=" CONFIG_BOOTARGS "\0" +#endif +#ifdef CONFIG_BOOTCOMMAND + "bootcmd=" CONFIG_BOOTCOMMAND "\0" +#endif +#if (CONFIG_BOOTDELAY >= 0) + "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0" +#endif +#if (CONFIG_BAUDRATE >= 0) + "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0" +#endif +#ifdef CONFIG_ETHADDR + "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0" +#endif +#ifdef CONFIG_IPADDR + "ipaddr=" MK_STR(CONFIG_IPADDR) "\0" +#endif +#ifdef CONFIG_SERVERIP + "serverip=" MK_STR(CONFIG_SERVERIP) "\0" +#endif + "\0" +}; + +static int flash_io (int mode); +static uchar *envmatch(uchar *s1, uchar *s2); +static int env_init(void); +static int parse_config(void); + + +/* + * Search the environment for a variable. + * Return the value, if found, or NULL, if not found. + */ +unsigned char *fw_getenv (unsigned char *name) +{ + uchar *env, *nxt; + + if (env_init()) + return (NULL); + + for (env=environment.data; *env; env=nxt+1) { + uchar *val; + + for (nxt=env; *nxt; ++nxt) { + if (nxt >= &environment.data[ENV_SIZE]) { + fprintf (stderr, "## Error: " + "environment not terminated\n"); + return (NULL); + } + } + val=envmatch(name, env); + if (!val) + continue; + return (val); + } + return (NULL); +} + +/* + * Print the current definition of one, or more, or all + * environment variables + */ +void fw_printenv(int argc, char *argv[]) +{ + uchar *env, *nxt; + int i, n_flag; + + if (env_init()) + return; + + if (argc == 1) { /* Print all env variables */ + for (env=environment.data; *env; env=nxt+1) { + for (nxt=env; *nxt; ++nxt) { + if (nxt >= &environment.data[ENV_SIZE]) { + fprintf (stderr, "## Error: " + "environment not terminated\n"); + return; + } + } + + printf("%s\n", env); + } + return; + } + + if (strcmp(argv[1], "-n") == 0) { + n_flag = 1; + ++argv; + --argc; + if (argc != 2) { + fprintf (stderr, "## Error: " + "`-n' option requires exactly one argument\n"); + return; + } + } else { + n_flag = 0; + } + + for (i=1; i<argc; ++i) { /* print single env variables */ + uchar *name = argv[i]; + uchar *val = NULL; + + for (env=environment.data; *env; env=nxt+1) { + + for (nxt=env; *nxt; ++nxt) { + if (nxt >= &environment.data[ENV_SIZE]) { + fprintf (stderr, "## Error: " + "environment not terminated\n"); + return; + } + } + val=envmatch(name, env); + if (val) { + if (!n_flag) { + fputs (name, stdout); + putc ('=', stdout); + } + puts (val); + break; + } + } + if (!val) + fprintf (stderr, "## Error: \"%s\" not defined\n", + name); + } +} + +/* + * Deletes or sets environment variables. Returns errno style error codes: + * 0 - OK + * EINVAL - need at least 1 argument + * EROFS - certain variables ("ethaddr", "serial#") cannot be + * modified or deleted + * + */ +int fw_setenv (int argc, char *argv[]) +{ + int i, len; + uchar *env, *nxt; + uchar *oldval = NULL; + uchar *name; + + if (argc < 2) { + return (EINVAL); + } + + if (env_init()) + return (errno); + + name = argv[1]; + + /* + * search if variable with this name already exists + */ + for (env=environment.data; *env; env=nxt+1) { + for (nxt=env; *nxt; ++nxt) { + if (nxt >= &environment.data[ENV_SIZE]) { + fprintf (stderr, "## Error: " + "environment not terminated\n"); + return (EINVAL); + } + } + if ((oldval=envmatch(name, env)) != NULL) + break; + } + + /* + * Delete any existing definition + */ + if (oldval) { + /* + * Ethernet Address and serial# can be set only once + */ + if ((strcmp (name, "ethaddr") == 0) || + (strcmp (name, "serial#") == 0) ) { + fprintf (stderr, "Can't overwrite \"%s\"\n", name); + return (EROFS); + } + + if (*++nxt == '\0') { + *env = '\0'; + } else { + for (;;) { + *env = *nxt++; + if ((*env == '\0') && (*nxt == '\0')) + break; + ++env; + } + } + *++env = '\0'; + } + + /* Delete only ? */ + if (argc < 3) + goto WRITE_FLASH; + + /* + * Append new definition at the end + */ + for (env=environment.data; *env || *(env+1); ++env) + ; + if (env > environment.data) + ++env; + /* + * Overflow when: + * "name" + "=" + "val" +"\0\0" > CFG_ENV_SIZE - (env-environment) + */ + len = strlen(name) + 2; + /* add '=' for first arg, ' ' for all others */ + for (i=2; i<argc; ++i) { + len += strlen(argv[i]) + 1; + } + if (len > (&environment.data[ENV_SIZE]-env)) { + fprintf (stderr, + "Error: environment overflow, \"%s\" deleted\n", + name); + return (-1); + } + while ((*env = *name++) != '\0') + env++; + for (i=2; i<argc; ++i) { + uchar *val = argv[i]; + + *env = (i==2) ? '=' : ' '; + while ((*++env = *val++) != '\0') + ; + } + + /* end is marked with double '\0' */ + *++env = '\0'; + +WRITE_FLASH: + + /* Update CRC */ + environment.crc = crc32(0, 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 (0); +} + +static int flash_io (int mode) +{ + int fd, fdr, rc, otherdev, len, resid; + erase_info_t erase; + char *data; + + if ((fd = open(DEVNAME(curdev), mode)) < 0) { + fprintf (stderr, + "Can't open %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } + + len = sizeof(environment.crc) + sizeof(environment.flags); + + if (mode == O_RDWR) { +#ifdef HAVE_REDUND + /* 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; + len = sizeof(environment.crc); +#endif + printf("Unlocking flash...\n"); + erase.length = DEVESIZE(otherdev); + erase.start = 0; + ioctl (fdr, MEMUNLOCK, &erase); + +#ifdef HAVE_REDUND + erase.length = DEVESIZE(curdev); + erase.start = 0; + ioctl (fd, MEMUNLOCK, &erase); + environment.flags = active_flag; +#endif + 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, CFG_ENV_SIZE, SEEK_SET) == -1) { + fprintf (stderr, + "seek error on %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } + if ((rc = read (fdr, data, resid)) != resid) { + fprintf (stderr, + "read error on %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } + if (lseek (fdr, 0, SEEK_SET) == -1) { + fprintf (stderr, + "seek error on %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } + } + + printf("Erasing old environment...\n"); + + erase.length = DEVESIZE(otherdev); + if (ioctl (fdr, MEMERASE, &erase) != 0) { + fprintf (stderr, "MTD erase error on %s: %s\n", + DEVNAME(otherdev), strerror(errno)); + return (-1); + } + + printf("Done\n"); + + printf("Writing environment to %s...\n",DEVNAME(otherdev)); + if (write(fdr, &environment, len) != len) { + fprintf (stderr, + "CRC write error on %s: %s\n", + DEVNAME(otherdev), strerror(errno)); + return (-1); + } + if (write(fdr, environment.data, ENV_SIZE) != ENV_SIZE) { + fprintf (stderr, + "Write error on %s: %s\n", + DEVNAME(otherdev), 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 HAVE_REDUND + /* change flag on current active env partition */ + if (lseek (fd, 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)) { + fprintf (stderr, + "Write error on %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } +#endif + printf("Done\n"); + printf("Locking ...\n"); + erase.length = DEVESIZE(otherdev); + erase.start = 0; + ioctl (fdr, MEMLOCK, &erase); +#ifdef HAVE_REDUND + erase.length = DEVESIZE(curdev); + erase.start = 0; + ioctl (fd, MEMLOCK, &erase); + if (close(fdr)) { + fprintf (stderr, + "I/O error on %s: %s\n", + DEVNAME(otherdev), strerror(errno)); + return (-1); + } +#endif + printf("Done\n"); + } else { +#ifndef HAVE_REDUND + len = sizeof(environment.crc); +#endif + 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); + } + } + + if (close(fd)) { + fprintf (stderr, + "I/O error on %s: %s\n", + DEVNAME(curdev), strerror(errno)); + return (-1); + } + + /* everything ok */ + return (0); +} + +/* + * s1 is either a simple 'name', or a 'name=value' pair. + * s2 is a 'name=value' pair. + * If the names match, return the value of s2, else NULL. + */ + +static uchar * +envmatch (uchar *s1, uchar *s2) +{ + + while (*s1 == *s2++) + if (*s1++ == '=') + return(s2); + if (*s1 == '\0' && *(s2-1) == '=') + return(s2); + return(NULL); +} + +/* + * Prevent confusion if running from erased flash memory + */ +static int env_init(void) +{ + int crc1, crc1_ok; + uchar *addr1; +#ifdef HAVE_REDUND + int crc2, crc2_ok; + uchar flag1, flag2, *addr2; +#endif + + + if (!valid) { + + if (parse_config()) /* should fill envdevices */ + return 1; + + if ((addr1 = calloc (1, ENV_SIZE)) == NULL) { + fprintf (stderr, + "Not enough memory for environment (%ld bytes)\n", + ENV_SIZE); + return (errno); + } + + /* read environment from FLASH to local buffer */ + environment.data = addr1; + curdev = 0; + if (flash_io (O_RDONLY)) { + return (errno); + } + + crc1_ok = ((crc1 = crc32(0, environment.data, ENV_SIZE)) + == environment.crc); +#ifndef HAVE_REDUND + if (!crc1_ok) { + fprintf (stderr, + "Warning: Bad CRC, using default environment\n"); + environment.data = default_environment; + free(addr1); + } +#else + flag1 = environment.flags; + + curdev = 1; + if ((addr2 = calloc (1, ENV_SIZE)) == NULL) { + fprintf (stderr, + "Not enough memory for environment (%ld bytes)\n", + ENV_SIZE); + return (errno); + } + environment.data = addr2; + + if (flash_io (O_RDONLY)) { + return (errno); + } + + crc2_ok = ((crc2 = crc32(0, 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) { + fprintf (stderr, + "Warning: Bad CRC, using default environment\n"); + environment.data = default_environment; + curdev = 0; + free(addr2); + 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; + free(addr1); + } +#endif + valid = 1; + } + return (0); +} + + +static int parse_config() +{ + struct stat st; + + if (stat (DEVICE1_NAME, &st)) { + fprintf (stderr, + "Cannot access MTD device %s: %s\n", + DEVICE1_NAME, strerror(errno)); + return 1; + } + + strcpy(DEVNAME(0), DEVICE1_NAME); + ENVSIZE(0) = ENV1_SIZE; + DEVESIZE(0) = DEVICE1_ESIZE; +#ifdef HAVE_REDUND + if (stat (DEVICE2_NAME, &st)) { + fprintf (stderr, + "Cannot access MTD device %s: %s\n", + DEVICE2_NAME, strerror(errno)); + return 1; + } + strcpy(DEVNAME(1), DEVICE2_NAME); + ENVSIZE(1) = ENV2_SIZE; + DEVESIZE(1) = DEVICE2_ESIZE; +#endif + return 0; +} |