/************************************************************
 *
 * 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>
 * Updated by M.Hiramatsu <hiramatu@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>
#include <asm/uaccess.h>	/* for copy_to_user() */

	/* 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 u_int64_t	count_max = 0x7ffffffffffffffULL;
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(evhandler_id, "i");
MODULE_PARM(maskset_id_base, "i");
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 lkst_buffer_id count_buffer_id[LKST_CPU_MAX] __cacheline_aligned;

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;
	unsigned long	flags;

	if(!(bufferp = lkst_evhandlerprim_select_buffer_id(
		       count_buffer_id[smp_processor_id()])))
		return;
	
	local_irq_save(flags);
	event_entry_p = &LKST_BUFFER_BODY(bufferp)[event_type+LKST_BUFFER_INIT_POS];

	event_entry_p->log_time = lkst_evhandlerprim_mc();/*last event time*/
	if (event_entry_p->log_arg2 == 0UL)
		event_entry_p->log_arg2 = event_entry_p->log_time; /*first event time*/
	event_entry_p->log_arg1++;
	event_entry_p->log_arg3 = LKST_ARG32(0,jiffies);
	local_irq_restore(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;
	unsigned long	flags;
	u_int64_t  event_count;

	if(!(bufferp = lkst_evhandlerprim_select_buffer_id(
			count_buffer_id[smp_processor_id()])))
		return;
	
	local_irq_save(flags);

	event_entry_p = &LKST_BUFFER_BODY(bufferp)[event_type+LKST_BUFFER_INIT_POS];

	event_entry_p->log_time = lkst_evhandlerprim_mc();/*last event time*/
	if (event_entry_p->log_arg2 == 0UL)
		event_entry_p->log_arg2 = lkst_evhandlerprim_mc(); /*first event time*/

	event_count = event_entry_p->log_arg1++;
	event_entry_p->log_arg3 = LKST_ARG32(0,jiffies);

	local_irq_restore(flags);

	if (event_count > count_max) {
		lkst_maskset_set(LKST_MASKSET_ID_RNOTHING);
	}
}

static int clear_count_buffer(int cpu)
{
	int i;
	unsigned long	flags;
	struct lkst_event_buffer *buffer;
	u_int64_t pmc;

	local_irq_save(flags);
	if(!(buffer = lkst_evhandlerprim_select_buffer_id(
			count_buffer_id[cpu]))) return -EINVAL;

	pmc = lkst_evhandlerprim_mc();
	/* clear count buffer */
	for(i=0;i<4095;i++){
		register struct lkst_event_record * event_entry_p = 
			&LKST_BUFFER_BODY(buffer)[i+LKST_BUFFER_INIT_POS];
		event_entry_p->log_recid = i;
		event_entry_p->log_time = pmc;
		event_entry_p->log_event_type = i;
		event_entry_p->log_pid = 0;
		event_entry_p->log_arg1 = 0;
		event_entry_p->log_arg2 = 0;
		event_entry_p->log_arg3 = 0;
		event_entry_p->log_arg4 = 0;
	}
	local_irq_restore(flags);
	return 0;
}

static int lkst_evhandler_event_count_ctrl(void *buf, size_t bufsize)
{
	if (bufsize == 0) {
		unsigned long allowed;
		int cpu, ret;
		
		cpu = (int)buf;/*it's OK to convert from pointer vaule*/
		if(!lkst_cpu_is_online(cpu))  return -EINVAL;
		
		lkst_cpu_migrate_save(&allowed, cpu);
		ret = clear_count_buffer(cpu);
		lkst_cpu_migrate_restore(allowed);
		return ret;
	} else if (bufsize == sizeof(lkst_buffer_id)*LKST_CPU_MAX) {
		int i;
		for (i=0;i<LKST_CPU_MAX;i++)
			((lkst_buffer_id *)buf)[i] = count_buffer_id[i];
		return 0;
	}else {
		printk("option error("LKST_FMT_SZT")\n",bufsize);
		return -EINVAL;
	}
}

static int lkst_evhandler_event_count_stopper_ctrl(void *buf, size_t bufsize)
{
	if (bufsize == sizeof(u_int64_t)) {
		count_max = *((u_int64_t *)buf);
	}else {
		return -EINVAL;
	}
	return 0;
}

static int create_count_buffer(int cpu)
{
	unsigned long allowed;
	int ret;
	if(!lkst_cpu_is_online(cpu)) {
		count_buffer_id[cpu]=LKST_BUFFER_ID_VOID;
		return 0;
	}
	lkst_cpu_migrate_save(&allowed, cpu);
	ret = lkst_buffer_create(LKST_BUFFER_ID_VOID,
				 4096*LKST_EVENT_RECORD_BYTE, 
				 LKST_BUFFER_ID_VOID);
	if(ret<0){
		printk("lkst_mod_event_count; failed to create buffer!\n");
		return -1;
	}
	count_buffer_id[cpu]=ret;
	
	ret = clear_count_buffer(cpu);
	lkst_cpu_migrate_restore(allowed);
	return ret;
}

static void delete_count_buffer(int cpu)
{
	unsigned long allowed;
	if(count_buffer_id[cpu] != LKST_BUFFER_ID_VOID) {
		lkst_cpu_migrate_save(&allowed, cpu);
		lkst_buffer_delete(count_buffer_id[cpu]);	
		lkst_cpu_migrate_restore(allowed);
	}
}

static int lkst_mod_init()
{
	int retval;
	int iCnt, i;
	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_stopper_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register %s!\n", EVHANDLER_NAME_STOPPER);
		goto mod_cleanup;
	}
	stopper_id = (lkst_evhandler_id) retval;

	/* create buffers for counting */
	for (i = 0;i<LKST_CPU_MAX;i++){
		create_count_buffer(i);
	}

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

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

	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;
	int i;

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

	for(i=0;i<LKST_CPU_MAX;i++)
		delete_count_buffer(i);
	
		/* 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 */
