/*
 * (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
 */

/*
 * Support for persistent environment data
 */
#include <ppcboot.h>
#include <command.h>
#include <cmd_nvedit.h>
#include <linux/stddef.h>
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#include <net.h>
#endif

/*
 * Table with supported baudrates (defined in config_xyz.h)
 */
static const unsigned long baudrate_table[] = CFG_BAUDRATE_TABLE;
#define	N_BAUDRATES (sizeof(baudrate_table) / sizeof(baudrate_table[0]))

/************************************************************************
 *
 * The environment storages is simply a list of '\0'-terminated
 * "name=value" strings, the end of the list marked by a double '\0'.
 * New entries are always added at the end. Deleting an entry shifts
 * the remaining entries to the front. Replacing an entry is a
 * combination of deleting the old and adding the new value.
 *
 * The environment is preceeded by a 32 bit CRC over the data part.
 *
 ************************************************************************
 */

/*---------------------------------------------------------------------*/
#undef DEBUG_ENV

#ifdef DEBUG_ENV
#define DEBUGF(fmt,args...) printf(fmt ,##args)
#else
#define DEBUGF(fmt,args...)
#endif
/*---------------------------------------------------------------------*/


#if defined(CFG_ENV_IS_IN_FLASH)
# ifndef  CFG_ENV_ADDR
#  define CFG_ENV_ADDR	(CFG_FLASH_BASE + CFG_ENV_OFFSET)
# endif
# ifndef  CFG_ENV_SIZE
#  define CFG_ENV_SIZE	CFG_ENV_SECT_SIZE
# endif
# if (CFG_ENV_ADDR >= CFG_MONITOR_BASE) && \
     (CFG_ENV_ADDR+CFG_ENV_SIZE) <= (CFG_MONITOR_BASE + CFG_MONITOR_LEN)
#  define ENV_IS_EMBEDDED
# endif
#endif	/* CFG_ENV_IS_IN_FLASH */

#define ENV_SIZE (CFG_ENV_SIZE - sizeof(long))

#if !defined(ENV_IS_EMBEDDED)
#include <malloc.h>
#endif


typedef	struct environment_s {
	ulong	crc;			/* CRC32 over data bytes	*/
	uchar	data[ENV_SIZE];
} env_t;

/*---  NVRAM  ----------------------------------------------------------*/
#ifdef CFG_ENV_IS_IN_NVRAM			/* Environment is in NVRAM	*/

static env_t *env_ptr = (env_t *)CFG_ENV_ADDR;

/*---  EEPROM ----------------------------------------------------------*/
#elif defined(CFG_ENV_IS_IN_EEPROM)		/* Environment is in EEPROM	*/
static env_t *env_ptr = NULL;

/*---  FLASH  ----------------------------------------------------------*/
#elif defined(CFG_ENV_IS_IN_FLASH)		/* Environment is in Flash	*/

# if defined(ENV_IS_EMBEDDED)			/*     embedded within PPCBoot	*/

extern uchar environment[];
static env_t *env_ptr = (env_t *)(&environment[0]);
#  if ((CONFIG_COMMANDS&(CFG_CMD_ENV|CFG_CMD_FLASH)) == (CFG_CMD_ENV|CFG_CMD_FLASH))
static uchar *flash_addr = &environment[0];
#  endif /* ENV, FLASH */

# else	/* !ENV_IS_EMBEDDED*/			/* not embedded	within PPCBoot	*/
static env_t *env_ptr = (env_t *)CFG_ENV_ADDR;
/* need both ENV and flash */
#  if ((CONFIG_COMMANDS&(CFG_CMD_ENV|CFG_CMD_FLASH)) == (CFG_CMD_ENV|CFG_CMD_FLASH))
static uchar *flash_addr = (uchar *)CFG_ENV_ADDR;
#  endif /* ENV, FLASH */

# endif	/* ENV_IS_EMBEDDED */
#endif	/* CFG_ENV_IS_IN_FLASH */

