/* 2009-05-12: File added by Sony Corporation */
/*
 *  Snapshot Boot Core - proc interface
 *
 *  Copyright 2008-2009 Sony Corporation.
 *
 *  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;  version 2 of the  License.
 *
 *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  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.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <linux/pfn.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/freezer.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include "internal.h"

/* proc file permissions */
#define SSBOOT_PROC_RDWR	0644
#define SSBOOT_PROC_RDONLY	0444
#define SSBOOT_PROC_WRONLY	0200

/* customized proc operations */
typedef struct ssboot_proc_ops {
	struct seq_operations *seq_ops;
	int    (*open)(struct inode *inode, struct file *file);
	int    (*release)(struct inode *inode, struct file *file);
	size_t write_max;
	size_t write_len;
	char   *write_buf;
	void   *data;
	mode_t mode;
} ssboot_proc_ops_t;

/* list of proc interface */
typedef struct ssboot_proc_list {
	const char        *name;
	ssboot_proc_ops_t *ops;
} ssboot_proc_list_t;

/* iterator for 'section' */
typedef struct ssboot_sect_iter {
	loff_t        pos;
	loff_t        num;
	sbi_region_t  reg;
	int           attr;
	unsigned long norm;
	unsigned long crit;
} ssboot_sect_iter_t;

/* iterator for extra regions */
typedef struct ssboot_region_iter {
	loff_t        pos;
	unsigned long pfn;
	unsigned long num;
	u_int32_t     attr;
} ssboot_region_iter_t;

/* for normal type seq_file operation */
#define ssboot_seq_proc(_name, _mode, _write_max)	\
static struct seq_operations _name##_seq_ops = {	\
	.start     = _name##_start,			\
	.stop      = proc_stop,				\
	.next      = _name##_next,			\
	.show      = _name##_show,			\
};							\
static ssboot_proc_ops_t _name##_ops = {		\
	.seq_ops   = &_name##_seq_ops,			\
	.open      = _name##_open,			\
	.release   = _name##_release,			\
	.write_max = _write_max,			\
	.mode      = _mode,				\
};

/* for single type seq_file operation */
#define ssboot_single_proc(_name, _mode, _write_max)	\
static struct seq_operations _name##_seq_ops = {	\
	.start     = proc_single_start,			\
	.stop      = proc_stop,				\
	.next      = proc_single_next,			\
	.show      = _name##_show,			\
};							\
static ssboot_proc_ops_t _name##_ops = {		\
	.seq_ops   = &_name##_seq_ops,			\
	.open      = _name##_open,			\
	.release   = _name##_release,			\
	.write_max = _write_max,			\
	.mode      = _mode,				\
};

/* macros for convenience */
#define num_proc(_list)		(sizeof(_list) / sizeof(ssboot_proc_list_t))
#define m_to_ops(_m)		(ssboot_proc_ops_t *)_m->private
#define file_to_ops(_file)	(ssboot_proc_ops_t *)((struct seq_file *) \
				 _file->private_data)->private

/* proc entries for directory */
static struct proc_dir_entry *top_entry, *region_entry;

/*
 * Wrapper functions for customized proc operations
 */
static int
proc_open(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = (ssboot_proc_ops_t *)PDE(inode)->data;
	int ret;

	/* call seq_file layer */
	ret = seq_open(file, ops->seq_ops);
	if (ret < 0) {
		return ret;
	}
	((struct seq_file *)file->private_data)->private = (void *)ops;

	/* allocate write buffer */
	if (ops->write_max > 0) {
		ops->write_buf = kmalloc(ops->write_max, GFP_KERNEL);
		if (ops->write_buf == NULL) {
			seq_release(inode, file);
			return -ENOMEM;
		}
	}

	/* call open method */
	if (ops->open != NULL) {
		ret = ops->open(inode, file);
	}
	return ret;
}

static int
proc_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);

	/* call release method */
	if (ops->release != NULL) {
		ops->release(inode, file);
	}

	/* free write buffer */
	if (ops->write_max > 0) {
		kfree(ops->write_buf);
	}
	ops->write_len = 0;

	/* call seq_file layer */
	return seq_release(inode, file);
}

