/* $Id: lkst_mod_event_count.c,v 1.12 2002/08/07 03:10:58 ke-hata Exp $ */
/* addons/lkst_mod_event_count.c */

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

/*******************************************************
 * Known bugs :
 * -In SMP systems, count_max means not total num of 
 *    events but num of events on one CPU. So it seems
 *    that specified counter seems to be exceed count_max.
 *******************************************************/

#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>

	/* Define serial No. of events. */
#define LKST_ETYPE_DEF_ENUM_BEGIN					\
	typedef enum {

#define LKST_ETYPE_DEF(event_type, prio, hooktype, mnemonic, description, EVENT_NAME_STRING,\
            DISPLAY_FILTER, EVENT_ARG1, EVENT_ARG2, EVENT_ARG3, EVENT_ARG4 )	\
	LKST_ETYPE_SERIAL_##mnemonic,

#define LKST_ETYPE_DEF_ENUM_END						\
		LKST_ETYPE_SERIAL_MAX				\
	} lkst_event_type_serial;

#define	LKST_ETYPE_DEF_TOCLEAN

#include <linux/lkst_events.h>
	/* End of definition of serial No. of events. */


	/* Define buffer offsets for events. */
#define LKST_ETYPE_DEF_ENUM_BEGIN       \
	static const	short entry_index_offset [] =	{

#define LKST_ETYPE_DEF(event_type, prio, hooktype, mnemonic, description,EVENT_NAME_STRING,\
            DISPLAY_FILTER,EVENT_ARG1,EVENT_ARG2,EVENT_ARG3,EVENT_ARG4 )	\
        [(event_type)] = LKST_ETYPE_SERIAL_##mnemonic,

#define LKST_ETYPE_DEF_ENUM_END						\
        [0x100] = LKST_ETYPE_SERIAL_MAX+1,				\
        [0x102] = LKST_ETYPE_SERIAL_MAX+2				\
	};

#define	ENTRY_INDEX_OFFSET_MAX				(LKST_ETYPE_SERIAL_MAX+2+2)

#define	LKST_ETYPE_DEF_TOCLEAN

#include <linux/lkst_events.h>
	/* End of definition of buffer offsets for events. */

#define	EVHANDLER_NAME_COUNT "lkst_evhandler_event_count"
#define EVHANDLER_NAME_STOPPER	"lkst_evhandler_event_count_stopper"

static lkst_maskset_id maskset_id_new = LKST_MASKSET_ID_VOID;
static lkst_maskset_id maskset_id_orig = LKST_MASKSET_ID_VOID;
static lkst_maskset_id maskset_id_base = LKST_MASKSET_ID_VOID;
static struct lkst_maskset_param	maskset_param;
static struct lkst_maskset_body 	body;
static unsigned long	start, stop;

static int	count_max = 0x7ffffff0;
static int	evhandler_id = LKST_EVHANDLER_ID_VOID;
static lkst_evhandler_id counter_id = LKST_EVHANDLER_ID_VOID;
static lkst_evhandler_id stopper_id = LKST_EVHANDLER_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 test module to sum up events.");
MODULE_LICENSE("GPL");

MODULE_PARM(count_max, "i");
MODULE_PARM(evhandler_id, "i");
MODULE_PARM(maskset_id_base, "i");
MODULE_PARM_DESC(count_max, "maxi value of counters to stop counting(1-0x7ffffffe)");
MODULE_PARM_DESC(evhandler_id, "id of event-hanlder to regist, specified id and id+1 will be used.");
MODULE_PARM_DESC(maskset_id_base, "ID of a maskset used as a template.");

static void reset_buffer (int	clear)
{
	int	iCnt;
	struct lkst_event_buffer	*bufferp;
	struct lkst_event_record	*event_entry_p;

	for (iCnt=0; iCnt < smp_num_cpus; iCnt++) {
		bufferp = lkst_evhandlerprim_select_buffer_cpu(iCnt);

		if (clear)
			memset((char *) LKST_BUFFER_BODY_TOP(bufferp),
			       0, bufferp->size);

		atomic_set(&(bufferp->writep),
			   sizeof(struct lkst_event_record) * ENTRY_INDEX_OFFSET_MAX);
				/* event_entry_p points entry #0 */
		event_entry_p = lkst_evhandlerprim_entry(bufferp, 0, 0);

		if (event_entry_p == NULL) 
			continue;

				/* readp points entry #1 */
		bufferp->readp = 0; // TODO sizeof(struct lkst_event_record);
		bufferp->last_recid = event_entry_p->log_recid = 0;
		
	}
}

void lkst_evhandler_event_count(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	*bufferp;
	struct lkst_event_record	*event_entry_p;
	int	event_count=0;
	unsigned long	first_time;
	unsigned long	flags;

	LKST_CLI(flags);

	bufferp = lkst_evhandlerprim_getw_buffer();

	event_entry_p = lkst_evhandlerprim_entry(bufferp,
						 event_type,
						 entry_index_offset[event_type]);

	if (event_entry_p) {

// TODO:	event_count = atomic_read_and_add(1, &(event_entry_p->arg1));

		event_count = event_entry_p->log_arg1;

		if (!(first_time = event_entry_p->log_arg2))
			first_time = lkst_evhandlerprim_mc();

		stop = jiffies;


		// TODO : lkst_evhandlerprim_entry_header_log
		lkst_evhandlerprim_entry_log_l1(event_entry_p, 
						event_type,
						event_type, ++event_count,
						first_time, stop,
						LKST_ARG32(0, 0));

	}

	lkst_evhandlerprim_putw_buffer(bufferp);

	LKST_STI(flags);
}


