#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/kmemprof.h>
#include "logbuf.h"
#include "strtab.h"
#include "packet.h"

MODULE_LICENSE("GPL");

static int num_cpus = NR_CPUS;
MODULE_PARM(num_cpus, "i");

static void kmemprof_trace_packet_pack(struct kmemprof_trace_packet *pkt,
						struct kmemprof_event *ev)
{
	pkt->type = ev->type;
	pkt->flags = ev->flags;
	pkt->cache_name_id = kmemprof_strtab_store(kmem_cache_name(ev->cache));
	pkt->cache_size = kmem_cache_size(ev->cache);
	pkt->objp = ev->obj;

	pkt->jiffies = jiffies;
	pkt->eip = ev->eip;
	pkt->interrupt = in_interrupt();

	pkt->pid = current->pid;
	pkt->pgrp = current->pgrp;
	pkt->comm_id = kmemprof_strtab_store(current->comm);
}

int slab_notifier(struct notifier_block *self, unsigned long val,
			void *arg)
{
	struct kmemprof_event * event = (struct kmemprof_event *) arg;
	struct kmemprof_trace_packet packet;

	kmemprof_trace_packet_pack(&packet, event);
	kmemprof_logbuf_write (&packet, sizeof(packet));

	return NOTIFY_OK;
}

static struct notifier_block slab_nb = {
	.notifier_call = &slab_notifier,
	.priority = 0
};

struct proc_dir_entry * kmemprof_trace_dir;

static int __init mymodule_init(void)
{
	int ret;

	if (num_cpus < 1 || NR_CPUS < num_cpus) {
		return -EINVAL;
	}

	if (!(kmemprof_trace_dir = proc_mkdir("kmemprof-trace", NULL)))
		return -ENOMEM;

	if ((ret = kmemprof_logbuf_create(num_cpus)) < 0)
		goto out1;
	if ((ret = kmemprof_strtab_create(num_cpus)) < 0)
		goto out2;
	kmemprof_notifier_chain_register(&slab_nb);

	return 0;
out2:
	kmemprof_logbuf_remove();
out1:
	remove_proc_entry("kmemprof-trace", NULL);

	return ret;
}

static void __exit mymodule_exit(void)
{
	kmemprof_notifier_chain_unregister(&slab_nb);
	schedule();
	kmemprof_logbuf_remove();
	kmemprof_strtab_remove();
	remove_proc_entry("kmemprof-trace", NULL);
}

module_init(mymodule_init);
module_exit(mymodule_exit);