static ssize_t
proc_write(struct file *file, const char __user *buffer, size_t len,
	   loff_t *offset)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	loff_t pos = *offset;

	/* sanity check */
	if (!ops->write_buf) {
		return -EINVAL;
	}
	if (pos < 0) {
		return -EINVAL;
	}

	/* ignore data if buffer is full */
	if (pos >= ops->write_max) {
		return len;
	}

	/* get data from user */
	if (len > ops->write_max - pos) {
		len = ops->write_max - pos;
	}
	if (copy_from_user(ops->write_buf + pos, buffer, len)) {
		return -EFAULT;
	}
	if (pos + len > ops->write_len) {
		ops->write_len = len + file->f_pos;
	}
	*offset = pos + len;

	return len;
}

static void
proc_stop(struct seq_file *m, void *v)
{
	return;
}

static void*
proc_single_start(struct seq_file *m, loff_t *pos)
{
	return NULL + (*pos == 0);
}

static void*
proc_single_next(struct seq_file *m, void *v, loff_t *pos)
{
	++*pos;
	return NULL;
}

/*
 * sbiname
 */
static int
sbiname_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
sbiname_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%s\n", ssboot.sbiname);

	return 0;
}

static int
sbiname_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	size_t len;

	/* check if data exists */
	len = ops->write_len;
	if (len == 0) {
		return 0;
	}

	/* set sbiname */
	strncpy(ssboot.sbiname, ops->write_buf, len);
	if ((len > 1) && (ssboot.sbiname[len - 2] == '\n')) {
		ssboot.sbiname[len - 2] = '\0';
	}
	ssboot.sbiname[len - 1] = '\0';

	return 0;
}
ssboot_single_proc(sbiname, SSBOOT_PROC_RDWR, PATH_MAX);

/*
 * mode
 */
static const char * const imgmode_str[SSBOOT_IMGMODE_NUM] = {
	[SSBOOT_IMGMODE_NULL]     = "null",
	[SSBOOT_IMGMODE_MIN]      = "min",
	[SSBOOT_IMGMODE_MAX]      = "max",
	[SSBOOT_IMGMODE_ADAPTIVE] = "adaptive",
};

static int
mode_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
mode_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%s\n", imgmode_str[ssboot.imgmode]);

	return 0;
}

static int
mode_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	ssboot_imgmode_t mode;
	const char *str;
	char *lf;
	int len;

	/* check if data exists */
	if (ops->write_len == 0) {
		return 0;
	}

	/* get length without line feed */
	lf = memchr(ops->write_buf, '\n', ops->write_len);
	len = lf ? lf - ops->write_buf : ops->write_len;

	/* parse string */
	for (mode = 0; mode < SSBOOT_IMGMODE_NUM; mode++) {
		str = imgmode_str[mode];
		if (len == strlen(str) &&
		    !strncmp(ops->write_buf, str, len)) {
			break;
		}
	}
	if (mode == SSBOOT_IMGMODE_NUM) {
		ssboot_err("invalid image creation mode\n");
		return 0;
	}

	/* check capability for adaptive mode */
	if (mode == SSBOOT_IMGMODE_ADAPTIVE &&
	    ssboot.writer->get_capacity == NULL) {
		ssboot_err("adaptive mode is not supported\n");
		return 0;
	}

	/* set image creation mode */
	ssboot.imgmode = mode;

	return 0;
}
ssboot_single_proc(mode, SSBOOT_PROC_RDWR, 16);

/*
 * compress
 */
static int
compress_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
compress_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%d\n", ssboot.compress);

	return 0;
}

static int
compress_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	char *lf;
	int len, mode = -1;

	/* check if data exists */
	if (ops->write_len == 0) {
		return 0;
	}

	/* get length without line feed */
	lf = memchr(ops->write_buf, '\n', ops->write_len);
	len = lf ? lf - ops->write_buf : ops->write_len;

	/* parse string */
	if (len == 1) {
		mode = simple_strtoul(ops->write_buf, NULL, 0);
	}
	if (mode < 0 || mode > 1) {
		ssboot_err("invalid compress mode\n");
		return 0;
	}

	/* set compress mode */
	ssboot.compress = mode;

	return 0;
}
ssboot_single_proc(compress, SSBOOT_PROC_RDWR, 4);