void lkst_evhandler_event_count_stopper(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	*bufferp;
	struct lkst_event_record	*event_entry_p;
	int	event_count=0;
	unsigned long	first_time;
	unsigned long	flags;

	LKST_CLI(flags);

	bufferp = lkst_evhandlerprim_getw_buffer();

	event_entry_p = lkst_evhandlerprim_entry(bufferp,
						 event_type,
						 entry_index_offset[event_type]);

	if (event_entry_p) {

// TODO:	event_count = atomic_read_and_add(1, &(event_entry_p->arg1));

		event_count = event_entry_p->log_arg1;

		if (!(first_time = event_entry_p->log_arg2))
			first_time = lkst_evhandlerprim_mc();

		stop = jiffies;


		// TODO : lkst_evhandlerprim_entry_header_log
		lkst_evhandlerprim_entry_log_l1(event_entry_p, 
						event_type,
						event_type, ++event_count,
						first_time, stop,
						LKST_ARG32(0, 0));
	}

	lkst_evhandlerprim_putw_buffer(bufferp);

	LKST_STI(flags);

	if (event_count > count_max) {
		lkst_maskset_id	id = LKST_MASKSET_ID_RNOTHING;
		lkst_maskset_xchg(&id);
		printk ("LKST: test DONE\n");

		reset_buffer (0);
	}
}


/* lkst_summarize_ctrl function */
int lkst_evhandler_event_count_ctrl(void *buf, size_t bufsize)
{
  return 0;
}

static int lkst_mod_init()
{
	int retval;
	int	iCnt;
	lkst_maskset_id id;
	lkst_evhandler_id cid_hope = LKST_EVHANDLER_ID_VOID;
	lkst_evhandler_id sid_hope = LKST_EVHANDLER_ID_VOID;

	/* Initialize Default Event Handler(evhandler_id) */
	if (evhandler_id != LKST_EVHANDLER_ID_VOID) {
		cid_hope = evhandler_id;
		sid_hope = evhandler_id + 1;
	}
	
	retval = lkst_evhandler_register(cid_hope,
					 EVHANDLER_NAME_COUNT,
					 &lkst_evhandler_event_count,
					 &lkst_evhandler_event_count_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register %s!\n", EVHANDLER_NAME_COUNT);
		goto mod_cleanup;
	}
	counter_id = (lkst_evhandler_id) retval;

	retval = lkst_evhandler_register(sid_hope,
					 EVHANDLER_NAME_STOPPER,
					 &lkst_evhandler_event_count_stopper,
					 &lkst_evhandler_event_count_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register %s!\n", EVHANDLER_NAME_STOPPER);
		goto mod_cleanup;
	}
	stopper_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 = 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 (iCnt=0; iCnt < body.len; iCnt++) {
		if (body.entry[iCnt].id == LKST_EVHANDLER_ID_DEFAULT) {
				/* replace handler */
			body.entry[iCnt].id = counter_id;
		}
	}
	body.entry[LKST_ETYPE_SYSCALL_ENTRY].id = stopper_id;
	body.entry[LKST_ETYPE_SYSCALL_EXIT].id  = stopper_id;
	strcpy(body.name, "maskset_event_counter");


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

	/* stop tracing */
	id = LKST_MASKSET_ID_RNOTHING;
	if ((retval = lkst_maskset_xchg(&id))) goto mod_cleanup;
	maskset_id_orig = id;

	/* Clear log buffer */
	reset_buffer(1);

	// TODO: currently not used...
	start = jiffies;

	/* change to new maskset */
	id = maskset_id_new;

#if DEBUG
	printk("id=%d\n", maskset_id_new);
#endif /* DEBUG */

	retval = lkst_maskset_xchg(&id);
	if (retval) goto mod_cleanup;

	// TODO: lkst_evhandler_default()
	lkst_evhandler_event_count(NULL, 0x100, 0,0,0,0);

	return 0;

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

static void lkst_mod_cleanup()
{
	int ret = 0;

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

		/* deregister the event handler. */
	if ( counter_id != LKST_EVHANDLER_ID_VOID ) {
		ret = lkst_evhandler_unregister( counter_id );
		if ( ret < 0 ) {
			printk(KERN_ERR "cannot unregister %s!\n", EVHANDLER_NAME_COUNT);
		}
	}
	
	if ( stopper_id != LKST_EVHANDLER_ID_VOID ) {
		ret = lkst_evhandler_unregister( stopper_id );
		if ( ret < 0 ) {
			printk(KERN_ERR "cannot unregister %s!\n", EVHANDLER_NAME_STOPPER);
		}
	}

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

	return;
}

/* END */
