/*
 *   Creation Date: <2003/12/01 00:26:13 samuel>
 *   Time-stamp: <2004/01/07 19:59:53 samuel>
 *
 *	<nvram.c>
 *
 *	medium-level NVRAM handling
 *
 *   Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se)
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   version 2
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "arch/common/nvram.h"
#include "packages/nvram.h"

//#define CONFIG_DEBUG_NVRAM 1

#ifdef CONFIG_DEBUG_NVRAM
#define DPRINTF(fmt, args...) \
do { printk("NVRAM: " fmt , ##args); } while (0)
#else
#define DPRINTF(fmt, args...) do {} while(0)
#endif

#define DEF_SYSTEM_SIZE	0xc10

#define NV_SIG_SYSTEM	0x70
#define NV_SIG_FREE	0x7f


typedef struct {
	unsigned char	signature;
	unsigned char	checksum;
	unsigned char	len_hi;
	unsigned char	len_lo;
	char		name[12];
	char		data[0];
} nvpart_t;

static struct {
	char		*data;
	int		size;

	nvpart_t	*config;
	int		config_size;
} nvram;


/************************************************************************/
/*	generic								*/
/************************************************************************/

static unsigned int
nvpart_checksum( nvpart_t* hdr )
{
	unsigned char *p = (unsigned char*)hdr;
	int i, val = p[0];

	for( i=2; i<16; i++ ) {
		val += p[i];
		if( val > 255 )
			val = (val - 256 + 1) & 0xff;
	}
	return val;
}

static inline int
nvpart_size( nvpart_t *p )
{
	return (p->len_lo | ((int)p->len_hi<<8)) * 16;
}

static int
next_nvpart( nvpart_t **p )
{
	nvpart_t *end = (nvpart_t*)(nvram.data + nvram.size);
	int len;

	if( !*p ) {
		*p = (nvpart_t*)nvram.data;
		return 1;
	}

	if( !(len=nvpart_size(*p)) ) {
		printk("invalid nvram partition length\n");
		return -1;
	}
	*p = (nvpart_t*)((char*)*p + len);
	if( *p < end )
		return 1;
	if( *p == end )
		return 0;
	return -1;
}

static void
create_free_part( char *ptr, int size )
{
	nvpart_t *nvp = (nvpart_t*)ptr;
	memset( nvp, 0, size );

	strncpy( nvp->name, "77777777777", sizeof(nvp->name) );
	nvp->signature = NV_SIG_FREE;
	nvp->len_hi = (size /16) >> 8;
	nvp->len_lo = size /16;
	nvp->checksum = nvpart_checksum(nvp);
}

static int
create_nv_part( int signature, const char *name, int size )
{
	nvpart_t *p = NULL;
	int fs;

	while( next_nvpart(&p) > 0 ) {
		if( p->signature != NV_SIG_FREE )
			continue;

		fs = nvpart_size( p );
		if( fs < size )
			size = fs;
		p->signature = signature;
		memset( p->name, 0, sizeof(p->name) );
		strncpy( p->name, name, sizeof(p->name) );
		p->len_hi = (size>>8)/16;
		p->len_lo = size/16;
		p->checksum = nvpart_checksum(p);
		if( fs > size ) {
			char *fp = (char*)p + size;
			create_free_part( fp, fs-size );
		}
		return size;
	}
	printk("create-failed\n");
	return -1;
}

static void
zap_nvram( void )
{
	create_free_part( nvram.data, nvram.size );
	create_nv_part( NV_SIG_SYSTEM, "common", DEF_SYSTEM_SIZE );
}

#if 0
static void
show_partitions( void )
{
	nvpart_t *p = NULL;
	char buf[13];

	while( next_nvpart(&p) > 0 ) {
		memcpy( buf, p->name, sizeof(p->name) );
		buf[12] = 0;
		printk("[%02x] %-13s:  %03x\n",
		       p->signature, buf, nvpart_size(p));
	}
}
#endif