/*
 * encrypt
 */
static int
encrypt_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
encrypt_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%d\n", ssboot.encrypt);

	return 0;
}

static int
encrypt_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	char *lf;
	int len, mode = -1;

	/* check if data exists */
	if (ops->write_len == 0) {
		return 0;
	}

	/* get length without line feed */
	lf = memchr(ops->write_buf, '\n', ops->write_len);
	len = lf ? lf - ops->write_buf : ops->write_len;

	/* parse string */
	if (len == 1) {
		mode = simple_strtoul(ops->write_buf, NULL, 0);
	}
	if (mode < 0 || mode > 1) {
		ssboot_err("invalid encrypt mode\n");
		return 0;
	}

	/* set encrypt mode */
	ssboot.encrypt = mode;

	return 0;
}
ssboot_single_proc(encrypt, SSBOOT_PROC_RDWR, 4);

/*
 * operation
 */
static const char * const opmode_str[SSBOOT_OPMODE_NUM] = {
	[SSBOOT_OPMODE_NORMAL]   = "normal",
	[SSBOOT_OPMODE_SHUTDOWN] = "shutdown",
	[SSBOOT_OPMODE_REBOOT]   = "reboot",
};

static int
operation_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
operation_show(struct seq_file *m, void *v)
{
	seq_printf(m, "%s\n", opmode_str[ssboot.opmode]);

	return 0;
}

static int
operation_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	ssboot_opmode_t mode;
	const char *str;
	char *lf;
	int len;

	/* check if data exists */
	if (ops->write_len == 0) {
		return 0;
	}

	/* get length without line feed */
	lf = memchr(ops->write_buf, '\n', ops->write_len);
	len = lf ? lf - ops->write_buf : ops->write_len;

	/* parse string */
	for (mode = 0; mode < SSBOOT_OPMODE_NUM; mode++) {
		str = opmode_str[mode];
		if (len == strlen(str) &&
		    !strncmp(ops->write_buf, str, len)) {
			break;
		}
	}
	if (mode == SSBOOT_OPMODE_NUM) {
		ssboot_err("invalid operation mode\n");
		return 0;
	}

	/* set operation mode */
	ssboot.opmode = mode;

	return 0;
}
ssboot_single_proc(operation, SSBOOT_PROC_RDWR, 16);

/*
 * section
 */
static int
section_open(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	int ret;

	/* stop user processes */
	ret = freeze_tasks(FREEZER_USER_SPACE);
	if (ret < 0) {
		goto thaw;
	}

	/* allocate page bitmaps */
	ret = ssboot_alloc_page_bitmap();
	if (ret < 0) {
		goto thaw;
	}

	/* create page bitmap to estimate sections */
	ret = ssboot_create_page_bitmap();
	if (ret < 0) {
		goto thaw;
	}

	/* shrink image by freeing memory */
	ret = ssboot_shrink_image();
	if (ret < 0) {
		goto thaw;
	}

	/* stop kernel threads */
	ret = freeze_tasks(FREEZER_KERNEL_THREADS);
	if (ret < 0) {
		goto thaw;
	}

	local_irq_disable();

	/* create page bitmap with irqs disabled */
	ret = ssboot_create_page_bitmap();
	if (ret < 0) {
		goto thaw;
	}

	local_irq_enable();

	/* create region list */
	ret = ssboot_create_region_list();
	if (ret < 0) {
		goto thaw;
	}

 thaw: 	/* thaw processes */
	thaw_processes();

	/* allocate iterator */
	ops->data = kmalloc(sizeof(ssboot_sect_iter_t), GFP_KERNEL);
	if (ops->data == NULL) {
		ret = -ENOMEM;
	}
	return ret;
}