/*----------------------------------------------------------------------*/

/************************************************************************
 * Default settings to be used when no valid environment is found
 */
#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
#ifdef	CONFIG_RAMBOOTCOMMAND
	"ramboot="	CONFIG_RAMBOOTCOMMAND		"\0"
#endif
#ifdef	CONFIG_NFSBOOTCOMMAND
	"nfsboot="	CONFIG_NFSBOOTCOMMAND		"\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
	"bootdelay="	MK_STR(CONFIG_BOOTDELAY)	"\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
	"baudrate="	MK_STR(CONFIG_BAUDRATE)		"\0"
#endif
#ifdef	CONFIG_LOADS_ECHO
	"loads_echo="	MK_STR(CONFIG_LOADS_ECHO)	"\0"
#endif
#ifdef	CONFIG_ETHADDR
	"ethaddr="	MK_STR(CONFIG_ETHADDR)		"\0"
#endif
#ifdef	CONFIG_ETH2ADDR
	"eth2addr="	MK_STR(CONFIG_ETH2ADDR)		"\0"
#endif
#ifdef	CONFIG_ETH3ADDR
	"eth3addr="	MK_STR(CONFIG_ETH3ADDR)		"\0"
#endif
#ifdef	CONFIG_IPADDR
	"ipaddr="	MK_STR(CONFIG_IPADDR)		"\0"
#endif
#ifdef	CONFIG_SERVERIP
	"serverip="	MK_STR(CONFIG_SERVERIP)		"\0"
#endif
#ifdef	CFG_AUTOLOAD
	"autoload="	CFG_AUTOLOAD			"\0"
#endif
#ifdef	CONFIG_PREBOOT
	"preboot="	CONFIG_PREBOOT			"\0"
#endif
#ifdef	CONFIG_ROOTPATH
	"rootpath="	MK_STR(CONFIG_ROOTPATH)		"\0"
#endif
#ifdef	CONFIG_GATEWAYIP
	"gatewayip="	MK_STR(CONFIG_GATEWAYIP)	"\0"
#endif
#ifdef	CONFIG_NETMASK
	"netmask="	MK_STR(CONFIG_NETMASK)		"\0"
#endif
#ifdef	CONFIG_HOSTNAME
	"hostname="	MK_STR(CONFIG_HOSTNAME)		"\0"
#endif
#ifdef	CONFIG_BOOTFILE
	"bootfile="	MK_STR(CONFIG_BOOTFILE)		"\0"
#endif
#ifdef	CONFIG_LOADADDR
	"loadaddr="	MK_STR(CONFIG_LOADADDR)		"\0"
#endif
#ifdef  CONFIG_CLOCKS_IN_MHZ
	"clocks_in_mhz=1\0"
#endif
	"\0"
};


/************************************************************************
************************************************************************/

static int envmatch (uchar *, int);

/************************************************************************
************************************************************************/

#ifdef CFG_ENV_IS_IN_EEPROM
static uchar  get_env_char_eeprom(int);
#endif
static uchar  get_env_char_memory(int);
static uchar *get_env_addr_memory(int);

/* Function that returns a character from the environment */
static uchar (*get_env_char)(int) =
#ifdef CFG_ENV_IS_IN_EEPROM
	get_env_char_eeprom;
#else
	get_env_char_memory;
#endif
/* Function that returns a pointer to a value from the environment */
/* (Only memory version supported / needed). */
static uchar *(*get_env_addr)(int) = get_env_addr_memory;

/************************************************************************
************************************************************************/

void env_relocate (ulong offset)
{
	init_data_t *idata = (init_data_t*)(CFG_INIT_RAM_ADDR+CFG_INIT_DATA_OFFSET);

	DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,offset);
