/* 2008-04-16: File added and changed by Sony Corporation */
/*
 * drivers/base/section_group.c - section group support
 */

#include <linux/sysdev.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/topology.h>
#include <linux/capability.h>
#include <linux/device.h>
#include <linux/memory.h>
#include <linux/kobject.h>
#include <linux/memory_hotplug.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/mempolicy.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
#include <asm/setup.h>

/*
 * Initialize the sysfs support for section groups...
 */

#define SEC_GRP_CLASS_NAME	"section_group"

struct sysdev_class sec_grp_sysdev_class = {
	set_kset_name(SEC_GRP_CLASS_NAME),
};


static const char *sec_grp_uevent_name(struct kset *kset, struct kobject *kobj)
{
	return SEC_GRP_CLASS_NAME;
}

static int sec_grp_uevent(struct kset *kset, struct kobject *kobj, char **envp,
			int num_envp, char *buffer, int buffer_size)
{
	int retval = 0;
	return retval;
}

static struct kset_uevent_ops sec_grp_uevent_ops = {
	.name		= sec_grp_uevent_name,
	.uevent		= sec_grp_uevent,
};

int __init sec_grp_dev_init(void)
{
	int ret;

	sec_grp_sysdev_class.kset.uevent_ops = &sec_grp_uevent_ops;
	ret = sysdev_class_register(&sec_grp_sysdev_class);

	if (ret)
		printk(KERN_ERR "%s() failed: %d\n", __FUNCTION__, ret);
	return ret;
}

static struct section_group sg = {0, };

static ssize_t show_sec_grp_start(struct sys_device *dev, char *buf)
{
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);
	return sprintf(buf, "0x%08lx\n", sg->start);
}

static ssize_t show_sec_grp_size(struct sys_device *dev, char *buf)
{
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);
	return sprintf(buf, "0x%08lx\n", sg->size);
}

static ssize_t show_sec_grp_nid(struct sys_device *dev, char *buf)
{
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);
	return sprintf(buf, "%d\n", sg->nid);
}

static ssize_t show_sec_grp_name(struct sys_device *dev, char *buf)
{
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);
	return sprintf(buf, "%s\n", sg->name);
}

extern struct memory_block *find_memory_block(struct mem_section *section);
extern int memory_block_change_state(struct memory_block *mem,
				     unsigned long to_state, unsigned long from_state_req);

/* Change state of all the sections in sec_grp.
 * state == SEC_GRP_ONLINE: online
 * state == SEC_GRP_OFFLINE: offline
 */
static int change_sec_grp_state(struct sec_grp *sg, int state)
{
	int ret = 0, old_state, to_state;

	old_state = sg->state;
	if (old_state == state) {
		printk(KERN_WARNING "memplug: section group (nid: %u, size: %lx, start: %lx) already %s\n",
		       sg->nid, sg->size, sg->start, (state == SEC_GRP_OFFLINE) ? "offlined" : "onlined");
		return -EINVAL;
	}
	if (state == SEC_GRP_ONLINE){
		to_state = SEC_GRP_ONLINE;
		ret = online_pages((sg->start) >> PAGE_SHIFT,
				   (sg->size) >> PAGE_SHIFT);
	}
	if (state == SEC_GRP_OFFLINE) {
		to_state = SEC_GRP_OFFLINE;
		sg->state = SEC_GRP_GOING_OFFLINE;
		ret = remove_memory(sg->start, sg->size);
	}
	if (ret) {
		sg->state = old_state;
		printk("section group %s %s failed.\n",
		       sg->name, (to_state==SEC_GRP_ONLINE) ? "online" : "offline" );
	}
	else {
		sg->state = to_state;
		printk("section group %s %s succeeded.\n",
		       sg->name, (to_state==SEC_GRP_ONLINE) ? "online" : "offline" );
	}

	return ret;
}


static ssize_t show_sec_grp_state(struct sys_device *dev, char *buf)
{
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);
	ssize_t len = 0;

	switch (sg->state) {
		case SEC_GRP_ONLINE:
			len = sprintf(buf, "online\n");
			break;
		case SEC_GRP_OFFLINE:
			len = sprintf(buf, "offline\n");
			break;
		case SEC_GRP_GOING_OFFLINE:
			len = sprintf(buf, "going-offline\n");
			break;
		default:
			len = sprintf(buf, "ERROR-UNKNOWN-%d\n",
					sg->state);
			WARN_ON(1);
			break;
	}

	return len;
}
static ssize_t
store_sec_grp_state(struct sys_device *dev, const char *buf, size_t count)
{
	int ret = 0;
	struct sec_grp *sg =
		container_of(dev, struct sec_grp, sysdev);

	if (!strncmp(buf, "online", min((int)count, 6)))
		ret = change_sec_grp_state(sg, SEC_GRP_ONLINE);
	else if(!strncmp(buf, "offline", min((int)count, 7)))
		ret = change_sec_grp_state(sg, SEC_GRP_OFFLINE);
	else {
		printk(KERN_WARNING "secgrp: parameter format incorrect.\n");
	}

	if (ret)
		return ret;
	return count;
}