static void*
section_start(struct seq_file *m, loff_t *pos)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_sect_iter_t *iter = (ssboot_sect_iter_t *)ops->data;
	int ret;

	/* pos = 0 is for header */
	if (*pos == 0) {
		iter->pos = 0;
		return (void *)pos;
	}

	/* check end of list */
	if (iter->pos == -1UL) {
		return NULL;
	}

	/* get section info */
	ret = ssboot_get_region_list(*pos - 1, &iter->reg, &iter->attr);
	if (ret < 0) {
		iter->num = iter->pos;
		iter->pos = -1UL;
	} else {
		iter->pos = *pos;
	}
	return (void *)pos;
}

static void*
section_next(struct seq_file *m, void *v, loff_t *pos)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_sect_iter_t *iter = (ssboot_sect_iter_t *)ops->data;
	int ret;

	++*pos;

	/* check end of list */
	if (iter->pos == -1UL) {
		return NULL;
	}

	/* get section info */
	ret = ssboot_get_region_list(*pos - 1, &iter->reg, &iter->attr);
	if (ret < 0) {
		iter->num = iter->pos;
		iter->pos = -1UL;
	} else {
		iter->pos = *pos;
	}
	return (void *)pos;
}

static int
section_show(struct seq_file *m, void *v)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_sect_iter_t *iter = (ssboot_sect_iter_t *)ops->data;
	unsigned long pfn, num, total;

	pfn = iter->reg.start_pfn;
	num = iter->reg.num_pages;

	/* print header */
	if (iter->pos == 0) {
		seq_printf(m, "Section  Physical Address          "
			      "KByte   Pages  Attribute\n");
		iter->num = 0;
		iter->norm = 0;
		iter->crit = 0;
		return 0;
	}

	/* print footer */
	if (iter->pos == -1UL) {
		total = iter->norm + iter->crit;
		seq_printf(m, "\n");
		seq_printf(m, "Section  : %5lld sections\n", iter->num);
		seq_printf(m, "Critical : %5ld pages (%3ldMB)\n",
			   iter->crit, iter->crit / SSBOOT_PG_1MB);
		seq_printf(m, "Normal   : %5ld pages (%3ldMB)\n",
			   iter->norm, iter->norm / SSBOOT_PG_1MB);
		seq_printf(m, "Total    : %5ld pages (%3ldMB)\n",
			   total, total / SSBOOT_PG_1MB);
		return 0;
	}

	/* print body */
	seq_printf(m, "#%06lld  0x%08llx - 0x%08llx  %6ld  %6ld  ",
		   iter->pos, PFN_PHYS((u_int64_t)pfn),
		   PFN_PHYS((u_int64_t)pfn + num) - 1,
		   PFN_PHYS(num) / SSBOOT_SZ_1KB, num);
	if (iter->attr) {
		seq_printf(m, "Critical\n");
		iter->crit += num;
	} else {
		seq_printf(m, "Normal\n");
		iter->norm += num;
	}
	return 0;
}

static int
section_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);

	/* free region list */
	ssboot_free_region_list();

	/* free page bitmaps */
	ssboot_free_page_bitmap();

	/* free iterator */
	kfree(ops->data);

	return 0;
}
ssboot_seq_proc(section, SSBOOT_PROC_RDONLY, 0);

/*
 * region
 */
static int
region_open(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);

	/* allocate iterator */
	ops->data = kmalloc(sizeof(ssboot_region_iter_t), GFP_KERNEL);
	if (ops->data == NULL) {
		return -ENOMEM;
	}
	return 0;
}

static void*
region_start(struct seq_file *m, loff_t *pos, u_int32_t attr)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_region_iter_t *iter = (ssboot_region_iter_t *)ops->data;
	unsigned long pfn, num;
	loff_t i;

	/* pos = 0 is for header */
	if (*pos == 0) {
		iter->pos = 0;
		return (void *)pos;
	}

	/* get first extra region */
	iter->attr = ssboot_exreg_find_first(attr, &pfn, &num);
	if (pfn == SSBOOT_PFN_NONE) {
		return NULL;
	}

	/* seek for specified position */
	for (i = 1; i < *pos; i++) {
		iter->attr = ssboot_exreg_find_next(attr, &pfn, &num);
		if (pfn == SSBOOT_PFN_NONE) {
			return NULL;
		}
	}
	iter->pfn = pfn;
	iter->num = num;
	iter->pos = *pos;

	return (void *)pos;
}