#if !defined(ENV_IS_EMBEDDED) || defined(CFG_ENV_IS_IN_NVRAM)
	/*
	 * We must allocate a buffer for the environment
	 * (We _could_ work directly in NVRAM, however we want
	 * to modify persistent values only using "saveenv",
	 * so we need a RAM copy here, too.
	 */
	env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
	DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else	/* ENV_IS_EMBEDDED */
	/*
	 * The environment buffer is embedded with the text segment,
	 * just relocate the environment pointer
	 */
	env_ptr = (env_t *)((ulong)env_ptr + offset);
	DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif

	/*
	 * Update all function pointers
	 * After relocation to RAM, we can always use the "memory" functions
	 */
	get_env_char = get_env_char_memory;
	get_env_addr = get_env_addr_memory;

	if (idata->env_valid == 0) {
#if defined(CONFIG_GTH)		/* Environment not changable */
		printf ("Using default environment\n\n");
#else
		printf ("*** Warning - bad CRC, using default environment\n\n");
#endif
		/*
		 * TODO: We should check here that the
		 * default environment does not overflow the buffer.
		 */
		memset (env_ptr, 0, sizeof(env_t));
		memcpy (env_ptr->data,
			default_environment,
			sizeof(default_environment));
		idata->env_valid = 1;
	}
#if !defined(ENV_IS_EMBEDDED)
	else {
		DEBUGF ("%s[%d] ENV is valid\n", __FUNCTION__,__LINE__);
# if defined(CFG_ENV_IS_IN_EEPROM)
		DEBUGF ("%s[%d] read ENV from EEPROM\n", __FUNCTION__,__LINE__);
		eeprom_read (CFG_ENV_OFFSET+offsetof(env_t,data),
			     env_ptr->data,
			     ENV_SIZE);
# else
		DEBUGF ("%s[%d] read ENV from NVRAM/FLASH\n",__FUNCTION__,__LINE__);
		memcpy (env_ptr->data,
			((env_t *)CFG_ENV_ADDR)->data,
			ENV_SIZE);
# endif
	}
#endif
	idata->env_addr = (ulong)&(env_ptr->data);
}
/************************************************************************
************************************************************************/

/*
 * Utility function when we can read directly from memory
 */
static uchar get_env_char_memory (int index)
{
	init_data_t *idata = (init_data_t*)(CFG_INIT_RAM_ADDR+CFG_INIT_DATA_OFFSET);

	if (idata->env_valid) {
		return ( *((uchar *)(idata->env_addr + index)) );
	} else {
		return ( default_environment[index] );
	}
}


static uchar *get_env_addr_memory(int index)
{
	init_data_t *idata = (init_data_t*)(CFG_INIT_RAM_ADDR+CFG_INIT_DATA_OFFSET);

	if (idata->env_valid) {
		return ( ((uchar *)(idata->env_addr + index)) );
	} else {
		return (&default_environment[index]);
	}
}

#ifdef CFG_ENV_IS_IN_EEPROM
/*
 * Utility function when we have to read from serial device
 */
static uchar get_env_char_eeprom (int index)
{
	uchar c;
	init_data_t *idata = (init_data_t*)(CFG_INIT_RAM_ADDR+CFG_INIT_DATA_OFFSET);

	/* if the EEPROM crc was bad, use the default environment */
	if (idata->env_valid)
		eeprom_read (CFG_ENV_OFFSET+index+offsetof(env_t,data), &c, 1);
	else
		c = default_environment[index];

	return (c);
}
#endif	/* CFG_ENV_IS_IN_EEPROM */

/************************************************************************
 * Command interface: print one or all environment variables
 */

