#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/spinlock.h>
#include <linux/kmemprof.h>
#include "region.h"

#define NULL_BUCKET(region)	&(region)->bucket[0]

static inline
struct bucket * next_bucket(struct region * region, struct bucket * bucket)
{
	unsigned int next = bucket->chain.next;

#ifdef DEBUG
	BUG_ON(region->prof_len <= next);
	BUG_ON(bucket == NULL_BUCKET(region));
#endif

	return &region->bucket[next];
}

static inline
struct bucket * get_bucket(struct region * region, unsigned int index)
{
#ifdef DEBUG
	BUG_ON(region->prof_len <= index);
#endif
	return &region->bucket[region->prof_table[index]];
}

static inline
struct bucket * alloc_bucket(struct region * region, unsigned int index)
{
	unsigned int bucket_index, *mark;
	struct bucket * entry;

#ifdef DEBUG
	BUG_ON(region->prof_len <= index);
#endif
	if (region->bucket_len <= (bucket_index = region->bucket_head++))
		return NULL_BUCKET(region);


	mark = &region->prof_table[index];
	for (entry = get_bucket(region, index); entry != NULL_BUCKET(region);
		entry = next_bucket(region, entry)) {
		mark = &entry->chain.next;
	}
	*mark = bucket_index;

	return &region->bucket[bucket_index];
}

int region_populate_arc(struct region * region,
	enum kmemprof_event_type type, enum kmemprof_objclass class,
	unsigned int size, unsigned int from, unsigned int to)
{
	unsigned int index;
	unsigned long save_flags;
	struct bucket * entry;

	if (from < region->offset || region->offset + region->size -1 < from)
		return 0;

	index = (from - region->offset) >> region->prof_shift;
#ifdef DEBUG
	BUG_ON(region->prof_len <= index);
#endif
	spin_lock_irqsave(&region->lock, save_flags);
	for (entry = get_bucket(region, index); entry != NULL_BUCKET(region);
		entry = next_bucket(region, entry)) {
		if (entry->chain.address == to)
			goto out;
	}

	if ((entry = alloc_bucket(region, index)) == NULL_BUCKET(region)) {
		spin_unlock_irqrestore(&region->lock, save_flags);
		return -1;
	}
	entry->chain.address = to;

out:
	entry->per_evtype_amount[type].per_objclass_amount[class].count++;
	entry->per_evtype_amount[type].per_objclass_amount[class].bytes
		+= size;
	spin_unlock_irqrestore(&region->lock, save_flags);

	return 1;
}

#define BUCKET_RATIO	512

int region_create(struct region * region,
	unsigned int offset, unsigned int size, unsigned int prof_shift)
{
	int ret;

	if ((region->prof_len = size >> prof_shift) == 0)
		return -EINVAL;

	if (!(region->prof_table = vmalloc(region->prof_len * sizeof(unsigned int))))
		return -ENOMEM;
	memset(region->prof_table, 0, region->prof_len * sizeof(unsigned int));

	region->bucket_len = region->prof_len / BUCKET_RATIO;

	if (region->bucket_len > 2048)
		region->bucket_len = 2048;
	if (region->bucket_len < 128)
		region->bucket_len = 128;

	if (!(region->bucket = vmalloc(region->bucket_len * sizeof(struct bucket)))) {
		ret = -ENOMEM;
		goto out;
	}
	memset(region->bucket, 0, region->bucket_len * sizeof(struct bucket));
	region->bucket_head = 1;

	region->offset = offset;
	region->size = size;
	region->prof_shift = prof_shift;
	spin_lock_init(&region->lock);

	return 0;
out:
	vfree(region->prof_table);
	memset(region, 0, sizeof(*region));

	return ret;
}

void region_remove(struct region *region)
{
	printk("used: %d(%d)\n", region->bucket_head - 1, region->bucket_len);
	if (region->prof_table)
		vfree(region->prof_table);
	if (region->bucket)
		vfree(region->bucket);
	memset(region, 0, sizeof(*region));
}