void
update_nvram( void )
{
	PUSH( pointer2cell(nvram.config->data) );
	PUSH( nvram.config_size );
	fword("nvram-store-configs");
	arch_nvram_put( nvram.data );
}

void
nvconf_init( void )
{
	int once=0;

	/* initialize nvram structure completely */
	nvram.config = NULL;
	nvram.config_size = 0;

	nvram.size = arch_nvram_size();
	nvram.data = malloc( nvram.size );
	arch_nvram_get( nvram.data );

	for( ;; ) {
		nvpart_t *p = NULL;
		int err;

		while( (err=next_nvpart(&p)) > 0 ) {
			if( nvpart_checksum(p) != p->checksum ) {
				err = -1;
				break;
			}
			if( p->signature == NV_SIG_SYSTEM ) {
				nvram.config = p;
				nvram.config_size = nvpart_size(p) - 0x10;

				if( !once++ ) {
					PUSH( pointer2cell(p->data) );
					PUSH( nvram.config_size );
					fword("nvram-load-configs");
				}
			}
		}
		if( err || !nvram.config ) {
			printk("nvram error detected, zapping pram\n");
			zap_nvram();
			if( !once++ )
				fword("set-defaults");
			continue;
		}
		break;
	}
}


/************************************************************************/
/*	nvram								*/
/************************************************************************/

typedef struct {
	unsigned int   mark_hi;
	unsigned int   mark_lo;
} nvram_ibuf_t;

DECLARE_UNNAMED_NODE( nvram, INSTALL_OPEN, sizeof(nvram_ibuf_t ));

/* ( pos_lo pos_hi -- status ) */
static void
nvram_seek( nvram_ibuf_t *nd )
{
	int pos_hi = POP();
	int pos_lo = POP();

	DPRINTF("seek %08x %08x\n", pos_hi, pos_lo );
	nd->mark_lo = pos_lo;
	nd->mark_hi = pos_hi;

	if( nd->mark_lo >= nvram.size ) {
		PUSH(-1);
		return;
	}

	/* 0=success, -1=failure (1=legacy success) */
	PUSH(0);
}

/* ( addr len -- actual ) */
static void
nvram_read( nvram_ibuf_t *nd )
{
	int len = POP();
	char *p = (char*)cell2pointer(POP());
	int n=0;

	while( nd->mark_lo < nvram.size && n < len ) {
		*p++ = nvram.data[nd->mark_lo++];
		n++;
	}
	PUSH(n);
	DPRINTF("read %p %x -- %x\n", p, len, n);
}

/* ( addr len -- actual ) */
static void
nvram_write( nvram_ibuf_t *nd )
{
	int len = POP();
	char *p = (char*)cell2pointer(POP());
	int n=0;

	while( nd->mark_lo < nvram.size && n < len ) {
		nvram.data[nd->mark_lo++] = *p++;
		n++;
	}
	PUSH(n);
	DPRINTF("write %p %x -- %x\n", p, len, n );
}

/* ( -- size ) */
static void
nvram_size( __attribute__((unused)) nvram_ibuf_t *nd )
{
	DPRINTF("nvram_size %d\n", nvram.size);
	PUSH( nvram.size );
}

static void
nvram_open( __attribute__((unused)) nvram_ibuf_t *nd )
{
	RET(-1);
}

static void
nvram_close( __attribute__((unused)) nvram_ibuf_t *nd )
{
}

NODE_METHODS( nvram ) = {
	{ "open",	(void*)nvram_open	},
	{ "close",	(void*)nvram_close	},
	{ "size",	(void*)nvram_size	},
	{ "read",	(void*)nvram_read	},
	{ "write",	(void*)nvram_write	},
	{ "seek",	(void*)nvram_seek	},
	{ "update-nvram",	(void*)update_nvram	},
};


phandle_t
nvram_init( const char *path )
{
	phandle_t ph;

	push_str(path);
	fword("find-device");

	fword("new-device");

	ph = get_cur_dev();

	push_str("nvram");
	fword("device-name");

	BIND_NODE_METHODS(get_cur_dev(), nvram);
	fword("finish-device");

	return ph;
}