void do_printenv (cmd_tbl_t *cmdtp, bd_t *bd, int flag, int argc, char *argv[])
{
	int i, j, k, nxt;

	if (argc == 1) {		/* Print all env variables	*/
		for (i=0; get_env_char(i) != '\0'; i=nxt+1) {
			for (nxt=i; get_env_char(nxt) != '\0'; ++nxt)
				;
			for (k=i; k<nxt; ++k)
				putc(get_env_char(k));
			putc  ('\n');

			if (ctrlc()) {
				printf ("\n ** Abort\n");
				return;
			}
		}

		printf("\nEnvironment size: %d/%d bytes\n", i, ENV_SIZE);

		return;
	}

	for (i=1; i<argc; ++i) {	/* print single env variables	*/
		char *name = argv[i];

		k = -1;

		for (j=0; get_env_char(j) != '\0'; j=nxt+1) {

			for (nxt=j; get_env_char(nxt) != '\0'; ++nxt)
				;
			k = envmatch(name, j);
			if (k < 0) {
				continue;
			}
			puts (name);
			putc ('=');
			while (k < nxt)
				putc(get_env_char(k++));
			putc ('\n');
			break;
		}
		if (k < 0)
			printf ("## Error: \"%s\" not defined\n", name);
	}
}

/************************************************************************
 * Set a new environment variable,
 * or replace or delete an existing one.
 *
 * This function will ONLY work with a in-RAM copy of the environment
 */

void _do_setenv (bd_t *bd, int flag, int argc, char *argv[])
{
	int   i, len, oldval;
	int   console = -1;
	uchar *env, *nxt;
	uchar *name;

	uchar *env_data = get_env_addr(0);

	if (!env_data)	/* need copy in RAM */
		return;

	name = argv[1];

	/*
	 * search if variable with this name already exists
	 */
	oldval = -1;
	for (env=env_data; *env; env=nxt+1) {
		for (nxt=env; *nxt; ++nxt)
			;
		if ((oldval = envmatch(name, env-env_data)) >= 0)
			break;
	}

	/*
	 * Delete any existing definition
	 */
	if (oldval >= 0) {
#ifndef CONFIG_ENV_OVERWRITE

		/*
		 * Ethernet Address and serial# can be set only once
		 */
		if ( (strcmp (name, "serial#") == 0) ||
		    ((strcmp (name, "ethaddr") == 0)
#if defined(CONFIG_OVERWRITE_ETHADDR_ONCE) && defined(CONFIG_ETHADDR)
		     && (strcmp (get_env_addr(oldval),MK_STR(CONFIG_ETHADDR)) != 0)
#endif	/* CONFIG_OVERWRITE_ETHADDR_ONCE && CONFIG_ETHADDR */
		    ) ) {
			printf ("Can't overwrite \"%s\"\n", name);
			return;
		}
#endif

		/* Check for console redirection */
		if (strcmp(name,"stdin") == 0) {
			console = stdin;
		} else if (strcmp(name,"stdout") == 0) {
			console = stdout;
		} else if (strcmp(name,"stderr") == 0) {
			console = stderr;
		}

		if (console != -1) {
			if (argc < 3) {		/* Cannot delete it! */
				printf("Can't delete \"%s\"\n", name);
				return;
			}

			/* Try assigning specified device */
			if (console_assign (console, argv[2]) < 0)
				return;
		}

		/*
		 * Switch to new baudrate if new baudrate is supported
		 */
		if (strcmp(argv[1],"baudrate") == 0) {
			int baudrate = simple_strtoul(argv[2], NULL, 10);
			int i;
			for (i=0; i<N_BAUDRATES; ++i) {
				if (baudrate == baudrate_table[i])
					break;
			}
			if (i == N_BAUDRATES) {
				printf ("## Baudrate %d bps not supported\n",
					baudrate);
				return;
			}
			printf ("## Switch baudrate to %d bps and press ENTER ...\n",
				baudrate);
			udelay(50000);
			serial_setbrg (bd->bi_intfreq, baudrate);
			udelay(50000);
			for (;;) {
				if (getc() == '\r')
				      break;
			}
			bd->bi_baudrate = baudrate;
		}

		if (*++nxt == '\0') {
			if (env > env_data) {
				env--;
			} else {
				*env = '\0';
			}
		} else {
			for (;;) {
				*env = *nxt++;
				if ((*env == '\0') && (*nxt == '\0'))
					break;
				++env;
			}
		}
		*++env = '\0';
	}

	/* Delete only ? */
	if ((argc < 3) || argv[2] == NULL) {
		/* Update CRC */
		env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);

		return;
	}

	/*
	 * Append new definition at the end
	 */
	for (env=env_data; *env || *(env+1); ++env)
		;
	if (env > env_data)
		++env;
	/*
	 * Overflow when:
	 * "name" + "=" + "val" +"\0\0"  > ENV_SIZE - (env-env_data)
	 */
	len = strlen(name) + 2;
	/* add '=' for first arg, ' ' for all others */
	for (i=2; i<argc; ++i) {
		len += strlen(argv[i]) + 1;
	}
	if (len > (&env_data[ENV_SIZE]-env)) {
		printf ("## Error: environment overflow, \"%s\" deleted\n", name);
		return;
	}
	while ((*env = *name++) != '\0')
		env++;
	for (i=2; i<argc; ++i) {
		char *val = argv[i];

		*env = (i==2) ? '=' : ' ';
		while ((*++env = *val++) != '\0')
			;
	}

	/* end is marked with double '\0' */
	*++env = '\0';

	/* Update CRC */
	env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);

	/*
	 * Some variables should be updated when the corresponding
	 * entry in the enviornment is changed
	 */

	if (strcmp(argv[1],"ethaddr") == 0) {
		char *s = argv[2];	/* always use only one arg */
		char *e;
		for (i=0; i<6; ++i) {
			bd->bi_enetaddr[i] = s ? simple_strtoul(s, &e, 16) : 0;
			if (s) s = (*e) ? e+1 : e;
		}
		return;
	}

	if (strcmp(argv[1],"ipaddr") == 0) {
		char *s = argv[2];	/* always use only one arg */
		char *e;
		bd->bi_ip_addr = 0;
		for (i=0; i<4; ++i) {
			ulong val = s ? simple_strtoul(s, &e, 10) : 0;
			bd->bi_ip_addr <<= 8;
			bd->bi_ip_addr  |= (val & 0xFF);
			if (s) s = (*e) ? e+1 : e;
		}
		return;
	}
	if (strcmp(argv[1],"loadaddr") == 0) {
		load_addr = simple_strtoul(argv[2], NULL, 16);
		return;
	}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
	if (strcmp(argv[1],"bootfile") == 0) {
		copy_filename (BootFile, argv[2], sizeof(BootFile));
		return;
	}