static void*
region_next(struct seq_file *m, void *v, loff_t *pos, u_int32_t attr)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_region_iter_t *iter = (ssboot_region_iter_t *)ops->data;
	unsigned long pfn, num;

	++*pos;

	if (*pos == 1) {
		/* get first extra region */
		iter->attr = ssboot_exreg_find_first(attr, &pfn, &num);
	} else {
		/* get next extra region */
		iter->attr = ssboot_exreg_find_next(attr, &pfn, &num);
	}
	if (pfn == SSBOOT_PFN_NONE) {
		return NULL;
	}
	iter->pfn = pfn;
	iter->num = num;
	iter->pos = *pos;

	return (void *)pos;
}

static int
region_show(struct seq_file *m, void *v)
{
	ssboot_proc_ops_t *ops = m_to_ops(m);
	ssboot_region_iter_t *iter = (ssboot_region_iter_t *)ops->data;

	/* print header */
	if (iter->pos == 0) {
		seq_printf(m, "Physical Address          "
			      "KByte   Pages  Attribute\n");
		return 0;
	}

	/* print body */
	seq_printf(m, "0x%08llx - 0x%08llx  %6ld  %6ld  ",
		   PFN_PHYS((u_int64_t)iter->pfn),
		   PFN_PHYS((u_int64_t)iter->pfn + iter->num) - 1,
		   PFN_PHYS(iter->num) / SSBOOT_SZ_1KB, iter->num);
	if (iter->attr & SSBOOT_EXREG_KERNEL) {
		seq_printf(m, "Kernel");
	} else {
		seq_printf(m, "User");
	}
	seq_printf(m, "\n");

	return 0;
}

static int
region_release(struct inode *inode, struct file *file, u_int32_t attr,
	       int remove)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	u_int64_t phys_addr;
	unsigned long len;
	char *start, *end;

	/* check if data exists */
	if (ops->write_len == 0) {
		goto free;
	}

	/* skip first spaces */
	start = ops->write_buf;
	while (isspace(*start)) {
		start++;
	}

	/* get first parameter */
	end = start;
	while (*end != '\0' && !isspace(*end)) {
		end++;
	}
	*end = '\0';

	/* abort if too short */
	if (end - ops->write_buf >= ops->write_len) {
		goto free;
	}
	phys_addr = simple_strtoull(start, NULL, 0);

	/* skip spaces */
	start = end + 1;
	while (isspace(*start)) {
		start++;
	}

	/* get second parameter */
	end = start;
	while (*end != '\0' && !isspace(*end)) {
		end++;
	}
	*end = '\0';

	/* abort if too short */
	if (end - ops->write_buf >= ops->write_len) {
		goto free;
	}
	len = simple_strtoul(start, NULL, 0);

	/* check alignlent */
	if (((phys_addr | len) & ~PAGE_MASK) != 0) {
		ssboot_err("cannot %s region (invalid align): "
			   "0x%08llx-0x%08llx\n",
			   remove ? "unregister" : "register",
			   phys_addr, phys_addr + len - 1);
	} else {
		if (remove) {
			/* unregister extra region */
			ssboot_exreg_unregister(SSBOOT_EXREG_USER,
						PFN_DOWN(phys_addr),
						PFN_DOWN(len));
		} else {
			/* register extra region */
			ssboot_exreg_register(SSBOOT_EXREG_USER | attr,
					      PFN_DOWN(phys_addr),
					      PFN_DOWN(len));
		}
	}

 free:	/* free iterator */
	kfree(ops->data);

	return 0;
}

/*
 * region/normal
 */
static int
region_normal_open(struct inode *inode, struct file *file)
{
	return region_open(inode, file);
}

static void*
region_normal_start(struct seq_file *m, loff_t *pos)
{
	return region_start(m, pos, SSBOOT_EXREG_NORMAL);
}

