#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/kmemprof.h>
#include <asm/uaccess.h>
#include "cgprof.h"
#include "region.h"

#define MAX_REGIONS	128

static int num_regions;
static unsigned int region_offset[MAX_REGIONS];
static unsigned int region_size[MAX_REGIONS];
static unsigned int region_shift[MAX_REGIONS];

MODULE_PARM(region_offset, "1-" __MODULE_STRING(MAX_REGIONS) "i");
MODULE_PARM(region_size, "1-" __MODULE_STRING(MAX_REGIONS) "i");
MODULE_PARM(region_shift, "1-" __MODULE_STRING(MAX_REGIONS) "i");

struct region kmemprof_region[MAX_REGIONS];

static unsigned int small_bound = 64;
static unsigned int medium_bound = 512;
static unsigned int large_bound = 4096;

MODULE_PARM(small_bound, "i");
MODULE_PARM(medium_bound, "i");
MODULE_PARM(large_bound, "i");

static int region_open (struct inode * inode, struct file * file)
{
	struct proc_dir_entry * de = inode->u.generic_ip;
	struct region * region = de->data;

	file->private_data = region;

	return 0;
}

static ssize_t region_read (struct file * file, char * buf, size_t count,
		loff_t * pos)
{
	struct region * region = file->private_data;
	unsigned long p = *pos;
	size_t rest = count;
	size_t header_end, table_end, bucket_end;
	size_t read;

	unsigned int flatprof_header[] = {
		region->offset,
		region->size,
		region->prof_len,
		region->prof_shift,
		small_bound,
		medium_bound,
		large_bound
	};

	/*  copy header	*/

	header_end = sizeof(flatprof_header);
	if (p < header_end && rest > 0) {
		read = min(header_end - (size_t)p, rest);
		copy_to_user(buf, (char *)flatprof_header+p, read);

		buf += read;
		p += read;
		rest -= read;
	}

	/*  copy region->prof_table[0...prof_len-1]	*/

	table_end = header_end + region->prof_len * sizeof(unsigned int);
	if (p < table_end && rest > 0) {
		read = min(table_end - (size_t)p, rest);
		copy_to_user(buf,
			(char *)region->prof_table + p - header_end, read);

		buf += read;
		p += read;
		rest -= read;
	}

	/*  copy region->bucket[0...bucket_head-1]	*/

	bucket_end = table_end + region->bucket_len * sizeof(struct bucket);
	if (p < bucket_end && rest > 0) {
		read = min(bucket_end - (size_t)p, rest);
		copy_to_user(buf,
			(char *)region->bucket + p - table_end, read);

		buf += read;
		p += read;
		rest -= read;
	}

	*pos = p;

	return count - rest;
}

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

static struct file_operations region_fops = {
	.open = region_open,
	.read = region_read,
	.release = region_release
};

static int regions_fixup()
{
	int i;
	unsigned long max = 0;

	for (i = 0; i < MAX_REGIONS && region_size[i]; i++) {
		struct region * region = &kmemprof_region[i];
		unsigned long offset = region_offset[i];
		unsigned long size = region_size[i];
		unsigned long shift = region_shift[i];

		if ((offset + size - 1 < offset)	/* overflow */
				|| (offset < max))	/* overlap */
			return -EINVAL;

		max = offset + size;
	}
	num_regions = i;

	return 0;
}

static int bounds_check()
{
	if (small_bound > medium_bound || medium_bound > large_bound) {
		return -EINVAL;
	}

	return 0;
}

#define PROC_REGION_FNAME_FMT	"%08x-%08x"

static struct proc_dir_entry * kmemprof_cgprof_dir;

int kmemprof_cgprof_init ()
{
	int i, ret;
	char name[100];
	struct proc_dir_entry * entry;

	if ((ret = regions_fixup()) < 0 || (ret = bounds_check()) < 0)
		return ret;

	if (!(kmemprof_cgprof_dir = proc_mkdir("kmemprof-cgprof", NULL)))
		return -ENOMEM;
	kmemprof_cgprof_dir->owner = THIS_MODULE;

	for (i = 0; i < MAX_REGIONS && region_size[i]; i++) {
		struct region * region = &kmemprof_region[i];
		unsigned int offset = region_offset[i];
		unsigned int size = region_size[i];
		unsigned int shift = region_shift[i];

		if ((ret = region_create(region, offset, size, shift)) < 0) {
			goto out;
		}
		sprintf(name, PROC_REGION_FNAME_FMT, offset, offset + size - 1);
		if (!(entry = create_proc_entry
				(name, 0, kmemprof_cgprof_dir))) {
			ret = -ENOMEM;
			i++;
			goto out;
		}
		entry->data = region;
		entry->proc_fops = &region_fops;
	}

	return 0;
out:
	while (i-- > 0) {
		struct region * region = &kmemprof_region[i];

		sprintf(name, PROC_REGION_FNAME_FMT,
				region->offset, region->size);
		remove_proc_entry(name, kmemprof_cgprof_dir);
		region_remove(region);
	}
	remove_proc_entry("kmemprof-cgprof", NULL);

	return ret;
}

void kmemprof_cgprof_destroy ()
{
	int i;
	char name[100];

	for (i = 0; i < num_regions; i++) {
		struct region * region = &kmemprof_region[i];

		sprintf(name, PROC_REGION_FNAME_FMT,
			region->offset, region->offset + region->size - 1);
		remove_proc_entry(name, kmemprof_cgprof_dir);
		region_remove(region);
	}
	remove_proc_entry("kmemprof-cgprof", NULL);
}

static inline enum kmemprof_objclass which_objclass(unsigned int size)
{
	if (size <= small_bound) return SMALL_OBJ;
	else if (size <= medium_bound) return MEDIUM_OBJ;
	else if (size <= large_bound) return LARGE_OBJ;
	else return XLARGE_OBJ;
}

#ifdef __i386__
static unsigned int frame_traverse(unsigned int * frame)
{
	unsigned int next, pc = 0;

	next = *(unsigned int *) (*frame);
	if ((*frame) < next && next <= (unsigned int)current + THREAD_SIZE) {
		pc = *((unsigned int *) (*frame) + 1);
		*frame = next;
	}

	return pc;
}
#else
static unsigned int frame_traverse(unsigned int * frame)
{
	return 0;
}
#endif

int kmemprof_cgprof_populate_arcs(enum kmemprof_event_type type,
		unsigned int size, unsigned int frame,
		unsigned int eip, unsigned int max_traversal)
{
	int i, ret = 0;
	enum kmemprof_objclass class = which_objclass(size);
	unsigned int to, from;

	
	for (to = ((type == KMEM_ALLOC) ? 0 : 1), from = eip;
		max_traversal-- > 0 && from != 0;
		to = from, from = frame_traverse(&frame)) {

		for (i = 0; i < num_regions; i++) {
			struct region * region = &kmemprof_region[i];

			if (ret = region_populate_arc(region, type, class,
						size, from, to))
				break;
		}
	}

	return ret;
}