#endif	/* CFG_CMD_NET */
}

void setenv (char *varname, char *varvalue)
{
	char *argv[4] = { "setenv", varname, varvalue, NULL };
	_do_setenv (bd_ptr, 0, 3, argv);
}

void do_setenv (cmd_tbl_t * cmdtp, bd_t * bd, int flag, int argc,
		char *argv[])
{
	if (argc < 2) {
		printf ("Usage:\n%s\n", cmdtp->usage);
		return;
	}

	_do_setenv (bd, flag, argc, argv);
}

/************************************************************************
 * Prompt for environment variable
 */

#if (CONFIG_COMMANDS & CFG_CMD_ASKENV)
void do_askenv (cmd_tbl_t * cmdtp, bd_t * bd, int flag, int argc,
		char *argv[])
{
	extern char console_buffer[CFG_CBSIZE];
	char message[CFG_CBSIZE];
	int size = CFG_CBSIZE - 1;
	int len;
	char *local_args[4];

	local_args[0] = argv[0];
	local_args[1] = argv[1];
	local_args[2] = NULL;
	local_args[3] = NULL;

	if (argc < 2) {
		printf ("Usage:\n%s\n", cmdtp->usage);
		return;
	}
	/* Check the syntax */
	switch (argc) {
	case 1:
		printf ("Usage:\n%s\n", cmdtp->usage);
		return;

	case 2:		/* askenv envname */
		sprintf (message, "Please enter '%s':", argv[1]);
		break;

	case 3:		/* askenv envname size */
		sprintf (message, "Please enter '%s':", argv[1]);
		size = simple_strtoul (argv[2], NULL, 10);
		break;

	default:	/* askenv envname message1 ... messagen size */
		{
			int i;
			int pos = 0;

			for (i = 2; i < argc - 1; i++) {
				if (pos) {
					message[pos++] = ' ';
				}
				strcpy (message+pos, argv[i]);
				pos += strlen(argv[i]);
			}
			message[pos] = '\0';
			size = simple_strtoul (argv[argc - 1], NULL, 10);
		}
	}

	if (size >= CFG_CBSIZE)
		size = CFG_CBSIZE - 1;

	if (size <= 0)
		return;

	/* prompt for input */
	len = readline (message);

	if (size < len)
		console_buffer[size] = '\0';

	len = 2;
	if (console_buffer[0] != '\0') {
		local_args[2] = console_buffer;
		len = 3;
	}

	// Continue calling setenv code
	_do_setenv (bd, flag, len, local_args);
}
#endif	/* CFG_CMD_ASKENV */