static void*
region_normal_next(struct seq_file *m, void *v, loff_t *pos)
{
	return region_next(m, v, pos, SSBOOT_EXREG_NORMAL);
}

static int
region_normal_show(struct seq_file *m, void *v)
{
	return region_show(m, v);
}

static int
region_normal_release(struct inode *inode, struct file *file)
{
	return region_release(inode, file, SSBOOT_EXREG_NORMAL, 0);
}
ssboot_seq_proc(region_normal, SSBOOT_PROC_RDWR, PAGE_SIZE);

/*
 * region/critical
 */
static int
region_critical_open(struct inode *inode, struct file *file)
{
	return region_open(inode, file);
}

static void*
region_critical_start(struct seq_file *m, loff_t *pos)
{
	return region_start(m, pos, SSBOOT_EXREG_CRITICAL);
}

static void*
region_critical_next(struct seq_file *m, void *v, loff_t *pos)
{
	return region_next(m, v, pos, SSBOOT_EXREG_CRITICAL);
}

static int
region_critical_show(struct seq_file *m, void *v)
{
	return region_show(m, v);
}

static int
region_critical_release(struct inode *inode, struct file *file)
{
	return region_release(inode, file, SSBOOT_EXREG_CRITICAL, 0);
}
ssboot_seq_proc(region_critical, SSBOOT_PROC_RDWR, PAGE_SIZE);

/*
 * region/discard
 */
static int
region_discard_open(struct inode *inode, struct file *file)
{
	return region_open(inode, file);
}

static void*
region_discard_start(struct seq_file *m, loff_t *pos)
{
	return region_start(m, pos, SSBOOT_EXREG_DISCARD);
}

static void*
region_discard_next(struct seq_file *m, void *v, loff_t *pos)
{
	return region_next(m, v, pos, SSBOOT_EXREG_DISCARD);
}

static int
region_discard_show(struct seq_file *m, void *v)
{
	return region_show(m, v);
}

static int
region_discard_release(struct inode *inode, struct file *file)
{
	return region_release(inode, file, SSBOOT_EXREG_DISCARD, 0);
}
ssboot_seq_proc(region_discard, SSBOOT_PROC_RDWR, PAGE_SIZE);

/*
 * region/work
 */
static int
region_work_open(struct inode *inode, struct file *file)
{
	return region_open(inode, file);
}

static void*
region_work_start(struct seq_file *m, loff_t *pos)
{
	return region_start(m, pos, SSBOOT_EXREG_WORK);
}

static void*
region_work_next(struct seq_file *m, void *v, loff_t *pos)
{
	return region_next(m, v, pos, SSBOOT_EXREG_WORK);
}

static int
region_work_show(struct seq_file *m, void *v)
{
	return region_show(m, v);
}

static int
region_work_release(struct inode *inode, struct file *file)
{
	return region_release(inode, file, SSBOOT_EXREG_WORK, 0);
}
ssboot_seq_proc(region_work, SSBOOT_PROC_RDWR, PAGE_SIZE);

/*
 * region/remove
 */
static int
region_remove_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
region_remove_show(struct seq_file *m, void *v)
{
	return 0;
}

static int
region_remove_release(struct inode *inode, struct file *file)
{
	return region_release(inode, file, 0, 1);
}
ssboot_single_proc(region_remove, SSBOOT_PROC_WRONLY, PAGE_SIZE);

/*
 * region/init
 */
static int
region_init_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int
region_init_show(struct seq_file *m, void *v)
{
	return 0;
}

static int
region_init_release(struct inode *inode, struct file *file)
{
	ssboot_proc_ops_t *ops = file_to_ops(file);
	unsigned long pfn, num;
	int ret;

	/* check if data exists */
	if (ops->write_len == 0) {
		return 0;
	}

	/* unregister all user entries */
	for (;;) {
		ssboot_exreg_find_first(SSBOOT_EXREG_USER, &pfn, &num);
		if (pfn == SSBOOT_PFN_NONE) {
			break;
		}
		ret = ssboot_exreg_unregister(SSBOOT_EXREG_USER, pfn, num);
		if (ret < 0) {
			BUG();
		}
	}
	return 0;
}
ssboot_single_proc(region_init, SSBOOT_PROC_WRONLY, PAGE_SIZE);

