#include <linux/init.h>
#include <linux/module.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/ctype.h>
#include <asm/avalanche/adam2_env.h>

#ifdef CONFIG_SYSCTL

#define DEV_ADAM2	    6
#define DEV_ADAM2_ENV	    1

#define ADAM2_ENV_STR_SIZE  80

static char info[ADAM2_ENV_STR_SIZE];


/*------------------------------------------------------------------------------------------*\
 * kopien aus tffs.h
\*------------------------------------------------------------------------------------------*/
extern char *adam2_env_get_value_by_id(unsigned int id);
#define FLASH_FS_ANNEX              0x01A9
#define FLASH_FS_FIRMWARE_VERSION   0x01A6

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int adam2_getenv_proc(ctl_table *ctl, int write, struct file * filp, void *buffer, size_t *lenp) {
    char *val, len;
    char *default_val = NULL;
    int free_val;

    /*--- printk("adam2_getenv_firmware_proc: filp->f_pos=%u ctl->ctl_name=%u, write=%u *lenp=%u\n", ---*/ 
            /*--- (int)filp->f_pos, (int)ctl->ctl_name, (int)write, (int)*lenp); ---*/

    if(write) {
        printk("write not supported\n");
        return -EFAULT;
    }
	if (!*lenp || (filp->f_pos && !write)) {
		*lenp = 0;
		return 0;
	}
        
	switch(ctl->ctl_name) {
        case FLASH_FS_ANNEX:
            default_val = "B";
            break;
        case FLASH_FS_FIRMWARE_VERSION:
            default_val = "";
            break;
        default:
            printk("adam2_getenv_proc: id %x not supported\n", ctl->ctl_name);
            return -EFAULT;
    }

    free_val = 1;
    val = adam2_env_get_value_by_id(ctl->ctl_name);

    if(val == NULL) {
        if(default_val) {
            val = default_val;
        } else {
            val = "";
        }
        free_val = 0;
    }

    len   = 0;

    /* Scan thru the flash, looking for each possible variable index */
    len = strlen(val);
    memcpy(info, val, len + 1);

    if(free_val)
        kfree(val);

    if(len > *lenp)
        len = *lenp;
        
    if(len) {
        if(copy_to_user(buffer, info, len))
            return -EFAULT;
    }

    *lenp = len;
    filp->f_pos += len;

    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int adam2_setenv_sysctl(ctl_table *ctl, int write, struct file * filp,
			void *buffer, size_t *lenp)
{
	char *var, *val, *ptr;
        int ret, i;
        size_t total, len;
	

	if (!*lenp || (filp->f_pos && !write))
	{
		*lenp = 0;
		return 0;
	}
        
	if(ctl->ctl_name != DEV_ADAM2_ENV)
        {
                return -ENODEV;
        }

	if(write)
	{
		ret = proc_dostring(ctl, write, filp, buffer, lenp);

                ptr = strpbrk(info, " \t");
                if(!ptr)	
		{
			/* We have no way to distinguish between unsetting a 
			 * variable and setting a non-value variable.
			 * 
			 * Consequently, we will treat any variable
			 * without a value as an unset request.
			 */
			adam2_env_unset_variable(info);
		}
		else
		{
			/* Set the variable-value pair in flash */
			*ptr++ = 0;
			if(adam2_env_set_variable(info, ptr) != 0)
			{
				printk(KERN_NOTICE "Defragging the environment variable region.\n");
				adam2_env_defrag();
				if( adam2_env_set_variable(info, ptr) != 0 )
					printk(KERN_WARNING "Failed to write %s to environment variable region.\n", info);
			}
		}

	}
	else
	{
		total=0;
		len=0;
		/* Scan thru the flash, looking for each possible variable index */
		for(i = 0; i < MAX_ENV_ENTRY; i++)
		{
            if( (var=adam2_env_get_variable(i))!=NULL )
            {
				if( (val=adam2_env_get_value(var)) != NULL) {
					len = sprintf(info, "%s\t%s\n", var, val);
					kfree(val);
				}

				kfree(var);

				if(len > *lenp - total)
					len = *lenp - total;
			        
				if(len)
				{
					if(copy_to_user(buffer+total, info, len))
						return -EFAULT;
				}
#if !defined(CONFIG_TFFS_ENV)
				else
				{
					break;
				}
#endif /*--- #if !defined(CONFIG_TFFS_ENV) ---*/
				total += len;
                len = 0; /*--- M.Pommerenke@avm.de ---*/
			} 
		}
		*lenp = total;
		filp->f_pos += total;
	}
	
        return 0;
}

/*------------------------------------------------------------------------------------------*\
 *   Place files in /proc/sys/dev/adam2
\*------------------------------------------------------------------------------------------*/
ctl_table adam2_env_table[] = {
	{DEV_ADAM2_ENV, "environment",      info, ADAM2_ENV_STR_SIZE, 0644, NULL, &adam2_setenv_sysctl },
	{FLASH_FS_FIRMWARE_VERSION, "firmware_version", info, ADAM2_ENV_STR_SIZE, 0644, NULL, &adam2_getenv_proc},
	{FLASH_FS_ANNEX, "annex",            info, ADAM2_ENV_STR_SIZE, 0644, NULL, &adam2_getenv_proc},
	{0}
	};

ctl_table adam2_table[] = {
	{DEV_ADAM2, "adam2", NULL, 0, 0555, adam2_env_table},
	{0}
	};

/* Make sure that /proc/sys/dev is there */
ctl_table adam2_root_table[] = {
#ifdef CONFIG_PROC_FS
	{CTL_DEV, "dev", NULL, 0, 0555, adam2_table},
#endif /* CONFIG_PROC_FS */
	{0}
	};

static struct ctl_table_header *adam2_sysctl_header;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void adam2_env_sysctl_register(void)
{
	static int initialized;

	if (initialized == 1)
		return;

	adam2_sysctl_header  = register_sysctl_table(adam2_root_table, 1);
	adam2_root_table->child->de->owner = THIS_MODULE;

	/* set the defaults */
	info[0] = 0;

	initialized = 1;
}

static void adam2_env_sysctl_unregister(void)
{
	unregister_sysctl_table(adam2_sysctl_header);
}

int adam2_env_init(void)
{
	adam2_env_sysctl_register();
	printk(KERN_INFO "Adam2 environment variables API installed.\n");
	return 0;
}

#if defined(MODLE)
void adam2_env_exit(void)
{
	adam2_env_sysctl_unregister();
}
#endif /*--- #if defined(MODLE) ---*/

#endif /* CONFIG_SYSCTL */
