/************************************************************
 *
 * COPYRIGHT (C) HITACHI,LTD. 2003 ALL RIGHTS RESERVED.
 * WRITTEN BY HITACHI SYSTEMS DEVELOPMENT LABORATORY,
 *            HITACHI CENTRAL RESEARCH LABORATORY.
 *
 * Created by M. Hiramatsu <hiramatu@sdl.hitachi.co.jp>
 * 
 * Only for Intel P6 famiry (except Pentium4 & Xeon)
 *
 ************************************************************/

#include<linux/version.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/fs.h>
#include <linux/sched.h>
#include<linux/lkst_private.h>

#include <asm/msr.h>

static int	evh_id_start = LKST_EVHANDLER_ID_VOID;
static int	evh_id_end = 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("M. Hiramatsu <hiramatu@sdl.hitachi.co.jp>");
MODULE_DESCRIPTION("LKST performance monitoring sample module.");
MODULE_LICENSE("GPL");


/* events for P6(i686) famiry (except Pentium4 & Xeon)*/
#define EV_P6_DATA_MEM_REFS	0x43 /*No.of memory reference*/
#define EV_P6_MISALIGN_MEM_REF	0x05 /*No.of miss-aligned memory reference*/
#define EV_P6_INST_RETIRED	0xc0 /*No.of retired instructions */
#define EV_P6_UOPS_RETIRED	0xc2 /*No.of retired micro-ops */
#define EV_P6_INST_DECORDED	0xd0 /*No.of decorded instructions */

int events[] = {
	EV_P6_DATA_MEM_REFS,
	EV_P6_MISALIGN_MEM_REF,
	EV_P6_INST_RETIRED,
	EV_P6_UOPS_RETIRED,
	EV_P6_INST_DECORDED,
	-1
};

struct event_sel {	
	unsigned event:8;
	unsigned umask:8;
	unsigned usr:1;
	unsigned os:1;
	unsigned e:1;
	unsigned pc:1;
	unsigned intr:1;
	unsigned _reserved:1;
	unsigned en:1;
	unsigned inv:1;
	unsigned cmask:8;
} __attribute__((packed));

union prefevtsel{
	struct event_sel reg;
	u32 val;
} pes_target;

static void lkst_evhandler_pmc_sample_start(void *phookrec, int event_type,
					    lkst_arg_t arg1, lkst_arg_t arg2,
					    lkst_arg_t arg3, lkst_arg_t arg4)
{
	wrmsr(MSR_P6_EVNTSEL0, pes_target.val, 0);
	wrmsr(MSR_P6_PERFCTR0, 0, 0);
	lkst_evhandlerprim_entry_log(event_type, 
				     arg1,arg2,arg3,arg4);
	/* you can get CPI from pmc/(the difference of rdtsc(LKST's mc))*/
}

static void lkst_evhandler_pmc_sample_end(void *phookrec, int event_type,
					  lkst_arg_t arg1, lkst_arg_t arg2,
					  lkst_arg_t arg3, lkst_arg_t arg4)
{
	u32 h,l;
	rdpmc(0, l, h);
	lkst_evhandlerprim_entry_log(event_type, 
				     arg1,arg2,arg3,
				     /*since pmc is 40 bits.*/
				     LKST_ARG32((h&0xff),l));
}

static int lkst_evhandler_pmc_sample_ctrl(void *buf, size_t bufsize)
{
	int target,i;
	((char *)buf)[bufsize -1] = '\0';
	target = simple_strtol((char *)buf,NULL, 0);
	for(i =0 ; events[i] != -1; i ++){
		if ( events[i] == target ) {
			pes_target.reg.event = target;
			return 0;
		}
	}
	return -EINVAL;
}

static int lkst_mod_init()
{
	int	retval;

	pes_target.reg.cmask = 0;
	pes_target.reg.inv = 0;
	pes_target.reg.en = 1;
	pes_target.reg.intr = 0;
	pes_target.reg.pc = 0;
	pes_target.reg.e = 0;
	pes_target.reg.os = 1;
	pes_target.reg.usr = 0;
	pes_target.reg.umask = 0;
	pes_target.reg.event =  EV_P6_INST_RETIRED;

	/* register event handlers */
	retval = lkst_evhandler_register(evh_id_start,
					 "pmc_sample_start",
					 &lkst_evhandler_pmc_sample_start,
					 &lkst_evhandler_pmc_sample_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register event-handler function!\n");
		return retval;
	}
	evh_id_start = (lkst_evhandler_id) retval;

	retval = lkst_evhandler_register(evh_id_end,
					 "pmc_sample_end",
					 &lkst_evhandler_pmc_sample_end,
					 &lkst_evhandler_pmc_sample_ctrl);
	if (retval < 0) {
		printk(KERN_ERR "cannot register event-handler function!\n");
		lkst_mod_cleanup();
		return retval;
	}
	evh_id_end = (lkst_evhandler_id) retval;

	return 0;
}

static void lkst_mod_cleanup()
{
	/* deregister the event handlers. */
	if (evh_id_start != LKST_EVHANDLER_ID_VOID)
		lkst_evhandler_unregister((lkst_evhandler_id)evh_id_start);
	if (evh_id_end != LKST_EVHANDLER_ID_VOID)
		lkst_evhandler_unregister((lkst_evhandler_id)evh_id_end);

	return;
}