/************************************************************************
 * Look up variable from environment,
 * return address of storage for that variable,
 * or NULL if not found
 */

char *getenv (uchar *name)
{
	int i, nxt;

	for (i=0; get_env_char(i) != '\0'; i=nxt+1) {
		int val;

		for (nxt=i; get_env_char(nxt) != '\0'; ++nxt) {
			if (nxt >= CFG_ENV_SIZE) {
				return (NULL);
			}
		}
		if ((val=envmatch(name, i)) < 0)
			continue;
		return (get_env_addr(val));
	}

	return (NULL);
}

int getenv_r (uchar *name, uchar *buf, unsigned len)
{
	int i, nxt;

	for (i=0; get_env_char(i) != '\0'; i=nxt+1) {
		int val, n;

		for (nxt=i; get_env_char(nxt) != '\0'; ++nxt) {
			if (nxt >= CFG_ENV_SIZE) {
				return (-1);
			}
		}
		if ((val=envmatch(name, i)) < 0)
			continue;
		/* found; copy out */
		n = 0;
		while ((len > n++) && (*buf++ = get_env_char(val++)) != '\0')
			;
		if (len == n)
			*buf = '\0';
		return (n);
	}
	return (-1);
}


/************************************************************************
 * Match a name / name=value pair
 *
 * s1 is either a simple 'name', or a 'name=value' pair.
 * i2 is the environment index for a 'name2=value2' pair.
 * If the names match, return the index for the value2, else NULL.
 */

static int
envmatch (uchar *s1, int i2)
{

	while (*s1 == get_env_char(i2++))
		if (*s1++ == '=')
			return(i2);
	if (*s1 == '\0' && get_env_char(i2-1) == '=')
		return(i2);
	return(-1);
}


#ifdef CFG_ENV_IS_IN_NVRAM

void do_saveenv  (cmd_tbl_t *cmdtp, bd_t *bd, int flag, int argc, char *argv[])
{
	printf ("Saving Environment to NVRAM...\n");
	memcpy ((char *)CFG_ENV_ADDR, env_ptr, CFG_ENV_SIZE);
}

#elif CFG_ENV_IS_IN_EEPROM

void do_saveenv  (cmd_tbl_t *cmdtp, bd_t *bd, int flag, int argc, char *argv[])
{
	printf ("Saving Environment to EEPROM...\n");
	eeprom_write (CFG_ENV_OFFSET, (uchar *)env_ptr, CFG_ENV_SIZE);
}

#else	/* !CFG_ENV_IS_IN_NVRAM, !CFG_ENV_IS_IN_EEPROM => Must be flash, then */

/* need both ENV and flash */
#if ((CONFIG_COMMANDS & (CFG_CMD_ENV|CFG_CMD_FLASH)) == (CFG_CMD_ENV|CFG_CMD_FLASH))

