/************************************************************
 *
 * COPYRIGHT (C) HITACHI,LTD. 2002 ALL RIGHTS RESERVED.
 * WRITTEN BY HITACHI SYSTEMS DEVELOPMENT LABORATORY,
 *            HITACHI CENTRAL RESEARCH LABORATORY.
 *
 * Created by K.SERIZAWA <serizawa@sdl.hitachi.co.jp>
 *
 ************************************************************/

#include<linux/version.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include<linux/string.h>	/* for memset() */
#include <linux/sched.h>
#include <linux/config.h>
#include<linux/lkst_private.h>

	/* Size of lkst_event_record_ext must be same as 
	   LKST_SIZEOF_LKST_EVENT_RECORD defined in <linux/lkst_buffer.h>, 
	   It must be power of 2.       */
	/* This value will be verified in initialization of 
	   LKST in DEBUG mode. */
struct lkst_event_record_ext {
	int log_recid;
	int log_event_type;
	int log_recid_org;
	int log_event_type_ext;

	lkst_arg_t log_arg1;
	lkst_arg_t log_arg2;
	lkst_arg_t log_arg3;
	lkst_arg_t log_arg4;
	lkst_arg_t log_arg5;
	lkst_arg_t log_arg6;
};

static lkst_maskset_id maskset_id_new  = LKST_MASKSET_ID_VOID;
static lkst_maskset_id maskset_id_orig = LKST_MASKSET_ID_VOID;
static struct lkst_maskset_param	maskset_param;
static struct lkst_maskset_body 	body;

static int evhandler_id = LKST_EVHANDLER_ID_VOID;
static int maskset_id_base = LKST_MASKSET_ID_VOID;

static int	lkst_mod_init(void);
static void	lkst_mod_cleanup(void);

module_init(lkst_mod_init);
module_exit(lkst_mod_cleanup);

MODULE_AUTHOR("K. Serizawa <serizawa@sdl.hitachi.co.jp>");
MODULE_DESCRIPTION("LKST module to add recording stack trace at each trace point.");
MODULE_LICENSE("GPL");
MODULE_PARM(maskset_id_base, "i");
MODULE_PARM(evhandler_id, "i");
MODULE_PARM_DESC(maskset_id_base, "ID of a maskset used as a template.");
MODULE_PARM_DESC(evhandler_id, "id of event-hanlder to regist.");

void lkst_evhandler_stacktrace(void *phookrec, 
			       int event_type,
				lkst_arg_t arg1,
				lkst_arg_t arg2,
				lkst_arg_t arg3,
				lkst_arg_t arg4)
{
	struct lkst_event_buffer *buffer;
	struct lkst_event_record * event_entry_p;
	unsigned long flags;
	int pos_st, pos_ed;
	int baseid;
	lkst_tsc_t evtime;

	if (! LKST_CURRENT_BUFFER_LOAD(buffer)) return ;

	local_irq_save(flags);

	/* get a entry position */ {
		baseid = buffer->baseid++;
		pos_st = baseid - buffer->write_offset;
	}

	/* adjust the entry position */
	if ( pos_st >= buffer->size )
		pos_st %= buffer->size;

	evtime = lkst_evhandlerprim_mc();
	/* logging */ {
		event_entry_p = &(LKST_BUFFER_BODY(buffer)[pos_st]);
		event_entry_p->log_recid = baseid;
		event_entry_p->log_time = evtime;
		event_entry_p->log_event_type = event_type;
		event_entry_p->log_pid = current->pid; /* current pid */ ;
		event_entry_p->log_arg1 = arg1;
		event_entry_p->log_arg2 = arg2;
		event_entry_p->log_arg3 = arg3;
		event_entry_p->log_arg4 = arg4;
	}


	/* log stacks */{
		struct task_struct *tsk = (struct task_struct *)current;
		unsigned long esp = tsk->thread.esp;
		int i;
		unsigned long addr;
		unsigned long * retaddr_save;
		unsigned long * stack = (unsigned long *)esp;
		
		/* get a entry position */ {
			baseid = buffer->baseid++;
			pos_ed = baseid - buffer->write_offset;
		}
		/* adjust the entry position */
		if ( pos_ed >= buffer->size )
			pos_ed %= buffer->size;
		
		event_entry_p = &(LKST_BUFFER_BODY(buffer)[pos_ed]);

		event_entry_p->log_recid = baseid;
		event_entry_p->log_time = ++evtime;
		event_entry_p->log_event_type = LKST_ETYPE_LKST_EXTENDE;
		event_entry_p->log_pid = current->pid; /* current pid */ ;

		// from trap.c::show_trace_task()

		retaddr_save = (unsigned long *)(&(event_entry_p->log_arg1));

			/* TODO: trace stack FRAMES...*/
		stack = (unsigned long*)&stack;

		i = 1;
		while (((long) stack & (THREAD_SIZE-1)) != 0) {
			if (i > 8) break;
			addr = *stack++;
			if (lkst_evhandlerprim_kernel_text_address(addr)) {
				*retaddr_save++ = addr;
				i++;
			}
		}
		for (; i <=8; i++) {
			*retaddr_save++ = 0xffffffff;
		}
	}

	/* Check buffer wrapping. */
	if ( pos_st == 0 || pos_ed < pos_st ) {
		buffer->write_offset += buffer->size;
		/* Wrapped. */
		/* Process buffer-over-flow event. */
		lkst_entry_log(NULL, 
			       LKST_ETYPE_LKST_BUFF_OVFLOW,
			       smp_processor_id(),
			       buffer->next,
			       event_type,
			       LKST_ARGP(buffer));
	}
	local_irq_restore(flags);

}

