/*
 *  SOURCE:         ubootBootcounterAccess.c 
 *  FIRST EDITION:  02.12.03 
 *  LAST UPDATE:    02.12.03
 *  AUTHOR(S):      Steffen Rumler  (Steffen.Rumler@siemens.com)
 *
 *  PURPOSE:	    Provide read/write access to the Uboot bootcounter
 *                  via PROC FS 
 */

#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/io.h>


#ifndef CONFIG_PROC_FS
#error "PROC FS support must be switched-on"
#endif


#ifdef CONFIG_8260
#define UBOOT_BOOTCOUNT_OFFSET        0x80F4	/* offset of bootcounter from IMMR */
#define UBOOT_BOOTCOUNT_MAGIC_OFFSET  0x80F8	/* offset of magic number from IMMR */
#define UBOOT_BOOTCOUNT_MAGIC         0xB001C041 /* magic number value */
#else
/*
 * add other platforms here
 */
#error "platform not yet supported"
#endif

#define UBOOT_BOOTCOUNT_PROC_ENTRY   "driver/bootcount"	/* PROC FS entry under '/proc' */

/*
 * This macro frees the machine specific function from bounds checking and
 * this like that... 
 */
#define PRINT_PROC(fmt,args...) \
        do { \
                *len += sprintf( buffer+*len, fmt, ##args ); \
                if (*begin + *len > offset + size) \
                        return( 0 ); \
                if (*begin + *len < offset) { \
                        *begin += *len; \
                        *len = 0; \
                } \
        } while(0)

/*
 * read U-Boot bootcounter
 */
static int
read_bootcounter_info (char *buffer, int *len, off_t * begin, off_t offset,
		       int size)
{
	unsigned char *immr = (unsigned char *) IMAP_ADDR;
	unsigned long magic;
	unsigned long counter;


	magic = *((unsigned long *) (immr + UBOOT_BOOTCOUNT_MAGIC_OFFSET));
	counter = *((unsigned long *) (immr + UBOOT_BOOTCOUNT_OFFSET));

	if (magic == UBOOT_BOOTCOUNT_MAGIC) {
		PRINT_PROC ("%lu\n", counter);
	} else {
		PRINT_PROC ("bad magic: 0x%lx != 0x%lx\n", magic,
			    UBOOT_BOOTCOUNT_MAGIC);
	}

	return 1;
}

/*
 * read U-Boot bootcounter (wrapper)
 */
static int
read_bootcounter (char *buffer, char **start, off_t offset, int size,
		  int *eof, void *arg)
{
	int len = 0;
	off_t begin = 0;


	*eof = read_bootcounter_info (buffer, &len, &begin, offset, size);

	if (offset >= begin + len)
		return 0;

	*start = buffer + (offset - begin);
	return size < begin + len - offset ? size : begin + len - offset;
}

/*
 * write new value to U-Boot bootcounter
 */
static int
write_bootcounter (struct file *file, const char *buffer, unsigned long count,
		   void *data)
{
	unsigned char *immr = (unsigned char *) IMAP_ADDR;
	unsigned long magic;
	unsigned long *counter_ptr;


	magic = *((unsigned long *) (immr + UBOOT_BOOTCOUNT_MAGIC_OFFSET));
	counter_ptr = (unsigned long *) (immr + UBOOT_BOOTCOUNT_OFFSET);

	if (magic == UBOOT_BOOTCOUNT_MAGIC)
		*counter_ptr = simple_strtol (buffer, NULL, 10);
	else
		return -EINVAL;

	return count;
}

/*
 * init entry point
 */
static int __init uboot_bootcount_init (void)
{
	struct proc_dir_entry *bootcount;


	printk ("%s (%d) %s:  ", __FILE__, __LINE__, __FUNCTION__);

	if ((bootcount =
	     create_proc_entry (UBOOT_BOOTCOUNT_PROC_ENTRY, 0600,
				NULL)) == NULL) {

		printk (KERN_ERR "\n%s (%d): cannot create /proc/%s\n",
			__FILE__, __LINE__, UBOOT_BOOTCOUNT_PROC_ENTRY);
	} else {

		bootcount->read_proc = read_bootcounter;
		bootcount->write_proc = write_bootcounter;
		printk ("created \"/proc/%s\"\n", UBOOT_BOOTCOUNT_PROC_ENTRY);
	}

	return 0;		/* success */
}

static void __exit uboot_bootcount_cleanup (void)
{
	remove_proc_entry (UBOOT_BOOTCOUNT_PROC_ENTRY, NULL);
}

/*  for loading the module dynamically
 */
module_init (uboot_bootcount_init);
module_exit (uboot_bootcount_cleanup);

MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("Steffen Rumler <steffen.rumler@siemens.com>");
MODULE_DESCRIPTION ("Provide (read/write) access to the U-Boot bootcounter via PROC FS");