void do_saveenv  (cmd_tbl_t *cmdtp, bd_t *bd, int flag, int argc, char *argv[])
{
	int len, rc;
	ulong end_addr;
	uchar *flash_sect_addr;
#if defined(CFG_ENV_SECT_SIZE) && (CFG_ENV_SECT_SIZE > CFG_ENV_SIZE)
	uchar *flash_offset;
	uchar  env_buffer[CFG_ENV_SECT_SIZE];
#else
	uchar *env_buffer = (char *)env_ptr;
#endif	/* CFG_ENV_SECT_SIZE */

#if defined(CFG_ENV_SECT_SIZE) && (CFG_ENV_SECT_SIZE > CFG_ENV_SIZE)

	flash_offset    = (uchar *)(((ulong)flash_addr) &  (CFG_ENV_SECT_SIZE-1));
	flash_sect_addr = (uchar *)(((ulong)flash_addr) & ~(CFG_ENV_SECT_SIZE-1));

	/* copy old contents to temporary buffer */
	memcpy (env_buffer, flash_sect_addr, CFG_ENV_SECT_SIZE);

	/* copy current environment to temporary buffer */
	memcpy ((uchar *)((unsigned long)env_buffer + (unsigned long)flash_offset),
		env_ptr,
		CFG_ENV_SIZE);

	len	 = CFG_ENV_SECT_SIZE;
#else
	flash_sect_addr = flash_addr;
	len	 = CFG_ENV_SIZE;
# endif	/* CFG_ENV_SECT_SIZE */

	end_addr = (ulong)flash_sect_addr + len - 1;

	flash_sect_protect (0, (ulong)flash_sect_addr, end_addr);

	printf ("Erasing Flash...");
	flash_sect_erase ((ulong)flash_sect_addr, end_addr);

	printf ("Saving Environment to Flash...\n");
	switch (rc = flash_write(env_buffer, (ulong)flash_sect_addr, len)) {
	case 0: break;
	case 1: printf ("Timeout writing to Flash\n");
		break;
	case 2: printf ("Flash not Erased\n");
		break;
	case 4: printf ("Can't write to protected Flash sectors\n");
		break;
	case 8: printf ("Outside available Flash\n");
		return;
	default:
		printf ("%s[%d] FIXME: rc=%d\n",__FILE__,__LINE__,rc);
	}

	flash_sect_protect (1, (ulong)flash_sect_addr, end_addr);
}

#endif	/* CFG_CMD_ENV + CFG_CMD_FLASH */

#endif	/* CFG_ENV_IS_IN_NVRAM */

/************************************************************************
 * Initialize Environment use
 *
 * We are still running from ROM, so data use is limited
 */
#ifndef	CFG_ENV_IS_IN_EEPROM
void env_init(init_data_t *idata)
{
	if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
		idata->env_addr  = (ulong)&(env_ptr->data);
		idata->env_valid = 1;
	} else {
		idata->env_addr  = (ulong)&default_environment[0];
		idata->env_valid = 0;
	}
}
#else	/* CFG_ENV_IS_IN_EEPROM */
/*
 * Use a (moderately small) buffer on the stack
 */
void env_init(init_data_t *idata)
{
	ulong crc, len, new;
	unsigned off;
	uchar buf[64];

	eeprom_init ();	/* prepare for EEPROM read/write */

	/* read old CRC */
	eeprom_read (CFG_ENV_OFFSET+offsetof(env_t,crc), (uchar *)&crc,
			sizeof(ulong));

	new = 0;
	len = ENV_SIZE;
	off = offsetof(env_t,data);
	while (len > 0) {
		int n = (len > sizeof(buf)) ? sizeof(buf) : len;

		eeprom_read (CFG_ENV_OFFSET+off, buf, n);
		new = crc32 (new, buf, n);
		len -= n;
		off += n;
	}


	if (crc == new) {
		idata->env_addr  = offsetof(env_t,data);
		idata->env_valid = 1;
	} else {
		idata->env_addr  = 0;
		idata->env_valid = 0;
	}
}
#endif	/* CFG_ENV_IS_IN_EEPROM */