/*
 * Functions called from core
 */
static struct file_operations wrapper_ops = {
	.open		= proc_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.write		= proc_write,
	.release	= proc_release,
};

static ssboot_proc_list_t top_proc[] = {
	{ "mode",	&mode_ops		},
	{ "operation",	&operation_ops		},
	{ "section",	&section_ops		},
};

static ssboot_proc_list_t region_proc[] = {
	{ "normal",	&region_normal_ops	},
	{ "critical",	&region_critical_ops	},
	{ "discard",	&region_discard_ops	},
	{ "work",	&region_work_ops	},
	{ "remove",	&region_remove_ops	},
	{ "init",	&region_init_ops	},
};

static int
create_entry(const char *name, ssboot_proc_ops_t *ops,
	     struct proc_dir_entry *parent)
{
	struct proc_dir_entry *entry;

	entry = create_proc_entry(name, ops->mode, parent);
	if (entry == NULL) {
		return -EINVAL;
	}
	entry->proc_fops = &wrapper_ops;
	entry->data = (void*)ops;

	return 0;
}

int
ssboot_proc_create(sbi_writer_t *writer)
{
	int i, ret;

	/* create top directory */
	top_entry = proc_mkdir("ssboot", &proc_root);
	if (top_entry == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	/* create entries under top directory */
	for (i = 0; i < num_proc(top_proc); i++) {
		ret = create_entry(top_proc[i].name, top_proc[i].ops,
				   top_entry);
		if (ret < 0) {
			goto err;
		}
	}

	/* create 'sbiname' entry under top directory */
	if (writer->default_sbiname != NULL) {
		ret = create_entry("sbiname", &sbiname_ops, top_entry);
		if (ret < 0) {
			goto err;
		}
	}

	/* create 'compress' entry under top directory */
	if (writer->capability & SBI_WRITER_COMPRESS) {
		ret = create_entry("compress", &compress_ops, top_entry);
		if (ret < 0) {
			goto err;
		}
	}

	/* create 'encrypt' entry under top directory */
	if (writer->capability & SBI_WRITER_ENCRYPT) {
		ret = create_entry("encrypt", &encrypt_ops, top_entry);
		if (ret < 0) {
			goto err;
		}
	}

	/* create 'region' directory under top directory */
	region_entry = proc_mkdir("region", top_entry);
	if (region_entry == NULL) {
		ret = -EINVAL;
		goto err;
	}

	/* create entries under 'region' directory */
	for (i = 0; i < num_proc(region_proc); i++) {
		ret = create_entry(region_proc[i].name, region_proc[i].ops,
				   region_entry);
		if (ret < 0) {
			goto err;
		}
	}
	return 0;
 err:
	ssboot_proc_remove(writer);
	return ret;
}

void
ssboot_proc_remove(sbi_writer_t *writer)
{
	int i;

	/* remove entries under 'region' directory */
	for (i = 0; i < num_proc(region_proc); i++) {
		remove_proc_entry(region_proc[i].name, region_entry);
	}

	/* remove 'region' directory */
	remove_proc_entry("region", top_entry);

	/* remove 'sbiname' entry */
	if (writer->default_sbiname != NULL) {
		remove_proc_entry("sbiname", top_entry);
	}

	/* remove 'compress' entry */
	if (writer->capability & SBI_WRITER_COMPRESS) {
		remove_proc_entry("compress", top_entry);
	}

	/* remove 'encrypt' entry */
	if (writer->capability & SBI_WRITER_ENCRYPT) {
		remove_proc_entry("encrypt", top_entry);
	}

	/* remove entries under top directory */
	for (i = 0; i < num_proc(top_proc); i++) {
		remove_proc_entry(top_proc[i].name, top_entry);
	}

	/* remove top directory */
	remove_proc_entry("ssboot", &proc_root);
}