static SYSDEV_ATTR(start, 0444, show_sec_grp_start, NULL);
static SYSDEV_ATTR(size, 0444, show_sec_grp_size, NULL);
static SYSDEV_ATTR(nid, 0444, show_sec_grp_nid, NULL);
static SYSDEV_ATTR(name, 0444, show_sec_grp_name, NULL);
static SYSDEV_ATTR(state, 0644, show_sec_grp_state, store_sec_grp_state);

#define sec_grp_create_simple_file(sec_grp, attr_name)	\
	sysdev_create_file(&sec_grp->sysdev, &attr_##attr_name)
extern struct sysdev_class memory_sysdev_class;
void add_sec_grp(void)
{
	int i, err;
	struct sec_grp *sec_grp = NULL;

	for (i = 0; i < sg.group_nr; i++) {
		if (sg.sec_grp[i].nid >= MAX_NUMNODES) {
			panic("section group %s's node id >= MAX_NUMNODES(%d). Stop booting.\n",
			      sg.sec_grp[i].name, MAX_NUMNODES);
		}

 		if (-EEXIST == add_memory(sg.sec_grp[i].nid, sg.sec_grp[i].start, sg.sec_grp[i].size)) {
 			panic("section group %s has memory already onlined. Stop booting.\n",
			      sg.sec_grp[i].name);
 		}

		sec_grp = &sg.sec_grp[i];

		sec_grp -> sysdev.cls = &sec_grp_sysdev_class;
		sec_grp -> sysdev.id = i;
		err = sysdev_register(&sec_grp->sysdev);
		if (!err)
			err = sec_grp_create_simple_file(sec_grp, size);
		if (!err)
			err = sec_grp_create_simple_file(sec_grp, start);
		if (!err)
			err = sec_grp_create_simple_file(sec_grp, nid);
		if (!err)
			err = sec_grp_create_simple_file(sec_grp, name);
		if (!err)
			err = sec_grp_create_simple_file(sec_grp, state);

		if (!err)
			err = sysfs_create_link(&memory_sysdev_class.kset.kobj,
					&sec_grp->sysdev.kobj, sec_grp->name);
		if (err)
			printk(KERN_ERR "symbol link for section group failed.\n");

		if (sec_grp -> init_state == SEC_GRP_ONLINE)
			change_sec_grp_state(sec_grp, SEC_GRP_ONLINE);

		printk(KERN_INFO "memplug: section group (id: %u, size: %lx, start: %lx)added\n",
		       sg.sec_grp[i].nid, sg.sec_grp[i].size, sg.sec_grp[i].start);
	}
}

static int __init cmdline_parse_sec_grp(char *p)
{
	int i;
 	unsigned long size;
	if (sg.group_nr >= MAX_SECTION_GROUP_NR) {
		panic("secgrp=%s: number of secgrp exceeds MAX_SECTION_GROUP_NR(%d). Stop booting.\n",
		      p, MAX_SECTION_GROUP_NR);
	}
	sg.sec_grp[sg.group_nr].state = SEC_GRP_OFFLINE;

	size = memparse(p, &p) & ~((1 << SECTION_SIZE_BITS) -1);
  	if (size == 0) {
  		panic("secgrp=%s: size too small. Stop booting.\n", p);
  	}
  	sg.sec_grp[sg.group_nr].size = size;

	if (*p == '@')
		sg.sec_grp[sg.group_nr].start = memparse(p + 1, &p) &
			~((1 << SECTION_SIZE_BITS) -1);
	else
		goto L1;
	if (*p == '@')
		p ++;
	else
		goto L1;

	sg.sec_grp[sg.group_nr].nid = simple_strtoul(p, &p, 0);

	if (*p == ':') {
		p ++;
		if ( 0 == strncmp(p, "online", i = strlen("online")))
			sg.sec_grp[sg.group_nr].init_state = SEC_GRP_ONLINE;
		else if (0 == strncmp(p, "offline", i = strlen("offline")))
			sg.sec_grp[sg.group_nr].init_state = SEC_GRP_OFFLINE;
		else
			goto L1;
		p += i;
	}
	else
		goto L1;
	if (*p == ':') {
		p ++;
		i = 0;
		while (*p != ' ' && *p != '\0') {
			sg.sec_grp[sg.group_nr].name[i++] = *p++;
		}

	}
	else if (*p == ' ' || *p == '\0') {
		/* name is not a must. If no name is specified,
		   section_group%d is used. */
		sprintf(sg.sec_grp[sg.group_nr].name,
			"section_group%ld",
			sg.group_nr);

	}
	else
		goto L1;

	sg.group_nr ++;
	return 1;
L1:
	printk(KERN_WARNING "secgrp: parameter format incorrect.\n");
	return 1;
}

__setup("secgrp=", cmdline_parse_sec_grp);

int is_nid_in_sec_grp(unsigned long nid)
{
	int i;
	for (i = 0; i < sg.group_nr; i++) {
		if (sg.sec_grp[i].nid == nid)
			return 1;
	}
	return 0;
}

int __init is_range_overlap_with_secgrp(unsigned long start, unsigned long end)
{
	int i;
	for (i = 0; i < sg.group_nr; i++) {
		if (end <= sg.sec_grp[i].start ||
		    start >= (sg.sec_grp[i].start + sg.sec_grp[i].size))
			continue;
		else
			return 1;
	}
	return 0;
}

/* Return whether a node is specified by secgrp= cmd line,
 * no matter it is online or offline.
 */
int is_node_specified_by_secgrp(int nid)
{
	int i;
	for (i = 0; i < sg.group_nr; i++) {
		if (sg.sec_grp[i].start !=0 &&
		    sg.sec_grp[i].size != 0 &&
		    sg.sec_grp[i].nid == nid)
			return 1;
	}
	return 0;
}

#ifdef CONFIG_SNSC_USE_NODE_ORDER
static void count_pages(unsigned long pfn, unsigned long num, unsigned long *vm_stat)
{
	unsigned long end = pfn + num;
	memset(vm_stat, 0, sizeof(unsigned int) * NR_VM_ZONE_STAT_ITEMS);

	for (; pfn < end; pfn++) {
		struct page *page = pfn_to_page(pfn);
		struct address_space *mapping;
		lock_page(page);
		mapping = page_mapping(page);
		if (PageReserved(page)) {
			/* ignore reserved page.  nothing to do */
		}
		else if (page_count(page) == 0) {
			vm_stat[NR_FREE_PAGES] ++;
		} else if (page->mapping == 0) { /* swap cache */
			vm_stat[NR_FILE_MAPPED] ++;
		} else if ((unsigned long)(page->mapping) & PAGE_MAPPING_ANON) { /* anonymous page */
			vm_stat[NR_ANON_PAGES] ++;
		} else if (!((unsigned long)(page->mapping) & PAGE_MAPPING_ANON)) { /* mapped */
			vm_stat[NR_FILE_MAPPED] ++;
			if (page->mapping->open_type == OPENTYPE_RW)
				vm_stat[NR_NODEORDER_RW] ++;
			else
				vm_stat[NR_NODEORDER_RO] ++;
		}
		unlock_page(page);
	}
}

ssize_t print_secgrp_stat(struct class *class, char *buf)
{
	int i;
	unsigned long vm_stat[NR_VM_ZONE_STAT_ITEMS];
	char *ptr = buf;
	char *end = buf + PAGE_SIZE; /* see fs/sysfs/file.c: fill_read_buffer() */
#ifdef CONFIG_ARM
	extern struct meminfo meminfo;
	for (i = 0; i < meminfo.nr_banks; i++) {
		ptr += scnprintf(ptr, end - ptr, "mem%d\n", i);
		ptr += scnprintf(ptr, end - ptr, "addr: 0x%08lx-0x%08lx\n",
				 meminfo.bank[i].start,
				 meminfo.bank[i].start + meminfo.bank[i].size);
		ptr += scnprintf(ptr, end - ptr, "node: %d\n",
				 meminfo.bank[i].node);

		count_pages(meminfo.bank[i].start >> PAGE_SHIFT,
			    meminfo.bank[i].size >> PAGE_SHIFT,
			    vm_stat);

		ptr += scnprintf(ptr, end - ptr, "total: %ld KB\n",
				 meminfo.bank[i].size >> 10);
		ptr += scnprintf(ptr, end - ptr, "anonymous: %ld KB\n",
				 vm_stat[NR_ANON_PAGES] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "file_mapped: %ld KB\n",
				 vm_stat[NR_FILE_MAPPED] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "ro: %ld KB\n",
				 vm_stat[NR_NODEORDER_RO] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "rw: %ld KB\n\n",
				 vm_stat[NR_NODEORDER_RW] << (PAGE_SHIFT - 10));
	}
#endif
	for (i = 0; i < sg.group_nr; i++) {
		ptr += scnprintf(ptr, end - ptr, "name: %s\n",
				 sg.sec_grp[i].name);
		ptr += scnprintf(ptr, end - ptr, "addr: 0x%08lx-0x%08lx\n",
				 sg.sec_grp[i].start,
				 sg.sec_grp[i].start + sg.sec_grp[i].size);
		ptr += scnprintf(ptr, end - ptr, "node: %d\n",
				 sg.sec_grp[i].nid);

		count_pages(sg.sec_grp[i].start >> PAGE_SHIFT,
			    sg.sec_grp[i].size >> PAGE_SHIFT,
			    vm_stat);

		ptr += scnprintf(ptr, end - ptr, "total: %ld KB\n",
				 sg.sec_grp[i].size >> 10);
		ptr += scnprintf(ptr, end - ptr, "anonymous: %ld KB\n",
				 vm_stat[NR_ANON_PAGES] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "file_mapped: %ld KB\n",
				 vm_stat[NR_FILE_MAPPED] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "ro: %ld KB\n",
				 vm_stat[NR_NODEORDER_RO] << (PAGE_SHIFT - 10));
		ptr += scnprintf(ptr, end - ptr, "rw: %ld KB\n\n",
				 vm_stat[NR_NODEORDER_RW] << (PAGE_SHIFT - 10));
	}
	return ptr - buf;
}
#endif