int lkst_evhandler_stacktrace_ctrl(void *buf, size_t bufsize)
{
  //TODO: change depth of stack trace.
	return (0);
}

static int lkst_mod_init()
{
	int	retval;
	int	i;
	lkst_maskset_id id;

#if DEBUG
	if (LKST_SIZEOF_LKST_EVENT_RECORD == sizeof(struct lkst_event_record_ext)) {
		/* Verification of LKST_SIZEOF_LKST_EVENT_RECORD is failed. */
		printk(KERN_ERR "LKST error(init): sizeof(struct lkst_event_record) is not equal to LKST_SIZEOF_LKST_EVENT_RECORD.\n");
		return -1;
	}
#endif /* DEBUG */

	/* Register an event handler */
	retval = lkst_evhandler_register(evhandler_id,
					 "lkst_evhandler_stacktrace",
					 &lkst_evhandler_stacktrace,
					 &lkst_evhandler_stacktrace_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register event-handler function!\n");
		return retval;
	}
	evhandler_id = (lkst_evhandler_id) retval;

	/* Initialize Maskset */
	body.len = LKST_MASKSET_TABLE_LEN_MAX;
	
	/* This ID can be defined as a module parameter. */
	maskset_param.id = (lkst_maskset_id)maskset_id_base;
	maskset_param.maskset_size = LKST_MASKSET_SIZE(body.len);
	maskset_param.maskset = &body;

	/* copy from template. */
	if ((retval = lkst_maskset_read(&maskset_param)))
		goto mod_cleanup;

	for (i = 0; i < body.len; i++) {
		if (body.entry[i].id == LKST_EVHANDLER_ID_DEFAULT) {
			/* replace handler */
			body.entry[i].id = evhandler_id;
		}
	}
	strcpy(body.name, "maskset_stacktrace");
	
	/* Write new maskset */
	maskset_param.id = LKST_MASKSET_ID_VOID;
	if ((retval = lkst_maskset_write(&maskset_param))) {
		goto mod_cleanup;
	}
	maskset_id_new = maskset_param.id;

	/* Switch to the maskset. */
	id = maskset_id_new;
	if ((retval = lkst_maskset_xchg(&id))) {
		goto mod_cleanup;
	}
	maskset_id_orig = id;

	return 0;
mod_cleanup:
	lkst_mod_cleanup();
	return retval < 0 ? retval : -1;
}

static void lkst_mod_cleanup()
{
  		/* Switch to saved maskset. */
	if (maskset_id_orig != LKST_MASKSET_ID_VOID)
		lkst_maskset_xchg(&maskset_id_orig);

		/* deregister the event handler. */
	if (evhandler_id != LKST_EVHANDLER_ID_VOID)
		lkst_evhandler_unregister((lkst_evhandler_id)evhandler_id);

		/* Clean up maskset */
	if ( maskset_id_new != LKST_MASKSET_ID_VOID )
		lkst_maskset_delete(maskset_id_new);

	return;
}

/* END */
