/* $Id: lkst_mod_stacktrace.c,v 1.2 2002/03/27 17:36:28 serizawa Exp $ */
/* testtools/lkst_mod_stacktrace.c */

/************************************************************
 *
 * 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>
 *
 ************************************************************/

/* includes */
#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/lkst_private.h>

/* defines */

#define LKST_EVHANDLER_ID_STACKTRACE 0x81

/* typedef, structs */

	/* 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;
};

/* externals */

/* macros */

/* globals */

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

/* forward declarations */

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

/* module init/cleanup */

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_PARM(maskset_id_base, "i");
MODULE_PARM_DESC(maskset_id_base, "ID of a maskset used as a template.");
MODULE_LICENSE("GPL");

/* inline functions */

/* functions */

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;
	struct lkst_event_record_ext *event_entry_ext_p;
	int	log_recid_ext = 0;
	unsigned long flags;

	LKST_CLI(flags);

	buffer = lkst_evhandlerprim_getw_buffer();

	event_entry_p = lkst_evhandlerprim_entry_next(buffer, event_type);
	(struct lkst_event_record *)event_entry_ext_p = 
		lkst_evhandlerprim_entry_next(buffer, LKST_ETYPE_LKST_EXTENDE);

	if (event_entry_p)
		lkst_evhandlerprim_entry_log_l1(event_entry_p, lkst_evhandlerprim_recid_inc(), event_type, arg1, arg2, arg3, arg4);

	if (event_entry_ext_p) {
		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 *)(&(((struct lkst_event_record *)event_entry_ext_p)->log_arg1));
//TODO:			(unsigned long *)(&(event_entry_ext_p->log_arg1));
		unsigned long * stack = (unsigned long *)esp;

		log_recid_ext = lkst_evhandlerprim_recid_inc();

		event_entry_ext_p->log_event_type = LKST_ETYPE_LKST_EXTENDE;

		// from trap.c::show_trace_task()

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

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

	lkst_evhandlerprim_putw_buffer(buffer);

	LKST_STI(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;

#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 for panic dump. */
	retval = lkst_evhandler_register(LKST_EVHANDLER_ID_STACKTRACE,
					 "lkst_evhandler_stacktrace",
					 &lkst_evhandler_stacktrace,
					 &lkst_evhandler_stacktrace_ctrl);
	if (retval) {
		printk(KERN_ERR "cannot register event-handler function!\n");
		return (retval);
	}

	/* Initialize Maskset */
	body.len = LKST_MASKSET_TABLE_LEN_MAX;
				/* This ID can be defined as 
				   a module parameter. */
	maskset_param.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)))
		return(retval);

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

	/* Switch to the maskset. */
	maskset_id_orig = maskset_id_new;
	if ((retval = lkst_maskset_xchg(&maskset_id_orig))) {
		lkst_mod_cleanup();
		return(retval);
	}

	return (0);
}

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

		/* deregister the event handler. */
	lkst_evhandler_register((lkst_evhandler_id) LKST_EVHANDLER_ID_STACKTRACE,
					  "", NULL, NULL);

	/* Clean up maskset */
	lkst_maskset_delete(maskset_id_new);

	return;
}

/* END */
